diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ec3a619d..916bf663e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ cmake_minimum_required (VERSION 2.6) project (zoneminder) set(zoneminder_VERSION "1.28.100") +# Engine version is used for external plugin compilation set(ZM_ENGINE_VERSION 29) # make API version a minor of ZM version set(zoneminder_API_VERSION "${zoneminder_VERSION}.1") @@ -64,12 +65,14 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") # Modules that we need: include (GNUInstallDirs) include (CheckIncludeFile) +include (CheckIncludeFileCXX) include (CheckIncludeFiles) include (CheckFunctionExists) include (CheckPrototypeDefinition_fixed) include (CheckTypeSize) include (CheckStructHasMember) include (CheckSendfile) +include (FindPkgConfig) # Configuration options mark_as_advanced( @@ -82,6 +85,7 @@ mark_as_advanced( ZM_TARGET_DISTRO ZM_CONFIG_DIR ZM_PLUGIN_SUPPORT + ZM_PLUGIN_COMPIL ZM_PLUGINSLIBDIR ZM_PLUGINSPKGLIBDIR ZM_PLUGINSCONFDIR @@ -149,17 +153,20 @@ set(ZM_PERL_SEARCH_PATH "" CACHE PATH set(ZM_TARGET_DISTRO "" CACHE STRING "Build ZoneMinder for a specific distribution. Currently, valid names are: f21, f20, el6, OS13") set(ZM_PLUGIN_SUPPORT "OFF" CACHE BOOL - "Set to ON to enable plugins support. This is EXPERIMENTAL. default: OFF") + "Set to ON to enable plugin support in ZoneMinder. This is EXPERIMENTAL, default: OFF") +set(ZM_PLUGIN_COMPIL "OFF" CACHE BOOL + "Set to ON to enable compilation of internal plugins. This is EXPERIMENTAL, default: OFF") + if(ZM_PLUGIN_SUPPORT) set(ZM_PREFIX ${CMAKE_INSTALL_PREFIX}) set(ZM_PLUGINSLIBDIR "\${prefix}/${CMAKE_INSTALL_LIBDIR}" CACHE PATH - "Location of the plugin files, default: ${CMAKE_INSTALL_LIBDIR}") + "Location of the plugin development library, default: ${CMAKE_INSTALL_LIBDIR}") set(ZM_PLUGINSPKGLIBDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBEXECDIR}/zoneminder/plugins" CACHE PATH - "Location of the plugin files, default: /${CMAKE_INSTALL_LIBEXECDIR}/zoneminder/plugins") + "Location of the plugin file, default: /${CMAKE_INSTALL_LIBEXECDIR}/zoneminder/plugins") set(ZM_PLUGINSCONFDIR "/${CMAKE_INSTALL_SYSCONFDIR}/plugins.d" CACHE PATH - "Location of the plugin files, default: /${CMAKE_INSTALL_SYSCONFDIR}/plugins.d") + "Location of the plugin configuration files, default: /${CMAKE_INSTALL_SYSCONFDIR}/plugins.d") set(ZM_PLUGINSWEBDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/zoneminder/plugins" CACHE PATH - "Location of the plugin files, default: /${CMAKE_INSTALL_DATADIR}/zoneminder/plugins") + "Location of the plugin web files, default: /${CMAKE_INSTALL_DATADIR}/zoneminder/plugins") set(ZM_PROJECTNAME ${CMAKE_PROJECT_NAME}) set(ZM_PLUGINSEXT ".so" CACHE STRING "Plugin extension, default: .so") @@ -534,6 +541,29 @@ if(NOT ZM_NO_LIBVLC) endif(LIBVLC_LIBRARIES) endif(NOT ZM_NO_LIBVLC) +if(ZM_PLUGIN_COMPIL) + if(PKG_CONFIG_FOUND) + pkg_search_module(opencv opencv>=2.3.1) + if(NOT opencv_FOUND) + message(FATAL_ERROR "opencv.pc not found") + endif(NOT opencv_FOUND) + else(PKG_CONFIG_FOUND) + message(FATAL_ERROR "pkg-config not found") + endif(PKG_CONFIG_FOUND) + find_package(OpenCV COMPONENTS core imgproc highgui objdetect REQUIRED) + if(OpenCV_FOUND) + include_directories(${OpenCV_INCLUDE_DIR}) + else(OpenCV_FOUND) + message(FATAL_ERROR "Cannot build application without OpenCV. Please set OpenCV_INCLUDE_DIR.") + endif(OpenCV_FOUND) + find_package(Boost COMPONENTS program_options REQUIRED) + if(Boost_FOUND) + include_directories(${Boost_INCLUDE_DIR}) + else(Boost_FOUND) + message(FATAL_ERROR "Cannot build application without Boost. Please set Boost_INCLUDE_DIR.") + endif(Boost_FOUND) +endif(ZM_PLUGIN_COMPIL) + # *** END OF LIBRARY CHECKS *** # Check for gnutls or crypto @@ -622,6 +652,29 @@ if(HAVE_LIBCRYPTO AND HAVE_OPENSSL_MD5_H AND HAVE_MD5_OPENSSL) set(HAVE_GNUTLS_OPENSSL_H 0) endif(HAVE_LIBCRYPTO AND HAVE_OPENSSL_MD5_H AND HAVE_MD5_OPENSSL) +if(ZM_PLUGIN_COMPIL) + check_include_file_cxx("alpr.h" HAVE_ALPR_H) + if(NOT HAVE_ALPR_H) + message(FATAL_ERROR "Missing include file alpr.h") + endif(NOT HAVE_ALPR_H) + check_include_file_cxx("opencv2/core/core.hpp" HAVE_OPENCV2_CORE_CORE_HPP) + if(NOT HAVE_OPENCV2_CORE_CORE_HPP) + message(FATAL_ERROR "Missing include file opencv2/core/core.hpp") + endif(NOT HAVE_OPENCV2_CORE_CORE_HPP) + check_include_file_cxx("opencv2/imgproc/imgproc.hpp" HAVE_OPENCV2_IMGPROC_IMGPROC_HPP) + if(NOT HAVE_OPENCV2_IMGPROC_IMGPROC_HPP) + message(FATAL_ERROR "Missing include file opencv2/imgproc/imgproc.hpp") + endif(NOT HAVE_OPENCV2_IMGPROC_IMGPROC_HPP) + check_include_file_cxx("boost/algorithm/string.hpp" HAVE_BOOST_ALGORITHM_STRING_HPP) + if(NOT HAVE_BOOST_ALGORITHM_STRING_HPP) + message(FATAL_ERROR "Missing include file boost/algorithm/string.hpp") + endif(NOT HAVE_BOOST_ALGORITHM_STRING_HPP) + check_include_file_cxx("boost/program_options.hpp" HAVE_BOOST_PROGRAM_OPTIONS_HPP) + if(NOT HAVE_BOOST_PROGRAM_OPTIONS_HPP) + message(FATAL_ERROR "Missing include file boost/program_options.hpp") + endif(NOT HAVE_BOOST_PROGRAM_OPTIONS_HPP) +endif(ZM_PLUGIN_COMPIL) + # Check for Perl find_package(Perl) if(NOT PERL_FOUND) @@ -718,6 +771,11 @@ if(ZM_ONVIF) add_subdirectory(onvif) endif(ZM_ONVIF) +# Enable compilation of internal plugins +if(ZM_PLUGIN_COMPIL) + add_subdirectory(plugins) +endif(ZM_PLUGIN_COMPIL) + # Process distro subdirectories if((ZM_TARGET_DISTRO STREQUAL "f21") OR (ZM_TARGET_DISTRO STREQUAL "f20")) add_subdirectory(distros/fedora) diff --git a/Makefile.am b/Makefile.am index ce93b03cc..135addb35 100644 --- a/Makefile.am +++ b/Makefile.am @@ -13,13 +13,18 @@ if COND_ONVIF MAYBE_ONVIF = onvif endif +if ZM_PLUGIN_COMPIL + MAYBE_PLUGINS = plugins +endif + SUBDIRS = \ src \ web \ scripts \ db \ misc \ - $(MAYBE_ONVIF) + $(MAYBE_ONVIF) \ + $(MAYBE_PLUGINS) EXTRA_DIST = \ zm.conf.in \ diff --git a/configure.ac b/configure.ac index 40e4ea9fb..b1f225769 100644 --- a/configure.ac +++ b/configure.ac @@ -221,6 +221,7 @@ AC_ARG_WITH(cgidir, AC_SUBST(CGI_PREFIX) ENABLE_PLUGIN_SUPPORT=no +ENABLE_PLUGIN_COMPIL=no AC_ARG_ENABLE([plugin-support], [ --enable-plugin-support= enable or disable plugin support, default disabled], [ENABLE_PLUGIN_SUPPORT=$enable_plugin_support], @@ -236,6 +237,14 @@ if test "$ENABLE_PLUGIN_SUPPORT" == "yes"; then If you really want plugin support you must not call configure with --enable-shared=no or --disabled-shared]) fi + AC_ARG_ENABLE([plugin-compilation], + [ --enable-plugin-compilation= enable or disable plugin compilation, default disabled], + [ENABLE_PLUGIN_COMPIL=$enable_plugin_compilation], + [AC_MSG_WARN([You can call configure with the --enable-plugin-compilation= + or --disable-plugin-compilation option. + This tells configure whether to compile provided plugins. + e.g. --enable-plugin-compilation=yes or --disable-plugin-compilation])] + ) ZM_PREFIX="${prefix}" AC_SUBST(ZM_PREFIX) ZM_PLUGINSLIBDIR="${libdir}" @@ -269,6 +278,7 @@ if test "$ENABLE_PLUGIN_SUPPORT" == "yes"; then AC_DEFINE(ZM_PLUGINS_ON,1,"Whether plugin support is switched on and compiled") fi AM_CONDITIONAL([ZM_PLUGIN_SUPPORT], [test "x$ENABLE_PLUGIN_SUPPORT" = xyes]) +AM_CONDITIONAL([ZM_PLUGIN_COMPIL], [test "x$ENABLE_PLUGIN_COMPIL" = xyes]) WEB_USER=apache AC_ARG_WITH(webuser, @@ -495,6 +505,44 @@ AC_CHECK_LIB([curl], [AC_SUBST([CURL_LIBS],["-lcurl"])AC_DEFINE([HAVE_LIBCURL],[1],[Define to 1 if you have the 'curl' library (-lcurl).])] ) +if test "$ENABLE_PLUGIN_COMPIL" == "yes"; then + AC_CHECK_LIB([boost_program_options], + [main], + [AC_SUBST([BOOST_PROGRAM_OPTION_LIBS],["-lboost_program_options"])AC_DEFINE([HAVE_BOOST_PROGRAM_OPTION],[1],[Define to 1 if you have the 'boost_program_options' library (-lboost_program_options).])], + [AC_MSG_ERROR([libboost_program_options is required for plugin compilation])] + ) + AC_CHECK_LIB([opencv_core], + [main], + [AC_SUBST([OPENCV_CORE_LIBS],["-lopencv_core"])AC_DEFINE([HAVE_LIBOPENCV_CORE],[1],[Define to 1 if you have the 'opencv_core' library (-lopencv_core).])], + [AC_MSG_ERROR([libopencv_core is required for plugin compilation])] + ) + AC_CHECK_LIB([opencv_imgproc], + [main], + [AC_SUBST([OPENCV_IMGPROC_LIBS],["-lopencv_imgproc"])AC_DEFINE([HAVE_LIBOPENCV_IMGPROC],[1],[Define to 1 if you have the 'opencv_imgproc' library (-lopencv_imgproc).])], + [AC_MSG_ERROR([libopencv_imgproc is required for plugin compilation])] + ) + AC_CHECK_LIB([opencv_highgui], + [main], + [AC_SUBST([OPENCV_HIGHGUI_LIBS],["-lopencv_highgui"])AC_DEFINE([HAVE_LIBOPENCV_HIGHGUI],[1],[Define to 1 if you have the 'opencv_highgui' library (-lopencv_highgui).])], + [AC_MSG_ERROR([libopencv_highgui is required for plugin compilation])] + ) + AC_CHECK_LIB([opencv_objdetect], + [main], + [AC_SUBST([OPENCV_OBJDETECT_LIBS],["-lopencv_objdetect"])AC_DEFINE([HAVE_LIBOPENCV_OBJDETECT],[1],[Define to 1 if you have the 'opencv_objdetect' library (-lopencv_objdetect).])], + [AC_MSG_ERROR([libopencv_objdetect is required for plugin compilation])] + ) + PKG_CHECK_MODULES([OPENCV], + [opencv >= 2.3.1], + , + [AC_MSG_ERROR([${OPENCV_PKG_ERRORS}])] + ) + AC_CHECK_LIB([openalpr], + [main], + [AC_SUBST([OPENALPR_LIBS],["-lopenalpr"])AC_DEFINE([HAVE_LIBOPENALPR],[1],[Define to 1 if you have the 'openalpr' library (-lopenalpr).])], + [AC_MSG_ERROR([libopenalpr is required for plugin compilation])] + ) +fi + # Checks for header files. AC_FUNC_ALLOCA AC_HEADER_STDC @@ -545,6 +593,15 @@ AC_CHECK_HEADERS(zlib.h,,,) AC_CHECK_HEADERS(vlc/vlc.h,,,) AC_CHECK_HEADERS(curl/curl.h,,,) +if test "$ENABLE_PLUGIN_COMPIL" == "yes"; then + AC_CHECK_HEADERS(alpr.h,,AC_MSG_ERROR(plugins require openalpr headers - check that development package is installed),) + AC_CHECK_HEADERS(boost/algorithm/string.hpp,,AC_MSG_ERROR(plugins require boost string headers - check that development package is installed),) + AC_CHECK_HEADERS(boost/program_options.hpp,,AC_MSG_ERROR(plugins require boost program_options headers - check that development package is installed),) + AC_CHECK_HEADERS(opencv2/core/core.hpp,,AC_MSG_ERROR(plugins require opencv core headers - check that development package is installed),) + AC_CHECK_HEADERS(opencv2/imgproc/imgproc.hpp,,AC_MSG_ERROR(plugins require opencv imgproc headers - check that development package is installed),) + AC_CHECK_HEADERS(opencv2/highgui/highgui.hpp,,AC_MSG_ERROR(plugins require opencv highgui headers - check that development package is installed),) +fi + if test "$ZM_SSL_LIB" == "openssl"; then AC_CHECK_DECLS(MD5,,AC_MSG_ERROR([zm requires openssl/md5.h - use ZM_SSL_LIB option to select gnutls instead]),[#include #include ]) @@ -613,7 +670,7 @@ fi AC_SUBST(PERL_MM_PARMS) AC_SUBST(EXTRA_PERL_LIB) -AC_CONFIG_FILES([Makefile zm.conf zmconfgen.pl db/Makefile db/zm_create.sql misc/Makefile misc/apache.conf misc/libzoneminder.pc misc/logrotate.conf misc/syslog.conf misc/com.zoneminder.systemctl.policy misc/com.zoneminder.systemctl.rules misc/plugins.conf onvif/Makefile onvif/scripts/Makefile scripts/Makefile scripts/zm scripts/zmaudit.pl scripts/zmcontrol.pl scripts/zmdc.pl scripts/zmfilter.pl scripts/zmpkg.pl scripts/zmtrack.pl scripts/zmcamtool.pl scripts/zmsystemctl.pl scripts/zmtrigger.pl scripts/zmupdate.pl scripts/zmvideo.pl scripts/zmwatch.pl scripts/zmx10.pl scripts/zmdbbackup scripts/zmdbrestore scripts/zmeventdump scripts/zmlogrotate.conf scripts/ZoneMinder/lib/ZoneMinder/Base.pm scripts/ZoneMinder/lib/ZoneMinder/Config.pm scripts/ZoneMinder/lib/ZoneMinder/Memory.pm scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm src/Makefile src/zm_config.h web/Makefile web/ajax/Makefile web/css/Makefile web/graphics/Makefile web/includes/Makefile web/includes/config.php web/js/Makefile web/lang/Makefile web/skins/Makefile web/skins/classic/Makefile web/skins/classic/ajax/Makefile web/skins/classic/css/Makefile web/skins/classic/css/classic/Makefile web/skins/classic/css/classic/views/Makefile web/skins/classic/css/flat/Makefile web/skins/classic/css/flat/views/Makefile web/skins/classic/graphics/Makefile web/skins/classic/includes/Makefile web/skins/classic/js/Makefile web/skins/classic/lang/Makefile web/skins/classic/views/Makefile web/skins/classic/views/js/Makefile web/skins/mobile/Makefile web/skins/mobile/ajax/Makefile web/skins/mobile/css/Makefile web/skins/mobile/graphics/Makefile web/skins/mobile/includes/Makefile web/skins/mobile/lang/Makefile web/skins/mobile/views/Makefile web/skins/mobile/views/css/Makefile web/tools/Makefile web/tools/mootools/Makefile web/views/Makefile web/skins/xml/Makefile web/skins/xml/views/Makefile web/skins/xml/includes/Makefile]) +AC_CONFIG_FILES([Makefile zm.conf zmconfgen.pl db/Makefile db/zm_create.sql misc/Makefile misc/apache.conf misc/libzoneminder.pc misc/logrotate.conf misc/syslog.conf misc/com.zoneminder.systemctl.policy misc/com.zoneminder.systemctl.rules misc/plugins.conf onvif/Makefile onvif/scripts/Makefile plugins/Makefile plugins/libzm_plugin_openalpr/Makefile plugins/libzm_plugin_openalpr/src/Makefile plugins/libzm_plugin_openalpr/data/Makefile scripts/Makefile scripts/zm scripts/zmaudit.pl scripts/zmcontrol.pl scripts/zmdc.pl scripts/zmfilter.pl scripts/zmpkg.pl scripts/zmtrack.pl scripts/zmcamtool.pl scripts/zmsystemctl.pl scripts/zmtrigger.pl scripts/zmupdate.pl scripts/zmvideo.pl scripts/zmwatch.pl scripts/zmx10.pl scripts/zmdbbackup scripts/zmdbrestore scripts/zmeventdump scripts/zmlogrotate.conf scripts/ZoneMinder/lib/ZoneMinder/Base.pm scripts/ZoneMinder/lib/ZoneMinder/Config.pm scripts/ZoneMinder/lib/ZoneMinder/Memory.pm scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm src/Makefile src/zm_config.h web/Makefile web/ajax/Makefile web/css/Makefile web/graphics/Makefile web/includes/Makefile web/includes/config.php web/js/Makefile web/lang/Makefile web/skins/Makefile web/skins/classic/Makefile web/skins/classic/ajax/Makefile web/skins/classic/css/Makefile web/skins/classic/css/classic/Makefile web/skins/classic/css/classic/views/Makefile web/skins/classic/css/flat/Makefile web/skins/classic/css/flat/views/Makefile web/skins/classic/graphics/Makefile web/skins/classic/includes/Makefile web/skins/classic/js/Makefile web/skins/classic/lang/Makefile web/skins/classic/views/Makefile web/skins/classic/views/js/Makefile web/skins/mobile/Makefile web/skins/mobile/ajax/Makefile web/skins/mobile/css/Makefile web/skins/mobile/graphics/Makefile web/skins/mobile/includes/Makefile web/skins/mobile/lang/Makefile web/skins/mobile/views/Makefile web/skins/mobile/views/css/Makefile web/tools/Makefile web/tools/mootools/Makefile web/views/Makefile web/skins/xml/Makefile web/skins/xml/views/Makefile web/skins/xml/includes/Makefile]) # Create the definitions for compilation and defaults for the database AC_CONFIG_COMMANDS([src/zm_config_defines.h],[perl ./zmconfgen.pl]) diff --git a/distros/debian8/control b/distros/debian8/control index 05eaee17e..e932cdcad 100644 --- a/distros/debian8/control +++ b/distros/debian8/control @@ -129,3 +129,12 @@ Description: XML interface for ZoneMinder . This package provides a XML interface mainly intended for use with the eyeZm iPhone Application, but can be used with any other custom programs as well. + +Package: libzoneminder-plugin-openalpr +Section: libs +Architecture: any +Depends: zoneminder-core (= ${binary:Version}), ${shlibs:Depends}, + ${misc:Depends} +Description: License Plate Recognition plugin with openalpr library + This plugin add support for recognition of license plate number in zoneminder + with the use of the OpenALPR library. diff --git a/distros/debian8/libzoneminder-plugin-openalpr.install b/distros/debian8/libzoneminder-plugin-openalpr.install new file mode 100644 index 000000000..24bb2af7f --- /dev/null +++ b/distros/debian8/libzoneminder-plugin-openalpr.install @@ -0,0 +1,4 @@ +usr/lib/zoneminder/plugins/libzm_plugin_openalpr.so +usr/share/zoneminder/plugins/libzm_plugin_openalpr/config.php +usr/share/zoneminder/plugins/libzm_plugin_openalpr/lang/*_*.php +etc/zm/plugins.d/openalpr.conf diff --git a/distros/debian8/rules b/distros/debian8/rules index 6f3cf8e73..a84543f0e 100755 --- a/distros/debian8/rules +++ b/distros/debian8/rules @@ -54,7 +54,8 @@ override_dh_auto_configure: --with-ffmpeg=/usr --with-cgidir=/usr/lib/cgi-bin \ --with-webuser=www-data --with-webgroup=www-data \ --enable-crashtrace=no --enable-mmap=yes $(DEBOPT) \ - --enable-plugin-support --with-pluginsdir=/usr/lib/zoneminder/plugins + --enable-plugin-support --with-pluginsdir=/usr/lib/zoneminder/plugins \ + --enable-plugin-compilation=yes override_dh_clean: # Add here commands to clean up after the build process. @@ -73,6 +74,9 @@ override_dh_clean: done || true override_dh_install: + # Clean up libtool crap + find debian/tmp -name '*.la' -exec rm '{}' ';' + # dh_install --fail-missing # # NOTE: This is a short-term kludge; hopefully changes in the next diff --git a/distros/debian8/zoneminder-core.install b/distros/debian8/zoneminder-core.install index afd9ada95..20c3252dc 100644 --- a/distros/debian8/zoneminder-core.install +++ b/distros/debian8/zoneminder-core.install @@ -1,4 +1,5 @@ -etc/zm +etc/zm/plugins.conf +etc/zm/zm.conf usr/bin usr/share/polkit-1/actions usr/share/polkit-1/rules.d diff --git a/distros/debian8_cmake/control b/distros/debian8_cmake/control index a378742dc..0b4cd8104 100644 --- a/distros/debian8_cmake/control +++ b/distros/debian8_cmake/control @@ -129,3 +129,12 @@ Description: XML interface for ZoneMinder . This package provides a XML interface mainly intended for use with the eyeZm iPhone Application, but can be used with any other custom programs as well. + +Package: libzoneminder-plugin-openalpr +Section: libs +Architecture: any +Depends: zoneminder-core (= ${binary:Version}), ${shlibs:Depends}, + ${misc:Depends} +Description: License Plate Recognition plugin with openalpr library + This plugin add support for recognition of license plate number in zoneminder + with the use of the OpenALPR library. diff --git a/distros/debian8_cmake/libzoneminder-plugin-openalpr.install b/distros/debian8_cmake/libzoneminder-plugin-openalpr.install new file mode 100644 index 000000000..24bb2af7f --- /dev/null +++ b/distros/debian8_cmake/libzoneminder-plugin-openalpr.install @@ -0,0 +1,4 @@ +usr/lib/zoneminder/plugins/libzm_plugin_openalpr.so +usr/share/zoneminder/plugins/libzm_plugin_openalpr/config.php +usr/share/zoneminder/plugins/libzm_plugin_openalpr/lang/*_*.php +etc/zm/plugins.d/openalpr.conf diff --git a/distros/debian8_cmake/rules b/distros/debian8_cmake/rules index 6b00f8dcf..f83c4d96e 100755 --- a/distros/debian8_cmake/rules +++ b/distros/debian8_cmake/rules @@ -37,7 +37,8 @@ override_dh_auto_configure: -DZM_WEB_GROUP=www-data \ -DCMAKE_INSTALL_SYSCONFDIR=etc/zm \ -DZM_PLUGIN_SUPPORT=ON \ - -DZM_PLUGINSPKGLIBDIR=/usr/lib/zoneminder/plugins + -DZM_PLUGINSPKGLIBDIR=/usr/lib/zoneminder/plugins \ + -DZM_PLUGIN_COMPIL=ON override_dh_auto_install: dh_auto_install --buildsystem=cmake diff --git a/distros/debian8_cmake/zoneminder-core.install b/distros/debian8_cmake/zoneminder-core.install index 0926e7a51..20c3252dc 100644 --- a/distros/debian8_cmake/zoneminder-core.install +++ b/distros/debian8_cmake/zoneminder-core.install @@ -1,5 +1,5 @@ -etc/zm/zm.conf etc/zm/plugins.conf +etc/zm/zm.conf usr/bin usr/share/polkit-1/actions usr/share/polkit-1/rules.d diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt new file mode 100644 index 000000000..0a00cfb87 --- /dev/null +++ b/plugins/CMakeLists.txt @@ -0,0 +1,4 @@ +# CMakeLists.txt for the plugins directory + +# Process subdirectories +add_subdirectory(libzm_plugin_openalpr) diff --git a/plugins/Makefile.am b/plugins/Makefile.am new file mode 100644 index 000000000..144358924 --- /dev/null +++ b/plugins/Makefile.am @@ -0,0 +1,4 @@ +AUTOMAKE_OPTIONS = gnu + +SUBDIRS = \ + libzm_plugin_openalpr diff --git a/plugins/libzm_plugin_openalpr/CMakeLists.txt b/plugins/libzm_plugin_openalpr/CMakeLists.txt new file mode 100644 index 000000000..505590e6a --- /dev/null +++ b/plugins/libzm_plugin_openalpr/CMakeLists.txt @@ -0,0 +1,6 @@ +# CMakeLists.txt for openalpr plugin. +# Created by Emmanuel Papin (manupap01@gmail.com) + +# Process subdirectories +add_subdirectory(data) +add_subdirectory(src) diff --git a/plugins/libzm_plugin_openalpr/Makefile.am b/plugins/libzm_plugin_openalpr/Makefile.am new file mode 100644 index 000000000..cd9671e01 --- /dev/null +++ b/plugins/libzm_plugin_openalpr/Makefile.am @@ -0,0 +1,5 @@ +AUTOMAKE_OPTIONS = foreign + +SUBDIRS = \ + data \ + src diff --git a/plugins/libzm_plugin_openalpr/README.md b/plugins/libzm_plugin_openalpr/README.md new file mode 100644 index 000000000..d2d11cdb0 --- /dev/null +++ b/plugins/libzm_plugin_openalpr/README.md @@ -0,0 +1,73 @@ +libzm-plugin-openalpr +===================== + +## Overview + +libzm-plugin-openalpr is a plugin for Automatic Licence Plate Recognition (ALPR) in ZoneMinder. +It is based on openalpr library (https://github.com/openalpr/openalpr). +The recognized license plates are added to Zoneminder's event notes. + +## Requirements + +libzoneminder-plugin-openalpr requires: +- A ZoneMinder installation with the plugin framework +- The OpenALPR library + +### Configuration + +After installation, please make some adjustments in file `/etc/zm/plugins.d/openalpr.conf`. +Most of default values can be kept, but if you live in Europe, you can set the `country_code` setting to `eu` to improve reading of plates with EU format + +All the next configuration steps are done through the web interface. + +Firstly, the plugin loading has to be enabled in ZM options (please check the `LOAD_PLUGIN` setting in `Config` tab). + +Then, you can configure the plugin settings from each `Zone` configuration page. + +![Zone](https://github.com/manupap1/libzoneminder-plugin-openalpr/blob/master/misc/zone.png) + +Available plugins are listed with a color code under the `Plugins` row: +- `Default color` - Plugin is not enabled for the zone +- `Green` - Plugin is enabled for the zone +- `Grey` - Plugin loading is disabled (please check `LOAD_PLUGIN` setting in `Config` tab) +- `Orange` - Plugin is enabled for the zone but not active (configuration setting mismatch) +- `Red` - ZoneMinder failed to load the plugin object (software error) + +Once a plugin object is loaded, the `Plugin` configuration page is accessed by clicking on the plugin name. + +![Plugin](https://github.com/manupap1/libzoneminder-plugin-openalpr/blob/master/misc/plugin.png) + +The first options are available for all plugins: +- `Enabled` - A yes/no select box to enable or disable the plugin +- `Require Native Detection` - A yes/no select box to specify if native detection is required before to process plugin analysis. This option allow to limit CPU usage by using the plugin for post processing after native detection. This option is recommended for this plugin as it may use a lot of CPU ressources +- `Include Native Detection` - A yes/no select box to specify if native detection shall be included in alarm score and image overlay +- `Reinit. Native Detection` - A yes/no select box to specify if native detection shall be reinitialized after detection. ZoneMinder's native detection is performed by comparing the current image to a reference image. By design, the reference image is assigned when analysis is activated, and this image is not periodically refreshed. This operating method is not necessarily optimal because some plugins may require native detection only when motion is truly detected (current image different from the previous image). This option is recommended for libzoneminder-plugin-openalpr. For example, without this option enabled, if a vehicle appears and parks in the camera field of view, the native detection will be be triggered as long as the vehicle is parked, and therefore the plugin analysis would be performed for an unnecessary period of time. With this option enabled, the plugin analysis stops when the vehicle stops. +- `Alarm Score` - A text box to enter the score provided by the plugin in case of detection + +The next options are specifics to this plugin and can be used to adjust the detection accuracy: +- `Minimum Confidence` - A text box to enter the minimum confidence level. All plates with a lower confidence level will be excluded. +- `Min. Number of Characters` - A text box to enter the minimum number of characters in a license plate. All plates with a lower number of detected characters will be excluded. +- `Max. Number of Characters` - A text box to enter the maximum number of characters in a license plate. All plates with a greater number of detected characters will be excluded. +- `List of Targeted Plates` - A list to specify targeted plates (detected plates will have a 100% confidence). +- `Detect only Targeted Plates` - A yes/no select box to specify if plates not in target list shall be ignored. +- `Strict Targeting` - A yes/no select box to specify if target matching shall be strict (plates must match exactly). +- `Assume target matching` - When strict targeting is off plates included in wider strings are detected (within the max. number of characters limit). This yes/no select box allow to specify if such detected plates shall be considered as being equal to the target. If yes is selected, the plugin will report these plates with the target string and with a 100% confidence. If no is selected, the plugin will report these plates individually. This option can be helpfull if the plate format includes a field for a logo which may be considered as a character by openalpr. + +The configuration is saved to the database and applied when clicking on the `Save` button. + +### Using + +When a license plate is detected, this triggers an event with alarmed frame(s). +Depending on your configuration settings and video content, an event may contain multiple alarmed frames. + +![Events](https://github.com/manupap1/libzoneminder-plugin-openalpr/blob/master/misc/events.png) + +Licenses plates are stored in the event note field accessible by a click on the event detection cause. + +![Event](https://github.com/manupap1/libzoneminder-plugin-openalpr/blob/master/misc/event.png) + +Alarmed frames are highlighted with the plate's detection area(s). + +![Frame](https://github.com/manupap1/libzoneminder-plugin-openalpr/blob/master/misc/frame.png) + +The detected plate number(s) can be added in email or sms notifications by using the %ED% token in EMAIL_BODY or MESSAGE_BODY option. diff --git a/plugins/libzm_plugin_openalpr/data/CMakeLists.txt b/plugins/libzm_plugin_openalpr/data/CMakeLists.txt new file mode 100644 index 000000000..5fd2e66b1 --- /dev/null +++ b/plugins/libzm_plugin_openalpr/data/CMakeLists.txt @@ -0,0 +1,5 @@ +# CMakeLists.txt for openalpr plugin. + +install (FILES openalpr.conf DESTINATION ${ZM_PLUGINSCONFDIR}) +install (FILES config.php DESTINATION "${ZM_PLUGINSWEBDIR}/libzm_plugin_openalpr") +install (FILES en_gb.php fr_fr.php DESTINATION "${ZM_PLUGINSWEBDIR}/libzm_plugin_openalpr/lang") diff --git a/plugins/libzm_plugin_openalpr/data/Makefile.am b/plugins/libzm_plugin_openalpr/data/Makefile.am new file mode 100644 index 000000000..354186592 --- /dev/null +++ b/plugins/libzm_plugin_openalpr/data/Makefile.am @@ -0,0 +1,14 @@ +AUTOMAKE_OPTIONS = foreign + +sysconfdir = @ZM_PLUGINSCONFDIR@ +sysconf_DATA = \ + openalpr.conf + +webdir = @ZM_PLUGINSWEBDIR@/libzm_plugin_openalpr +dist_web_DATA = \ + config.php + +langdir = $(webdir)/lang +dist_lang_DATA = \ + en_gb.php \ + fr_fr.php diff --git a/plugins/libzm_plugin_openalpr/data/config.php b/plugins/libzm_plugin_openalpr/data/config.php new file mode 100644 index 000000000..921ebeeba --- /dev/null +++ b/plugins/libzm_plugin_openalpr/data/config.php @@ -0,0 +1,85 @@ + + * + * Authors: Emmanuel Papin + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + * ****************************************************************************/ + +$pluginOptions = array( + 'MinConfidence' => array( + 'Type' => 'integer', + 'Min' => '0', + 'Max' => '100', + 'Value' => '0', + 'Require' => array( + array( + 'Name' => 'OnlyTargets', + 'Value' => 'No', + ), + ), + ), + 'MinCharacters' => array( + 'Type' => 'integer', + 'Min' => '1', + 'Max' => '20', + 'Value' => '1', + 'Require' => array( + array( + 'Name' => 'OnlyTargets', + 'Value' => 'No', + ), + ), + ), + 'MaxCharacters' => array( + 'Type' => 'integer', + 'Min' => '1', + 'Max' => '20', + 'Value' => '20', + 'Require' => array( + array( + 'Name' => 'StrictTargets', + 'Value' => 'No', + ), + ), + ), + 'TargetList' => array( + 'Type' => 'list', + ), + 'OnlyTargets' => array( + 'Type' => 'select', + 'Choices' => 'Yes,No', + 'Value'=> 'No', + ), + 'StrictTargets' => array( + 'Type' => 'select', + 'Choices' => 'Yes,No', + 'Value'=> 'No', + ), + 'AssumeTargets' => array( + 'Type' => 'select', + 'Choices' => 'Yes,No', + 'Value'=> 'No', + 'Require' => array( + array( + 'Name' => 'StrictTargets', + 'Value' => 'No', + ), + ), + ), +); +?> diff --git a/plugins/libzm_plugin_openalpr/data/en_gb.php b/plugins/libzm_plugin_openalpr/data/en_gb.php new file mode 100644 index 000000000..c4fc9e7d9 --- /dev/null +++ b/plugins/libzm_plugin_openalpr/data/en_gb.php @@ -0,0 +1,33 @@ + + * + * Authors: Emmanuel Papin + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + * ****************************************************************************/ + +$PLANG = array( + 'AssumeTargets' => 'Assume target matching', + 'MaxCharacters' => 'Max. Number of Characters', + 'MinCharacters' => 'Min. Number of Characters', + 'MinConfidence' => 'Minimum Confidence', + 'OnlyTargets' => 'Detect only Targeted Plates', + 'StrictTargets' => 'Strict Targeting', + 'TargetList' => 'List of Targeted Plates', +); + +?> diff --git a/plugins/libzm_plugin_openalpr/data/fr_fr.php b/plugins/libzm_plugin_openalpr/data/fr_fr.php new file mode 100644 index 000000000..ea5433906 --- /dev/null +++ b/plugins/libzm_plugin_openalpr/data/fr_fr.php @@ -0,0 +1,33 @@ + + * + * Authors: Emmanuel Papin + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + * ****************************************************************************/ + +$PLANG = array( + 'AssumeTargets' => 'Supposer la correspondance', + 'MaxCharacters' => 'Nombre max. de caractères', + 'MinCharacters' => 'Nombre min. de caractères', + 'MinConfidence' => 'Confiance minimum', + 'OnlyTargets' => 'Détecter seul. plaques ciblées', + 'StrictTargets' => 'Ciblage strict', + 'TargetList' => 'Liste de plaques ciblées', +); + +?> diff --git a/plugins/libzm_plugin_openalpr/data/openalpr.conf b/plugins/libzm_plugin_openalpr/data/openalpr.conf new file mode 100644 index 000000000..9bf58bdd3 --- /dev/null +++ b/plugins/libzm_plugin_openalpr/data/openalpr.conf @@ -0,0 +1,24 @@ +# The options commented out show the default values + +[libzm_plugin_openalpr] + +# Path to the openalpr.conf file (used by openalpr library) +config_file = /etc/openalpr/openalpr.conf + +# Country code to identify (either us for USA or eu for Europe) +#country_code = us + +# Attempt to match the plate number against a region template (e.g., md for Maryland, ca for California) +#template_region = + +# Max number of possible plate numbers to return +#topn = 10 + +# Attempt to detect the region of the plate image +#detect_region = false + +# Detection cause for ZM events +#det_cause = Plate Detected + +# Prefix for plugin entries in ZM log files +#log_prefix = OPENALPR PLUGIN diff --git a/plugins/libzm_plugin_openalpr/misc/event.png b/plugins/libzm_plugin_openalpr/misc/event.png new file mode 100644 index 000000000..f86a5eccb Binary files /dev/null and b/plugins/libzm_plugin_openalpr/misc/event.png differ diff --git a/plugins/libzm_plugin_openalpr/misc/events.png b/plugins/libzm_plugin_openalpr/misc/events.png new file mode 100644 index 000000000..34b9574a9 Binary files /dev/null and b/plugins/libzm_plugin_openalpr/misc/events.png differ diff --git a/plugins/libzm_plugin_openalpr/misc/frame.png b/plugins/libzm_plugin_openalpr/misc/frame.png new file mode 100644 index 000000000..c9aac9e50 Binary files /dev/null and b/plugins/libzm_plugin_openalpr/misc/frame.png differ diff --git a/plugins/libzm_plugin_openalpr/misc/plugin.png b/plugins/libzm_plugin_openalpr/misc/plugin.png new file mode 100644 index 000000000..ec8e4bf8e Binary files /dev/null and b/plugins/libzm_plugin_openalpr/misc/plugin.png differ diff --git a/plugins/libzm_plugin_openalpr/misc/zone.png b/plugins/libzm_plugin_openalpr/misc/zone.png new file mode 100644 index 000000000..9a9bb661b Binary files /dev/null and b/plugins/libzm_plugin_openalpr/misc/zone.png differ diff --git a/plugins/libzm_plugin_openalpr/src/CMakeLists.txt b/plugins/libzm_plugin_openalpr/src/CMakeLists.txt new file mode 100644 index 000000000..c7748fd6f --- /dev/null +++ b/plugins/libzm_plugin_openalpr/src/CMakeLists.txt @@ -0,0 +1,17 @@ +# CMakeLists.txt for openalpr plugin. + +add_library(zm_plugin_openalpr SHARED openalpr_plugin.cpp) +include_directories(${zoneminder_SOURCE_DIR}/src ${zoneminder_BINARY_DIR}/src) + +target_link_libraries( + zm_plugin_openalpr -lopenalpr -lboost_program_options -lopencv_core + -lopencv_imgproc) + +# Do not set the soname when linking the plugin binary. +# Definition of a soname version in a plugin prevents some tools +# (like dpkg-shlibdeps for debian) to clearly identify the binary as a plugin. +# This results in a bunch of unwanted warnings about unresolvable reference to +# symbols (which are provided by the program that loads this plugins). +set_property(TARGET zm_plugin_openalpr PROPERTY NO_SONAME 1) + +install (TARGETS zm_plugin_openalpr DESTINATION ${ZM_PLUGINSPKGLIBDIR}) diff --git a/plugins/libzm_plugin_openalpr/src/Makefile.am b/plugins/libzm_plugin_openalpr/src/Makefile.am new file mode 100644 index 000000000..ed8077799 --- /dev/null +++ b/plugins/libzm_plugin_openalpr/src/Makefile.am @@ -0,0 +1,24 @@ +AUTOMAKE_OPTIONS = gnu + +pkglibdir = @ZM_PLUGINSPKGLIBDIR@ + +pkglib_LTLIBRARIES = libzm_plugin_openalpr.la + +# This prevents to link the plugin with unnecessary libraries +LIBS= + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src $(opencv_CFLAGS) + +libzm_plugin_openalpr_la_SOURCES = \ + openalpr_plugin.cpp + +libzm_plugin_openalpr_la_LDFLAGS = \ + -avoid-version -module -shared -export-dynamic + +libzm_plugin_openalpr_la_LIBADD = \ + @OPENALPR_LIBS@ @BOOST_PROGRAM_OPTION_LIBS@ @OPENCV_CORE_LIBS@ \ + @OPENCV_IMGPROC_LIBS@ + +noinst_HEADERS = \ + openalpr_plugin.h diff --git a/plugins/libzm_plugin_openalpr/src/openalpr_plugin.cpp b/plugins/libzm_plugin_openalpr/src/openalpr_plugin.cpp new file mode 100644 index 000000000..3acfcad75 --- /dev/null +++ b/plugins/libzm_plugin_openalpr/src/openalpr_plugin.cpp @@ -0,0 +1,455 @@ +/***************************************************************************** + * Copyright (C) 2014 Emmanuel Papin + * + * Authors: Emmanuel Papin + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#include "openalpr_plugin.h" + +using namespace alpr; +using namespace boost::program_options; + +//! Retrieve the engine version we're going to expect +extern "C" int getEngineVersion() +{ + return ZM_ENGINE_VERSION; +} + +//! Tells us to register our functionality to an engine kernel +extern "C" void registerPlugin(PluginManager &PlM, std::string sPluginName) +{ + PlM.getImageAnalyser().addDetector(std::auto_ptr(new OpenALPRPlugin(sPluginName))); +} + +OpenALPRPlugin::OpenALPRPlugin() + : Detector(), + m_sConfigFilePath(DEFAULT_CONFIG_FILE), + m_sCountry(DEFAULT_COUNTRY_CODE), + m_sRegionTemplate(DEFAULT_TEMPLATE_REGION), + m_nMaxPlateNumber(DEFAULT_TOPN), + m_bRegionIsDet(DEFAULT_DETECT_REGION) +{ + m_sDetectionCause = DEFAULT_DETECTION_CAUSE; + m_sLogPrefix = DEFAULT_PLUGIN_LOG_PREFIX; + + Info("%s: Plugin object has been created", m_sLogPrefix.c_str()); +} + +OpenALPRPlugin::OpenALPRPlugin(std::string sPluginName) + : Detector(sPluginName), + m_sConfigFilePath(DEFAULT_CONFIG_FILE), + m_sCountry(DEFAULT_COUNTRY_CODE), + m_sRegionTemplate(DEFAULT_TEMPLATE_REGION), + m_nMaxPlateNumber(DEFAULT_TOPN), + m_bRegionIsDet(DEFAULT_DETECT_REGION) +{ + m_sDetectionCause = DEFAULT_DETECTION_CAUSE; + m_sLogPrefix = DEFAULT_PLUGIN_LOG_PREFIX; + + Info("%s: Plugin object has been created", m_sLogPrefix.c_str()); +} + +/*! \fn OpenALPRPlugin::loadConfig(std::string sConfigFileName, std::map > mapPluginConf) + * \param sConfigFileName is path to configuration to load parameters from + * \param mapPluginConf is the map of configuration parameters for each zone +*/ +int OpenALPRPlugin::loadConfig(std::string sConfigFileName, std::map > mapPluginConf) +{ + try + { + options_description config_file("Configuration file options."); + variables_map vm; + config_file.add_options() + ((m_sConfigSectionName + std::string(".config_file")).c_str(), + value()->default_value(DEFAULT_CONFIG_FILE)) + ((m_sConfigSectionName + std::string(".country_code")).c_str(), + value()->default_value(DEFAULT_COUNTRY_CODE)) + ((m_sConfigSectionName + std::string(".template_region")).c_str(), + value()->default_value(DEFAULT_TEMPLATE_REGION)) + ((m_sConfigSectionName + std::string(".topn")).c_str(), + value()->default_value(DEFAULT_TOPN)) + ((m_sConfigSectionName + std::string(".detect_region")).c_str(), + value()->default_value(DEFAULT_DETECT_REGION)) + ((m_sConfigSectionName + std::string(".det_cause")).c_str(), + value()->default_value(DEFAULT_DET_CAUSE)) + ((m_sConfigSectionName + std::string(".log_prefix")).c_str(), + value()->default_value(DEFAULT_PLUGIN_LOG_PREFIX)); + try + { + std::ifstream ifs(sConfigFileName.c_str()); + store(parse_config_file(ifs, config_file, false), vm); + notify(vm); + } + catch(error& er) + { + Error("%s: Plugin is not configured (%s)", m_sLogPrefix.c_str(), er.what()); + return 0; + } + m_sConfigFilePath = vm[(m_sConfigSectionName + std::string(".config_file")).c_str()].as(); + m_sCountry = vm[(m_sConfigSectionName + std::string(".country_code")).c_str()].as(); + m_sRegionTemplate = vm[(m_sConfigSectionName + std::string(".template_region")).c_str()].as(); + m_nMaxPlateNumber = vm[(m_sConfigSectionName + std::string(".topn")).c_str()].as(); + m_bRegionIsDet = vm[(m_sConfigSectionName + std::string(".detect_region")).c_str()].as(); + m_sDetectionCause = vm[(m_sConfigSectionName + std::string(".det_cause")).c_str()].as(); + m_sLogPrefix = vm[(m_sConfigSectionName + std::string(".log_prefix")).c_str()].as(); + } + catch(std::exception& ex) + { + Error("%s: Plugin is not configured (%s)", m_sLogPrefix.c_str(), ex.what()); + return 0; + } + + pConf pluginConf; + for (std::map >::iterator it = mapPluginConf.begin(); it != mapPluginConf.end(); ++it) { + while ( pluginConfig.size() < (it->first + 1) ) + pluginConfig.push_back(pluginConf); + // Overwrite default values with database values + for (std::map::iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + if (it2->second.empty()) continue; + if (it2->first == "AlarmScore") { + pluginConfig[it->first].alarmScore = (unsigned int)strtoul(it2->second.c_str(), NULL, 0); + } else if (it2->first == "AssumeTargets") { + if (it2->second == "Yes") { + pluginConfig[it->first].assumeTargets = true; + } else { + pluginConfig[it->first].assumeTargets = false; + } + } else if (it2->first == "MaxCharacters") { + pluginConfig[it->first].maxCharacters = (unsigned int)strtoul(it2->second.c_str(), NULL, 0); + } else if (it2->first == "MinCharacters") { + pluginConfig[it->first].minCharacters = (unsigned int)strtoul(it2->second.c_str(), NULL, 0); + } else if (it2->first == "MinConfidence") { + pluginConfig[it->first].minConfidence = (unsigned int)strtoul(it2->second.c_str(), NULL, 0); + } else if (it2->first == "OnlyTargets") { + if (it2->second == "Yes") { + pluginConfig[it->first].onlyTargets = true; + } else { + pluginConfig[it->first].onlyTargets = false; + } + } else if (it2->first == "StrictTargets") { + if (it2->second == "Yes") { + pluginConfig[it->first].strictTargets = true; + } else { + pluginConfig[it->first].strictTargets = false; + } + } else if (it2->first == "TargetList") { + boost::split(pluginConfig[it->first].targetList, it2->second, boost::is_any_of(",")); + } + } + } + + // Create an instance of class Alpr and set basic configuration + ptrAlpr = new Alpr(m_sCountry, m_sConfigFilePath); + ptrAlpr->setTopN(m_nMaxPlateNumber); + if ( m_bRegionIsDet ) + ptrAlpr->setDetectRegion(m_bRegionIsDet); + if ( !m_sRegionTemplate.empty() ) + ptrAlpr->setDefaultRegion(m_sRegionTemplate); + + // Initialize some lists + int nb_zones = pluginConfig.size(); + plateList.resize(nb_zones); + tmpPlateList.resize(nb_zones); + + if ( ptrAlpr->isLoaded() ) { + Info("%s: Plugin is configured", m_sLogPrefix.c_str()); + } else { + Error("%s: Plugin is not configured (%s)", m_sLogPrefix.c_str(), strerror(errno)); + delete ptrAlpr; + return 0; + } + + return 1; +} + + +OpenALPRPlugin::~OpenALPRPlugin() +{ + delete ptrAlpr; +} + + +/*! \fn OpenALPRPlugin::OpenALPRPlugin(const OpenALPRPlugin& source) + * \param source is the object for copying + */ +OpenALPRPlugin::OpenALPRPlugin(const OpenALPRPlugin& source) + : Detector(source), + m_sConfigFilePath(source.m_sConfigFilePath), + m_sCountry(source.m_sCountry), + m_sRegionTemplate(source.m_sRegionTemplate), + m_nMaxPlateNumber(source.m_nMaxPlateNumber), + m_bRegionIsDet(source.m_bRegionIsDet) +{ +} + + +/*! \fn OpenALPRPlugin:: operator=(const OpenALPRPlugin& source) + * \param source is the object for copying + */ +OpenALPRPlugin & OpenALPRPlugin:: operator=(const OpenALPRPlugin& source) +{ + Detector::operator=(source); + m_sConfigFilePath = source.m_sConfigFilePath; + m_sCountry = source.m_sCountry; + m_sRegionTemplate = source.m_sRegionTemplate; + m_nMaxPlateNumber = source.m_nMaxPlateNumber; + m_bRegionIsDet = source.m_bRegionIsDet; + return *this; +} + + +/*! \fn OpenALPRPlugin::onCreateEvent(Zone *zone, unsigned int n_zone, Event *event) + * \param zone is a pointer to the zone that triggered the event + * \param n_zone is the zone id + * \param event is a pointer the new event + */ +void OpenALPRPlugin::onCreateEvent(Zone *zone, unsigned int n_zone, Event *event) +{ + Debug(1, "%s: Zone %s - Prepare plugin for event %d", m_sLogPrefix.c_str(), zone->Label(), event->Id()); + + /* Note: Do not clear the plate list here because in case of post processing + * the plate list is filled with results of first detection before + * event creation */ + +} + + +/*! \fn OpenALPRPlugin::onCloseEvent(Zone *zone, unsigned int n_zone, Event *event, std::string noteText) + * \param zone is a pointer to the zone that triggered the event + * \param n_zone is the zone id + * \param event is a pointer to the event that will be closed + * \param noteText is a string that can be used to output text to the event note + */ +void OpenALPRPlugin::onCloseEvent(Zone *zone, unsigned int n_zone, Event *event, std::string ¬eText) +{ + // Set the number of plates to output and exit if nothing to do + unsigned int topn = ( m_nMaxPlateNumber < plateList[n_zone].size() ) ? m_nMaxPlateNumber : plateList[n_zone].size(); + if (topn == 0) return; + + // Sort plates according confidence level (higher first) + sort(plateList[n_zone].begin(), plateList[n_zone].end(), sortByConf()); + + Info("%s: Zone %s - Add plates to event %d", m_sLogPrefix.c_str(), zone->Label(), event->Id()); + + // Output only the first topn plates to the event note + for(unsigned int i=0; iLabel(), plate.str().c_str()); + noteText += " " + plate.str() + "\n"; + } + + // Reset the lists for next use + plateList[n_zone].clear(); + tmpPlateList[n_zone].clear(); +} + + +/*! \fn OpenALPRPlugin::checkZone(Zone *zone, const Image *zmImage) + * \param zone is a zone where license plates will be detected + * \param n_zone is the zone id + * \param zmImage is an image to perform license plate detection (in the form of ZM' Image) + * \return true if there were objects detected in given image and false otherwise + */ +bool OpenALPRPlugin::checkZone(Zone *zone, unsigned int n_zone, const Image *zmImage) +{ + Polygon zone_polygon = Polygon(zone->GetPolygon()); // Polygon of interest of the processed zone. + + Image *pMaskImage = new Image(zmImage->Width(), zmImage->Height(), ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE ); + pMaskImage->Fill(BLACK); + // An temporary image in the form of ZM for making from it CvMat. + // If don't use temp image, after rgb->bgr it will change. + Image *tempZmImage = new Image(*zmImage); + int imgtype=CV_8UC1; + if (tempZmImage->Colours() == ZM_COLOUR_RGB24) + imgtype=CV_8UC3; + cv::Mat cvInputImage = cv::Mat( + tempZmImage->Height(), + tempZmImage->Width(), + imgtype, (unsigned char*)tempZmImage->Buffer()).clone(); + if (tempZmImage->Colours() == ZM_COLOUR_RGB24) + cvtColor(cvInputImage, cvInputImage, CV_RGB2BGR); + + //Process image + //std::vector buffer; + //cv::imencode(".bmp", cvInputImage, buffer); + std::vector regionsOfInterest; + regionsOfInterest.push_back(AlprRegionOfInterest(0,0, cvInputImage.cols, cvInputImage.rows)); + // Region of interest don't work as expected + //std::vector results = ptrAlpr->recognize(buffer, regionsOfInterest); + AlprResults results = ptrAlpr->recognize(cvInputImage.data, cvInputImage.elemSize(), cvInputImage.cols, cvInputImage.rows, regionsOfInterest); + double score = 0; + + for (unsigned int i = 0; i < results.plates.size(); i++) { + int x1 = results.plates[i].plate_points[0].x, y1 = results.plates[i].plate_points[0].y; + int x2 = results.plates[i].plate_points[1].x, y2 = results.plates[i].plate_points[1].y; + int x3 = results.plates[i].plate_points[2].x, y3 = results.plates[i].plate_points[2].y; + int x4 = results.plates[i].plate_points[3].x, y4 = results.plates[i].plate_points[3].y; + Coord rectVertCoords[4] = {Coord(x1, y1), Coord(x2, y2), Coord(x3, y3), Coord(x4, y4)}; + int nNumVertInside = 0; + + for (int p = 0; p < 4; p++) + nNumVertInside += zone_polygon.isInside(rectVertCoords[p]); + + // if at least three rectangle coordinates are inside polygon, + // consider rectangle as belonging to the zone + // otherwise process next object + if (nNumVertInside < 3) + { + Debug(1, "%s: Zone %s - Skip result (outside detection zone)", m_sLogPrefix.c_str(), zone->Label()); + continue; + } + + int cntDetPlates = 0; + + for (unsigned int k = 0; k < results.plates[i].topNPlates.size(); k++) + { + strPlate detPlate; + detPlate.num = results.plates[i].topNPlates[k].characters; + detPlate.conf = results.plates[i].topNPlates[k].overall_confidence; + + bool isTarget = false; + + // Check targeted plates first + for (std::vector::iterator it = pluginConfig[n_zone].targetList.begin(); it != pluginConfig[n_zone].targetList.end(); ++it) + { + // If plates match, add targeted plate and continue with next detected plate + if (*it == detPlate.num) + { + detPlate.conf = 100; + addPlate(zone, n_zone, detPlate); + cntDetPlates++; + isTarget = true; + break; + } + // Check if targeted plate is a substring of the detected plate + else if (detPlate.num.find(*it) != std::string::npos) + { + // If yes and strict targeting is on, disqualify targeted plate + if (pluginConfig[n_zone].strictTargets) + { + Debug(1, "%s: Zone %s - Skip targeted plate %s (strict targeting is on)", m_sLogPrefix.c_str(), zone->Label(), detPlate.num.c_str()); + // And continue with next targeted plate (maybe another in the list will strictly match) + continue; + } + // If yes but detected plate has too much characters, disqualify targeted plate + if (detPlate.num.size() > pluginConfig[n_zone].maxCharacters) + { + Debug(1, "%s: Zone %s - Skip targeted plate %s (number of characters is out of range)", m_sLogPrefix.c_str(), zone->Label(), detPlate.num.c_str()); + // And continue with next detected plate (this one is not valid) + break; + } + // Overwrite detected plate by target if we assume the matching is correct + if (pluginConfig[n_zone].assumeTargets) + { + detPlate.conf = 100; + detPlate.num = *it; + } + // Add targeted plate to list and continue with next detected plate + addPlate(zone, n_zone, detPlate); + cntDetPlates++; + isTarget = true; + break; + } + } + + // Skip detected plate if already added or if only targeted plates are accepted + if (isTarget || pluginConfig[n_zone].onlyTargets) + continue; + + // Disqualify plate if under the minimum confidence level + if (detPlate.conf < pluginConfig[n_zone].minConfidence) + { + Debug(1, "%s: Zone %s - Skip plate %s (under minimum confidence level)", m_sLogPrefix.c_str(), zone->Label(), detPlate.num.c_str()); + continue; + } + + // Disqualify plate if not enough characters or too much characters + if ((detPlate.num.size() < pluginConfig[n_zone].minCharacters) + || (detPlate.num.size() > pluginConfig[n_zone].maxCharacters)) + { + Debug(1, "%s: Zone %s - Skip plate %s (number of characters is out of range)", m_sLogPrefix.c_str(), zone->Label(), detPlate.num.c_str()); + continue; + } + + // Add plate to list (if already in list, update confidence by adding new value) + addPlate(zone, n_zone, detPlate); + cntDetPlates++; + } + + if (cntDetPlates == 0) + continue; + + // Raise an alarm if at least one plate has been detected + score = pluginConfig[n_zone].alarmScore; + + // Fill a polygon with object in the mask + Polygon platePolygon = Polygon(4, rectVertCoords); + pMaskImage->Fill(WHITE, 1, platePolygon); + } + + if (score == 0) { + delete pMaskImage; + delete tempZmImage; + return false; + } + + zone->SetScore((int)score); + + //Get mask by highlighting contours of objects and overlaying them with previous contours. + Rgb alarm_colour = RGB_GREEN; + Image *hlZmImage = pMaskImage->HighlightEdges(alarm_colour, ZM_COLOUR_RGB24, + ZM_SUBPIX_ORDER_RGB, &zone_polygon.Extent()); + + if (zone->Alarmed()) { + // if there were previous detection and they have already set up alarm image + // then overlay it with current mask + Image* pPrevZoneMask = new Image(*(zone->AlarmImage())); + pPrevZoneMask->Overlay(*hlZmImage); + zone->SetAlarmImage(pPrevZoneMask); + delete pPrevZoneMask; + } else + zone->SetAlarmImage(hlZmImage); + + delete pMaskImage; + delete tempZmImage; + + return true; +} + + +bool OpenALPRPlugin::addPlate(Zone *zone, unsigned int n_zone, strPlate detPlate) +{ + for (std::vector::iterator it = plateList[n_zone].begin(); it != plateList[n_zone].end(); ++it) + { + // If plate number exists in the list for this zone + if ((*it).num == detPlate.num) + { + // Add confidence + (*it).conf += detPlate.conf; + Debug(1, "%s: Zone %s - Raise confidence of plate %s to %f", m_sLogPrefix.c_str(), zone->Label(), (*it).num.c_str(), (*it).conf); + return false; + } + } + + // Add a new plate for this zone + Debug(1, "%s: Zone %s - Add plate %s with confidence %f", m_sLogPrefix.c_str(), zone->Label(), detPlate.num.c_str(), detPlate.conf); + plateList[n_zone].push_back(detPlate); + + return true; +} diff --git a/plugins/libzm_plugin_openalpr/src/openalpr_plugin.h b/plugins/libzm_plugin_openalpr/src/openalpr_plugin.h new file mode 100644 index 000000000..261da4398 --- /dev/null +++ b/plugins/libzm_plugin_openalpr/src/openalpr_plugin.h @@ -0,0 +1,143 @@ +/***************************************************************************** + * Copyright (C) 2014 Emmanuel Papin + * + * Authors: Emmanuel Papin + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifndef OPENALPR_PLUGIN_H +#define OPENALPR_PLUGIN_H + +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "zm_plugin_manager.h" +#include "zm_detector.h" +#include "zm_rgb.h" + +#define DEFAULT_CONFIG_FILE "/etc/openalpr/openalpr.conf" +#define DEFAULT_COUNTRY_CODE "us" +#define DEFAULT_TEMPLATE_REGION "" +#define DEFAULT_TOPN 10 +#define DEFAULT_DETECT_REGION 0 +#define DEFAULT_DET_CAUSE "Plate Detected" +#define DEFAULT_PLUGIN_LOG_PREFIX "OPENALPR PLUGIN" + +#define DEFAULT_ALARM_SCORE 99 +#define DEFAULT_ASSUME_TARGETS 0 +#define DEFAULT_MAX_CHARACTERS 99 +#define DEFAULT_MIN_CHARACTERS 0 +#define DEFAULT_MIN_CONFIDENCE 0 +#define DEFAULT_ONLY_TARGETS 0 +#define DEFAULT_STRICT_TARGETS 0 + +//! OpenALPR plugin class. +/*! The class derived from Detector. + * This class provides license plate detection based on tesseract OCR. + */ +class OpenALPRPlugin : public Detector { + public: + + //! Default Constructor. + OpenALPRPlugin(); + + //! Constructor. + OpenALPRPlugin(std::string sConfigSectionName); + + //! Destructor. + virtual ~OpenALPRPlugin(); + + //! Copy constructor. + OpenALPRPlugin(const OpenALPRPlugin& source); + + //! Overloaded operator=. + OpenALPRPlugin& operator=(const OpenALPRPlugin& source); + + int loadConfig(std::string sConfigFileName, std::map > mapPluginConf); + +protected: + + void onCreateEvent(Zone *zone, unsigned int n_zone, Event *event); + void onCloseEvent(Zone *zone, unsigned int n_zone, Event *event, std::string ¬eText); + bool checkZone(Zone *zone, unsigned int n_zone, const Image *zmImage); + + std::string m_sConfigFilePath; + std::string m_sCountry; + std::string m_sRegionTemplate; + unsigned int m_nMaxPlateNumber; + bool m_bRegionIsDet; + +private: + + alpr::Alpr *ptrAlpr; + + struct pConf + { + unsigned int alarmScore; + bool assumeTargets; + unsigned int maxCharacters; + unsigned int minCharacters; + unsigned int minConfidence; + bool onlyTargets; + bool strictTargets; + std::vector targetList; + pConf(): + alarmScore(DEFAULT_ALARM_SCORE), + assumeTargets(DEFAULT_ASSUME_TARGETS), + maxCharacters(DEFAULT_MAX_CHARACTERS), + minCharacters(DEFAULT_MIN_CHARACTERS), + minConfidence(DEFAULT_MIN_CONFIDENCE), + onlyTargets(DEFAULT_ONLY_TARGETS), + strictTargets(DEFAULT_STRICT_TARGETS) + {} + }; + + std::vector pluginConfig; + + struct strPlate + { + std::string num; + float conf; + }; + + struct sortByConf + { + bool operator()(const strPlate a, const strPlate b) const + { + return a.conf > b.conf; + } + }; + + std::vector > plateList; + std::vector > tmpPlateList; + + bool addPlate(Zone *zone, unsigned int n_zone, strPlate detPlate); + +}; +#endif // OPENALPR_PLUGIN_H