Encourage FUNIX

The consultation of the site is completely free and without advertisements. Donations are nevertheless appreciated to pay for hosting and encourage its author


CCTV system - ZoneMinder overview

CCTV systems

Introducing ZoneMinder

October 30, 2025


Presentation

This section aims to introduce you to various tools for setting up a Linux-based video surveillance system using independent surveillance cameras (IP or Wi-Fi), analog, digital, or simple webcams, also known as CCTV. ZoneMinder and Frigate are presented here. They all allow you to integrate a wide range of cameras, perform automatic motion detection, and create events. They are also web-based, allowing you to configure them, view the cameras in real time, and manage events via a browser, whether on a desktop or mobile device. ZoneMinder and Frigate are advanced tools with machine learning and object recognition capabilities. ZoneMinder can also be considered more complex than Frigate, which integrates with Home Assistant.
This section is divided into several pages:

Installing the latest stable version

Zoneminder is considered one of the leading open-source video surveillance software programs. The official website is https://zoneminder.com. You can download the latest version there and extract it by typing

tar xvfz zoneminder-1.36.35.tar.gz

We will first install the following packages

- lib64vlc-devel
- perl-Apache-Test-1.430.0-2.mga9.noarch
- perl-AppConfig-1.710.0-7.mga9.noarch
- perl-Archive-Zip-1.680.0-2.mga9.noarch
- perl-Astro-SunTime-0.60.0-5.mga9.noarch
- perl-BSD-Resource-1.291.100-10.mga9.x86_64
- perl-Class-Inspector-1.360.0-3.mga9.noarch
- perl-Class-Load-0.250.0-4.mga9.noarch
- perl-Class-Mix-0.6.0-5.mga9.noarch
- perl-Class-Singleton-1.600.0-2.mga9.noarch
- perl-Class-Std-0.13.0-6.mga9.noarch
- perl-Class-Std-Fast-0.0.8-14.mga9.noarch
- perl-Convert-BinHex-1.125.0-5.mga9.noarch
- perl-Crypt-Eksblowfish-0.9.0-23.mga9.x86_64
- perl-Crypt-Rijndael-1.160.0-4.mga9.x86_64
- perl-Data-Entropy-0.7.0-10.mga9.noarch
- perl-Data-Float-0.13.0-5.mga9.noarch
- perl-Data-Flow-1.20.0-10.mga9.noarch
- perl-Data-OptList-0.113.0-1.mga9.noarch
- perl-Data-UUID-1.227.0-1.mga9.x86_64
- perl-Date-Manip-6.900.0-1.mga9.noarch
- perl-DateTime-1.590.0-1.mga9.x86_64
- perl-DateTime-Locale-1.370.0-1.mga9.noarch
- perl-DateTime-TimeZone-2.600.0-1.mga9.noarch
- perl-DBD-mysql-4.50.0-8.mga9.x86_64
- perl-Device-SerialPort-1.40.0-26.mga9.x86_64
- perl-File-ShareDir-1.118.0-2.mga9.noarch
- perl-HTTP-Lite-2.440.0-6.mga9.noarch
- perl-IO-Interface-1.90.0-14.mga9.x86_64
- perl-IO-SessionData-1.30.0-9.mga9.noarch
- perl-IO-Socket-Multicast-1.120.0-26.mga9.x86_64
- perl-JSON-MaybeXS-1.4.4-1.mga9.noarch
- perl-Linux-Pid-0.40.0-24.mga9.x86_64
- perl-List-AllUtils-0.190.0-2.mga9.noarch
- perl-List-SomeUtils-0.590.0-1.mga9.noarch
- perl-List-UtilsBy-0.120.0-1.mga9.noarch
- perl-MIME-tools-5.510.0-1.mga9.noarch
- perl-Net-SFTP-Foreign-1.930.0-2.mga9.noarch
- perl-Number-Bytes-Human-0.110.0-5.mga9.noarch
- perl-Params-Classify-0.15.0-11.mga9.x86_64
- perl-Params-Util-1.102.0-4.mga9.x86_64
- perl-PHP-Serialization-0.340.0-11.mga9.noarch
- perl-Pod-POM-2.10.0-7.mga9.noarch
- perl-SOAP-Lite-1.270.0-3.mga9.noarch
- perl-SOAP-WSDL-3.4.0-3.mga9.noarch
- perl-Sub-Install-0.929.0-1.mga9.noarch
- perl-Sys-CPU-0.610.0-20.mga9.x86_64
- perl-Sys-MemInfo-0.990.0-13.mga9.x86_64
- perl-Sys-Mmap-0.200.0-6.mga9.x86_64
- perl-Task-Weaken-1.60.0-4.mga9.noarch
- perl-Template-Toolkit-3.101.0-1.mga9.x86_64
- perl-Time-ParseDate-2015.103.0-6.mga9.noarch
- perl-URI-Encode-1.1.1-4.mga9.noarch
- perl-X10-0.30.0-10c.mga9.noarch

We return to the zoneminder-1.36.35 directory and type successively

mkdir build
cd build/
cd ..
git clone "https://github.com/ZoneMinder/ZoneMinder.git" zoneminder_release
cd zoneminder_release/
cp -Rf .git ..
cd ..
cd build/
git submodule update --init --recursive

and finally the configuration command with the settings of the MySQL server name ( ZM_DB_HOST ) in this case based on MariaDB , the activation of the systemd service ( ZM_SYSTEMD ), the owner of the httpd process ( ZM_WEB_USER ) and the paths of the event storage directory ( ZM_DIR_EVENTS ), the cgi-bin executables ( ZM_CGI_DIR ), the installation prefix with /usr and not /usr/local ( CMAKE_INSTALL_PREFIX ), the path of the configuration file ( ZM_CONFIG_DIR ), the search path for perl modules ( ZM_PERL_SEARCH_PATH ) and the files accessible by browser ( ZM_WEBDIR ).

cmake .. -DMYSQLCLIENT_LIBRARIES=/usr/local/mysql/lib/libmysqlclient.so -DZM_DB_HOST=mariadb-serveur -DZM_WEB_USER=apache -DZM_DIR_EVENTS=/media/motion/events -DZM_CGIDIR=/usr/share/zoneminder/cgi-bin -DCMAKE_INSTALL_PREFIX=/usr -DZM_CONFIG_DIR=/etc/zm -DZM_CONFIG_SUBDIR=/etc/zm/conf.d -DZM_PERL_SEARCH_PATH=/usr/share/perl5/vendor_perl -DZM_SYSTEMD=ON -DZM_WEBDIR=/usr/share/zoneminder/www/

Here is the result

-- Detected compiler: /usr/bin/cc
-- Sendfile support: Linux/Solaris sendfile()
-- Building man pages: Yes (default)
-- Could NOT find LibJWT (missing: LIBJWT_LIBRARY)
-- Checking prototype MD5 for HAVE_MD5_OPENSSL - True
-- Using web user: apache
-- Using web group: apache
Building unit tests: No (default)
-- Optional libraries found: zlib cURL OpenSSL PCRE GCrypt AVFormat AVCodec AVDevice AVUtil SWScale SWResample libVLC
-- Optional libraries not found: LIBJWT libVNC
-- Running ZoneMinder configuration generator
Generating '/home/olivier/compilation/zoneminder-1.36.35/build/zm_config_defines.h'
Updating '/home/olivier/compilation/zoneminder-1.36.35/build/db/zm_create.sql'
-- ZoneMinder configuration Generator completed successfully
-- Configuring done (0.4s)
-- Generating done (0.1s)
-- Build files have been written to: /home/olivier/compilation/zoneminder-1.36.35/build


Then type `make` and then, as root, `make install`.
Despite playing around with the ZM_PERL_MM_PARMS variable , it wasn't installing the Perl modules in the correct location. I had to manually type the following commands:

