Contrôle d’accès par lecture de plaques d’immatriculation (ALPR)

Ce système permet d’autoriser l’accès à un parking par lecture des plaques d’immatriculation. Il s’appuie sur une caméra IP associée à une carte Raspberry Pi et le logiciel de reconnaissance de plaques openALPR. Une base de donnée mongoDB contient la liste des plaques autorisées. Le middleware est assuré par un “flow” node-red. (liaison openALPR / mongoDB, déclenchement de l’ordre d’ouverture et IHM pour la gestion de la base)

Diagramme de cas d’utilisation

La lecture des plaques d’immatriculation va s’intégrer dans le cas d’utilisation « Contrôle d’accès ». L’ajout et la mise à jour de nouveaux véhicules sera lié au cas d’utilisation « Mise à jour de la base données ». Le système autorise l’accès au parking aux véhicules dont le numéro de plaque d’immatriculation est inscrit dans la base de données.

Constitution du système

  • Une caméra IP, elle sera positionnée afin de capter la plaque d’immatriculation du véhicule lorsque ce dernier de présentera à l’entrée du parking.
  • Une barrière automatisée. Elle est fermée par défaut et s’ouvre si la plaque du véhicule est reconnue.
  • Un ordinateur qui contient la base de données des plaques autorisées, l’IHM web permettant la maintenance de la BDD, un logiciel de reconnaissance de plaques en relation avec la caméra IP et un interlogiciel (middleware) permettant de faire communiquer les différentes applications.

Topologie du système

Solution retenue

Matériels

  • Une carte Raspberry (2 ou 3) / 16GB raspbian JESSIE (nov 2016) -> serveur
  • Une carte Arduino avec le firmware Firmata Standard -> extension E/S
  • Une caméra IP. On doit pouvoir accéder à l’image via le web. Pour l’exemple nous utiliserons une petite caméra motorisée db power clone d’une caméra FOSCAM FI8908W[1]. Sur cette caméra, d’après la documentation[2], l’accès au flux s’effectue à l’URL : http://<IP_CAMERA>:<PORT_CAMERA>//videostream.cgi?user=admin&pwd=&resolution=32&rate=0
  • Une barrière automatique pilotée par un signal logique (« 1 » ouverture, « 0 » fermeture). La barrière gère elle-même la sécurité de passage.

Logiciels

  • openALPR pour Raspberry : logiciel open source de reconnaissance de plaques d’immatriculation.
  • MongoDB et mongoExpress : serveur de base de données et client web.
  • NODE-RED avec node-red-contrib-gpio, node-red-dashboard, node-red-contrib-mongoDB2, ObjectID et mongoDB2 : logiciel de programmation orienté flux, permettant l’interconnexion d’applications hétérogènes devant échanger des données.

Installation Logicielle.

Node-red

dans une console lancer node-red : node-red-start puis à l’aide d’un navigateur  accéder à http://<ip raspberry>:1880  . Si le noeud Johnny5 est visible, il n’y a rien à faire sinon voir Réinstallation de node-red avec Johnny5

OpenAlpr

voir openAlpr sur Raspberry Pi

Base de données mongoDB

voir installation de mongoDB et mongo-express

Mise en oeuvre.

Test de openALPR avec la caméra

Compléter le fichier /etc/openalpr/alprd.conf et indiquer le chemin d’accès au flux vidéo de la caméra IP

; This configuration file overrides the default values specified 
; in /usr/local/share/openalpr/config/alprd.defaults.conf
[daemon]

; country determines the training dataset used for recognizing plates. Valid values are us, eu
country = eu
pattern = fr

; text name identifier for this location
site_id = rpi01

; Declare each stream on a separate line
; each unique stream should be defined as stream = [url]
; adresse de la camera IP accessible par le web
stream = http://192.168.5.23:8080/videostream.cgi?user=admin&pwd=&resolution=32&rate=0

; topn is the number of possible plate character variations to report
topn = 5

; Determines whether images that contain plates should be stored to disk
store_plates = 0
store_plates_location = /home/pi/plateimages/

; upload address is the destination to POST to
upload_data = 1
upload_address = http://localhost:1880/plate/

