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 -f
et 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.
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 = [{"nom":"TestName","prenom"…
c’est-à-dire que les « ” » sont remplacés par leur code html « " ; » 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.