Zoneminder object detection

It’s hard to eliminate false positives in motion detecting security cameras without also missing genuine events. Some expensive security cameras offer a solution – object detection.

Object detection can be CPU intensive, and it’s not something you’d want to do continuously on multiple streams. What if it was possible to use Zoneminder’s standard motion detection as the first pass and only perform object detection to further filter and eliminate false positives?

It turns out that object detection can be added to Zoneminder without too much difficulty using zmEventNotification. As a bonus, you also get truly responsive alerts that are fired when events happen, overcoming another limitation of Zoneminder.

The setup and configuration of zmeventnotification is well documented, so I won’t repeat it all here. I had to do a few things differently because I chose to install in non-standard locations and I’m using CentOS rather than Ubuntu.

In my setup, I’m using /opt/local as a base to keep anything that isn’t installed from the standard repositories. I ended up with the following files installed:

/opt/local/bin/detect.py
/opt/local/bin/detect_wrapper.py
/opt/local/bin/zmeventnotification.py

/opt/local/etc/zmeventnotification.ini
/opt/local/etc/objectconfig.ini

And two directories to store the object configuration:

/opt/local/lib/known_faces
/opt/local/lib/models

All files have to be readable by the web server – on CentOS, that’s the apache user.

Finally, there’s a directory for the temporary images that should be writable by the web server. I have a separate mount point for Zoneminder, so I used that to give me the following directory structure:

/cctv
 /cctv/zoneminder
     /cctv/zoneminder/events

/cctv/zmeventnotification
   /cctv/zmeventnotification/images

All of these directories are owned by the apache user.

Zoneminder has an option to automatically run zmeventnoification:

It expects to find zmeventnotification.pl in the ZM_PATH_BIN directory, which defaults to /usr/bin.

If you want to install zmeventnotification somewhere other than the Zoneminder installation, as I did, you’ll need to create a symbolic link:

lrwxrwxrwx 1 root root 37 Jul 21 09:38 /bin/zmeventnotification.pl -> /opt/local/bin/zmeventnotification.pl

The Perl dependencies are mostly in the CentOS repositories:

perl-Crypt-MySQL
perl-Config-IniFiles.noarch
perl-Crypt-Eksblowfish.x86_64
perl-Net-MQTT-Simple
perl-LWP-Protocol-https.noarch
perl-JSON.noarch

The only package I had to install manually via CPAN was Net::WebSocket::Server

I then had to change zmeventnotification.pl to point to my configuration file:

# configuration constants
use constant {
    DEFAULT_CONFIG_FILE => "/opt/local/etc/zmeventnotification.ini",

When running zmeventnotification manually it is possible to provide the location of the config file via a command line option, but there isn’t a way to do that when automatically starting the daemon via Zoneminder – at least, not without editing the core zmdc.pl file that comes with Zoneminder.

The next step was to install the object detection dependencies, and to make life awkward for myself I chose to put them into a virtual environment to keep my base OS installation as clean as possible. I keep my various virtual environments under /opt/local/venv, and created a new one for zmeventnotification:

python3 -m venv /opt/local/venv/zmeventnotification

I then activated the virtual environment and installed the dependencies.

To use the virtual environment when running detect.py there are two options. You can change the path at the top of the script itself:

#!/opt/local/venv/zmeventnotification/bin/python

Doing that means that you’ll have to change that line any time you download an update to the script. The second option is to call the virtual environment Python binary directly, with the script as a parameter.

I opted for the second approach and changed the detect_wrapper.sh script accordingly. I added an additional parameter for the python binary and used that to build the detection script.

Instead of:

CONFIG_FILE="/etc/zm/objectconfig.ini"
EVENT_PATH="$5"

DETECTION_SCRIPT=(/usr/bin/detect.py --monitorid $2 --eventid $1 --config "${CONFIG_FILE}" --eventpath "${EVENT_PATH}") 

I have:

CONFIG_FILE="/opt/local/etc/objectconfig.ini"
EVENT_PATH="$5"

PYTHON_BIN="/opt/local/venv/zmeventnotification/bin/python"
PYTHON_SCRIPT="/opt/local/bin/detect.py"

DETECTION_SCRIPT=($PYTHON_BIN $PYTHON_SCRIPT --monitorid $2 --eventid $1 --config "${CONFIG_FILE}" --eventpath "${EVENT_PATH}
")

While that does mean that I’d have to recreate the change if a new version changed detect_wrapper.sh, I figured that it’s less likely to change than detect.py itself.

Testing from the command line with a likely looking event shows that it all works:

[root@linux2 ~]# sudo -u apache /opt/local/bin/detect_wrapper.sh 22744 1
[a] detected:person:99%

Setting it up to run automatically from Zoneminder was as simple as enabling OPT_USE_EVENTNOTIFICATION in the Zoneminder settings and I can now distinguish between genuine events and false alarms in the front end:

This combination of Zoneminder’s own motion detection and object recognition completely eliminates false positives without generating excessive load on the server.

In the next post, I’ll talk about why I’m not using the default notifications and instead choosing to pass the events through to Home Assistant for further processing.

in Home Automation

Related Posts