Lancer la commande alprd -fet placer une image comportant une plaque d’immatriculation devant la caméra

Observer la console :

pi@Raspberry Pipi:~ $ alprd -f
INFO - Running OpenALPR daemon in the foreground.
INFO - Using: /etc/openalpr/alprd.conf for daemon configuration
Missing config value for company_id
Missing config value for pattern
INFO - Using: /home/pi/plateimages/ for storing valid plate images
INFO - country: fr -- config file: /etc/openalpr/openalpr.conf
INFO - pattern:
INFO - Stream 1: http://192.168.5.23:8080/videostream.cgi?user=admin&pwd=&resolution=32&rate=0
INFO - Starting camera 1
INFO - Video stream connecting...
INFO - Video stream connected
DEBUG - Writing plate AV865XG (rpi01-cam1-1483036354101) to queue.

Video de Guy TechLab : mise en oeuvre de ce tuto.

OpenALPR et node-red : lecture et affichage d’une plaque.

Node-red permet d’interconnecter des applications hétérogènes devant échanger des informations. On parle d’échange de messages. Aplrd permet d’envoyer une requête POST à un serveur web lors de la lecture d’une plaque. Nous allons donc créer un flux qui intercepte cette requête POST, va en extraire la donnée et publier cette dernière dans une fenêtre de débogage.

Vérification du fichier fichier [shell]/etc/openalpr/alprd.conf[/shell]

Vérifier que les 3 dernières lignes du fichier alprd.cong correspondent à :

; upload address is the destination to POST to
upload_data = 1 ; « 1 » = envoi des données
upload_address = http://localhost:1880/plate/ ; adresse du serveur web où sont envoyées les requêtes POST

Ce qui impliquera que les données seront postées à l’adresse http://localhost:1880/plate

Flux node-red

Flux composé d’un node « http in », « http response » et « debug »

Lors de la reconnaissance d’une plaque on obtient un objet msg.payload en sortie du node « http in » qui une fois remis en forme ressemble à ceci :

