robmarkcole / HASS-Deepstack-object

Home Assistant custom component for using Deepstack object detection
https://community.home-assistant.io/t/face-and-person-detection-with-deepstack-local-and-free/92041
MIT License
440 stars 98 forks source link

Suggestion: accept filename as service call parameter #77

Closed jiiins closed 4 years ago

jiiins commented 5 years ago

When running several automations in parallel, it becomes very hard to grab the right saved image via the event, as the image_scan processing time can vary quite a bit. It would be very useful to be able to set the filename when calling the image_processing.scan service.

For example, in NodeRED I capture 10 frames upon motion detection, keep the one with the highest score and send the image out. With the current system I can't guarantee that the image is the correct one. Or maybe I'm missing something...

rbflurry commented 5 years ago

Mind sharing your node-red flow? I currently get spammed with images while motion is detected and like your approach.

Could you track the file_saved events to the object_detected events and use that information?

In node red I think you could combine the two events into one message. Wait for 10 combined messages and only pass the highest confidence?

jiiins commented 5 years ago

Here is the flow with 4 snaps. It's a bit contorted and still work in progress, but so far it's been pretty accurate at filtering false positives: [{"id":"1556434c.2d9d7d","type":"tab","label":"Flow 2","disabled":false,"info":""},{"id":"14634217.1d306e","type":"api-call-service","z":"1556434c.2d9d7d","name":"TensorFlow Scan","server":"31d2968c.bfafda","version":1,"service_domain":"image_processing","service":"scan","entityId":"{{data.image_processing_tensorflow}}","data":"","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":290,"y":620,"wires":[["c179e2ae.168ac"]]},{"id":"83f6e906.03da98","type":"server-state-changed","z":"1556434c.2d9d7d","name":"","server":"31d2968c.bfafda","version":1,"entityidfilter":"_vmd4_camera1profileany","entityidfiltertype":"substring","outputinitially":true,"state_type":"habool","haltifstate":"true","halt_if_type":"bool","halt_if_compare":"is","outputs":2,"output_only_on_state_change":false,"x":320,"y":50,"wires":[["3bf631df.4c34be"],[]]},{"id":"c179e2ae.168ac","type":"api-current-state","z":"1556434c.2d9d7d","name":"get TensorFlow state","server":"31d2968c.bfafda","version":1,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","override_topic":false,"entity_id":"{{data.image_processing_tensorflow}}","state_type":"num","state_location":"","override_payload":"none","entity_location":"data.imgproc_states","override_data":"msg","blockInputOverrides":false,"x":500,"y":600,"wires":[["e2b3b5f9.5b1ea8"]]},{"id":"bc870bfc.53bb38","type":"api-current-state","z":"1556434c.2d9d7d","name":"On/Off General","server":"31d2968c.bfafda","version":1,"outputs":2,"halt_if":"true","halt_if_type":"bool","halt_if_compare":"is","override_topic":false,"entity_id":"input_boolean.motion_detection","state_type":"habool","state_location":"","override_payload":"none","entity_location":"","override_data":"none","blockInputOverrides":true,"x":260,"y":130,"wires":[["1d0c9e22.566242"],["d17cafc8.2ecea"]]},{"id":"8945f5e9.ecfed8","type":"api-call-service","z":"1556434c.2d9d7d","name":"Deepstack Scan","server":"31d2968c.bfafda","version":1,"service_domain":"image_processing","service":"scan","entityId":"{{data.image_processing_deepstack}}","data":"","dataType":"json","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":280,"y":720,"wires":[["6d6d6cb1.f1a354"]]},{"id":"6d6d6cb1.f1a354","type":"api-current-state","z":"1556434c.2d9d7d","name":"get Deepstack state","server":"31d2968c.bfafda","version":1,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","override_topic":false,"entity_id":"{{data.image_processing_deepstack}}","state_type":"num","state_location":"","override_payload":"none","entity_location":"data.imgproc_states","override_data":"msg","blockInputOverrides":false,"x":500,"y":700,"wires":[["3fe4f8b3.3ee6c8"]]},{"id":"63bd790f.c76738","type":"debug","z":"1556434c.2d9d7d","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":690,"y":1030,"wires":[]},{"id":"1cdc4ae.81b6eb5","type":"function","z":"1556434c.2d9d7d","name":"Prepare Telegram","func":"\nmsg.payload = {\n \"photo\" : msg.snapshot,\n \"caption\" : msg.payload\n };\n\nmsg.method = \"sendPhoto\";\n\nreturn msg;","outputs":1,"noerr":0,"x":910,"y":910,"wires":[["5ecd7f.dd05028"]]},{"id":"d17cafc8.2ecea","type":"api-current-state","z":"1556434c.2d9d7d","name":"Entrance On/Off","server":"31d2968c.bfafda","version":1,"outputs":2,"halt_if":"true","halt_if_type":"bool","halt_if_compare":"is","override_topic":false,"entity_id":"input_boolean.motion_detection_entrance","state_type":"habool","state_location":"","override_payload":"none","entity_location":"","override_data":"none","blockInputOverrides":true,"x":500,"y":130,"wires":[["1d0c9e22.566242"],[]]},{"id":"7a2ab355.bd35fc","type":"change","z":"1556434c.2d9d7d","name":"Define variables","rules":[{"t":"set","p":"base_id","pt":"msg","to":"test_studio","tot":"str"},{"t":"set","p":"base_id","pt":"msg","to":"$match(initial_entity_id, /binary_sensor.(\\w+)_vmd4_.+/).groups[0].$string()","tot":"jsonata"},{"t":"set","p":"data.image_processing_deepstack","pt":"msg","to":"\"image_processing.deepstack_object_\" & $.base_id","tot":"jsonata"},{"t":"set","p":"data.image_processing_tensorflow","pt":"msg","to":"\"image_processing.tensorflow_\" & $.base_id","tot":"jsonata"},{"t":"set","p":"data.image_processing_tensorflow_2","pt":"msg","to":"\"image_processing.tensorflow_\" & $.base_id & \"_2\"","tot":"jsonata"},{"t":"set","p":"data.tf_image","pt":"msg","to":"$.data.img_dir & \"tensorflow/\" & $.base_id & \"_latest.jpg\"","tot":"jsonata"},{"t":"set","p":"data.ds_image","pt":"msg","to":"$.data.img_dir & \"deepstack/deepstack_latest_person.jpg\"","tot":"jsonata"},{"t":"set","p":"image_join_args","pt":"msg","to":"$.data.tf_image & \" \" & $.data.ds_image & \" -append \" & $.data.img_dir & $.base_id & \"_joined.jpg\"","tot":"jsonata"},{"t":"set","p":"snapshot","pt":"msg","to":"$.data.img_dir & \"snapshots/\" & $.base_id & \"_\" & $.myepoch & \".jpg\"","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":280,"y":540,"wires":[["9a454801.7ba7e8","2c418be3.010e84","8945f5e9.ecfed8","14634217.1d306e"]]},{"id":"e0e54097.852e3","type":"join","z":"1556434c.2d9d7d","name":"","mode":"custom","build":"object","property":"data.imgproc_states","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"2","count":"2","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":990,"y":660,"wires":[["84681bfe.0bf418"]]},{"id":"9a454801.7ba7e8","type":"change","z":"1556434c.2d9d7d","name":"Clear join","rules":[{"t":"set","p":"reset","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":740,"y":540,"wires":[["e0e54097.852e3"]]},{"id":"84681bfe.0bf418","type":"function","z":"1556434c.2d9d7d","name":"Test Person Consensus","func":"//node.warn(msg._msgid);\n//node.warn(msg.img_last);\n\nvar tf_data = msg.data.imgproc_states.tf || false;\nvar tf = 0;\nif (tf_data !== false){\n tf = tf_data.attributes.summary.person || 0;\n}\n\nvar ds_data = msg.data.imgproc_states.ds || false;\nvar ds = 0;\nif (ds_data !== false){\n ds = msg.data.imgproc_states.ds.attributes.all_predictions.person || 0; \n}\n\nmsg.fullMatch = false;\n\nvar msid = flow.get('motionArray');\n\n// retrieve saved highest score\nscores = msid[msg._msgid][1] || {};\n//scores = msid[msg._msgid][1];\n\n\n// get image processing scores\nif (tf > 0){\n var tf_person1 = (Math.round(msg.data.imgproc_states.tf.attributes.matches.person[0].score * 100) / 100) || 0;\n //node.warn('t' + tf_person1);\n}\n\nif (ds > 0){\n var ds_person1 = (Math.round(msg.data.imgproc_states.ds.attributes.target_confidences[0] * 100) / 100) || 0;\n //node.warn('d' + ds_person1);\n}\n\n// sum up scores\nvar new_sum = tf_person1 || 0 + ds_person1 || 0;\nvar old_sum = scores['tf'] || 0 + scores['ds'] || 0;\n\n//node.warn(msg._msgid + \" - \" + new_sum);\n\n// keep the highest composite score so far\nif (new_sum > old_sum){\n scores = {\"tf\" : tf_person1, \"ds\" : ds_person1, \"snapshot\" : msg.snapshot};\n //node.warn('new highest score');\n \n}\n\nmsid[msg._msgid][1] = scores;\n\n// if it's a clear match, just send it out - everything ends\nif (tf_person1 > 75 && ds_person1 > 60){ \n msg.payload = msg.base_id + \" - TF: \" + tf_person1 + \" - DS: \" + ds_person1;\n msg.fullMatch = true;\n\n msid[msg._msgid][0] = true;\n node.warn(msg._msgid + ' clear match');\n\n\n// else, if it's the last img, send out the best we got (if any)\n} else if (msg.img_last || false){\n \n node.warn(msg._msgid + ' final score: TF: ' + scores['tf'] + ' DS: ' + scores['ds']);\n \n var temp_snapshot = msg.snapshot;\n \n if (scores['tf'] > 70 || scores['ds'] > 40) {\n msg.payload = msg.base_id + \" - TF: \" + (scores['tf'] || 'x') + \" - DS: \" + (scores['ds'] || 'x');\n\n msg.snapshot = scores['snapshot'];\n msid[msg._msgid][0] = true;\n node.warn(msg._msgid + ' partial match');\n \n \n } else if (scores['tf'] > 50 || scores['ds'] > 35) {\n msg.payload = \"Trash? \" + msg.base_id + \" - TF: \" + (scores['tf'] || 'x') + \" - DS: \" + (scores['ds'] || 'x');\n\n msg.snapshot = scores['snapshot'];\n msid[msg._msgid][0] = true;\n node.warn(msg._msgid + ' probably trash');\n \n } else {\n node.warn(msg._msgid + ' score too low, discard');\n var msg2 = { payload : temp_snapshot };\n msg = undefined;\n }\n\n} else {\n \n //node.warn('delete snapshot');\n //var msg2 = { payload : msg.snapshot };\n msg = undefined;\n}\n\n// store back the flow array\nflow.set('motionArray', msid);\n\n//node.warn(msg || '');\nreturn [msg, msg2];\n","outputs":2,"noerr":0,"x":310,"y":830,"wires":[["88ed4ec9.cc9ff"],["d4d8a2c4.a578a"]]},{"id":"e2b3b5f9.5b1ea8","type":"change","z":"1556434c.2d9d7d","name":"","rules":[{"t":"set","p":"topic","pt":"msg","to":"tf","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":730,"y":620,"wires":[["e0e54097.852e3"]]},{"id":"3fe4f8b3.3ee6c8","type":"change","z":"1556434c.2d9d7d","name":"","rules":[{"t":"set","p":"topic","pt":"msg","to":"ds","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":730,"y":720,"wires":[["e0e54097.852e3"]]},{"id":"2c418be3.010e84","type":"api-call-service","z":"1556434c.2d9d7d","name":"Get snapshot for Telegram","server":"31d2968c.bfafda","version":1,"service_domain":"camera","service":"snapshot","entityId":"","data":"{\t\"entity_id\" : \"camera.\" & $.base_id,\t\"filename\" : $.data.img_dir_service & $.base_id & \"_\" & $.myepoch & \".jpg\"\t}","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":760,"y":450,"wires":[[]]},{"id":"21f4a465.81c92c","type":"simpletime","z":"1556434c.2d9d7d","name":"","x":290,"y":390,"wires":[["cfe3e11c.b9cec"]]},{"id":"1d0c9e22.566242","type":"change","z":"1556434c.2d9d7d","name":"Define constants","rules":[{"t":"set","p":"data.img_dir","pt":"msg","to":"/ha-www/","tot":"str"},{"t":"set","p":"data.img_dir_service","pt":"msg","to":"/config/www/snapshots/","tot":"str"},{"t":"set","p":"initial_entity_id","pt":"msg","to":"data.entity_id","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":270,"y":230,"wires":[["21f4a465.81c92c","86e3b472.998a68","4155f94c.a68e18","f7b02860.145cf8","b210b7ea.8a6658"]]},{"id":"ddb611c2.beb5e","type":"exec","z":"1556434c.2d9d7d","command":"rm","addpay":true,"append":"","useSpawn":"false","timer":"60","oldrc":false,"name":"Remove useless snapshot","x":480,"y":970,"wires":[[],["63bd790f.c76738"],[]]},{"id":"86e3b472.998a68","type":"delay","z":"1556434c.2d9d7d","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":540,"y":190,"wires":[["21f4a465.81c92c"]]},{"id":"4155f94c.a68e18","type":"delay","z":"1556434c.2d9d7d","name":"","pauseType":"delay","timeout":"2","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":540,"y":230,"wires":[["21f4a465.81c92c"]]},{"id":"f7b02860.145cf8","type":"delay","z":"1556434c.2d9d7d","name":"","pauseType":"delay","timeout":"3","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":540,"y":270,"wires":[["21f4a465.81c92c"]]},{"id":"d4d8a2c4.a578a","type":"delay","z":"1556434c.2d9d7d","name":"","pauseType":"delay","timeout":"2","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":240,"y":970,"wires":[["ddb611c2.beb5e"]]},{"id":"250da80d.a558a8","type":"debug","z":"1556434c.2d9d7d","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":1190,"y":820,"wires":[]},{"id":"88ed4ec9.cc9ff","type":"function","z":"1556434c.2d9d7d","name":"Store state for positive check upstream","func":"// retrive existing positive state\nmsid = flow.get('motionArray');\n\nmsid[msg._msgid][0] = true;\n \n// store the new state\nflow.set('motionArray', msid);\n\nnode.warn(msg._msgid + ' sending');\n\nreturn msg;","outputs":1,"noerr":0,"x":690,"y":830,"wires":[["1cdc4ae.81b6eb5"]]},{"id":"cfe3e11c.b9cec","type":"function","z":"1556434c.2d9d7d","name":"No positives yet?","func":"// retrieve positive state\nmsid = flow.get('motionArray');\n\n//node.warn(msid[msg._msgid]);\n\n// continue only if a positive hasn't been found\nif (msid[msg._msgid][0] === false ){\n return msg;\n}","outputs":1,"noerr":0,"x":490,"y":390,"wires":[["7a2ab355.bd35fc"]]},{"id":"b210b7ea.8a6658","type":"delay","z":"1556434c.2d9d7d","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":540,"y":310,"wires":[["74b1a13.216f96"]]},{"id":"5ecd7f.dd05028","type":"switch","z":"1556434c.2d9d7d","name":"","property":"fullMatch","propertyType":"msg","rules":[{"t":"true"},{"t":"false"}],"checkall":"true","repair":false,"outputs":2,"x":1010,"y":990,"wires":[["250da80d.a558a8"],["250da80d.a558a8"]]},{"id":"3bf631df.4c34be","type":"function","z":"1556434c.2d9d7d","name":"Create positive state","func":"// retrieve existing flow array and add our msgid entry as false to initialize it\n\nmsid = flow.get('motionArray') || [];\n\n//msid[msg._msgid] = [];\n\nvar msid_done = false;\nvar msid_scores = [];\n\n\nmsid[msg._msgid] = [msid_done, msid_scores];\n\n// get entries for delayed clean up\ncleanUp = flow.get('motionCleanUp') || [];\n\n\n// clean up old entries\nfor (const [key, value] of Object.entries(cleanUp)) {\n \n //node.warn(value);\n \n // delete entries older than 5 minutes in both arrays\n var age = Date.now() - Date.parse(value);\n \n if (age > 300000) {\n delete cleanUp[key];\n delete msid[key];\n }\n}\n\n// add current one\ncleanUp[msg._msgid] = msg.data.new_state.last_updated;\n\n// put back arrays in as flow vars\nflow.set('motionArray', msid);\nflow.set('motionCleanUp', cleanUp);\n\nreturn msg;","outputs":1,"noerr":0,"x":640,"y":50,"wires":[["bc870bfc.53bb38"]]},{"id":"74b1a13.216f96","type":"change","z":"1556434c.2d9d7d","name":"","rules":[{"t":"set","p":"img_last","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":750,"y":310,"wires":[["21f4a465.81c92c"]]},{"id":"31d2968c.bfafda","type":"server","z":"","name":"Home Assistant","legacy":false,"hassio":false,"rejectUnauthorizedCerts":false,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true}]

I can clarify if you want...

PS: this might not work as expected with NR 1.0 as the sync logic was changed.

robmarkcole commented 4 years ago

The correct approach is to use folder_watcher