Object Detection using node-red-contrib-tfjs-coco-ssd

In the meantime I worked on object detection using exclusively Node-Red and here is the result:
image
[EDIT] since v0.2.0, tfjs-coco-ssd pass : add option to pass image through node as msg.image

[{"id":"5617fe56.98f7b","type":"debug","z":"a8b1b208.7036f","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":460,"y":2640,"wires":[]},{"id":"83b9c0e7.1436b","type":"debug","z":"a8b1b208.7036f","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"classes","targetType":"msg","x":460,"y":2610,"wires":[]},{"id":"9fec0294.e4c1a","type":"base64","z":"a8b1b208.7036f","name":"","action":"str","property":"image","x":890,"y":2580,"wires":[["4cbfe86c.b89a18"]]},{"id":"e62c51ce.092fd","type":"http request","z":"a8b1b208.7036f","name":"","method":"GET","ret":"bin","paytoqs":false,"url":"https://loremflickr.com/320/240/sport","tls":"","persist":false,"proxy":"","authType":"","x":430,"y":2430,"wires":[["cd835566.3e1698"]]},{"id":"1581af1.85bcf51","type":"ui_button","z":"a8b1b208.7036f","name":"","group":"7c26c8d0.c1e658","order":3,"width":2,"height":1,"passthru":true,"label":"New Picture","tooltip":"","color":"","bgcolor":"","icon":"","payload":"","payloadType":"str","topic":"","x":280,"y":2430,"wires":[["e62c51ce.092fd"]]},{"id":"a295be8c.20765","type":"comment","z":"a8b1b208.7036f","name":"========== ========== TFJS COCO SSD ========== ==========","info":"\n    ","x":330,"y":2540,"wires":[]},{"id":"7dbdcf58.cfb77","type":"debug","z":"a8b1b208.7036f","name":"coco","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":440,"y":2670,"wires":[]},{"id":"9d67d128.0c0d6","type":"image","z":"a8b1b208.7036f","name":"","width":"400","data":"payload","dataType":"msg","thumbnail":false,"active":false,"x":290,"y":2730,"wires":[]},{"id":"44dc9948.c91258","type":"inject","z":"a8b1b208.7036f","name":"test","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":2430,"wires":[["1581af1.85bcf51"]]},{"id":"8e1912af.b653","type":"change","z":"a8b1b208.7036f","name":"pay to detect","rules":[{"t":"move","p":"payload","pt":"msg","to":"detect","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":460,"y":2580,"wires":[["94e6d3d.a32373"]]},{"id":"ccde240c.6ab728","type":"link in","z":"a8b1b208.7036f","name":"coco","links":["23ae07fd.88e498","c710c20f.8d8cb","ed0b6b69.870e78","d137d328.1051d"],"x":185,"y":2580,"wires":[["6a563402.3008bc"]]},{"id":"d137d328.1051d","type":"link out","z":"a8b1b208.7036f","name":"","links":["ccde240c.6ab728"],"x":725,"y":2480,"wires":[]},{"id":"ba873bc5.14dff8","type":"comment","z":"a8b1b208.7036f","name":"coco","info":"","x":120,"y":2580,"wires":[]},{"id":"6a563402.3008bc","type":"tensorflowCoco","z":"a8b1b208.7036f","name":"","model":"","scoreThreshold":"","passthru":true,"x":280,"y":2580,"wires":[["5617fe56.98f7b","83b9c0e7.1436b","8e1912af.b653","7dbdcf58.cfb77","e260cf07.f7485"]]},{"id":"ef49f5fa.acfcc8","type":"fileinject","z":"a8b1b208.7036f","name":"","x":160,"y":2480,"wires":[["cd835566.3e1698"]]},{"id":"94e6d3d.a32373","type":"function","z":"a8b1b208.7036f","name":"Map Objects Boxes get.Picture","func":"//========== retrieves and transmits the image ==========\n//var pictureBuffer = flow.get('pictureBuffer')|| [];\n//msg.pictureBuffer = pictureBuffer;  // send the picture\n\n//========== Detect Empty Objects ==========\nif(empty(msg.classes)){\nmsg.payload = \"no object detected\"  //prepare for the google translate\nmsg.class=\"no object detected\"      //prepare for display in Template Dashboard\nmsg.detect=[]                       //\nreturn msg\n} //no found object \nelse {\n// prepare the color and the thickness of the line in fct of the image size\nmsg.boxcolor = \"yellow\";\nmsg.textcolor = \"yellow\";\n \nif (msg.shape[1] < 300)                             {msg.textfontsize =\"10px\";msg.boxstroke =1;msg.textstroke = \"3px\";}\nelse if (msg.shape[1] >= 300 && msg.shape[1] < 500) {msg.textfontsize =\"15px\";msg.boxstroke =2;msg.textstroke = \"3px\";}\nelse if (msg.shape[1] >= 500 && msg.shape[1] < 900) {msg.textfontsize =\"20px\";msg.boxstroke =2;msg.textstroke = \"5px\";}\nelse if (msg.shape[1] >= 900 && msg.shape[1] < 2000){msg.textfontsize =\"50px\";msg.boxstroke =5;msg.textstroke = \"10px\";}\nelse if (msg.shape[1] >= 2000)                      {msg.textfontsize =\"80px\";msg.boxstroke =10;msg.textstroke = \"20px\";}\n\n//msg.textfontsize =(msg.shape[1] > 600) ? \"70px\" : \"10px\";\n//msg.boxstroke = (msg.shape[1] > 600) ? 5 : 2;\n//msg.textstroke = (msg.shape[1] > 600) ? \"10px\" : \"2px\";\n\n//========== indicates the type of objects and the quantity ==========\n// Get the array of names\nvar names = Object.keys(msg.classes);\nvar firstCount;\n\n// For each name, map it to \"n name\"\nvar parts = names.map((n,i) => {\n    var count = msg.classes[n];\n    if (i === 0) {\n        //Remember the first count to get the \"is/are\" right later\n        firstCount = count;\n    }\n    // Return \"n name\" and get the pluralisation right\n    return count+\" \"+n+(count>1?\"s\":\"\")\n})\n// If there is more than one name, pop off the last one for later\nvar lastName;\nif (parts.length > 1) {\n    lastName = parts.pop();\n}\n// Build up the response getting \"is/are\" right for the first count and joining\n// the array of names with a comma\nmsg.payload = \"There \"+(firstCount === 1 ? \"is\":\"are\")+\" \"+parts.join(\", \")\n// If there was a last name, add that on the end with an 'and', not a comma\nif (lastName) {\n    msg.payload += \" and \"+lastName;\n}\nreturn msg;\n}//fin de si detecte\n\n//========== EMPTY FONCTION ==========\n/*\nHere's a simpler(short) solution to check for empty variables. \nThis function checks if a variable is empty. \nThe variable provided may contain mixed values (null, undefined, array, object, string, integer, function).\n*/\nfunction empty(mixed_var) {\n if (!mixed_var || mixed_var == '0') {  return true; }\n if (typeof mixed_var == 'object') {  \n    for (var k in mixed_var) {   return false;  }\n    return true; \n }\nreturn false;\n}//EMPTY FONCTION END\n","outputs":1,"noerr":0,"x":680,"y":2580,"wires":[["9fec0294.e4c1a"]]},{"id":"cd835566.3e1698","type":"change","z":"a8b1b208.7036f","name":"","rules":[{"t":"set","p":"topic","pt":"msg","to":"testDetection","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":600,"y":2480,"wires":[["d137d328.1051d"]]},{"id":"b4c46bd1.78b018","type":"comment","z":"a8b1b208.7036f","name":"coco","info":"","x":790,"y":2480,"wires":[]},{"id":"b51df799.00cd48","type":"comment","z":"a8b1b208.7036f","name":"========== UPLOAD picture ==========","info":"new picture :320x240\n\n0: object\n    bbox: array[4]\n        0: 127\n        1: 86\n        2: 29\n        3: 90\n    class: \"person\"\n    score: 0.9015087080001831\n    \n    x=127 y=86 width =29  height=90\n    ","x":240,"y":2390,"wires":[]},{"id":"4cbfe86c.b89a18","type":"ui_template","z":"a8b1b208.7036f","group":"7c26c8d0.c1e658","name":"D3 templatev3 TextStroke","order":1,"width":"7","height":"5","format":"<head>\n    <style>\n        :root {\n        --boxcolor: {{msg.boxcolor}};\n        --boxstroke: {{msg.boxstroke}};\n        --textcolor: {{msg.textcolor}};\n        --textfontsize: {{msg.textfontsize}};\n        --textstroke: {{msg.textstroke}};\n        }\n        .imag {\n            width: 100%;\n            height: 100%;\n        }    \n        #svgimage text {\n            font-family: Arial;\n            font-size: var(--textfontsize, 18px);\n            fill       : var(--textcolor, yellow);\n            paint-order: stroke;\n            stroke: black;/*#ffffff;*//*yellow;*/\n            stroke-width:  var(--textstroke, 3px);/*3px;*/\n            font-weight: 600;\n        }\n        rect {\n            fill: blue;\n            fill-opacity: 0;\n            stroke: var(--boxcolor, yellow);\n            stroke-width: var(--boxstroke, 1);\n        }\n        #svgimage {\n            /*background-color: #cccccc;  Used if the image is unavailable */\n            background-repeat: no-repeat;\n            background-size: cover;\n        }\n    </style>\n</head>\n<body>\n    <svg preserveAspectRatio=\"xMidYMid meet\" id=\"svgimage\"  style=\"width:100%\" viewBox=\"0 0 {{msg.shape[1]}} {{msg.shape[0]}}\">\n        <image  class=\"imag\"  href=\"data:image/jpg;base64,{{msg.image}}\"/>\n    </svg>\n    <!-- \n    <svg preserveAspectRatio=\"xMidYMid meet\" id=\"svgimage\"  style=\"width:100%\" viewBox=\"0 0 {{msg.shape[1]}} {{msg.shape[0]}}\">\n        <image  class=\"imag\"  href=\"data:image/jpg;base64,{{msg.pictureBuffer}}\"/>\n    </svg>    \n    \n    -->\n    <div>{{msg.payload}}</div>\n    <script>\n        (function (scope) {\n            scope.$watch('msg', function (msg) {\n                if (msg && msg.detect) {\n                    var svg = d3.select(\"#svgimage\");\n\n                    var box = svg.selectAll(\"rect\").data(msg.detect)\n                        .attr(\"x\", function (d) { return d.bbox[0]; })\n                        .attr(\"y\", function (d) { return d.bbox[1]; })\n                        .attr(\"width\", function (d) { return d.bbox[2]; })\n                        .attr(\"height\", function (d) { return d.bbox[3]; });\n                    box.enter()\n                        .append(\"rect\")\n                        .attr(\"x\", function (d) { return d.bbox[0]; })\n                        .attr(\"y\", function (d) { return d.bbox[1]; })\n                        .attr(\"width\", function (d) { return d.bbox[2]; })\n                        .attr(\"height\", function (d) { return d.bbox[3]; });\n                    box.exit().remove();\n                    \n                    var text = svg.selectAll(\"text\").data(msg.detect)\n                        .text(function (d) { return d.class; })\n                        .attr(\"x\", function (d) { return d.bbox[0]; })\n                        .attr(\"y\", function (d) { return 10 + d.bbox[1]; });\n                    text.enter()\n                        .append(\"text\")\n                        .text(function (d) { return d.class; })\n                        .attr(\"x\", function (d) { return d.bbox[0]; })\n                        .attr(\"y\", function (d) { return 10 + d.bbox[1]; });\n                    text.exit().remove();\n                }\n            });\n        })(scope);\n    </script>\n</body>","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":1080,"y":2580,"wires":[[]]},{"id":"e260cf07.f7485","type":"change","z":"a8b1b208.7036f","name":"image to pay","rules":[{"t":"move","p":"image","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":280,"y":2690,"wires":[["9d67d128.0c0d6"]]},{"id":"7c26c8d0.c1e658","type":"ui_group","z":"","name":"detection","tab":"b123e6c8.d3e948","order":2,"disp":true,"width":"10","collapse":true},{"id":"b123e6c8.d3e948","type":"ui_tab","z":"","name":"Slide","icon":"dashboard","order":2,"disabled":false,"hidden":false}]

What do you think about @dceejay ?

Special Thanks to

  • @Andrei for the CSS help in the Template node
  • And @knolleary for helping me put the object list into sentences
5 Likes