{"id":1204,"date":"2026-09-02T17:13:55","date_gmt":"2026-09-03T01:13:55","guid":{"rendered":"https:\/\/salemdata.net\/johnpress\/?p=1204"},"modified":"2026-09-08T13:14:14","modified_gmt":"2026-09-08T21:14:14","slug":"build-your-own-artificial-intelligence-camera-system","status":"publish","type":"post","link":"https:\/\/salemdata.net\/johnpress\/?p=1204","title":{"rendered":"Build Your Own Artificial Intelligence Camera System"},"content":{"rendered":"<div class=\"gfmr-markdown-container\"><div class=\"gfmr-markdown-source\" style=\"display: none;\">&lt;p&gt;I have added artificial intelligence (&#8220;AI&#8221;) to my home surveillance camera system.&lt;\/p&gt;\n&lt;p&gt;No cloud service.&lt;\/p&gt;\n&lt;p&gt;No fees.&lt;\/p&gt;\n&lt;p&gt;No meters.&lt;\/p&gt;\n&lt;p&gt;Everything stays on premises and is totally under my control.&lt;\/p&gt;\n&lt;p&gt;The system I have designed is quite simple:&lt;\/p&gt;\n&lt;ul&gt;\n&lt;li&gt;you need a Raspberry Pi 4B (&lt;a href=&quot;https:\/\/www.adafruit.com\/product\/4296&quot;&gt;Adafruit&lt;\/a&gt; \\$120 &#8211; \\$190) or better,&lt;\/li&gt;\n&lt;li&gt;a Google Coral USB accelerator (~ \\$120, used to be \\$60),&lt;\/li&gt;\n&lt;li&gt;surveillance cameras that expose their rtsp feeds, and&lt;\/li&gt;\n&lt;li&gt;[optional ]Moonfire-nvr &#8211; a Rust-based video recording system developed by Scott Lamb, a former Google engineer.&lt;\/li&gt;\n&lt;\/ul&gt;\n&lt;p&gt;I&#8217;ve been using &lt;a href=&quot;https:\/\/github.com\/scottlamb\/moonfire-nvr&quot;&gt;Moonfire-nvr&lt;\/a&gt; since 2018 and have had up to 13 cameras surveilling my two adjacent properties.\u00a0 \u00a0Moonfire has been rock-solid.\u00a0 I cannot commend\u00a0 Moonfire enough.\u00a0 The reaction I get from people when I suggest Moonfire is that they want something that has AI detection.\u00a0 So I set out to see what I could assemble as a companion AI paradigm, keeping it simple and serviceable.\u00a0 I was thinking I might have to integrate into Moonfire, but I have decided to not pollute Scott&#8217;s code and live with some easy to configure and deploy scripts.\u00a0 Here&#8217;s what I have come up with.\u00a0 Yes, there are a lot of parts&#8230; that&#8217;s the beauty of owning the entire tech stack.\u00a0 And, everything I have added here are scripts, so you&#8217;re off to the races.&lt;\/p&gt;\n&lt;h2&gt;Background&lt;\/h2&gt;\n&lt;p&gt;The detection model I use with Google Coral expects an image 300 \u00d7 300 pixels in size, which is converted into the tensor the model processes. So I started from there and worked backwards because I want to minimize distortion where some software\u00a0 scrunches down and distorts the region in your video to fit the 300 x 300 format.\u00a0 You may not see how much distortion occurs.\u00a0 That&#8217;s the beauty of proprietary software; it hides the ugly shortcuts it might take.&lt;\/p&gt;\n&lt;p&gt;I run most of my cameras at their highest resolution &#8212; I want to have a chance of reading a license plate, or capturing the scar on someone&#8217;s face, or getting the pattern on their socks that readily identifies them.&lt;\/p&gt;\n&lt;p&gt;Yes, one thief served 6 months for stealing my contractor&#8217;s concrete saw.\u00a0 See https:\/\/salemdata.us\/videos\/sawthief.mp4&lt;\/p&gt;\n&lt;p&gt;&lt;img class=&quot;alignleft size-full wp-image-1216&quot; src=&quot;https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_175454_Wed.png&quot; alt=&quot;&quot; width=&quot;614&quot; height=&quot;384&quot; \/&gt;&lt;\/p&gt;\n&lt;p&gt;I capture a full-framed image from a video camera using ffmpeg, a set of software tools for video, audio, and other multimedia files and streams.&lt;\/p&gt;\n&lt;pre&gt;export PASSWORD=[*FILL IN*]\r\nffmpeg -rtsp_transport tcp \\\r\n-i &quot;rtsp:\/\/coral:${PASSWORD}@192.168.1.132:554\/h264Preview_01_main&quot; \\\r\n-ss 2 \\\r\n-hide_banner -loglevel error \\\r\n-frames:v 1 \\\r\n`date +&#039;%Y%m%d_%a_%H%M%S&#039;`_Court180_full.png\r\n&lt;\/pre&gt;\n&lt;p&gt;This produced an image 4608\u00d71728 which is the native resolution.\u00a0 I then load the image into a simple editor written in Python, &lt;strong&gt;roi_select_snapshot_resume.py&lt;\/strong&gt;, I created where I\u00a0 can place 300 x 300 pixel squares, or multiples thereof, e.g. 600 x 600 or 900 x 900 and then define regions of interest.\u00a0 It&#8217;s like using a cookie cutter on a rolled-out dough,&lt;\/p&gt;\n&lt;p&gt;Here&#8217;s a video (no audio) showing a sample session creating ROIs:&lt;\/p&gt;\n<div style=\"width: 0px;\" class=\"wp-video\"><video class=\"wp-video-shortcode\" id=\"video-1204-1\" preload=\"metadata\" controls=\"controls\"><source type=\"video\/mp4\" src=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/2026-09-02-16-55-54.mp4?_=1\" \/><source type=\"\" src=\"\" \/><a href=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/2026-09-02-16-55-54.mp4\">https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/2026-09-02-16-55-54.mp4<\/a><\/video><\/div>\n&lt;p&gt;I place these areas to define regions of interest (&#8220;ROI&#8221;).\u00a0 Here&#8217;s an image containing 8 regions of interest I have defined.&lt;\/p&gt;\n&lt;p&gt;&lt;img class=&quot;alignleft size-full wp-image-1206&quot; src=&quot;https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260824_1012_Court180_full_roi_template_20260902_170156-scaled.png&quot; alt=&quot;&quot; width=&quot;2560&quot; height=&quot;960&quot; \/&gt;&lt;\/p&gt;\n&lt;p&gt;The tool then exports a summary of all the ROIs I created with their coordinates.\u00a0 Here&#8217;s a peek of the ROI export file:&lt;\/p&gt;\n&lt;p&gt;&lt;img class=&quot;alignleft size-full wp-image-1207&quot; src=&quot;https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_170447_Wed.png&quot; alt=&quot;&quot; width=&quot;887&quot; height=&quot;322&quot; \/&gt;&lt;\/p&gt;\n&lt;p&gt;The summary file will be used by the Python program, &lt;strong&gt;detect_image_rois_watchdog.py&lt;\/strong&gt;, which extracts the regions, downsizes them to 300 x 300 if they are large, and then feeds the image to a server where I have running a Perl script, &lt;strong&gt;tpu_broker_cor&lt;\/strong&gt;&lt;br \/&gt;\n&lt;strong&gt;al_json.pl&lt;\/strong&gt;, which, in turn feeds the image to the Coral Tensor Processing Unit (&#8220;TPU&#8221;).\u00a0 The TPU, in turn, produces a JSON result file indicating what it found and gives a &#8220;confidence&#8221; rating as to the match.\u00a0 A reported \u2018person\u2019 at 90% is quite likely a real person; at 5%, it might be a cat, a shadow, a garden troll, or practically anything else. The server returns a JSON report:\u00a0 Here&#8217;s a sample JSON file:&lt;\/p&gt;\n&lt;p&gt;&lt;a href=&quot;http:\/\/salemdata.net\/jlp\/20260902_172305_014_frame006140_roi01_person_0.965.json.txt&quot;&gt;20260902_172305_014_frame006140_roi01_person_0.965.json&lt;\/a&gt;&lt;\/p&gt;\n&lt;p&gt;&lt;strong&gt;detect_image_rois_watchdog.py&lt;\/strong&gt; which prepared the image for submission to the TPU server then saves the 300 x 300 image in a local directory with a file name indicating the date and time and region of interest.\u00a0 \u00a0The detection script saves the returned reports in a SQLite database.&lt;\/p&gt;\n&lt;p&gt;So, thus far, we have a script which captures frames every second, chops up the image into ROIs, sends the square images to the Coral server and stores the image if a person was found and the return report is stored in a database.\u00a0 Currently, I have the current script configured to save the image and its associated JSON report only if the return report says it found a &#8220;person&#8221; with a confidence rating of 70% or more.\u00a0 Also I have all return JSON saved in a SQLite database, this will have to be taken off-line as the database grows too large and really having the extra data is for a study to help train a better model &#8212; but that is for another day.\u00a0 This runs 24\/7.&lt;\/p&gt;\n&lt;p&gt;Then I have a Perl script, &lt;strong&gt;motion_server.pl&lt;\/strong&gt;, which monitors the database and pushes new entries out to any web page subscribing to it.\u00a0 If a person with a confidence rating of 70% was found, &lt;strong&gt;motion_server.pl&lt;\/strong&gt; sends the image along with its report to any subscribing web page.\u00a0 So at the web page, I have a near real-time status page showing activity that is constantly updated as persons are detected.\u00a0 There are currently 8 cells with the newest at the top left and the oldest at the bottom right.\u00a0 Here are screenshots.&lt;\/p&gt;\n&lt;p&gt;The Cluster Page:&lt;\/p&gt;\n&lt;img class=&quot;size-full wp-image-1221&quot; src=&quot;https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_160351_Wed.png&quot; alt=&quot;&quot; width=&quot;1119&quot; height=&quot;784&quot; \/&gt; Cluster Console\n&lt;p&gt;Without the Coral inference box:&lt;\/p&gt;\n&lt;p&gt;&lt;img class=&quot;alignleft size-full wp-image-1208&quot; src=&quot;https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_161122_Wed.png&quot; alt=&quot;&quot; width=&quot;1301&quot; height=&quot;796&quot; \/&gt;&lt;\/p&gt;\n&lt;p&gt;With&lt;img class=&quot;alignleft size-full wp-image-1209&quot; src=&quot;https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_161107_Wed.png&quot; alt=&quot;&quot; width=&quot;1307&quot; height=&quot;800&quot; \/&gt; the Coral inference box:&lt;\/p&gt;\n&lt;p&gt;&nbsp;&lt;\/p&gt;\n&lt;p&gt;&nbsp;&lt;\/p&gt;\n&lt;p&gt;&nbsp;&lt;\/p&gt;\n&lt;p&gt;Lastly, I have another script which acts as a server and if an HTML page in someone&#8217;s browser is refreshed, they will be sent a report of current activity, or clusters of activity.\u00a0 The user can then look at the images making up the cluster, and\/or they can retrieve from Moonfire a video custom created for the cluster&#8217;s time segment.&lt;\/p&gt;\n&lt;p&gt;Here&#8217;s a screenshot of a cluster report, top of the page:&lt;\/p&gt;\n&lt;p&gt;&lt;img class=&quot;alignleft size-full wp-image-1210&quot; src=&quot;https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_160205_Wed.png&quot; alt=&quot;&quot; width=&quot;1313&quot; height=&quot;776&quot; \/&gt;&lt;\/p&gt;\n&lt;p&gt;Bottom of the page:&lt;\/p&gt;\n&lt;p&gt;&lt;img class=&quot;alignleft size-full wp-image-1211&quot; src=&quot;https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_160221_Wed.png&quot; alt=&quot;&quot; width=&quot;1311&quot; height=&quot;809&quot; \/&gt;&lt;\/p&gt;\n&lt;p&gt;Finally, there is an option to display a video relating only to the boundaries of the cluster.&lt;\/p&gt;\n&lt;p&gt;&lt;img class=&quot;alignleft size-full wp-image-1212&quot; src=&quot;https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_160308_Wed.png&quot; alt=&quot;&quot; width=&quot;1324&quot; height=&quot;526&quot; \/&gt;&lt;\/p&gt;\n&lt;p&gt;&nbsp;&lt;\/p&gt;\n&lt;p&gt;&nbsp;&lt;\/p&gt;\n<\/div><div class=\"gfmr-markdown-rendered\"><p>I have added artificial intelligence (&#8220;AI&#8221;) to my home surveillance camera system.<\/p>\n<p>No cloud service.<\/p>\n<p>No fees.<\/p>\n<p>No meters.<\/p>\n<p>Everything stays on premises and is totally under my control.<\/p>\n<p>The system I have designed is quite simple:<\/p>\n<ul>\n<li>you need a Raspberry Pi 4B (<a href=\"https:\/\/www.adafruit.com\/product\/4296\">Adafruit<\/a> \\$120 &#8211; \\$190) or better,<\/li>\n<li>a Google Coral USB accelerator (~ \\$120, used to be \\$60),<\/li>\n<li>surveillance cameras that expose their rtsp feeds, and<\/li>\n<li>[optional ]Moonfire-nvr &#8211; a Rust-based video recording system developed by Scott Lamb, a former Google engineer.<\/li>\n<\/ul>\n<p>I&#8217;ve been using <a href=\"https:\/\/github.com\/scottlamb\/moonfire-nvr\">Moonfire-nvr<\/a> since 2018 and have had up to 13 cameras surveilling my two adjacent properties.\u00a0 \u00a0Moonfire has been rock-solid.\u00a0 I cannot commend\u00a0 Moonfire enough.\u00a0 The reaction I get from people when I suggest Moonfire is that they want something that has AI detection.\u00a0 So I set out to see what I could assemble as a companion AI paradigm, keeping it simple and serviceable.\u00a0 I was thinking I might have to integrate into Moonfire, but I have decided to not pollute Scott&#8217;s code and live with some easy to configure and deploy scripts.\u00a0 Here&#8217;s what I have come up with.\u00a0 Yes, there are a lot of parts&#8230; that&#8217;s the beauty of owning the entire tech stack.\u00a0 And, everything I have added here are scripts, so you&#8217;re off to the races.<\/p>\n<h2>Background<\/h2>\n<p>The detection model I use with Google Coral expects an image 300 \u00d7 300 pixels in size, which is converted into the tensor the model processes. So I started from there and worked backwards because I want to minimize distortion where some software\u00a0 scrunches down and distorts the region in your video to fit the 300 x 300 format.\u00a0 You may not see how much distortion occurs.\u00a0 That&#8217;s the beauty of proprietary software; it hides the ugly shortcuts it might take.<\/p>\n<p>I run most of my cameras at their highest resolution &#8212; I want to have a chance of reading a license plate, or capturing the scar on someone&#8217;s face, or getting the pattern on their socks that readily identifies them.<\/p>\n<p>Yes, one thief served 6 months for stealing my contractor&#8217;s concrete saw.\u00a0 See https:\/\/salemdata.us\/videos\/sawthief.mp4<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignleft size-full wp-image-1216\" src=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_175454_Wed.png\" alt=\"\" width=\"614\" height=\"384\" srcset=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_175454_Wed.png 614w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_175454_Wed-300x188.png 300w\" sizes=\"auto, (max-width: 614px) 100vw, 614px\" \/><\/p>\n<p>I capture a full-framed image from a video camera using ffmpeg, a set of software tools for video, audio, and other multimedia files and streams.<\/p>\n<pre>export PASSWORD=[*FILL IN*]\r\nffmpeg -rtsp_transport tcp \\\r\n-i \"rtsp:\/\/coral:${PASSWORD}@192.168.1.132:554\/h264Preview_01_main\" \\\r\n-ss 2 \\\r\n-hide_banner -loglevel error \\\r\n-frames:v 1 \\\r\n`date +'%Y%m%d_%a_%H%M%S'`_Court180_full.png\r\n<\/pre>\n<p>This produced an image 4608\u00d71728 which is the native resolution.\u00a0 I then load the image into a simple editor written in Python, <strong>roi_select_snapshot_resume.py<\/strong>, I created where I\u00a0 can place 300 x 300 pixel squares, or multiples thereof, e.g. 600 x 600 or 900 x 900 and then define regions of interest.\u00a0 It&#8217;s like using a cookie cutter on a rolled-out dough,<\/p>\n<p>Here&#8217;s a video (no audio) showing a sample session creating ROIs:<\/p>\n<div style=\"width: 960px;\" class=\"wp-video\"><video class=\"wp-video-shortcode\" id=\"video-1204-2\" width=\"960\" height=\"540\" preload=\"metadata\" controls=\"controls\"><source type=\"video\/mp4\" src=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/2026-09-02-16-55-54.mp4?_=2\" \/><a href=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/2026-09-02-16-55-54.mp4\">https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/2026-09-02-16-55-54.mp4<\/a><\/video><\/div>\n<p>I place these areas to define regions of interest (&#8220;ROI&#8221;).\u00a0 Here&#8217;s an image containing 8 regions of interest I have defined.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignleft size-full wp-image-1206\" src=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260824_1012_Court180_full_roi_template_20260902_170156-scaled.png\" alt=\"\" width=\"2560\" height=\"960\" srcset=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260824_1012_Court180_full_roi_template_20260902_170156-scaled.png 2560w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260824_1012_Court180_full_roi_template_20260902_170156-300x113.png 300w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260824_1012_Court180_full_roi_template_20260902_170156-1920x720.png 1920w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260824_1012_Court180_full_roi_template_20260902_170156-768x288.png 768w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260824_1012_Court180_full_roi_template_20260902_170156-1536x576.png 1536w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260824_1012_Court180_full_roi_template_20260902_170156-2048x768.png 2048w\" sizes=\"auto, (max-width: 2560px) 100vw, 2560px\" \/><\/p>\n<p>The tool then exports a summary of all the ROIs I created with their coordinates.\u00a0 Here&#8217;s a peek of the ROI export file:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignleft size-full wp-image-1207\" src=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_170447_Wed.png\" alt=\"\" width=\"887\" height=\"322\" srcset=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_170447_Wed.png 887w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_170447_Wed-300x109.png 300w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_170447_Wed-768x279.png 768w\" sizes=\"auto, (max-width: 887px) 100vw, 887px\" \/><\/p>\n<p>The summary file will be used by the Python program, <strong>detect_image_rois_watchdog.py<\/strong>, which extracts the regions, downsizes them to 300 x 300 if they are large, and then feeds the image to a server where I have running a Perl script, <strong>tpu_broker_cor<\/strong><br \/>\n<strong>al_json.pl<\/strong>, which, in turn feeds the image to the Coral Tensor Processing Unit (&#8220;TPU&#8221;).\u00a0 The TPU, in turn, produces a JSON result file indicating what it found and gives a &#8220;confidence&#8221; rating as to the match.\u00a0 A reported \u2018person\u2019 at 90% is quite likely a real person; at 5%, it might be a cat, a shadow, a garden troll, or practically anything else. The server returns a JSON report:\u00a0 Here&#8217;s a sample JSON file:<\/p>\n<p><a href=\"http:\/\/salemdata.net\/jlp\/20260902_172305_014_frame006140_roi01_person_0.965.json.txt\">20260902_172305_014_frame006140_roi01_person_0.965.json<\/a><\/p>\n<p><strong>detect_image_rois_watchdog.py<\/strong> which prepared the image for submission to the TPU server then saves the 300 x 300 image in a local directory with a file name indicating the date and time and region of interest.\u00a0 \u00a0The detection script saves the returned reports in a SQLite database.<\/p>\n<p>So, thus far, we have a script which captures frames every second, chops up the image into ROIs, sends the square images to the Coral server and stores the image if a person was found and the return report is stored in a database.\u00a0 Currently, I have the current script configured to save the image and its associated JSON report only if the return report says it found a &#8220;person&#8221; with a confidence rating of 70% or more.\u00a0 Also I have all return JSON saved in a SQLite database, this will have to be taken off-line as the database grows too large and really having the extra data is for a study to help train a better model &#8212; but that is for another day.\u00a0 This runs 24\/7.<\/p>\n<p>Then I have a Perl script, <strong>motion_server.pl<\/strong>, which monitors the database and pushes new entries out to any web page subscribing to it.\u00a0 If a person with a confidence rating of 70% was found, <strong>motion_server.pl<\/strong> sends the image along with its report to any subscribing web page.\u00a0 So at the web page, I have a near real-time status page showing activity that is constantly updated as persons are detected.\u00a0 There are currently 8 cells with the newest at the top left and the oldest at the bottom right.\u00a0 Here are screenshots.<\/p>\n<p>The Cluster Page:<\/p>\n<figure id=\"attachment_1221\" aria-describedby=\"caption-attachment-1221\" style=\"width: 1119px\" class=\"wp-caption alignleft\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-1221\" src=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_160351_Wed.png\" alt=\"\" width=\"1119\" height=\"784\" srcset=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_160351_Wed.png 1119w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_160351_Wed-300x210.png 300w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_160351_Wed-768x538.png 768w\" sizes=\"auto, (max-width: 1119px) 100vw, 1119px\" \/><figcaption id=\"caption-attachment-1221\" class=\"wp-caption-text\">Cluster Console<\/figcaption><\/figure>\n<p>Without the Coral inference box:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignleft size-full wp-image-1208\" src=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_161122_Wed.png\" alt=\"\" width=\"1301\" height=\"796\" srcset=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_161122_Wed.png 1301w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_161122_Wed-300x184.png 300w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_161122_Wed-768x470.png 768w\" sizes=\"auto, (max-width: 1301px) 100vw, 1301px\" \/><\/p>\n<p>With<img loading=\"lazy\" decoding=\"async\" class=\"alignleft size-full wp-image-1209\" src=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_161107_Wed.png\" alt=\"\" width=\"1307\" height=\"800\" srcset=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_161107_Wed.png 1307w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_161107_Wed-300x184.png 300w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_161107_Wed-768x470.png 768w\" sizes=\"auto, (max-width: 1307px) 100vw, 1307px\" \/> the Coral inference box:<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>Lastly, I have another script which acts as a server and if an HTML page in someone&#8217;s browser is refreshed, they will be sent a report of current activity, or clusters of activity.\u00a0 The user can then look at the images making up the cluster, and\/or they can retrieve from Moonfire a video custom created for the cluster&#8217;s time segment.<\/p>\n<p>Here&#8217;s a screenshot of a cluster report, top of the page:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignleft size-full wp-image-1210\" src=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_160205_Wed.png\" alt=\"\" width=\"1313\" height=\"776\" srcset=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_160205_Wed.png 1313w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_160205_Wed-300x177.png 300w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_160205_Wed-768x454.png 768w\" sizes=\"auto, (max-width: 1313px) 100vw, 1313px\" \/><\/p>\n<p>Bottom of the page:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignleft size-full wp-image-1211\" src=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_160221_Wed.png\" alt=\"\" width=\"1311\" height=\"809\" srcset=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_160221_Wed.png 1311w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_160221_Wed-300x185.png 300w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_160221_Wed-768x474.png 768w\" sizes=\"auto, (max-width: 1311px) 100vw, 1311px\" \/><\/p>\n<p>Finally, there is an option to display a video relating only to the boundaries of the cluster.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignleft size-full wp-image-1212\" src=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_160308_Wed.png\" alt=\"\" width=\"1324\" height=\"526\" srcset=\"https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_160308_Wed.png 1324w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_160308_Wed-300x119.png 300w, https:\/\/salemdata.net\/johnpress\/wp-content\/uploads\/2026\/09\/20260902_160308_Wed-768x305.png 768w\" sizes=\"auto, (max-width: 1324px) 100vw, 1324px\" \/><\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<\/div><\/div>","protected":false},"excerpt":{"rendered":"<p>&lt;p&gt;I have added artificial intelligence (&#8220;AI&#8221;) to my home surveillance camera system.&lt;\/p&gt; &lt;p&gt;No cloud service.&lt;\/p&gt; &lt;p&gt;No fees.&lt;\/p&gt; &lt;p&gt;No meters.&lt;\/p&gt; &lt;p&gt;Everything stays on premises and is totally under my control.&lt;\/p&gt; &lt;p&gt;The system I have designed is quite simple:&lt;\/p&gt; &lt;ul&gt; &lt;li&gt;you need a Raspberry Pi 4B (&lt;a href=&quot;https:\/\/www.adafruit.com\/product\/4296&quot;&gt;Adafruit&lt;\/a&gt; \\$120 &#8211; \\$190) or better,&lt;\/li&gt; &lt;li&gt;a Google Coral USB [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":1210,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_gfmr_meta_descriptions":[],"_gfmr_multilingual_taxonomy_terms":[],"footnotes":""},"categories":[37],"tags":[143],"class_list":["post-1204","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai","tag-video-surveillance"],"_links":{"self":[{"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/posts\/1204","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1204"}],"version-history":[{"count":9,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/posts\/1204\/revisions"}],"predecessor-version":[{"id":1233,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/posts\/1204\/revisions\/1233"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/media\/1210"}],"wp:attachment":[{"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1204"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1204"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1204"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}