<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>http://wiki.staging.zoneminder.com/index.php?action=history&amp;feed=atom&amp;title=Python_Scripts_for_Image_Detection</id>
	<title>Python Scripts for Image Detection - Revision history</title>
	<link rel="self" type="application/atom+xml" href="http://wiki.staging.zoneminder.com/index.php?action=history&amp;feed=atom&amp;title=Python_Scripts_for_Image_Detection"/>
	<link rel="alternate" type="text/html" href="http://wiki.staging.zoneminder.com/index.php?title=Python_Scripts_for_Image_Detection&amp;action=history"/>
	<updated>2026-05-03T14:41:14Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.37.1</generator>
	<entry>
		<id>http://wiki.staging.zoneminder.com/index.php?title=Python_Scripts_for_Image_Detection&amp;diff=17765&amp;oldid=prev</id>
		<title>Burger: /* One */</title>
		<link rel="alternate" type="text/html" href="http://wiki.staging.zoneminder.com/index.php?title=Python_Scripts_for_Image_Detection&amp;diff=17765&amp;oldid=prev"/>
		<updated>2025-09-01T18:50:39Z</updated>

		<summary type="html">&lt;p&gt;&lt;span dir=&quot;auto&quot;&gt;&lt;span class=&quot;autocomment&quot;&gt;One&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 14:50, 1 September 2025&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l1&quot;&gt;Line 1:&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;Line 1:&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;These are alternatives to ZMES.&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;These are alternatives to ZMES.&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br/&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br/&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;==&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;One&lt;/del&gt;==&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;==&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;zmai.py&lt;/ins&gt;==&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-deleted&quot;&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;A copy of https://gist.github.com/mdetweiler/e987d56df9a63290b562e6e7b00489c6  in case it goes offline.&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&amp;lt;pre&amp;gt;&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&amp;lt;pre&amp;gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;#!/home/zmadmin/zmai/venv/bin/python3&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;#!/home/zmadmin/zmai/venv/bin/python3&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;</summary>
		<author><name>Burger</name></author>
	</entry>
	<entry>
		<id>http://wiki.staging.zoneminder.com/index.php?title=Python_Scripts_for_Image_Detection&amp;diff=17764&amp;oldid=prev</id>
		<title>Burger: Created page with &quot;These are alternatives to ZMES.  ==One== &lt;pre&gt; #!/home/zmadmin/zmai/venv/bin/python3 #  The path above is to the python virtual environment that can be created # #  zmai.py by Michael Detweiler # #  Script to prompt the Google Gemini Flash multi-modal image analysis AI model  #  and extract specified information from images. # #  For ZoneMinder motion dected image analysis. #  Can be used to gather any (prompted) specified details from images that cause an alert to fire....&quot;</title>
		<link rel="alternate" type="text/html" href="http://wiki.staging.zoneminder.com/index.php?title=Python_Scripts_for_Image_Detection&amp;diff=17764&amp;oldid=prev"/>
		<updated>2025-09-01T18:49:40Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;These are alternatives to ZMES.  ==One== &amp;lt;pre&amp;gt; #!/home/zmadmin/zmai/venv/bin/python3 #  The path above is to the python virtual environment that can be created # #  zmai.py by Michael Detweiler # #  Script to prompt the Google Gemini Flash multi-modal image analysis AI model  #  and extract specified information from images. # #  For ZoneMinder motion dected image analysis. #  Can be used to gather any (prompted) specified details from images that cause an alert to fire....&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;These are alternatives to ZMES.&lt;br /&gt;
