A Node-RED node for license plate recognizing via platerecognizer.com
Run the following npm command in your Node-RED user directory (typically ~/.node-red):
npm install node-red-contrib-plate-recognizer
Note that you need to signup for an account on platerecognizer.com, and paste your private token into this node's config screen. With a free account there is a limit to recognize 2500 images per month, but they also offer various paid license models.
Please buy my wife a coffee to keep her happy, while I am busy developing Node-RED stuff for you ...
This node will detect and recognise license plates in an image, using a deep learning (cloud) service. The AI (cloud) service has been trained for license plates for more than 100 countries. They also offer an SDK for local setups, which can easily be installed as a Docker container. See also our blog for an introduction.
Send an image (as buffer or base64 encoded string) via an input message, to start a recognition:
[{"id":"38586517.a5bf9a","type":"plate-recognizer","z":"d9a54719.b13a88","name":"","inputField":"payload","inputFieldType":"msg","outputField":"payload","outputFieldType":"msg","url":"https://api.platerecognizer.com/v1/plate-reader/","ignoreDuring":false,"makeAndModel":false,"statusText":"none","cameraId":"","regionFilter":false,"timestamp":false,"regionList":"","regionListType":"json","x":880,"y":660,"wires":[["30cb89da.c35546"],[]]},{"id":"e4699284.081c7","type":"inject","z":"d9a54719.b13a88","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":500,"y":660,"wires":[["9e2c9295.7f9a9"]]},{"id":"30cb89da.c35546","type":"debug","z":"d9a54719.b13a88","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":1060,"y":660,"wires":[]},{"id":"9e2c9295.7f9a9","type":"http request","z":"d9a54719.b13a88","name":"","method":"GET","ret":"bin","paytoqs":false,"url":"https://www.mercedes-benz.com/en/classic/history/h-number-plate-2020/_jcr_content/root/paragraph/paragraph-right/paragraphimage/image/MQ6-8-image-20191205151927/02-mercedes-benz-classic-h-number-plate-2020-2560x1440.jpeg","tls":"","persist":false,"proxy":"","authType":"","x":680,"y":660,"wires":[["38586517.a5bf9a"]]}]
The output message will contain the recognition results (in json format):
The "results" will be a json array, containing a separate element for each license plate that has been recognised. When the image contains N cars, then the array will contain N elements.
For each recognised license plate, some basic information will be delivered:
-
box: the bounding box (coordinates) where the vehicle is located inside the image.
-
plate: the license plate itself as plain text.
-
region: the region of the license plate (e.g. "be" for Belgium). This object contains some nested fields:
- code: the region code (from the region list).
- score: the confidence level for the region prediction, which is a value between 0 and 1 (with 1 the highest confidence).
-
vehicle: information about the vehicle itself. This object contains some nested fields:
- type: the type of vehicle (which can be Ambulance, Bus, Car, Limousine, Motorcycle, Taxi, Truck, Van, Unknown).
- score: the confidence level for the vehicle prediction, which is a value between 0 and 1 (with 1 the highest confidence).
- box: the bounding box (coordinates) where the vehicle is located inside the image.
-
score: the confidence level for the license plate text prediction, which is a value between 0 and 1 (with 1 the highest confidence).
-
candidates: sometimes the service isn't really sure whether it has recognised the license plate correctly. Therefore a list of possible plate 'candidates' will be supplied. The first candidate is the same plate that has already been offered at the higher level:
In this case the AI service thinks (with 90,3% certainty) that the plate its "s0k92h", but it might be that the plate is "sok92h" (with 90,1% certainty). In this case the confusion is between the number "0" and the character "o".
-
dscore: the confidence level for the license plate detection, which is a value between 0 and 1 (with 1 the highest confidence).
The node can be configured via a series of settings on the config screen:
The field of the input message which will need to contain the input image. By default msg.payload
will be used. The image should be a binary Buffer or a base64 encoded string.
The field of the output message where the recognition result will be stored (in JSON format). By default msg.payload
will be used.
Create an account at platerecogniser.com and enter your private API token here.
Specify the URL of the recognition service, to allow different kind of setups:
- Use the official cloud service, which will be the default (and most used) option.
- Use a local installation (based on the SDK).
- Use a local Docker container.
Optionally a camera id can be specified, which will be sent to the recognition service.
Specify how the recognition result needs to be displayed in the node status label:
- None: Show no recognition results.
- Plate count: Show the number of plates that have been recognised in the image.
- Plates: Show a (comma separted) list of the plates that have been recognized in the image.
- Plates and scores: Same as the previous option, but now the 'score' percentage is also added.
When selected images will automatically be skipped, when the previous image is still being recognized. When deselected multiple images can be recognized simultaneously.
When selected not only the plate will be recognized, but there will also be a prediction of the vehicle brand and type.
CAUTION: this is only supported for some paid account types!
When selected a separate output message will be send for each recognized license plate. If not selected a single output message will be send containing an array of ALL recognized license plates. See the section "Split output messages" below for more information.
When selected, an array of region codes can be specified (see supported regions.
The following flow explains some different use cases:
- Image contain a single car.
- Image containing a single car, but photografed from an angle. It is important to be able to recognize license plates at angles, because a camera won't always be positioned directly in front of the cars.
- Image containing two cars, which means the array will contain two individual recognitions.
- Image containing no cars, which means the array will be empty.
- Image containing a truck with a license plate, but also some texts on the truck itself. There will be multiple recognitions in the array (because the texts will also be detected!).
Note that the node-red-contrib-image-output node needs to be installed also!
[{"id":"fff72ad.6f8c7d8","type":"inject","z":"d9a54719.b13a88","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":920,"y":460,"wires":[["ee55dd94.02b2e"]]},{"id":"28523886.f82428","type":"debug","z":"d9a54719.b13a88","name":"Normal recognitions","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"recognition","targetType":"msg","x":1560,"y":580,"wires":[]},{"id":"ee55dd94.02b2e","type":"http request","z":"d9a54719.b13a88","name":"","method":"GET","ret":"bin","paytoqs":false,"url":"https://www.mercedes-benz.com/en/classic/history/h-number-plate-2020/_jcr_content/root/paragraph/paragraph-right/paragraphimage/image/MQ6-8-image-20191205151927/02-mercedes-benz-classic-h-number-plate-2020-2560x1440.jpeg","tls":"","persist":false,"proxy":"","authType":"","x":1100,"y":460,"wires":[["231d72b4.5233ae"]]},{"id":"10f189ae.db2d76","type":"inject","z":"d9a54719.b13a88","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":920,"y":660,"wires":[["24464bb6.a07b64"]]},{"id":"24464bb6.a07b64","type":"http request","z":"d9a54719.b13a88","name":"","method":"GET","ret":"bin","paytoqs":false,"url":"https://image.freepik.com/free-photo/empty-parking-lot_1127-3298.jpg","tls":"","persist":false,"proxy":"","authType":"","x":1100,"y":660,"wires":[["231d72b4.5233ae"]]},{"id":"5ee1acd5.4e0784","type":"comment","z":"d9a54719.b13a88","name":"No cars","info":"","x":890,"y":620,"wires":[]},{"id":"231d72b4.5233ae","type":"plate-recognizer","z":"d9a54719.b13a88","name":"","inputField":"payload","inputFieldType":"msg","outputField":"recognition","outputFieldType":"msg","url":"https://api.platerecognizer.com/v1/plate-reader/","ignoreDuring":false,"makeAndModel":false,"statusText":"scores","cameraId":"","regionFilter":false,"timestamp":false,"regionList":"","regionListType":"json","x":1320,"y":660,"wires":[["28523886.f82428","37b1b695.62fa4a"],["100a66ab.4607f9"]]},{"id":"37b1b695.62fa4a","type":"image","z":"d9a54719.b13a88","name":"Show analyzed image","width":"400","data":"payload","dataType":"msg","thumbnail":false,"active":true,"x":1740,"y":640,"wires":[]},{"id":"869cd924.d991d8","type":"inject","z":"d9a54719.b13a88","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":920,"y":960,"wires":[["f6b6ac8f.40806"]]},{"id":"f6b6ac8f.40806","type":"http request","z":"d9a54719.b13a88","name":"","method":"GET","ret":"bin","paytoqs":false,"url":"https://static.nieuwsblad.be/Assets/Images_Upload/2015/05/24/patton_drivers_2015_1.jpg?maxheight=460&maxwidth=638&scale=both","tls":"","persist":false,"proxy":"","authType":"","x":1100,"y":960,"wires":[["231d72b4.5233ae"]]},{"id":"6ac3b436.692fac","type":"inject","z":"d9a54719.b13a88","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":920,"y":760,"wires":[["d2c4249d.901fd8"]]},{"id":"d2c4249d.901fd8","type":"http request","z":"d9a54719.b13a88","name":"","method":"GET","ret":"bin","paytoqs":false,"url":"http://newscrane.com/wp-content/uploads/2019/09/Car-insurance-Newscrane-02.jpg","tls":"","persist":false,"proxy":"","authType":"","x":1100,"y":760,"wires":[["231d72b4.5233ae"]]},{"id":"9a21e444.b22fc8","type":"inject","z":"d9a54719.b13a88","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":920,"y":560,"wires":[["8e81e18d.39882"]]},{"id":"8e81e18d.39882","type":"http request","z":"d9a54719.b13a88","name":"","method":"GET","ret":"bin","paytoqs":false,"url":"http://www.piepenbroek.nl/foto2010/baltisch/IMG_1499.JPG","tls":"","persist":false,"proxy":"","authType":"","x":1100,"y":560,"wires":[["231d72b4.5233ae"]]},{"id":"5899f099.1b645","type":"comment","z":"d9a54719.b13a88","name":"Two cars","info":"","x":900,"y":520,"wires":[]},{"id":"1db8bd98.23d672","type":"comment","z":"d9a54719.b13a88","name":"One car","info":"","x":890,"y":420,"wires":[]},{"id":"100a66ab.4607f9","type":"debug","z":"d9a54719.b13a88","name":"Errors","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"recognition","targetType":"msg","x":1510,"y":680,"wires":[]},{"id":"cdfaa780.c2fa18","type":"comment","z":"d9a54719.b13a88","name":"Truck with labels","info":"","x":920,"y":920,"wires":[]},{"id":"c1d2f038.82ecc","type":"comment","z":"d9a54719.b13a88","name":"Car at angle","info":"","x":910,"y":720,"wires":[]},{"id":"8c4f0d01.426a2","type":"inject","z":"d9a54719.b13a88","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":920,"y":860,"wires":[["7b580c6e.105764"]]},{"id":"7b580c6e.105764","type":"http request","z":"d9a54719.b13a88","name":"","method":"GET","ret":"bin","paytoqs":false,"url":"https://askautoexperts.com/wp-content/uploads/1931-Dodge-1024x768.jpg","tls":"","persist":false,"proxy":"","authType":"","x":1100,"y":860,"wires":[["231d72b4.5233ae"]]},{"id":"da494e5a.e21ff","type":"comment","z":"d9a54719.b13a88","name":"Another car at angle","info":"","x":930,"y":820,"wires":[]}]
The output message will contain the recognition status (and statusText):
- When the service has finished the recognition without problems, the output message will be send on the first output with status 2xx.
- When the service isn't able to process the recognition, the output message will be send to the second output with status 4xx. The status code will explain what went wrong:
- 403: Forbidden due to incorrect API token.
- 413: The payload is too large and exceeds their upload limits.
- 429: Too many requests have been send in a given amount of time. Upgrade your license for higher number of calls per second.
Since the number of recognitions per month is limited (e.g. 2500 for a free account), it is very useful to determine from time to time how many recognitions are left. This way you can avoid situations where you are not aware that you have run out of recognitions...
A second node ("Plate statistics") has been provided to get the statistics of your account (with 'URL' and 'API token' settings identical as described above):
[{"id":"9b354f46.f081d","type":"plate-statistics","z":"d9a54719.b13a88","name":"","outputField":"payload","outputFieldType":"msg","url":"https://api.platerecognizer.com/v1/statistics/","x":980,"y":480,"wires":[["54063a77.493ae4"]]},{"id":"5653b69.13c4648","type":"inject","z":"d9a54719.b13a88","name":"Get statistics","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":770,"y":480,"wires":[["9b354f46.f081d"]]},{"id":"54063a77.493ae4","type":"debug","z":"d9a54719.b13a88","name":"Plate statistics","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":1190,"y":480,"wires":[]}]
The resulting statistics (in json format) contain the maximum number of statistics, and also the used number of statistics of the current month:
When the input message contains multiple license plates, then the output message will contain an array of license plates. Since not all Node-RED nodes can handle arrays as input, it might be required to split the array into separate items. In other words the single output message (containing an array of N license plates) need to be split into N separate output messages (each one containing a single license plate).
CAUTION: to avoid conflicts, the original input message will be cloned N times. But since the output message also contains the input image, that input image will also be cloned N times. As a result extra system resources (CPU and memory) will be used!
The Split node is a Node-RED core node that can be used to split a single message into multiple messages:
[{"id":"ef944a38.bbef38","type":"plate-recognizer","z":"c8a948fc.76ade8","name":"","inputField":"payload","inputFieldType":"msg","outputField":"payload","outputFieldType":"msg","url":"https://api.platerecognizer.com/v1/plate-reader/","ignoreDuring":true,"makeAndModel":false,"statusText":"count","cameraId":"","regionFilter":false,"regionList":"[]","regionListType":"json","x":780,"y":500,"wires":[["78a775a2.7e37cc","60071b7.6a1cae4"],[]]},{"id":"2e44447f.4e42dc","type":"split","z":"c8a948fc.76ade8","name":"Split array","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":1220,"y":500,"wires":[["745a5d4a.ff4e34"]]},{"id":"5a39a5a9.55c97c","type":"http request","z":"c8a948fc.76ade8","name":"Get video stream","method":"GET","ret":"bin","paytoqs":false,"url":"http://www.piepenbroek.nl/foto2010/baltisch/IMG_1499.JPG","tls":"","persist":false,"proxy":"","authType":"","x":570,"y":500,"wires":[["ef944a38.bbef38","dad27e21.9ab27"]]},{"id":"dad27e21.9ab27","type":"image","z":"c8a948fc.76ade8","name":"","width":"400","data":"payload","dataType":"msg","thumbnail":false,"active":true,"x":780,"y":580,"wires":[]},{"id":"745a5d4a.ff4e34","type":"debug","z":"c8a948fc.76ade8","name":"Show messages","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":1380,"y":500,"wires":[]},{"id":"15ee9680.b977ea","type":"inject","z":"c8a948fc.76ade8","name":"Start the test","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":370,"y":500,"wires":[["5a39a5a9.55c97c"]]},{"id":"78a775a2.7e37cc","type":"change","z":"c8a948fc.76ade8","name":"payload = payload.results","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.results","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1010,"y":500,"wires":[["2e44447f.4e42dc"]]},{"id":"60071b7.6a1cae4","type":"debug","z":"c8a948fc.76ade8","name":"Show messages","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":980,"y":440,"wires":[]}]
The flow explained step by step:
- Start the flow by pressing the button on the Inject-node
- The second node gets an image (e.g. via a http request from an ip camera)
- In the image-preview node you can see that the image contains two license plates
- The plate recognizer node detects two plates
- Via a debug node you can see the json output: it is a single message containing an array of two license plates.
- I move the payloads.result field to the payload field (because the next node expects the array in the payload field).
- The split node splits the array in the payload, which means the single message will be splitted in two separate messages.
- With a debug node you will see that we now have two separate messages, each one containing a single license plate (which can now be handled easily by other nodes in the flow...).
Since the Split node will cause our flow to become a bit more complex, this node offers a build-in split functionality. When the "Send separate message for each plate" checkbox is activated, an input image (containing N license plates) will result in N output messages (each one containing a single license plate).
Edge case: when the input message contains NO license plate, then a single output message will be sent containing an empty result:
Remark: in the latter case, we could have decided to send no output message (since no license plate has been detected). But when somebody sends a picture to the input, he will expect something back ...