{
    "version": 2,
    "data_type": "alpr_results",
    "epoch_time": 1483090959429,
    "img_width": 640,
    "img_height": 480,
    "processing_time_ms": 395.173157,
    "regions_of_interest": [],
    "results": [
        {
            "plate": "AV865XG",
            "confidence": 90.980667,
            "matches_template": 0,
            "plate_index": 0,
            "region": "",
            "region_confidence": 0,
            "processing_time_ms": 171.828781,
            "requested_topn": 5,
            "coordinates": [
                {…

On peut observer qu’il s’agit d’un objet JSON dont la propriété « results » est un tableau contenant les plaques reconnues avec leur score « confidence ».

On peut donc interroger l’objet msg.payload afin d’en extraire le numéro de plaque. Pour cela on introduit un script

 

On peut alors voir le numéro de la plaque apparaitre dans la fenêtre de débogage

Utilisation de mongoDB et ALPR dans node-red

L’objectif est de comparer la plaque lue par alprd avec une plaque contenue dans la base de données.

Ajout du node mongoDB2 à la palette de nodes :

Sélectionner le Menu de Node-red -> Manage palette

Sélectionner l’onglet « Install »

Dans l’espace de recherche taper « mongo »

On voit alors apparaitre les nodes disponibles. Cliquer sur le bouton « install » du node « node-red-contrib-mongoDB2 »

Le nœud s’installe est devient alors disponible dans la palette des nodes.

Installer aussi le node « node-red-contrib-objectid », il permet de gérer les propriétés « _id » des documents.

Paramétrage du nœud mongoDB2 et test de connexion

Faire glisser un node mongoDB2 dans l’espace de travail et l’éditer.

Sélectionner « External service » puis cliquer sur le petit crayon afin d’ajouter une connexion au serveur mongoDB Ajouter un serveur, l’URI est normalement : mongodb://localhost:27017/db
Une fois le serveur paramétré, sélectionner « db » dans l’option « Server », compléter l’option « Collection » avec le nom de la collection « LicencePlate » enfin dans « Operation » sélectionner « findOne » et cliquer sur « Done » Ajouter un nœud « Inject » dans l’espace de travail et le paramétrer de la manière suivante :

Compléter le flow de la manière suivante : ajouter un node « json » et un node « debug ». Lier l’ensemble.

Quand on clique sur le bouton du nœud inject, on voit apparaitre le document au format JSON dans la fenêtre de debug

code du Flow (à importer via le clipboard)
[{"id":"fcc8de73.b9a8e","type":"mongodb2 in","z":"de8a40ec.19323","service":"_ext_","configNode":"1d02b15c.4cf227","name":"","collection":"LicencePlate","operation":"findOne","x":1234.7665710449219,"y":157.85000610351562,"wires":[["c37c79dd.bbd448"]]},{"id":"5934a9db.1c3ee8","type":"json","z":"de8a40ec.19323","name":"","x":996.7665710449219,"y":158.01666259765625,"wires":[["fcc8de73.b9a8e"]]},{"id":"c37c79dd.bbd448","type":"debug","z":"de8a40ec.19323","name":"","active":true,"console":"false","complete":"false","x":1474.7666015625,"y":157.01666259765625,"wires":[]},{"id":"fea87bf7.46af38","type":"inject","z":"de8a40ec.19323","name":"","topic":"","payload":"{\"nom\":\"Hollande\"}","payloadType":"str","repeat":"","crontab":"","once":false,"x":774.7666320800781,"y":158.38333129882812,"wires":[["5934a9db.1c3ee8"]]},{"id":"1d02b15c.4cf227","type":"mongodb2","z":"","uri":"mongodb://localhost:27017/db","name":"db","options":"","parallelism":"-1"}]

 

Nous venons d’effectuer une requête dans la base mongoDB

Reconnaissance d’une plaque d’immatriculation.

En associant la gestion de la requête POST fournit par le logiciel alprd et une requête dans la base de données, on peut déterminer si l’accès est autorisé.

code du Flow (à importer via le clipboard)
[{"id":"cd7d103b.308a78","type":"mongoDB2 in","z":"1ee78c77.4f0c84","service":"_ext_","configNode":"d3816939.e303e8","name":"","collection":"LicencePlate","operation":"count","x":1201,"y":144,"wires":[["ae24a545.fb9ea","3fc0b1.16e58f5"]]},{"id":"ae24a545.fb9ea","type":"debug","z":"1ee78c77.4f0c84","name":"","active":true,"console":"false","complete":"false","x":1477.7666015625,"y":66.71665954589844,"wires":[]},{"id":"d96d3dcc.a4c3a8","type":"http in","z":"1ee78c77.4f0c84","name":"","url":"/plate","method":"post","swaggerDoc":"","x":697.7666015625,"y":147.18333435058594,"wires":[["e36dbbe7.4063c8"]]},{"id":"e36dbbe7.4063c8","type":"function","z":"1ee78c77.4f0c84","name":"extraction num plaque","func":"var plaque={}; //objet vide\nplaque.immatriculation=msg.payload.results[0].plate;\nmsg.payload=plaque;\nreturn msg;","outputs":1,"noerr":0,"x":939.7666015625,"y":145.9499969482422,"wires":[["cd7d103b.308a78","b918e1ce.d2721"]]},{"id":"b918e1ce.d2721","type":"debug","z":"1ee78c77.4f0c84","name":"","active":true,"console":"false","complete":"false","x":1184.7666015625,"y":61.716644287109375,"wires":[]},{"id":"3fc0b1.16e58f5","type":"http response","z":"1ee78c77.4f0c84","name":"","x":1457.7666015625,"y":142.4166717529297,"wires":[]},{"id":"d3816939.e303e8","type":"mongoDB2","z":"","uri":"mongoDB://localhost:27017/db","name":"db","options":"","parallelism":"-1"}]

 

Le script dans le node function (extraction num plaque) est le suivant :

var plaque={}; 
plaque.immatriculation=msg.payload.results[0].plate;
msg.payload=plaque;
return msg;

Le message de sortie sera donc de la forme : { "immatriculation": "AV865XG" }. Ce message sert d’argument d’entrée à la requête « count » du node mongoDB2. Autrement dit le node mongoDB2 va compter les documents qui contiennent un champ « immatriculation » ayant pour valeur « AV865XG ». Le message de sortie du node mongoDB sera un nombre. Si ce dernier vaut 0, c’est que le numéro de plaque n’est pas enregistré dans la base. Reste à commander l’ouverture de la barrière en fonction de cette information.

Pour cela nous allons utiliser une carte Arduino qui sera pilotée par la carte Raspberry.

Compléter le flow de la manière suivante en ajoutant les nœuds « switch », « trigger » et « gpio ».

code du Flow (à importer via le clipboard)
[{"id":"7ec1525b.cd9fac","type":"trigger","z":"1ee78c77.4f0c84","op1":"1","op2":"0","op1type":"str","op2type":"str","duration":"2000","extend":false,"units":"ms","reset":"","name":"","x":1270.7666015625,"y":243.18331909179688,"wires":[["1a99c7a1.ed34b8"]]},{"id":"1a99c7a1.ed34b8","type":"gpio out","z":"1ee78c77.4f0c84","name":"arduino","state":"OUTPUT","pin":"13","i2cDelay":"0","i2cAddress":"","i2cRegister":"","outputs":0,"board":"4a53a7ab.eb3e7","x":1475.7666015625,"y":242.11666870117188,"wires":[]},{"id":"fd89d95c.482bb","type":"switch","z":"1ee78c77.4f0c84","name":"","property":"payload","propertyType":"msg","rules":[{"t":"gte","v":"1","vt":"num"}],"checkall":"true","outputs":1,"x":1043.7666015625,"y":243.76663208007812,"wires":[["7ec1525b.cd9fac"]]},{"id":"4a53a7ab.eb3e7","type":"nodebot","z":"","name":"","username":"","password":"","boardType":"firmata","serialportName":"/dev/ttyUSB0","connectionType":"local","mqttServer":"","socketServer":"","pubTopic":"","subTopic":"","tcpHost":"","tcpPort":"","sparkId":"","sparkToken":"","beanId":"","impId":"","meshbluServer":"https://meshblu.octoblu.com","uuid":"","token":"","sendUuid":""}]

 

Le switch sera paramétré de la manière suivante :

Le trigger :

Le nœud gpio quant à lui sera paramétré pour piloter une broche digital d’une carte Arduino. La carte Arduino sera programmée avec le programme Firmata : (Menu fichier > Exemples > Firmata > StandardFirmata ) puis connectée à la carte Raspberry Pi par un câble USB.

Editer le nœud gpio puis cliquer sur le crayon afin de définir la cible :
Cliquer sur la loupe afin de trouver la carte Arduino connectée à la carte Raspberry (dans l’exemple c’est /dev/tty/USB0, mais cela peut être autre chose…). Configurer le reste comme suit :
Compléter alors le nœud gpio comme suit :

Si la configuration est correcte la broche 13 (et la led qui y est rattachée) passe à l’état logique « 1 » pour une durée de 2 sec lorsqu’une plaque est reconnue. Reste à interconnecter l’ensemble à la vraie barrière…

Outil d’administration de la base de donnée

voir : Gestion de mongoDB à l’aide de jsGrid et node-red

code du Flow (à importer via le clipboard)

il est disponible ici : http://flows.nodered.org/flow/e32f2b942bce77ef6079c0642b93c036

Ce flow fabrique une interface homme machine qui permet de maintenir la base de données des utilisateurs autorisés à accéder au parking. Il s’appuie sur un template html et le plugin jsGrid (dépend de jQuery). Son fonctionnement est dit « RESTfull » c’est-à-dire que la grille utilise une requête ajax de type POST pour insérer des valeurs dans la base, une requête de type PUT pour effectuer la mise à jour et une requête DELETE pour effacer un document. Pour fonctionner ce flow a besoin du node « ObjectId », qu’il faut installer si ce n’est pas déjà fait.

capture

Le format des documents doit être :

{
    "_id": ObjectID("585a4732ac11400192a60b85"),
    "nom": "nameOfUser",
    "prenom": "FirstNameOfUser",
    "immatriculation": "123456",
    "heure": "13",
    "minute": "30"
}

La grille est accessible à l’adresse : http://<node-red>:<port>/plates

Le code du node template est le suivant :

<!DOCTYPE html>
<html lang="fr">
<head>
    <title>Licence Plate</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-timepicker/0.5.2/css/bootstrap-timepicker.min.css" />
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid.min.css" />
    <link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid-theme.min.css" />
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid.min.js"></script>

    <script type="text/javascript">
        $(function () {
            var db = {{#payload}}{{{.}}}{{/payload}};
                $("#jsgrid").jsGrid({
                    width: "100%",
                    inserting: true,
                    editing: true,
                    sorting: true,
                    paging: true,

                    data: db,

                    fields: [
                        { title:"Nom", name: "nom", type: "text", width: 50 },
                        { title:"Prénom", name: "prenom", type: "text", width: 50 },
                        { title:"Plaque", name: "immatriculation", type: "text", width: 50 },
                        { title:"Heure", name: "heure", type:"number", width: 25},
                        { title:"Minute", align:"left",  name: "minute", type:"number" , width: 25},
                        { type: "control" }
                    ],
               
                    controller: {
                        insertItem: function(item) {
                            return $.ajax({
                                type: "POST",
                                url: "/insert",
                                data: item
                            });
                        },
                        updateItem: function(item) {
                            return $.ajax({
                                type: "PUT",
                                url: "/update",
                                data: item
                            });
                        },
                        deleteItem: function(item) {
                            return $.ajax({
                                type: "DELETE",
                                url: "/delete",
                                data: item
                            });
                        }
                    }   
                });
            });
    
  </script>
</head>
<body class="container">
    <section class="row">

        <div class="col-md-6"></div>
        <div class="col-md-6" id="jsgrid">
        </div>
    </section>
</body>
</html>

La ligne var db = {{#payload}}{{{.}}}{{/payload}};utilise la notation « mustache » c’est la notation qui permet de à la page web de récupérer les informations contenues dans le message d’entrée du node. Dans notre cas msg.payload est le résultat de la requête effectuée dans la base de données. Il s’agit d’un tableau d’objets JSON le format est le suivant :

[
    {
        "nom": "TestName",
        "prenom": "Toto",
        "immatriculation": "AV865XG",
        "_id": "58667499d93f5401a2b4d2d5"
    },
    {
        "_id": "586666b73a3b9e2011963257",
        "nom": "Hollande",
        "prenom": "Francois",
        "immatriculation": "HE123LY"
    }
]

Le moteur de template mustache va itérer le tableau et passer les objets qu’il contient au rendu de la page web

Lors du rendu de la page, le texte du template

…
        $(function () {
            var db = {{#payload}}{{{.}}}{{/payload}};
                $("#jsgrid").jsGrid({
                    width: "100%",
                    inserting: true,
…

est remplacé par

…
        $(function () {
            var db = [{"nom":"TestName","prenom":"Toto","immatriculation":"AV865XG","_id":"58667499d93f5401a2b4d2d5"},{"_id":"586666b73a3b9e2011963257","nom":"Hollande","prenom":"Francois","immatriculation":"HE123LY"}];
                $("#jsgrid").jsGrid({
                    width: "100%",
                    inserting: true,
…

{{{.}}}(Avec triple « mustache ») signifie que l’objet entier doit être passé de manière brute, c’est-à-dire sans être mise au format html

Avec une {{.}}(double mustache) le texte serait remplacé par

var db = [{&quot;nom&quot;:&quot;TestName&quot;,&quot;prenom&quot…

c’est-à-dire que les « ” » sont remplacés par leur code html « &quot ; » de ce fait la variable « db » n’est plus un tableau JSON au vu du script.

Voir https://github.com/janl/mustache.js/#non-empty-lists et https://github.com/janl/mustache.js/#variables pour plus d’informations.

  1. http://www.gadgetvictims.com/2008/08/foscam-fi8908w-firmware-history-page.html
  2. https://docs.google.com/fileview?id=0B9kFXSWngqLWYjBlYjcyOWUtYzgxYy00MWYxLThhYjAtOTgwODgzOTRmOGM5&hl=en