&lt;br /&gt;
==One==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/home/zmadmin/zmai/venv/bin/python3&lt;br /&gt;
#  The path above is to the python virtual environment that can be created&lt;br /&gt;
#&lt;br /&gt;
#  zmai.py by Michael Detweiler&lt;br /&gt;
#&lt;br /&gt;
#  Script to prompt the Google Gemini Flash multi-modal image analysis AI model &lt;br /&gt;
#  and extract specified information from images.&lt;br /&gt;
#&lt;br /&gt;
#  For ZoneMinder motion dected image analysis.&lt;br /&gt;
#  Can be used to gather any (prompted) specified details from images that cause an alert to fire.&lt;br /&gt;
#  For vehicle information and direction tracking in ZoneMinder monitor (ZM 1.37 and later):&lt;br /&gt;
#  Just add this script to your &amp;quot;Recording&amp;quot; option &amp;quot;Event Start Command&amp;quot; in your monitor config &amp;quot;Recording&amp;quot; section.&lt;br /&gt;
#  Specify the full path to the script... /home/zmadmin/zmai/zmai.py &lt;br /&gt;
#  Create one &amp;quot;Active&amp;quot; zone and a post event buffer of a few seconds or more as needed.&lt;br /&gt;
#&lt;br /&gt;
#  Note: You can use the output of the analysis to create alert audio over a speaker system&lt;br /&gt;
#  with TalkKonnect or Sonos.&lt;br /&gt;
#&lt;br /&gt;
#  Create a directory in /home/&amp;lt;user&amp;gt;/ for the script&lt;br /&gt;
#  mkdir zmai&lt;br /&gt;
#  chown www-data zmai&lt;br /&gt;
#  chgrp www-data zmai&lt;br /&gt;
#  On Ubuntu... chmod 755 /home/&amp;lt;user&amp;gt;&lt;br /&gt;
#  cd zmai/&lt;br /&gt;
#  Create the script and make sure permissions and ownership are set&lt;br /&gt;
#  chmod 755 zmai.py&lt;br /&gt;
#  chown www-data zmai.py&lt;br /&gt;
#  chgrp www-data zmai.py&lt;br /&gt;
#  sudo apt install python3.10-venv&lt;br /&gt;
#  python3 -m venv venv&lt;br /&gt;
#  source venv/bin/activate&lt;br /&gt;
#  In the virtual env...&lt;br /&gt;
#  pip3 install google-genai&lt;br /&gt;
#  pip3 install pillow&lt;br /&gt;
#  pip3 install mysql-connector-python&lt;br /&gt;
#  deactivate ... when done to exit the venv&lt;br /&gt;
#&lt;br /&gt;
#  Test with a previously recorded event for that monitor:&lt;br /&gt;
#  sudo su -s/bin/bash -c&amp;quot;/home/zmadmin/zmai/zmai.py 90608 1&amp;quot; www-data&lt;br /&gt;
&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
import mysql.connector&lt;br /&gt;
import logging&lt;br /&gt;
from time import sleep&lt;br /&gt;
#Image analysis imports&lt;br /&gt;
from PIL import Image&lt;br /&gt;
import io&lt;br /&gt;
from google import genai&lt;br /&gt;
from google.genai import types&lt;br /&gt;
import json&lt;br /&gt;
import re&lt;br /&gt;
&lt;br /&gt;
logging.basicConfig(&lt;br /&gt;
    filename=&amp;#039;/tmp/zmai.log&amp;#039;,&lt;br /&gt;
    format=&amp;#039;%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s&amp;#039;,&lt;br /&gt;
    level=logging.INFO,&lt;br /&gt;
    datefmt=&amp;#039;%Y-%m-%d %H:%M:%S&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
#/var/cache/zoneminder/events/1/2025-08-15/90640/00001-capture.jpg&lt;br /&gt;
base_img_path = &amp;#039;/var/cache/zoneminder/events/&amp;#039;&lt;br /&gt;
&lt;br /&gt;
# Initialize the Gemini client (ensure your API key is configured)&lt;br /&gt;
client = genai.Client(api_key=&amp;quot;AIzaSyBtH7GaCKhHlfnF7C1dz2NjxKZz2acyws8&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def main():&lt;br /&gt;
    logging.info(&amp;quot;----------START EVENT----------&amp;quot;)&lt;br /&gt;
    eventId = sys.argv[1]&lt;br /&gt;
    monitorId = sys.argv[2]&lt;br /&gt;
    logging.info(&amp;quot;eventId = {0}&amp;quot;.format(eventId))&lt;br /&gt;
    logging.info(&amp;quot;monitorId = {0}&amp;quot;.format(monitorId))&lt;br /&gt;
&lt;br /&gt;
    mydb = mysql.connector.connect(&lt;br /&gt;
        host=&amp;quot;localhost&amp;quot;,&lt;br /&gt;
        user=&amp;quot;zmuser&amp;quot;,&lt;br /&gt;
        password=&amp;quot;zmpass&amp;quot;,&lt;br /&gt;
        database=&amp;quot;zm&amp;quot;&lt;br /&gt;
    )&lt;br /&gt;
    connection = mydb&lt;br /&gt;
&lt;br /&gt;
    with connection.cursor() as cursor:&lt;br /&gt;
        query = &amp;quot;SELECT Name FROM Monitors WHERE Id = {0}&amp;quot;.format(monitorId)&lt;br /&gt;
        logging.info(query)&lt;br /&gt;
        try:&lt;br /&gt;
            cursor.execute(query)&lt;br /&gt;
            monitor_name = cursor.fetchone()[0]&lt;br /&gt;
            logging.info(&amp;quot;monitor_name = {0}&amp;quot;.format(monitor_name))&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logging.info(&amp;quot;Error: {0}&amp;quot;.format(e))&lt;br /&gt;
&lt;br /&gt;
        #Get the highest score image so far and hope it is a good one&lt;br /&gt;
        query = &amp;quot;SELECT TimeStamp,FrameId FROM Frames WHERE EventId = {0} ORDER BY score DESC LIMIT 1&amp;quot;.format(eventId)&lt;br /&gt;
        logging.info(query)&lt;br /&gt;
        try:&lt;br /&gt;
            cursor.execute(query)&lt;br /&gt;
            datestamp_obj,frame_id = cursor.fetchall()[0]&lt;br /&gt;
            #datestamp_obj = cursor.fetchone()[0]&lt;br /&gt;
            datestamp = datestamp_obj.strftime(&amp;quot;%Y-%m-%d&amp;quot;)&lt;br /&gt;
            logging.info(&amp;quot;**** Datestamp = {0} ****&amp;quot;.format(datestamp))&lt;br /&gt;
            logging.info(&amp;quot;**** FrameId = {0} ****&amp;quot;.format(frame_id))&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logging.info(&amp;quot;Error: {0}&amp;quot;.format(e))&lt;br /&gt;
&lt;br /&gt;
        total_length = 5 #Total length of the zero padded frame name number part&lt;br /&gt;
        padded_number = str(frame_id).zfill(total_length)&lt;br /&gt;
        #/var/cache/zoneminder/events/1/2025-08-15/90640/00001-capture.jpg&lt;br /&gt;
        ipath = base_img_path + monitorId + &amp;#039;/&amp;#039; + datestamp + &amp;#039;/&amp;#039; + eventId + &amp;#039;/&amp;#039; + padded_number + &amp;#039;-capture.jpg&amp;#039;&lt;br /&gt;
&lt;br /&gt;
        #Submit image path to LLM for analysis&lt;br /&gt;
        logging.info(ipath)&lt;br /&gt;
        img_data = analize_img(ipath)&lt;br /&gt;
        #Use the image data to create audio or other types of alerts!&lt;br /&gt;
        logging.info(img_data)&lt;br /&gt;
&lt;br /&gt;
    logging.info(&amp;quot;*----------END EVENT----------*&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
def analize_img(image_path):&lt;br /&gt;
    # Load the image&lt;br /&gt;
    with open(image_path, &amp;#039;rb&amp;#039;) as f:&lt;br /&gt;
        image_bytes = f.read()&lt;br /&gt;
    response = client.models.generate_content(&lt;br /&gt;
        model=&amp;quot;gemini-2.5-flash&amp;quot;,  # Or your chosen model&lt;br /&gt;
        contents=[&lt;br /&gt;
            &amp;#039;&amp;#039;&amp;#039;&amp;#039;Describe the content of this image as only the fields described in the following text.&lt;br /&gt;
            Use json output where all results are provided in a list of results like {&amp;quot;results&amp;quot;: [...]}. &lt;br /&gt;
            If there is a human, output the number of humans and the body size as adult or child in json &lt;br /&gt;
            format as {&amp;quot;subject&amp;quot;:&amp;quot;human&amp;quot;,&amp;quot;count&amp;quot;:&amp;quot;xxx&amp;quot;,&amp;quot;size&amp;quot;:&amp;quot;xxx&amp;quot;}. &lt;br /&gt;
            If there is an animal, output the number of animals and the species in json &lt;br /&gt;
            format as {&amp;quot;subject&amp;quot;:&amp;quot;animal&amp;quot;,&amp;quot;count&amp;quot;:&amp;quot;xxx&amp;quot;,&amp;quot;species&amp;quot;:&amp;quot;xxx&amp;quot;}. &lt;br /&gt;
            If there is a vehicle, output the make, model, year, color, vehicle&amp;#039;s most prominant orientation in the image &lt;br /&gt;
            view as left side or right side,&lt;br /&gt;
            type as car, suv, truck (Amazon, Fedex, UPS or the lettering on it if it forms words) &lt;br /&gt;
            and include the license plate numbers and letters if visible in json &lt;br /&gt;
            format as {&amp;quot;subject&amp;quot;:&amp;quot;vehicle&amp;quot;,&amp;quot;make&amp;quot;:&amp;quot;xxx&amp;quot;,&amp;quot;model&amp;quot;:&amp;quot;xxx&amp;quot;,&amp;quot;year&amp;quot;:&amp;quot;xxx&amp;quot;,&amp;quot;color&amp;quot;:&amp;quot;xxx&amp;quot;,&amp;quot;orientation&amp;quot;:&amp;quot;xxx&amp;quot;,&amp;quot;type&amp;quot;:&amp;quot;xxx&amp;quot;,&amp;quot;license&amp;quot;:&amp;quot;xxx&amp;quot;}&amp;#039;&amp;#039;&amp;#039;,&lt;br /&gt;
            types.Part.from_bytes(data=image_bytes, mime_type=&amp;quot;image/jpeg&amp;quot;),&lt;br /&gt;
        ],&lt;br /&gt;
        #config=types.GenerateContentConfig(&lt;br /&gt;
        #    thinking_config=types.ThinkingConfig(thinking_budget=0) # Disables thinking for speed&lt;br /&gt;
        #),&lt;br /&gt;
    )&lt;br /&gt;
    &lt;br /&gt;
    logging.info(&amp;quot;Model&amp;#039;s response&amp;quot;)&lt;br /&gt;
    logging.info(response.text)&lt;br /&gt;
    json_match = re.search(r&amp;quot;.*json\s*(\{.*\})\s*.*&amp;quot;,response.text, re.DOTALL)&lt;br /&gt;
    if json_match:&lt;br /&gt;
        json_string = json_match.group(1)&lt;br /&gt;
    else:&lt;br /&gt;
        json_string = response.text # Assume it&amp;#039;s a direct JSON string &lt;br /&gt;
    try:&lt;br /&gt;
        jdata = json.loads(json_string)&lt;br /&gt;
        img_data_str = &amp;quot;&amp;quot;&lt;br /&gt;
        for result in jdata[&amp;quot;results&amp;quot;]:&lt;br /&gt;
            if result[&amp;quot;subject&amp;quot;] == &amp;quot;vehicle&amp;quot;:&lt;br /&gt;
                direction = &amp;quot;Unknown&amp;quot;&lt;br /&gt;
                if &amp;quot;right&amp;quot; in result[&amp;quot;orientation&amp;quot;].lower():&lt;br /&gt;
                    direction = &amp;quot;departing&amp;quot;&lt;br /&gt;
                elif &amp;quot;left&amp;quot; in result[&amp;quot;orientation&amp;quot;].lower():&lt;br /&gt;
                    direction = &amp;quot;arriving&amp;quot;&lt;br /&gt;
                img_data_str += f&amp;#039;There is a {result[&amp;quot;color&amp;quot;]} {result[&amp;quot;year&amp;quot;]} {result[&amp;quot;make&amp;quot;]} {result[&amp;quot;model&amp;quot;]} {result[&amp;quot;type&amp;quot;]} {direction}.  &amp;#039;&lt;br /&gt;
            elif result[&amp;quot;subject&amp;quot;] == &amp;quot;human&amp;quot;:&lt;br /&gt;
                img_data_str += f&amp;#039;There is {result[&amp;quot;count&amp;quot;]} {result[&amp;quot;subject&amp;quot;]} that looks {result[&amp;quot;size&amp;quot;]} detected.  &amp;#039;&lt;br /&gt;
            elif jdata[&amp;quot;subject&amp;quot;] == &amp;quot;animal&amp;quot;:&lt;br /&gt;
                img_data_str += f&amp;#039;There is {result[&amp;quot;count&amp;quot;]} {result[&amp;quot;subject&amp;quot;]} that looks like a {result[&amp;quot;species&amp;quot;]} detected.  &amp;#039;&lt;br /&gt;
        logging.info(img_data_str)&lt;br /&gt;
        return jdata&lt;br /&gt;
&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        logging.info(&amp;quot;Error: {0}&amp;quot;.format(e))&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    main()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Burger</name></author>
	</entry>
</feed>