In the meantime I worked on object detection using exclusively Node-Red and here is the result:
[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