cp -Rf /root/perl5/lib/perl5/WSDiscovery11 /usr/share/perl5/vendor_perl
cp -Rf /root/perl5/lib/perl5/WSDiscovery10 /usr/share/perl5/vendor_perl
cp -Rf /root/perl5/lib/perl5/WSNotification /usr/share/perl5/vendor_perl
cp -Rf /root/perl5/lib/perl5/ONVIF` /usr/share/perl5/vendor_perl
cp -Rf /root/perl5/lib/perl5/WSNotification/ /usr/share/perl5/vendor_perl
cp -Rf /root/perl5/lib/perl5/ZoneMinder* /usr/share/perl5/vendor_perl
cp -Rf /root/perl5/lib/perl5/WSSecurity/ /usr/share/perl5/vendor_perl
cp -Rf /root/perl5/lib/perl5/WSDiscovery /usr/share/perl5/vendor_perl

You must first create a MySQL database by typing:

mariadb -u root -p < /usr/share/zoneminder/db/zm_create.sql

We check if the database is correctly created; it is simply named zm. We then create a privileged user, zmuser, on the zm database with the password zmpass, which will obviously need to be changed.

mariadb -u root -p

Here is the result

Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 25
Server version: 11.4.2-MariaDB Source distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> show databases;
+---------------------+
| Database |
+---------------------+
| MyMusic82 |
| MyMusic83 |
| MyVideos121 |
| MyVideos131 |
| digikam |
| digikam_similarites |
| digikam_vignettes |
| digikam_faces |
| information_schema |
| mysql |
| performance_schema |
| phpmyadmin |
| roundcubemail |
| spamassassin |
| syncstorage_rs |
| sys |
| tokenserver_rs |
| zm |
+----------------------+
18 rows in set (0.096 sec)

MariaDB [(none)]> use mysql;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
MariaDB [mysql]> CREATE USER 'zmuser' IDENTIFIED BY 'zmpass';
Query OK, 0 rows affected (0.087 sec)

MariaDB [mysql]> GRANT ALL ON zm.* TO 'zmuser'@'localhost';
Query OK, 0 rows affected (0.201 sec)

I have now created the file /usr/lib/systemd/system/zoneminder.service which will contain

[Unit]
Description=ZoneMinder CCTV recording and security system
After=network.target mariadb.service httpd.service
Requires=mariadb.service httpd.service

[Service]
User=apache
Type=forking
ExecStart=/usr/bin/zmpkg.pl start
ExecReload=/usr/bin/zmpkg.pl reload
ExecStop=/usr/bin/zmpkg.pl stop

[Install]
WantedBy=multi-user.target

Note that the user must correspond to the owner of the Apache httpd process . The service is activated by typing

systemctl enable zoneminder

[ back to top of page ]

The configuration file /etc/zm/zm.conf looks like this

# ============================================================
#
# ZoneMinder Base Configuration
#
# ============================================================
#
# *** DO NOT EDIT THIS FILE ***
#
# To make custom changes to the variables below, create a new configuration
# file, with an extension of .conf, under the /etc/zm/conf.d
# folder, containing your desired modifications.
#

# Path to installed data directory, used mostly for finding DB upgrade scripts
ZM_PATH_DATA=/usr/share/zoneminder

# Path to ZoneMinder binaries
ZM_PATH_BIN=/usr/bin

# Path to ZoneMinder libraries (none at present, for future use)
ZM_PATH_LIB=/usr/lib64

# Path to ZoneMinder configuration (this file only at present)
ZM_PATH_CONF=/etc/zm

# Path to ZoneMinder web files
ZM_PATH_WEB=/usr/share/zoneminder/www

# Path to ZoneMinder cgi files
ZM_PATH_CGI=/usr/share/zoneminder/cgi-bin

# Username and group that web daemon (httpd/apache) runs as
# user and group owner of the apache daemon httpd
ZM_WEB_USER=apache
ZM_WEB_GROUP=apache

# ZoneMinder database type: n/a far only mysql is supported
ZM_DB_TYPE=mysql

# ZoneMinder database hostname or ip address and optionally port or unix socket
# Acceptable formats include hostname[:port], ip_address[:port], or
# localhost:/path/to/unix_socket
# the name of the server on which the mariadb server is running
ZM_DB_HOST=mariadb-serveur

# ZoneMinder database name
ZM_DB_NAME=zm

# ZoneMinder database user
ZM_DB_USER=zmuser

# ZoneMinder database password
# the password as defined in mariadb
ZM_DB_PASS=zmpass

# SSL CA certificate for ZoneMinder database
ZM_DB_SSL_CA_CERT=

# SSL client key for ZoneMinder database
ZM_DB_SSL_CLIENT_KEY=

# SSL client cert for ZoneMinder database
ZM_DB_SSL_CLIENT_CERT=

# Do NOT set ZM_SERVER_HOST if you are not using Multi-Server
# You have been warned
#
# The name specified here must have a corresponding entry
# in the Servers tab under Options
ZM_SERVER_HOST=

Another file, /etc/zm/conf.d/01-system-paths.conf, which will contain

# ============================================================
#
# ZoneMinder System Paths Configuration
#
# ============================================================
#
# This config file contains the variables previously found under Options -> Paths
#
# *** DO NOT EDIT THIS FILE ***
#
# To make custom changes to the variables below, create a new configuration
# file, with an extension of .conf, containing your desired modifications.
#

# Full path to the folder events are recorded to.
# The web account user must have full read/write permission to this folder.
# directory where the events are recorded
# the owner of the httpd process must be the owner, it is apache
ZM_DIR_EVENTS=/media/motion/events

# Foldername under the webroot where ZoneMinder looks for optional sound files
# to play when an alarm is detected.
ZM_DIR_SOUNDS=sounds

# Full path to the folder where exported archives are stored
# The web account user must have full read/write permission to this folder.
# directory where the archives are exported
# the apache user must own the directory
ZM_DIR_EXPORTS=/var/tmp/zm

# ZoneMinder url path to the zms streaming server
ZM_PATH_ZMS=/zm/cgi-bin/nph-zms

# Full Path to ZoneMinder's mapped memory files
# The web account user must have full read/write permission to this folder.
ZM_PATH_MAP=/dev/shm

# Full Path to ZoneMinder's socket folder
# The web account user must have full read/write permission to this folder.
# sockets directory
# the apache user must own it
ZM_PATH_SOCKS=/var/run/zm

# Full path to ZoneMinder's log folder
# The web account user must have full read/write permission to this folder.
# logs directory
# the apache user must own it
ZM_PATH_LOGS=/var/log/zm

# Full path to ZoneMinder's swap folder
# The web account user must have full read/write permission to this folder.
ZM_PATH_SWAP=/var/tmp/zm

# Full path to optional arp binary
# ZoneMinder will find the arp binary automatically on most systems
ZM_PATH_ARP="/usr/sbin/arp"

#Full path to shutdown binary
ZM_PATH_SHUTDOWN="/sbin/shutdown"

# Path to FFmpeg binary
ZM_PATH_FFMPEG="/usr/local/bin/ffmpeg"

It will be necessary to ensure that the Apache user owns certain directories required for ZoneMinder to function.

chown -R apache:apache /var/run/zm
chown -R apache:apache /var/log/zm
chown -R apache:apache /var/lib/zoneminder
chown -R apache:apache /usr/share/zoneminder/www
chown -R apache:apache /var/cache/zoneminder
chown -R apache:apache /etc/zm

Incidentally, I created the following link

ln -s /var/cache/zoneminder/ /usr/share/zoneminder/www/cache

Next, you will need to modify the Apache httpd server configuration in the file /usr/local/apache2/conf/httpd/conf, starting with the list of useful and necessary modules.

LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule access_compat_module modules/
mod_access_compat.so LoadModule auth_basic_module modules
/mod_auth_basic.so LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
LoadModule reqtimeout_module modules/mod_reqtimeout.so
LoadModule filter_module modules/mod_filter.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule env_module modules/mod_env.so LoadModule
expires_module modules/mod_expires.so LoadModule
headers_module modules/mod_headers.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule version_module modules/mod_version.so
LoadModule ssl_module modules/mod_ssl.so
LoadModule unixd_module modules/mod_unixd.so LoadModule
status_module modules/ mod_status.so LoadModule
autoindex_module modules/mod_autoindex.so LoadModule
cgid_module modules/mod_cgid.so
LoadModule dir_module modules/mod_dir.so
LoadModule alias_module modules/mod_alias.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule php_module modules/libphp.so

(...)

# the owner of the apache/httpd daemon

User apache
Group apache

(...)

#zoneminder specific directives
<Directory "/usr/share/zoneminder/www">
   Options -Indexes +FollowSymLinks
   AllowOverride All
   Require all granted
</Directory>

<Directory "/var/cache/zoneminder">
    Options -Indexes +FollowSymLinks
    AllowOverride None
    Require all granted
</Directory>

<Directory "/usr/share/zoneminder/cgi-bin">
    Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
    AllowOverride All
    Require all granted
</Directory>

<Directory "/usr/share/zoneminder/www/api">
   RewriteEngine on
   RewriteRule ^$ app/webroot/ [L]
   RewriteRule (.*) app/webroot/$1 [L]
   RewriteBase /zm/api
</Directory>

<Directory "/usr/share/zoneminder/www/api/app">
   RewriteEngine on
   RewriteRule ^$ webroot/ [L]
   RewriteRule (.*) webroot/$1 [L]
   RewriteBase /zm/api
</Directory>

<Directory "/usr/share/zoneminder/www/api/app/webroot">
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
    RewriteBase /zm/api
</Directory>

#alias zoneminder in this specific order
ScriptAlias ​​/zm/cgi-bin "/usr/share/zoneminder/cgi-bin"
Alias ​​/zm/cache "/var/cache/zoneminder"
Alias ​​/zm "/usr/share/zoneminder/www"

We will also ensure that the time zone variable in the /usr/local/apache2/conf/php.ini file is properly initialized.

date.timezone = Europe/Paris

We restart Apache httpd to apply the update to these last two files

systemctl httpd restart

and we launch ZoneMinder by typing

systemctl start zoneminder

and this is what the command returns

systemctl status zoneminder

● zoneminder.service - ZoneMinder CCTV recording and security system
     Loaded: loaded (/usr/lib/systemd/system/zoneminder.service; disabled; preset: disabled)
     Active: active (running) since Fri 2024-08-09 16:03:47 CEST; 11s ago
    Process: 3570 ExecStart=/usr/bin/zmpkg.pl start (code=exited, status=0/SUCCESS)
   Main PID: 3578 (zmdc.pl)
      Tasks: 5 (limit: 9239)
     Memory: 91.5M
        CPU: 2.077s
     CGroup: /system.slice/zoneminder.service
             ├─3578 /usr/bin/perl -wT /usr/bin/zmdc.pl startup
             ├─3606 /usr/bin/perl -wT /usr/bin/zmfilter.pl --filter_id=1 --daemon
             ├─3610 /usr/bin/perl -wT /usr/bin/zmfilter.pl --filter_id=2 --daemon
             ├─3616 /usr/bin/perl -wT /usr/bin/zmwatch.pl
             └─3620 /usr/bin/perl -wT /usr/bin/zmstats.pl

Aug 09 16:03:44 ultra.kervao.fr systemd[1]: Starting zoneminder.service...
Aug 09 16:03:47 ultra.kervao.fr systemd[1]: Started zoneminder.service.

[ back to top of page ]

Update to the unstable version

We will download the latest version by typing the command

git clone https://github.com/ZoneMinder/ZoneMinder

This will create a ZoneMinder directory in which you type

mkdir build cd build git submodule update --init --recursive

At this point, we will ensure that the lib64gsoap-devel and json-devel packages are installed, then we type

cmake .. -DMYSQLCLIENT_LIBRARIES=/usr/local/mysql/lib/libmysqlclient.so -DZM_DB_HOST=mariadb-serveur -DZM_WEB_USER=apache -DZM_DIR_EVENTS=/media/motion/events -DZM_CGIDIR=/usr/share/zoneminder/cgi-bin -DCMAKE_INSTALL_PREFIX=/usr/local -DZM_CONFIG_DIR=/etc/zm -DZM_CONFIG_SUBDIR=/etc/zm/conf.d -DZM_PERL_SEARCH_PATH=/usr/share/perl5/vendor_perl -DZM_SYSTEMD=ON -DZM_WEBDIR=/usr/share/zoneminder/www

Here is the end of the result

-- Found PerlModules: TRUE 
-- Using web user: apache
-- Using web group: apache
-- Checking for one of the modules 'polkit-gobject-1;polkit'
-- Checking for module 'glib-2.0'
-- Found glib-2.0, version 2.76.3
-- Found GLIB2: /usr/lib64/libglib-2.0.so 
-- Found Polkit: /usr/lib64/libpolkit-gobject-1.so 
-- Found OpenSSL: /usr/lib64/libcrypto.so (found suitable version "3.0.15", minimum required is "1.0.1") 
-- The ASM compiler identification is GNU
-- Found assembler: /usr/bin/cc
INFO Expanding CMAKE_PREFIX_PATH to include package build dir
Building unit tests: No (default)
-- Optional libraries found: zlib cURL OpenSSL PCRE Mosquitto Mosquittopp libVLC gsoap
-- Optional libraries not found: libVNC
-- Enabled crypto backend: openssl
-- Enabled JWT backend: jwt_cpp
-- Running ZoneMinder configuration generator
Generating '/usr/local/linux/securite/ZoneMinder/build/zm_config_defines.h'
Updating '/usr/local/linux/securite/ZoneMinder/build/db/zm_create.sql'
-- ZoneMinder configuration generator completed successfully
-- Configuring done (10.4s)
-- Generating done (0.2s)
-- Build files have been written to: /usr/local/linux/securite/ZoneMinder/build

then type make and then as root

make install
cp -Rf /root/perl5/lib/perl5/WSDiscovery11 /usr/share/perl5/vendor_perl
cp -Rf /root/perl5/lib/perl5/WSDiscovery10 /usr/share/perl5/vendor_perl
cp -Rf /root/perl5/lib/perl5/WSNotification /usr/share/perl5/vendor_perl
cp -Rf /root/perl5/lib/perl5/ONVIF /usr/share/perl5/vendor_perl
cp -Rf /root/perl5/lib/perl5/WSNotification/ /usr/share/perl5/vendor_perl
cp -Rf /root/perl5/lib/perl5/ZoneMinder* /usr/share/perl5/vendor_perl
cp -Rf /root/perl5/lib/perl5/WSSecurity/ /usr/share/perl5/vendor_perl
cp -Rf /root/perl5/lib/perl5/WSDiscovery /usr/share/perl5/vendor_perl
cp -Rf /usr/share/zoneminder/cgi-bin/zms /usr/local/apache2/cgi-bin/nph-zms

and to avoid this mistake

avril 26 17:49:33 ultra.kervao.fr zmpkg[1997204]: FAT [Version mismatch, system is version 1.37.66, database is 1.36.35, please run zmupdate.pl to update.]
avril 26 17:49:33 ultra.kervao.fr systemd[1]: zoneminder.service: Control process exited, code=exited, status=255/EXCEPTION
avril 26 17:49:33 ultra.kervao.fr systemd[1]: zoneminder.service: Failed with result 'exit-code'.
avril 26 17:49:33 ultra.kervao.fr systemd[1]: Failed to start zoneminder.service.

We will update the ZoneMinder database by typing

/usr/local/bin/zmupdate.pl

Here is part of the result:

Initiating database upgrade to version 1.37.66 from version 1.36.35

Do you wish to take a backup of your database prior to upgrading?
This may result in a large file in /var/tmp/zm if you have a lot of events.
Press 'y' for a backup or 'n' to continue : y
Creating backup to /var/tmp/zm/zm-1.36.35.dump. This may take several minutes.
mysqldump: Deprecated program name. It will be removed in a future release, use '/usr/local/mysql/bin/mariadb-dump' instead
Database successfully backed up to /var/tmp/zm/zm-1.36.35.dump, proceeding to upgrade.

Upgrading database to version 1.37.66
Upgrading DB to 1.37.1 from 1.36.35
mysql: Deprecated program name. It will be removed in a future release, use '/usr/local/mysql/bin/mariadb' instead

(...)

Database successfully upgraded to version 1.37.62.
Upgrading DB to 1.37.63 from 1.36.35
mysql: Deprecated program name. It will be removed in a future release, use '/usr/local/mysql/bin/mariadb' instead

Database successfully upgraded to version 1.37.63.
Upgrading DB to 1.37.66 from 1.36.35
mysql: Deprecated program name. It will be removed in a future release, use '/usr/local/mysql/bin/mariadb' instead

Database successfully upgraded to version 1.37.66.

Database upgrade to version 1.37.66 successful.



Use

Here is the main screen with a camera added using the Add Camera command with Modect mode, meaning it will detect movements and create an event for each one.

It was necessary to define the video source of the view, by setting the path

Then we define the camera's characteristics based on the source.


In Storage, we will choose Camera Passthrough at the Video Writer level to avoid overloading zoneminder with encoding; this means that we write the video provided by the camera as is.

Note the % reference image fusion parameter , which affects the detection rate; for an outdoor camera, a value of 12.5% ​​is recommended. To trigger an alarm, a difference value can be entered, corresponding to the percentage of the image that has moved compared to the reference image.

With multiple cameras this gives


Clicking on the camera provides a real-time view; events are displayed in a list below the selected camera view.


To view a specific event, click on it; the event in question is marked in red in the timeline.


Note that for cameras using the HEVC codec, replay functionality will not be available in Firefox, as the foundation refuses to pay for the corresponding patents. However, it will work perfectly with Gnome Web (formerly Epiphany ), for example.

We have video replay tools to view the event in question; we can delete them at the previous level.
To get a video wall with the camera, we'll go to Editing


The Options menu will allow access to the different options.


Events are saved by default under /var/lib/zoneminder/www/events ; you can add more storage space by clicking on Add new storage


You have every reason to do regular cleaning to avoid blasting the disk; it is possible to do this automatically from the Filters menu by selecting PurgeWhenFull with a cleaning launched from a 95% disk occupancy.


To be notified by email of every event, configure the Email option in the Options.


but that's not enough, you then need to create a filter that will trigger the sending of emails when the detection score reaches at least 5.


To further refine the settings, detection zones can be defined with specific settings for each zone; drag the 4 green dots at the corner to materialize it.



For each camera, several detection zones can be defined; below there are 3 active zones and 5 inactive zones where no event will be reported.


For more details on the zones, you can consult the official documentation as well as this wiki page and this one (in English), this page in French is also a valuable help in understanding the types of zones and their settings.

It's worth noting that there are several alarm triggering methods: Alarmed Pixels, Filtered Pixels, and blobs. Without going into detail (as this is covered in the pages cited above), the blob method is considered the most effective. Detection thresholds are then defined based on the size of the detected objects.

Outdoor cameras remain the most difficult to adjust. I struggled quite a bit to find the right setting for my camera which is aimed at the gate which is rather far away; it wasn't detecting anything at all until I went back to the settings on this page, which were a good starting point, although not entirely satisfactory for objects the size of a person close to the gate.



In this case, you should use pixels rather than percentages, as they allow for finer adjustments. In my case (below), the area is 204,040 pixels, which, given the trapezoidal shape of my area, is roughly a rectangle of 900 x 225 pixels. From there, you need to imagine the size of a person to deduce their size in pixels (you can use GIMP to help ). In my case, it's roughly 25 x 60 pixels, or 1,500 pixels, which we'll find in the Minimum Area in Alarm field.


The remaining parameters must then be adjusted by trial and error. Now, upon detection of an event, the affected area(s) will be indicated.



Going even further with ZMES

The Event Notification Server or ZMES is a complementary tool to zoneminder ; it has machine learning which allows it to recognize objects, whether they are people or cars, for example.

The official website is  https://github.com/ZoneMinder/zmeventnotification you can download the source code there and extract it by typing

tar xvfz zmeventnotification-6.1.29.tar.gz

This results in the zmeventnotification-6.1.29 directory. You can also choose to install the development version by typing

git clone https://github.com/ZoneMinder/zmeventnotification

First, we will install the packages perl-YAML-LibYAML, perl-Crypt-MySQL, perl-Module-Build-Tiny, and perl-CPAN-DistnameInfo. Then, with CPAN

perl -MCPAN -e "install Crypt::Eksblowfish::Bcrypt"
perl -MCPAN -e "install Protocol::WebSocket"
perl -MCPAN -e "install Net::WebSocket::Server"

perl -MCPAN -e "install Net::MQTT::Simple"

under zmeventnotification-6.1.29 we type ./install.sh

Here is part of the result

which: no apt-get in (/root/perl5:/usr/local/sbin:/usr/sbin:/usr/local/bin:/usr/bin:/usr/local/mysql/bin:/usr/lib64/qt5/bin:/usr/lib64/qt6/bin)
which: no yum in (/root/perl5:/usr/local/sbin:/usr/sbin:/usr/local/bin:/usr/bin:/usr/local/mysql/bin:/usr/lib64/qt5/bin:/usr/lib64/qt6/bin)

----------- Configured Values ----------------------------
Your distro seems to be ubuntu
Your webserver user seems to be apache
Your webserver group seems to be apache
wget is /usr/bin/wget
installer software is
Install Event Server: prompt
Install Event Server config: prompt
Install Hooks: prompt
Install Hooks config: prompt
Upgrade Hooks config (if applicable): yes
Download and install models (if needed): yes

The Event Server will be installed to /usr/bin
The Event Server config will be installed to /etc/zm
Hooks will be installed to /var/lib/zmeventnotification sub-folders
Hook config files will be installed to /etc/zm

Models that will be checked/installed:
(Note, if you have already downloaded a model, it will not be deleted)
Yolo V3 (INSTALL_YOLOV3): yes
TinyYolo V3 (INSTALL_TINYYOLOV3): yes
Yolo V4 (INSTALL_YOLOV4): yes
Tiny Yolo V4 (INSTALL_TINYYOLOV4): yes
Google Coral Edge TPU (INSTALL_CORAL_EDGETPU): no

If any of this looks wrong, please hit Ctrl+C and edit the variables in this script...

Install Event Server Config [y/N]:y
Replacing ES config & rules file
Success:config copied
Replacing ES rules file
Success:rules copied
====> Remember to fill in the right values in the config files, or your system won't work! <=============



Install Hook [y/N]:y
Installing pip...
install: opérande de fichier cible manquant après 'pip'
Saisissez « install --help » pour plus d'informations.
Installing python3-opencv...
install: opérande de fichier cible manquant après 'python3-opencv'
Saisissez « install --help » pour plus d'informations.
*** Installing Hooks ***
Checking for YoloV3 data files....
yolov3.cfg exists, no need to download
coco.names exists, no need to download
yolov3.weights exists, no need to download

Checking for TinyYOLOV3 data files...
yolov3-tiny.cfg exists, no need to download
coco.names exists, no need to download
yolov3-tiny.weights exists, no need to download

Checking for TinyYOLOV4 data files...
yolov4-tiny.cfg exists, no need to download
coco.names exists, no need to download
yolov4-tiny.weights exists, no need to download

Checking for YOLOV4 data files...
WARNING:Note, you need OpenCV 4.4+ for Yolov4 to work
yolov4.cfg exists, no need to download
coco.names exists, no need to download
yolov4.weights exists, no need to download
*** Installing push api plugins ***
*** Installing detection scripts ***

*** Installing user contributions ***
Copying over contrib/example.py...
Copying over contrib/ftp_selective_upload.py...

Removing old version of zmes_hook_helpers, if any
Installing new version of hooks
sudo -H pip3 -v install hook/
Using pip 23.0.1 from /usr/lib/python3.10/site-packages/pip (python 3.10)
Processing ./hook

(...)

Installing package deps...
Installing gifsicle, if needed...
install : option invalide -- 'q'
Saisissez « install --help » pour plus d'informations.
Skipping Hook install


Install Hook Config [y/N]:y
Replacing Hook config file
Success:config copied
====> Remember to fill in the right values in the config files, or your system won't work! <=============
====> If you changed /etc/zm remember to fix  /var/lib/zmeventnotification/bin/zm_event_start.sh! <========


Creating a migrated objectconfig if required
Current version of file is 1.2
Nothing to migrate

*** Please remember to start the Event Server after this update ***

You will need to modify or create files under /etc/zm, first of all secrets.ini which will contain

[secrets]

# The URL to access ZoneMinder, https://ultra.kervao.fr/zm, needs to be adapted; the username and password are defined on the system configuration page.

ZMES_PICTURE_URL=https://ultra.kervao.fr/zm/index.php?view=image&eid=EVENTID&fid=objdetect&width=600
ZM_USER=zm-user
ZM_PASSWORD=password

ZM_PORTAL=https://ultra.kervao.fr/zm
ZM_API_PORTAL=https://ultra.kervao.fr/zm/api

# Certificate and key for an SSL connection; I reused those from my Apache server:
ES_CERT_FILE=/etc/ssl/public/apache.crt
ES_KEY_FILE=/etc/ssl/apache/apache.key

#Key for license plate recognition; you will need to create an account on this website: https://platerecognizer.com/
PLATEREC_ALPR_KEY=cb5ece0dhkhg1bea8dfd08408ec6cb8370d6c3b5c

We will now modify the /etc/zm/objectconfig.ini file which will contain (excerpts)

(...)

secrets = /etc/zm/secrets.ini

# portal/user/password are needed if you plan on using ZM's legacy
# auth mechanism to get images

# Do not modify anything; it will look in the secrets.ini file.

portal=!ZM_PORTAL
user=!ZM_USER
password=!ZM_PASSWORD

# api portal is needed if you plan to use tokens to get images
# requires ZM 1.33 or above
api_portal=!ZM_API_PORTAL

# This parameter must be set to yes for encrypted connections with a self-signed certificate:
allow_self_signed=yes

(...)

# If you need basic auth to access ZM

#If the directory is password protected with a .htaccess file, the password will be entered here.

basic_user=login-apache
basic_password=password-apache

(...)

# API/password for remote gateway
ml_user=!ML_USER
ml_password=!ML_PASSWORD

(...)

# The key for license plate recognition, same as in the secrets.ini file:
alpr_key=cb5ece0dhkhg1bea8dfd08408ec6cb8370d6c3b5c

We now modify /etc/zm/zmeventnotification.ini

[ssl]
# Enable SSL (default: yes)
enable = yes

#same key in the secrets.ini file
cert = /etc/ssl/public/apache.crt
key = /etc/ssl/apache/apache.key

Now, in the system options and configuration, check the boxes for OPT_USE_API   and OPT_USE_EVENTNOTIFICATION.

We restart ZoneMinder and launch ZMES by typing

sudo -u apache /usr/bin/zmeventnotification.pl

Here is the result

Use of uninitialized value $first_arg in string eq at ./zmeventnotification.pl line 62.
27/10/2024 17:31:03.690121 zmeventnotification[163691].INF [main:330] [Running on WebSocket library version:0.004]
27/10/2024 17:31:07.018532 zmeventnotification[163691].INF [main:1010] [PARENT: using config file: /etc/zm/zmeventnotification.ini]
10/27/2024 17:31:07.833499 zmeventnotification[163691].INF [main:1010] [PARENT: using secrets file: /etc/zm/secrets.ini]
Use of uninitialized value $es_debug_level in numeric ge (>
=) at ./zmeventnotification.pl line 999. Use of uninitialized value $es_debug_level in numeric ge (>=) at ./zmeventnotification.pl line 999.
Use of uninitialized value $skip_monitors in split at ./zmeventnotification.pl line 543.
Use of uninitialized value $es_debug_level in numeric ge (>=) at ./zmeventnotification.pl line 999.
Use of uninitialized value $hook_skip_monitors in split at ./zmeventnotification.pl line 732.

with an event

27/10/2024 17:31: 26.558583 zmeventnotification[163691].INF [ main:1010] [PARENT: Push enabled via FCM] 27/10/2024 17:31:26.558734 zmeventnotification[163691].INF [main:1010] [PARENT: MQTT Disabled]
27/10/2024 17:31:26.558814 zmeventnotification[163691].INF [main:1010] [PARENT: |------- Starting ES version: 6.1.29 ---------|]
Can't ignore signal CHLD, forcing to default. 10/27/2024 5:31:49
PM
[main:1010] [PARENT: Re-loading monitors]
Use of uninitialized value in concatenation (.) or string at ./zmeventnotification.pl line 1453.
10/27/2024 5:31:54.071086 zmeventnotification[163691].INF [main:1010] [PARENT: New event 380 reported for Monitor:4 (Name:Camera-4) Motion[last processed eid:]] 27/10/2024 17:31:56.427281
zmeventnotification[163691].INF [main:1010] [PARENT: Secure WS(WSS) is enabled...] 27/10/2024 17:31
:56.427365 zmeventnotification[163691].INF [main:1010] [PARENT: Web Socket Event Server listening on port 9000]

To activate it as a systemd service , we will create the file /usr/lib/systemd/system/zmes.service which will contain

[Unit]
Description=ZMES
After=zoneminder.service
Requires=zoneminder.service httpd.service

[Service]
User=apache
Type=simple
ExecStart=/usr/bin/zmeventnotification.pl
Restart=on-failure

[Install]
WantedBy=multi-user.target

We will activate it by typing

systemclt enable zmes

We launch it by typing

systemctl start zmes

here is its status when you type

systemctl status zmes

● zmes.service - ZMES
     Loaded: loaded (/usr/lib/systemd/system/zmes.service; disabled; preset: disabled)
     Active: active (running) since Thu 2024-10-31 10:43:42 CET; 36s ago
   Main PID: 243499 (zmeventnotifica)
      Tasks: 1 (limit: 9239)
     Memory: 46.3M
        CPU: 784ms
     CGroup: /system.slice/zmes.service
             └─243499 /usr/bin/perl -T /usr/bin/zmeventnotification.pl

Oct 31 10:43:42 ultra.kervao.fr systemd[1]: Started zmes.service.
Oct 31 10:43:42 ultra.kervao.fr zmeventnotification.pl[243499]: Use of uninitialized value $first_arg in string eq at /usr/bin/zmeventnotification.pl line 61.
Oct 31 10:43:43 ultra.kervao.fr zmeventnotification.pl[243499]: Use of uninitialized value $es_debug_level in numeric ge (>=) at /usr/bin/zmeventnotification.pl line 998.
Oct. 31 10:43:43 ultra.kervao.fr zmeventnotification.pl[243499]: Use of uninitialized value $es_debug_level in numeric ge (>=) at /usr/bin/zmeventnotification.pl line 998.
Oct. 31 10:43:43 ultra.kervao.fr zmeventnotification.pl[243499]: Use of uninitialized value $skip_monitors in split at /usr/bin/zmeventnotification.pl line 542.
Oct 31 10:43:43 ultra.kervao.fr zmeventnotification.pl[243499]: Use of uninitialized value $es_debug_level in numeric ge (>=) at /usr/bin/zmeventnotification.pl line 998.
Oct. 31 10:43:43 ultra.kervao.fr zmeventnotification.pl[243499]: Use of uninitialized value $hook_skip_monitors in split at /usr/bin/zmeventnotification.pl line 731.
Oct. 31 10:43:44 ultra.kervao.fr zmeventnotification.pl[243505]: Can't ignore signal CHLD, forcing to default.

And here's what it will look like on the interface, we can see that it has identified people and a vehicle (truck).


And in the Audit Events Report menu , you can click on the links where an object has been identified.

The object in question is circled; in this case, a person.


In recent versions 1.37.X, you will need to click on the event and on the screen on the left, there are thumbnails for "first alarmed frame", "frame with the most motion" and "detected objects", by clicking on the latter, you will find the image of the type above.



Update ZoneMinder

If ZoneMinder is updated, the database may not be in the correct format, and upon launching the program, you might see a message like this:

Version mismatch, system version 1.36.35, database version 1.36.33, please run zmupdate.pl to update.

In this case, with a MariaDB server, I first created the following links:

ln -s /usr/local/mysql/bin/mariadb-dump /usr/local/bin/mysqldump

and

ln -s /usr/local/mysql/bin/mariadb /usr/local/bin/mysql

Then we type

/usr/bin/zmupdate.pl

Here is the result

Initiating database upgrade to version 1.36.35 from version 1.36.33

Do you wish to take a backup of your database prior to upgrading?
This may result in a large file in /var/tmp/zm if you have a lot of events.
Press 'y' for a backup or 'n' to continue: y
Creating backup to /var/tmp/zm/zm-1.36.33.dump. This may take several minutes.
mysqldump: Deprecated program name. It will be removed in a future release, use '/usr/local/mysql/bin/mariadb-dump' instead
Database successfully backed up to /var/tmp/zm/zm-1.36.33.dump, proceeding to upgrade.

Upgrading database to version 1.36.35
Upgrading DB to 1.36.34 from 1.36.33
mysql: Deprecated program name. It will be removed in a future release, use '/usr/local/mysql/bin/mariadb' instead

Database successfully upgraded to version 1.36.34.

Database upgrade to version 1.36.35 successful.

Okay, it's finished, we can launch and use ZoneMinder again .

[ back to top of page ]

Controlling ZoneMinder from a mobile phone with zmNinja

It is possible to access ZoneMinder from a mobile device once you have activated your LAMP server to be visible on the internet as indicated here and if possible with an encrypted connection as explained there .

The display on mobile is not great and not very user-friendly, as you can see below:

It turns out there's a dedicated app called zmNinja. While it does cost a modest €4.69, it's a worthwhile investment for such a small amount of money for a comfortable viewing experience on your mobile device.

First, using ZoneMinder, I made some configurations to secure access. I initially created users: a standard admin account and a limited user account.

Next, in the system settings, I enabled ZoneMinder authentication by checking the boxes below.

Then everything happens on the mobile device, where you start entering the URL and passwords.


The first ZM authentication password corresponds to the password defined in zoneminder, the second corresponds to the password set under Apache with the .htaccess file to secure access to the directory.


The second part of the screen is below where we will enter the URL of the zoneminder server .

The main screen


Editing mode

[ back to top of page ]

Use a Google Coral accelerator key with ZoneMinder

To activate the Google Coral key with ZoneMinder, restart the zmes installation, specifying that you are using it as root.

cd zmeventnotification-6.1.29
INSTALL_CORAL_EDGETPU=yes ./install.sh

Here is the result

which: no apt-get in (/root/perl5:/usr/local/sbin:/usr/sbin:/usr/local/bin:/usr/bin:/usr/local/mysql/bin:/usr/lib64/qt5/bin:/usr/lib64/qt6/bin)
which: no yum in (/root/perl5:/usr/local/sbin:/usr/sbin:/usr/local/bin:/usr/bin:/usr/local/mysql/bin:/usr/lib64/qt5/bin:/usr/lib64/qt6/bin)

----------- Configured Values ​​----------------------------
Your distro seems to be ubuntu
Your webserver user seems to be apache
Your webserver group seems to be apache
wget is /usr/bin/wget
installer software is
Install Event Server: prompt
Install Event Server config: prompt
Install Hooks: prompt
Install Hooks config: prompt
Upgrade Hooks config (if applicable): yes
Download and install models (if needed): yes

The Event Server will be installed to /usr/bin
The Event Server config will be installed to /etc/zm
Hooks will be installed to /var/lib/zmeventnotification sub-folders
Hook config files will be installed to /etc/zm

Models that will be checked/installed:
(Note, if you have already downloaded a model, it will not be deleted)
Yolo V3 (INSTALL_YOLOV3): yes
TinyYolo V3 (INSTALL_TINYYOLOV3): yes
Yolo V4 (INSTALL_YOLOV4): yes
Tiny Yolo V4 (INSTALL_TINYYOLOV4): yes
Google Coral Edge TPU (INSTALL_CORAL_EDGETPU): no

If any of this looks wrong, please hit Ctrl+C and edit the variables in this script...


Install Event Server [y/N]:y
*** Installing ES Dependencies ***
 install libconfig-inifiles-perl libcrypt-mysql-perl libcrypt-eksblowfish-perl libmodule-build-perl libyaml-perl libjson-per liblwp-protocol-https-perl libgeos-devl
install: target 'libyaml-perl': No such file or folder
./install.sh: line 208: apt-cache: command not found
You will have to install Net::WebSocket::Server using cpan.
*** Installing ES ***
Success:Completed, but you will still have to install ES dependencies as per https://zmeventnotification.readthedocs.io/en/latest/guides/install.html#install-dependencies


Install Event Server Config [y/N]:y
Replacing ES config & rules file
Success:config copied
Replacing ES rules file
Success:rules copied
====> Remember to fill in the right values ​​in the config files, or your system won't work! <=============



Install Hook [y/N]:y
Installing pipe...


(...)

Success:Done

    |-------------------------- NOTE -------------------------------------|
   
     Hooks are installed, but please make sure you have the right version
     of OpenCV installed. I recommend removing any pip packages you may
     have installed of opencv* and compiling OpenCV 4.4.x+ from source.
     See https://zmeventnotification.readthedocs.io/en/latest/guides/hooks.html#opencv-install

    |----------------------------------------------------------------------|

Installing package deps...
Installing gifsicle, if needed...
install : option invalide -- 'q'
Saisissez « install --help » pour plus d'informations.
Skipping Hook install


Install Hook Config [y/N]:y
Replacing Hook config file
Success:config copied
====> Remember to fill in the right values in the config files, or your system won't work! <=============
====> If you changed /etc/zm remember to fix  /var/lib/zmeventnotification/bin/zm_event_start.sh! <========

    -------------------------- EdgeTPU note ----------------------------

    Note that while edgetpu support has been added, the expectation is
    that you have followed all the instructions at:
    https://coral.ai/docs/accelerator/get-started/ first. Specifically,
    you need to make sure you have:
    1. Installed the right libedgetpu library (max or std)
    2. Installed the right tensorflow-lite library
    3. Installed pycoral APIs as per https://coral.ai/software/#pycoral-api

    If you don't, things will break. Further, you also need to make sure
    your web user (apache) has access to the coral device.
    On my ubuntu system, I needed to do:
        sudo usermod -a -G plugdev www-data
    --------------------------------------------------------------------

Creating a migrated objectconfig if required
Current version of file is 1.2
Nothing to migrate

*** Please remember to start the Event Server after this update ***

now in the file /etc/zm/objectconfig.ini at the object section here are the modified lines

[object]

# Updated note: Don't use use_sequence=no. No longer supported reliably
# If you are using legacy format (use_sequence=no) then these parameters will
# be used during ML inferencing
object_detection_pattern=(person|car|motorbike|bus|truck|boat)
object_min_confidence=0.3
object_framework=coral_edgetpu
object_processor=tpu
object_weights={{base_data_path}}/models/coral_edgetpu/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite
object_labels={{base_data_path}}/models/coral_edgetpu/coco_indexed.names

# Google Coral
# The mobiledet model came out in Nov 2020 and is supposed to be faster and more accurate but YMMV
tpu_object_weights_mobiledet={{base_data_path}}/models/coral_edgetpu/ssdlite_mobiledet_coco_qat_postprocess_edgetpu.tflite
tpu_object_weights_mobilenet={{base_data_path}}/models/coral_edgetpu/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite
tpu_object_labels={{base_data_path}}/models/coral_edgetpu/coco_indexed.names
tpu_object_framework=coral_edgetpu
tpu_object_processor=tpu
tpu_min_confidence=0.6

tpu_max_frames=3
tpu_delegate = edgetpu
tflite_num_threads=2
object_detection_type=tflite
object_detection_framework=tensorflow
tflite_model=
{{base_data_path}} /models/coral_edgetpu/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite
labels=
{{base_data_path}} /models/coral_edgetpu/coco_indexed.names

We restart zmes with

systemctl restart zmes

to check if it works. We can type `zm_detect` based on an event with a specific object:

/var/lib/zmeventnotification/bin/zm_detect.py --config /etc/zm/objectconfig.ini --monitor 4 --eventid 53968 --debug

However, I encountered this

DBG2 error. [zmesdetect_m4] [Traceback (most recent call last):
  File "/usr/local/lib/python3.10/site-packages/pyzm/ml/detect_sequence.py", line 707, in detect_stream
    _b,_l,_c,_m = m.detect(image=frame)
  File "/usr/local/lib/python3.10/site-packages/pyzm/ml/object.py", line 78, in detect
    b,l,c,_model_names = self.model.detect(image)
  File "/usr/local/lib/python3.10/site-packages/pyzm/ml/coral_edgetpu.py", line 167, in detect
    _, scale = common.set_resized_input(
  File "/usr/local/lib/python3.10/site-packages/pycoral/adapters/common.py", line 98, in set_resized_input
    result = resize((w, h))
  File "/usr/local/lib/python3.10/site-packages/pyzm/ml/coral_edgetpu.py", line 168, in <lambda>
    self.model, img.size, lambda size: img.resize(size, Image.ANTIALIAS))
AttributeError: module 'PIL.Image' has no attribute 'ANTIALIAS'
]

To resolve this as root, I edited the file /usr/local/lib/python3.10/site-packages/pyzm/ml/coral_edgetpu.py. In the header, I added

import PIL
import numpy as np

and then replaced this line

    #self.model, img.size, lambda size: img.resize(size, Image.ANTIALIAS))
      self.model, img.size, lambda size: img.resize(size, PIL.Image.LANCZOS))

we retype zm_detect

/var/lib/zmeventnotification/bin/zm_detect.py --config /etc/zm/objectconfig.ini --monitor 4 --eventid 57983 --debug

and bingo it works, that's it result

10/28/25 20:11:30 zmesdetect_m4[1435625] INF ZMLog.py:292 [Setting up signal handler for logs]

10/28/25 20:11:30 zmesdetect_m4[1435625] INF ZMLog.py:301 [Switching global logger to ZMLog]

DBG1 [zmesdetect_m4] [---------| app:6.1.29, pyzm:0.3.64, ES:(?) , OpenCV:4.10.0|------------]
10/28/25 20:11:30 zmesdetect_m4[1435625] INF utils.py:404 [Reading config from: /etc/zm/objectconfig.ini]

10/28/25 20:11:30 zmesdetect_m4[1435625] INF utils.py:409 [Reading secrets from: /etc/zm/secrets.ini]

DBG2 [zmesdetect_m4] [Secret token found in config: !ZM_PORTAL]
DBG2 [zmesdetect_m4] [Secret token found in config: !ZM_USER]
DBG2 [zmesdetect_m4]
[Secret token found in config: !ZM_PASSWORD] DBG2 [zmesdetect_m4] [Secret token found in config: !ZM_API_PORTAL]
DBG2 [zmesdetect_m4] [Secret token found in config: !ML_USER]
DBG2 [zmesdetect_m4] [Secret token found in config: !ML_PASSWORD]
DBG2 [zmesdetect_m4] [Secret token found in config: !PLATEREC_ALPR_KEY]
DBG1 [zmesdetect_m4] [allowing self-signed certs to work...]
DBG2 [zmesdetect_m4] [Now checking for monitor overrides]
DBG2 [zmesdetect_m4] [import_zm_zones: match_reason=False and reason=None]
DBG2 [zmesdetect_m4] [Getting ZM zones using https://ultra.kervao.fr/zm/api/zones/forMonitor/4.json?username=xxx&password=yyy&user=xxx&pass=yyy]
DBG2 [zmesdetect_m4] [importing zoneminder polygon: all [525,249 1279,212 1279,719 470,719]]
DBG3 [zmesdetect_m4] [Finally, doing parameter substitution]
DBG1 [zmesdetect_m4] [Importing local classes for Object/Face]
DBG2 [zmesdetect_m4] [API SSL certificate check has been disbled]
DBG1 [zmesdetect_m4] [using username/password for login]
DBG1 [zmesdetect_m4] [{"version":"1.37.70","apiversion":"2.0"}]
DBG2 [zmesdetect_m4] [Using new API token]
DBG2 [zmesdetect_m4] [using ml_sequence]
DBG2 [zmesdetect_m4] [using stream_sequence]
DBG1 [zmesdetect_m4] [Resetting models, will be loaded on next run]
DBG2 [zmesdetect_m4] [Called detect_stream(stream=57983, ml_overrides={}, options={'frame_strategy': 'most_models', 'frame_set': 'snapshot,alarm', 'contig_frames_before_error': 5, 'max_attempts': 3, 'sleep_between_attempts': 4, 'resize': 800, 'api': <pyzm.api.ZMApi object at 0x7efcd5fc3fd0>, 'polygons': [{'name': 'all', 'value': [(525, 249), (1279, 212), (1279, 719), (470, 719)], 'pattern': None}], 'mid':'4'})]
DBG3 [zmesdetect_m4] [Using manual locking as we are only using one model]
DBG2 [zmesdetect_m4] [Media get SSL certificate check has been disbled]
DBG2 [zmesdetect_m4] [Using URL 57983 for stream]
DBG2 [zmesdetect_m4] [We will only process frames: ['snapshot', 'alarm']]
DBG2 [zmesdetect_m4] [No need to start streams, we are picking images from https://ultra.kervao.fr/zm/index.php?view=image&eid=57983]
DBG2 [zmesdetect_m4] [in detect_stream() polygons=[{'name': 'all', 'value': [(525, 249), (1279, 212), (1279, 719), (470, 719)], 'pattern': None}]]
DBG3 [zmesdetect_m4] [Reading https://ultra.kervao.fr/zm/index.php?view=image&eid=57983&fid=snapshot]
DBG3 [zmesdetect_m4] [make_request called with url=https://ultra.kervao.fr/zm/index.php?view=image&eid=57983&fid=snapshot payload={} type=get ​​query={'token': ''}]
DBG1 [zmesdetect_m4] [perf: Starting for frame:snapshot]
DBG1 [zmesdetect_m4] [Sequence of detection types to execute: ['object']]
DBG1 [zmesdetect_m4] [============ Frame: snapshot Running object detection type in sequence ==================]
DBG2 [zmesdetect_m4] [Loading sequence: TPU object detection]
DBG2 [zmesdetect_m4] [Initializing model type:object with options: {'name': 'TPU object detection', 'enabled': 'yes', 'object_weights': '/var/lib/zmeventnotification/models/coral_edgetpu/ssdlite_mobiledet_coco_qat_postprocess_edgetpu.tflite', 'object_labels': '/var/lib/zmeventnotification/models/coral_edgetpu/coco_indexed.names', 'object_min_confidence': 0.6, 'object_framework': 'coral_edgetpu', 'tpu_max_processes': 1, 'tpu_max_lock_wait': 100, 'max_detection_size': '90%', 'disable_locks': 'no'}]
DBG2 [zmesdetect_m4] [portalock: max:1, name:pyzm_uid5001_tpu_lock, timeout:100]
DBG2 [zmesdetect_m4] [Loading sequence: YoloV4 GPU/CPU]
DBG2 [zmesdetect_m4] [Initializing model type:object with options:{'name': 'YoloV4 GPU/CPU', 'enabled': 'yes', 'object_config': '/var/lib/zmeventnotification/models/yolov4/yolov4.cfg', 'object_weights': '/var/lib/zmeventnotification/models/yolov4/yolov4.weights', 'object_labels': '/var/lib/zmeventnotification/models/yolov4/coco.names', 'object_min_confidence': 0.3, 'object_framework': 'opencv', 'object_processor': 'gpu', 'gpu_max_processes': 1, 'gpu_max_lock_wait': 100, 'cpu_max_processes': 3, 'cpu_max_lock_wait': 100, 'max_detection_size': '90%', 'disable_locks': 'no'}]
DBG2 [zmesdetect_m4] [portalock: max:1, name:pyzm_uid5001_gpu_lock, timeout:100]
DBG2 [zmesdetect_m4] [Waiting for pyzm_uid5001_tpu_lock portalock...]
DBG2 [zmesdetect_m4] [Got pyzm_uid5001_tpu_lock portalock]
DBG2 [zmesdetect_m4] [Waiting for
pyzm_uid5001_gpu_lock portalock...] DBG2 [zmesdetect_m4] [Got pyzm_uid5001_gpu_lock portalock]
DBG3 [zmesdetect_m4] [object has a same_model_sequence strategy of first]
DBG1 [zmesdetect_m4] [--------- Frame:snapshot Running variation: #1 -------------]
DBG2 [zmesdetect_m4] [pyzm_uid5001_tpu_lock portalock already acquired]
DBG1 [zmesdetect_m4] [|--------- Loading "TPU object detection" model from disk -------------|]
DBG1 [zmesdetect_m4] [perf: processor:tpu TPU initialization (loading /var/lib/zmeventnotification/models/coral_edgetpu/ssdlite_mobiledet_coco_qat_postprocess_edgetpu.tflite from disk) took: 2657.96 ms]
DBG1 [zmesdetect_m4] [|---------- TPU (input image: 800w*450h) ----------|]
DBG2 [zmesdetect_m4] [Released portalock pyzm_uid5001_tpu_lock]
DBG1 [zmesdetect_m4] [perf: processor:tpu Coral TPU detection took: 36.23 ms]
DBG3 [zmesdetect_m4] [Coral object returning: [],[],[]]
DBG2 [zmesdetect_m4] [core model detection over, got 0 objects. Now filtering]
DBG2 [zmesdetect_m4] [Max object size found to be: 90%]
DBG2 [zmesdetect_m4] [Converted 90% to 324000.0]
DBG2 [zmesdetect_m4] [Returning filtered list of 0 objects.]
DBG2 [zmesdetect_m4] [This model iteration inside object found: labels: [],conf:[]]
DBG2 [zmesdetect_m4] [Called _filter_detections(seq=object, box=[], label=[], conf=[], polygons=[{'name': 'all', 'value': [(525, 249), (1279, 212), (1279, 719), (470, 719)], 'pattern': None}], h=450, w=800, model_names=[])]
DBG2 [zmesdetect_m4] [Max object size found to be: 90%]
DBG2 [zmesdetect_m4] [Converted 90% to 324000.0]
DBG3 [zmesdetect_m4] [resized polygons x=0.625/y=0.625: [{'name': 'all', 'value': [(328, 155), (799, 132), (799, 449), (293, 449)], 'pattern': None}]]
DBG1 [zmesdetect_m4] [--------- Frame:snapshot Running variation: #2 -------------]
DBG2 [zmesdetect_m4] [detect extracted image dimensions as: 800wx450h]
DBG2 [zmesdetect_m4] [pyzm_uid5001_gpu_lock portalock already acquired]
DBG1 [zmesdetect_m4] [|-------- Loading "YoloV4 GPU/CPU" model from disk -------------|]
DBG1 [zmesdetect_m4] [You are using OpenCV >= 4.5.4, making sure we fix getUnconnectedOutLayers() API]
DBG1 [zmesdetect_m4] [perf: processor:gpu Yolo initialization (loading /var/lib/zmeventnotification/models/yolov4/yolov4.weights model from disk) took: 136.60 ms]
DBG2 [zmesdetect_m4] [Setting CUDA backend for OpenCV]
DBG3 [zmesdetect_m4] [If you did not set your CUDA_ARCH_BIN correctly during OpenCV compilation, you will get errors during detection related to invalid device/make_policy]
DBG1 [zmesdetect_m4] [|---------- YOLO (input image: 800w*450h, model resize dimensions: 416w*416h) ----------|]
[ WARN:0@4.781] global net_impl.cpp:178 setUpNet DNN module was not built with CUDA backend; switching to CPU
DBG2 [zmesdetect_m4] [Released pyzm_uid5001_gpu_lock portalock]
DBG1 [zmesdetect_m4] [perf: processor:gpu Yolo detection took: 3932.00 ms]
DBG2 [zmesdetect_m4] [perf: processor:gpu Yolo NMS filtering took: 1.77 ms]
DBG2 [zmesdetect_m4] [core model detection over, got 11 objects. Now filtering]
DBG2 [zmesdetect_m4] [Max object size found to be: 90%]
DBG2 [zmesdetect_m4] [Converted 90% to 324000.0]
DBG2 [zmesdetect_m4] [Ignoring pottedplant [542, 182, 562, 200] as conf. level 0.279738187789917 is lower than 0.3]
DBG2 [zmesdetect_m4] [Returning filtered list of 10 objects.]
DBG2 [zmesdetect_m4] [This model iteration inside object found: labels: ['pottedplant', 'person', 'pottedplant', 'pottedplant', 'pottedplant', 'vase', 'pottedplant', 'pottedplant', 'pottedplant', 'pottedplant'],conf:[0.9017574191093445, 0.8222572207450867, 0.7311447858810425, 0.547961413860321, 0.539945662021637, 0.4644719064235687, 0.43742167949676514, 0.39541205763816833, 0.3663233518600464, 0.31749823689460754]]
DBG2 [zmesdetect_m4] [Called _filter_detections(seq=object, box=[[501, 220, 567, 286], [370, 220, 412, 372], [578, 206, 604, 228], [630, 188, 664, 244], [472, 132, 520, 220], [529, 272, 549, 294], [476, 204, 500, 224], [606, 222, 626, 248], [538, 123, 566, 199], [522, 244, 566, 292]], label=['pottedplant', 'person', 'pottedplant', 'pottedplant', 'pottedplant', 'vase', 'pottedplant', 'pottedplant', 'pottedplant', 'pottedplant'], conf=[0.9017574191093445, 0.8222572207450867, 0.7311447858810425, 0.547961413860321, 0.539945662021637, 0.4644719064235687, 0.43742167949676514, 0.39541205763816833, 0.3663233518600464, 0.31749823689460754], polygons=[{'name': 'all', 'value': [(328, 155), (799, 132), (799, 449), (293, 449)], 'pattern': None}], h=450, w=800, model_names=['yolo', 'yolo', 'yolo', 'yolo', 'yolo', 'yolo', 'yolo', 'yolo', 'yolo', 'yolo'])]
DBG2 [zmesdetect_m4] [Max object size found to be: 90%]
DBG2 [zmesdetect_m4] [Converted 90% to 324000.0]
DBG2 [zmesdetect_m4] [intersection: object:pottedplant,POLYGON ((501 220, 567 220, 567 286, 501 286, 501 220)) intersects polygon:all,POLYGON ((328 155, 799 132, 799 449, 293 449, 328 155))]
DBG2 [zmesdetect_m4] [Using global match pattern: (person|car|motorbike|bus|truck|boat)]
DBG2 [zmesdetect_m4] [all intersects object:pottedplant[[(501, 220), (567,220), (567, 286), (501, 286)]] but does NOT match your detect pattern filter]
DBG2 [zmesdetect_m4] [intersection: object:person,POLYGON ((370 220, 412 220, 412 372, 370 372, 370 220)) intersects polygon:all,POLYGON ((328 155, 799 132, 799 449, 293 449, 328 155))]
DBG2 [zmesdetect_m4] [Using global match pattern: (person|car|motorbike|bus|truck|boat)]
DBG2 [zmesdetect_m4] [all intersects object:person[[(370, 220), (412, 220), (412, 372), (370, 372)]]]
DBG2 [zmesdetect_m4] [intersection: object:pottedplant,POLYGON ((578 206, 604 206, 604 228, 578 228, 578 206)) intersects polygon:all,POLYGON ((328 155, 799 132, 799 449, 293 449, 328 155))]
DBG2 [zmesdetect_m4] [Using global match pattern: (person|car|motorbike|bus|truck|boat)]
DBG2 [zmesdetect_m4] [all intersects object:pottedplant[[(578, 206), (604, 206), (604, 228), (578, 228)]] but does NOT match your detect pattern filter]
DBG2 [zmesdetect_m4] [intersection: object:pottedplant,POLYGON ((630 188, 664 188, 664 244, 630 244, 630 188)) intersects polygon:all,POLYGON ((328 155, 799 132, 799 449, 293 449, 328 155))]
DBG2 [zmesdetect_m4] [Using global match pattern: (person|car|motorbike|bus|truck|boat)]
DBG2 [zmesdetect_m4] [all intersects object:pottedplant[[(630, 188), (664, 188), (664, 244), (630, 244)]] but does NOT match your detect pattern filter]
DBG2 [zmesdetect_m4] [intersection: object:pottedplant,POLYGON ((472 132, 520 132, 520 220, 472 220, 472 132)) intersects polygon:all,POLYGON ((328 155, 799 132, 799 449, 293 449, 328 155))]
DBG2 [zmesdetect_m4] [Using global match pattern: (person|car|motorbike|bus|truck|boat)]
DBG2 [zmesdetect_m4] [all intersects object:pottedplant[[(472, 132), (520, 132), (520, 220), (472, 220)]] but does NOT match your detection pattern filter]
DBG2 [zmesdetect_m4] [intersection: object:vase,POLYGON ((529 272, 549 272, 549 294, 529 294, 529 272)) intersects polygon:all,POLYGON ((328 155, 799 132, 799 449, 293 449, 328 155))]
DBG2 [zmesdetect_m4] [Using global match pattern: (person|car|motorbike|bus|truck|boat)]
DBG2 [zmesdetect_m4] [all intersects object:vase[[(529, 272), (549, 272), (549, 294), (529, 294)]] but does NOT match your detect pattern filter]
DBG2 [zmesdetect_m4] [intersection: object:pottedplant,POLYGON ((476 204, 500 204, 500 224, 476 224, 476 204)) intersects polygon:all,POLYGON ((328 155, 799 132, 799 449, 293 449, 328 155))]
DBG2 [zmesdetect_m4] [Using global match pattern: (person|car|motorbike|bus|truck|boat)]
DBG2 [zmesdetect_m4] [all intersects object:pottedplant[[(476, 204), (500, 204), (500, 224), (476, 224)]] but does NOT match your detection pattern filter]
DBG2 [zmesdetect_m4] [intersection: object:pottedplant,POLYGON ((606 222, 626 222, 626 248, 606 248, 606 222)) intersects polygon:all,POLYGON ((328 155, 799 132, 799 449, 293 449, 328 155))]
DBG2 [zmesdetect_m4] [Using global match pattern: (person|car|motorbike|bus|truck|boat)]
DBG2 [zmesdetect_m4] [all intersects object:pottedplant[[(606, 222), (626, 222), (626, 248), (606, 248)]] but does NOT match your detect pattern filter]
DBG2 [zmesdetect_m4] [intersection: object:pottedplant,POLYGON ((538 123, 566 123, 566 199, 538 199, 538 123)) intersects polygon:all,POLYGON ((328 155, 799 132, 799 449, 293 449, 328 155))]
DBG2 [zmesdetect_m4] [Using global match pattern: (person|car|motorbike|bus|truck|boat)]
DBG2 [zmesdetect_m4] [all intersects object:pottedplant[[(538, 123), (566, 123), (566, 199), (538, 199)]] but does NOT match your detect pattern filter]
DBG2 [zmesdetect_m4] [intersection: object:pottedplant,POLYGON ((522 244, 566 244, 566 292, 522 292, 522 244)) intersects polygon:all,POLYGON ((328 155, 799 132, 799 449, 293 449, 328 155))]
DBG2 [zmesdetect_m4] [Using global match pattern: (person|car|motorbike|bus|truck|boat)]
DBG2 [zmesdetect_m4] [all intersects object:pottedplant[[(522, 244), (566, 244), (566, 292), (522, 292)]] but does NOT match your detect pattern filter]
DBG2 [zmesdetect_m4] [breaking out of same model loop, as matches found and strategy is "first"]
DBG3 [zmesdetect_m4] [Reading https://ultra.kervao.fr/zm/index.php?view=image&eid=57983&fid=alarm]
DBG3 [zmesdetect_m4] [make_request called with url=https://ultra.kervao.fr/zm/index.php?view=image&eid=57983&fid=alarm payload={} type=get ​​query={'token': ''}]
DBG1 [zmesdetect_m4] [perf: Starting for frame:alarm]
DBG1 [zmesdetect_m4] [Sequence of detection types to execute: ['object']]
DBG1 [zmesdetect_m4] [============ Frame: alarm Running object detection type in sequence ==================]
DBG3 [zmesdetect_m4] [object has a same_model_sequence strategy of first]
DBG1 [zmesdetect_m4] [--------- Frame:alarm Running variation: #1 -------------]
DBG2 [zmesdetect_m4] [Waiting for pyzm_uid5001_tpu_lock portalock...]
DBG2 [zmesdetect_m4] [Got pyzm_uid5001_tpu_lock portalock]
DBG1 [zmesdetect_m4] [|---------- TPU (input image: 800w*450h) ----------|]
DBG2 [zmesdetect_m4] [Released portalock pyzm_uid5001_tpu_lock]
DBG1 [zmesdetect_m4] [perf: processor:tpu Coral TPU detection took: 19.13 ms]
DBG3 [zmesdetect_m4] [Coral object returning: [],[],[]]
DBG2 [zmesdetect_m4] [core model detection over, got 0 objects. Now filtering]
DBG2 [zmesdetect_m4] [Max object size found to be: 90%]
DBG2 [zmesdetect_m4] [Converted 90% to 324000.0]
DBG2 [zmesdetect_m4] [Returning filtered list of 0 objects.]
DBG2 [zmesdetect_m4] [This model iteration inside object found: labels: [],conf:[]]
DBG2 [zmesdetect_m4] [Called _filter_detections(seq=object, box=[], label=[], conf=[], polygons=[{'name': 'all', 'value': [(328, 155), (799, 132), (799, 449), (293, 449)], 'pattern': None}], h=450, w=800, model_names=[])]
DBG2 [zmesdetect_m4] [Max object size found to be: 90%]
DBG2 [zmesdetect_m4] [Converted 90% to 324000.0]
DBG1 [zmesdetect_m4] [--------- Frame:alarm Running variation: #2 -------------]
DBG2 [zmesdetect_m4] [detect extracted image dimensions as: 800wx450h]
DBG2 [zmesdetect_m4] [Waiting for pyzm_uid5001_gpu_lock portalock...]
DBG2 [zmesdetect_m4] [Got pyzm_uid5001_gpu_lock portalock]
DBG1 [zmesdetect_m4] [|---------- YOLO (input image: 800w*450h, model resize dimensions: 416w*416h) ----------|]
DBG2 [zmesdetect_m4] [Released pyzm_uid5001_gpu_lock portalock]
DBG1 [zmesdetect_m4] [perf: processor:gpu Yolo detection took: 1958.64 ms]
DBG2 [zmesdetect_m4] [perf: processor:gpu Yolo NMS filtering took: 1.87 ms]
DBG2 [zmesdetect_m4] [core model detection over, got 11 objects. Now filtering]
DBG2 [zmesdetect_m4] [Max object size found to be: 90%]
DBG2 [zmesdetect_m4] [Converted 90% to 324000.0]
DBG2 [zmesdetect_m4] [Ignoring pottedplant [542, 182, 562, 200] as conf. level 0.279738187789917 is lower than 0.3]
DBG2 [zmesdetect_m4] [Returning filtered list of 10 objects.]
DBG2 [zmesdetect_m4] [This model iteration inside object found: labels: ['pottedplant', 'person', 'pottedplant', 'pottedplant', 'pottedplant', 'vase', 'pottedplant', 'pottedplant', 'pottedplant', 'pottedplant'],conf:[0.9017574191093445, 0.8222572207450867, 0.7311447858810425, 0.547961413860321, 0.539945662021637, 0.4644719064235687, 0.43742167949676514, 0.39541205763816833, 0.3663233518600464, 0.31749823689460754]]
DBG2 [zmesdetect_m4] [Called _filter_detections(seq=object, box=[[501, 220, 567, 286], [370, 220, 412, 372], [578, 206, 604, 228], [630, 188, 664, 244], [472, 132, 520, 220], [529, 272, 549, 294], [476, 204, 500, 224], [606, 222, 626, 248], [538, 123, 566, 199], [522, 244, 566, 292]], label=['pottedplant', 'person', 'pottedplant', 'pottedplant', 'pottedplant', 'vase', 'pottedplant', 'pottedplant', 'pottedplant', 'pottedplant'], conf=[0.9017574191093445, 0.8222572207450867, 0.7311447858810425, 0.547961413860321, 0.539945662021637, 0.4644719064235687, 0.43742167949676514, 0.39541205763816833, 0.3663233518600464, 0.31749823689460754], polygons=[{'name': 'all', 'value': [(328, 155), (799, 132), (799, 449), (293, 449)], 'pattern': None}], h=450, w=800, model_names=['yolo', 'yolo', 'yolo', 'yolo', 'yolo', 'yolo', 'yolo', 'yolo', 'yolo', 'yolo'])]
DBG2 [zmesdetect_m4] [Max object size found to be: 90%]
DBG2 [zmesdetect_m4] [Converted 90% to 324000.0]
DBG2 [zmesdetect_m4] [intersection: object:pottedplant,POLYGON ((501 220, 567 220, 567 286, 501 286, 501 220)) intersects polygon:all,POLYGON ((328 155, 799 132, 799 449, 293 449, 328 155))]
DBG2 [zmesdetect_m4] [Using global match pattern: (person|car|motorbike|bus|truck|boat)]
DBG2 [zmesdetect_m4] [all intersects object:pottedplant[[(501, 220), (567, 220), (567, 286), (501, 286)]] but does NOT match your detect pattern filter]
DBG2 [zmesdetect_m4] [intersection: object:person,POLYGON ((370 220, 412 220, 412 372, 370 372, 370 220)) intersects polygon:all,POLYGON ((328 155, 799 132, 799 449, 293 449, 328 155))]
DBG2 [zmesdetect_m4] [Using global match pattern: (person|car|motorbike|bus|truck|boat)]
DBG2 [zmesdetect_m4] [all intersects object:person[[(370, 220), (412, 220), (412, 372), (370, 372)]]]
DBG2 [zmesdetect_m4] [intersection: object:pottedplant,POLYGON ((578 206, 604 206, 604 228, 578 228, 578 206)) intersects polygon:all,POLYGON ((328 155, 799 132, 799 449, 293 449, 328 155))]
DBG2 [zmesdetect_m4] [Using global match pattern: (person|car|motorbike|bus|truck|boat)]
DBG2 [zmesdetect_m4] [all intersects object:pottedplant[[(578, 206), (604, 206), (604, 228), (578, 228)]] but does NOT match your detect pattern filter]
DBG2 [zmesdetect_m4] [intersection: object:pottedplant,POLYGON ((630 188, 664 188, 664 244, 630 244, 630 188)) intersects polygon:all,POLYGON ((328 155, 799 132, 799 449, 293 449, 328 155))]
DBG2 [zmesdetect_m4] [Using global match pattern:(person|car|motorbike|bus|truck|boat)]
DBG2 [zmesdetect_m4] [all intersects object:pottedplant[[(630, 188), (664, 188), (664, 244), (630, 244)]] but does NOT match your detect pattern filter]
DBG2 [zmesdetect_m4] [intersection: object:pottedplant,POLYGON ((472 132, 520 132, 520 220, 472 220, 472 132)) intersects polygon:all,POLYGON ((328 155, 799 132, 799 449, 293 449, 328 155))]
DBG2 [zmesdetect_m4] [Using global match pattern: (person|car|motorbike|bus|truck|boat)]
DBG2 [zmesdetect_m4] [all intersects object:pottedplant[[(472, 132), (520, 132), (520, 220), (472, 220)]] but does NOT match your detect pattern filter]
DBG2 [zmesdetect_m4] [intersection: object:vase,POLYGON ((529 272, 549 272, 549 294, 529 294, 529 272)) intersects polygon:all,POLYGON ((328 155, 799 132, 799 449, 293 449, 328 155))]
DBG2 [zmesdetect_m4] [Using global match pattern: (person|car|motorbike|bus|truck|boat)]
DBG2 [zmesdetect_m4] [all intersects object:vase[[(529, 272), (549, 272), (549, 294), (529, 294)]] but does NOT match your detect pattern filter]
DBG2 [zmesdetect_m4] [intersection: object:pottedplant,POLYGON ((476 204, 500 204, 500 224, 476 224, 476 204)) intersects polygon:all,POLYGON ((328 155, 799 132, 799 449, 293 449, 328 155))]
DBG2 [zmesdetect_m4] [Using global match pattern: (person|car|motorbike|bus|truck|boat)]
DBG2 [zmesdetect_m4] [all intersects object:pottedplant[[(476, 204), (500, 204), (500, 224), (476, 224)]] but does NOT match your detect pattern filter]
DBG2 [zmesdetect_m4] [intersection: object:pottedplant,POLYGON ((606 222, 626 222, 626 248, 606 248, 606 222)) intersects polygon:all,POLYGON ((328 155, 799 132, 799 449, 293 449, 328 155))]
DBG2 [zmesdetect_m4] [Using global match pattern: (person|car|motorbike|bus|truck|boat)]
DBG2 [zmesdetect_m4] [all intersects object:pottedplant[[(606, 222), (626, 222), (626, 248), (606, 248)]] but does NOT match your detect pattern filter]
DBG2 [zmesdetect_m4] [intersection: object:pottedplant,POLYGON ((538 123, 566 123, 566 199, 538 199, 538 123)) intersects polygon:all,POLYGON ((328 155, 799 132, 799 449, 293 449, 328 155))]
DBG2 [zmesdetect_m4] [Using global match pattern: (person|car|motorbike|bus|truck|boat)]
DBG2 [zmesdetect_m4] [all intersects object:pottedplant[[(538, 123), (566, 123), (566, 199), (538, 199)]] but does NOT match your detect pattern filter]
DBG2 [zmesdetect_m4] [intersection: object:pottedplant,POLYGON ((522 244, 566 244, 566 292, 522 292, 522 244)) intersects polygon: all,POLYGON ((328 155, 799 132, 799 449, 293 449, 328 155))]
DBG2 [zmesdetect_m4] [Using global match pattern: (person|car|motorbike|bus|truck|boat)]
DBG2 [zmesdetect_m4] [all intersects object:pottedplant[[(522, 244), (566, 244), (566, 292), (522, 292)]] but does NOT match your detect pattern filter]
DBG2 [zmesdetect_m4] [breaking out of same model loop, as matches found and strategy is "first"] DBG2 [zmesdetect_m4
] [pyzm_uid5001_tpu_lock portalock already released] DBG2 [zmesdetect_m4]
[pyzm_uid5001_gpu_lock portalock already released]
DBG1 [zmesdetect_m4] [perf: TOTAL detection sequence (with image loads) took: 9666.38 ms to process 57983]
10/28/25 20:11:40 zmesdetect_m4[1435625] INF zm_detect.py:476 [Prediction string:[s] detected:person:82% ]

DBG1 [zmesdetect_m4] [Prediction string JSON:{"labels": ["person"], "boxes": [[370, 220, 412, 372]], "frame_id": "snapshot", "confidences": [0.8222572207450867], "image_dimensions": {"original": [720, 1280], "resized": [450, 800]}}]
[s] detected:person:82% --SPLIT--{"labels": ["person"], "boxes": [[370, 220, 412, 372]], "frame_id": "snapshot", "confidences": [0.8222572207450867], "image_dimensions": {"original": [720, 1280], "resized": [450, 800]}}
DBG1 [zmesdetect_m4] [Closing logs]

If you have the following error in the logs

requests.exceptions.HTTPError: 500 Server Error: Internal Server Error for url: https://ultra.kervao.fr/zm/api/host/login.json

The problem is that PHP is missing the apcu extension . We'll get it from here: https://github.com/krakjoe/apcu/releases . We'll extract it by typing

tar xvfz apcu-5.1.24.tar.gz
cd apcu-5.1.24
phpize
./configure --enable-apcu
make

and as root, run make install

and modify /usr/local/apache2/conf/php.ini by adding

extension=apcu
apc.enabled=1
apc.enable_cli=1

We restart Apache. Regarding this other error

Oct 27 16:10:04 ultra.kervao.fr zm_detect.py[149699]: ERR [zmesdetect_m4] [Turning DB logging off. Could not connect to DB, message was:(mysql.connector.errors.DatabaseError) 1273 (HY000): Unknown collation: 'utf8mb4_0900_ai_ci'
                                                       (Background on this error at: http://sqlalche.me/e/13/4xp6)]

This is because I'm using MariaDB, which hasn't adopted some of MySQL 's settings ; I had to replace the occurrences of utf8mb4_0900_ai_ci with utf8mb4_unicode_520_ci in the files

/usr/local/lib64/python3.10/site-packages/mysql/connector/aio/charsets.py
/usr/local/lib64/python3.10/site-packages/mysql/connector/charsets.py

The command below will allow you to see if everything is working correctly; you will need to specify an event and camera number.

sudo -u apache /var/lib/zmeventnotification/bin/zm_detect.py --config /etc/zm/objectconfig.ini --eventid 375 --monitorid 4 --debug

If ZoneMinder seems to be working but you have a white screen instead of the image, you will probably need to edit the file /etc/zm/conf.d/01-system-paths.conf and specify the following:

ZM_PATH_ZMS=/zm/cgi-bin/nph-zms

Some websites for more information
and of course the forum https://forums.zoneminder.com/