Axis 207 and On-Camera motion detection solved

Anything you want added or changed in future versions of ZoneMinder? Post here and there's a chance it will get in! Search to make sure it hasn't already been requested.
cayfer
Posts: 3
Joined: Thu Dec 20, 2007 4:17 pm
Location: Ankara/Turkey

Axis 207 and On-Camera motion detection solved

Post by cayfer »

I had this performance problem while monitoring and event recording for 8-10 IP cameras using the ZM.

ZM is too good a software to abandon just for performance issues. So I decided I should pay more attention to the motion detection feature of the cameras I have and relieve the ZM server from the task of detecting motion.

I've finally managed to use the on-camera motion detection feature of Axis-207 and here are my notes:

I've set my Axis camera as follows:

Image

The text "1|on 5|5|cause|text|showtext" means that
alarm will be triggered for the monitor which has ID=1,
alarm will set recording on
alarm will be cleared after 5 seconds.
The score of the alarm is 5 ( any score >0 is OK)
"text" and "showtext" are just explanatory notes.

The ZM specs tells us that the format of the message can include a "duration" as long as the message is sent as:
"1|on+5|5|cause|text|showtext" bu the problem is the Axis camera cannot or will not save the "+" sign which appears in the message. A CGI parameter conversion problem I presume...

I've taken care of this "+" problem with a small modification in zmtrigger.pl source (see below).

Next, I specified the host IP address of the zm computer and its default zmtrigger port of 6802.

Image

The second important setup is the "motion detection" setup on the Axis. I created a window which covers the area of interest; kept the object size as small as possible, pulled the history slider all the way back to zero and left the sensitivity slide somewhere past the %50 value.

Image

Now to the ZM settings...

There is no special setting for the ZM except that I've assigned the "Nodect" function to my camera monitor.

Image

Finally, I modified the zmtrigger.pl file so that the first few lines of the
function handleMessage looks like this:

Code: Select all

sub handleMessage
{
        my $connection = shift;
        my $message = shift;
        #
        #  CUA  - Axis camera send the message quoted with"
        #  CUA  - Also Axis camera cannot save the plus sign which
        #  CUA  - possibly exists in the 1|on+20|score|cause|text|showtext formatted msg

        $message=~ s/^\"//g;
        $message=~ s/\"$//g;
        $message=~ s/on /on\+/;

        #  CUA - end of modifications

        my ( $id, $action, $score, $cause, $text, $showtext ) = split( /\|/, $message );
a few other modifications I made in the zmtrigger.pl file are commenting out some unused modules and pushes:

Code: Select all

use ZoneMinder::Trigger::Channel::Inet;
#CUA use ZoneMinder::Trigger::Channel::Unix;
#CUA use ZoneMinder::Trigger::Channel::Serial;
use ZoneMinder::Trigger::Connection;

my @connections;
push( @connections, ZoneMinder::Trigger::Connection->new( name=>"Chan1", channel=>ZoneMinder::Trigger::Channel::Inet->new( port=>6802 ), mode=>"rw" ) );
#CUA push( @connections, ZoneMinder::Trigger::Connection->new( name=>"Chan2", channel=>ZoneMinder::Trigger::Channel::Unix->new( path=>'/tmp/test.sock' ), mode=>"rw" ) );
#push( @connections, ZoneMinder::Trigger::Connection->new( name=>"Chan3", channel=>ZoneMinder::Trigger::Channel::File->new( path=>'/tmp/zmtrigger.out' ), mode=>"w" ) );
#CUA push( @connections, ZoneMinder::Trigger::Connection->new( name=>"Chan4", channel=>ZoneMinder::Trigger::Channel::Serial->new( path=>'/dev/ttyS0' ), mode=>"rw" ) );



my modifs are marked with "# CUA"



These three regular expressions remove the quotes sent by Axis which envelope the message string and insert a plus sign if the "on" command is followed by a space). These 3 statements convert an incoming message of the form

Code: Select all

"1|on 25|5|cause|text|showtext"
to

Code: Select all

1|on+25|5|cause|text|showtext
and of course, you shouln"t forget to restart zmtrigger.pl; or better still modify the zm startup script so that it starts zmtrigger.pl everytime zm is [re]started.

I added the line '/usr/bin/zmtrigger.pl &" in the start section of
/etc/init.d/zoneminder and also added the line "pkill zmtrigger.pl" in the stop section.

Code: Select all

start() {
        echo -n "Starting $prog: "
        $command start
        # CUA
        /usr/bin/zmtrigger.pl &


        RETVAL=$?
        [ $RETVAL = 0 ] && echo success
        [ $RETVAL != 0 ] && echo failure
        echo
        [ $RETVAL = 0 ] && touch /var/lock/zm
        return $RETVAL
}
stop() {
        echo -n $"Stopping $prog: "
        #
        # Why is this status check being done?
        # as $command stop returns 1 if zoneminder
        # is stopped, which will result in
        # this returning 1, which will stuff
        # dpkg when it tries to stop zoneminder before
        # uninstalling . . .
        #
        # CUA
        pkill zmtrigger.pl

I just hope that this helps other people trying to implement the on-camera motion detection with ZM.

Again; many thanks to those people who developed ZM at the first place and to those who helped it to become a perfect opensource project.
User avatar
cordel
Posts: 5210
Joined: Fri Mar 05, 2004 4:47 pm
Location: /USA/Washington/Seattle

Post by cordel »

Thank you for your time and sharing this with us. This is extreamly useful.

Regards,
Corey
User avatar
zoneminder
Site Admin
Posts: 5215
Joined: Wed Jul 09, 2003 2:07 pm
Location: Bristol, UK
Contact:

Post by zoneminder »

Wow, what a useful and comprehensive post. I don't have a 207 so didn't even realise it had this facility. I think I should probably try and get my hands on some of the newer models so they can be supported without such modifications as you have had to make.

The only I think you need to change is the modifications to your ZM start script to get zmtrigger.pl to run. This should not be necessary. If you make sure that Options->System->ZM_OPT_TRIGGERS is checked then zmtrigger.pl should be started and stopped when ZM does, under the control of zmpkg.pl like all the other daemons. This also ensures that it will get restarted if it ever crashes or gets killed.

Apart from that though, a very helpful and informative post.
Phil
jameswilson
Posts: 5111
Joined: Wed Jun 08, 2005 8:07 pm
Location: Midlands UK

Post by jameswilson »

Phil I have a 207 here for you , i will bring it down i promise!
James Wilson

Disclaimer: The above is pure theory and may work on a good day with the wind behind it. etc etc.
http://www.securitywarehouse.co.uk
skyking
Posts: 84
Joined: Sat Nov 03, 2007 4:07 am

up with the good stuff

Post by skyking »

Hello,and thank you for your efforts. I have done this with the 207MW and 1.23.1 and it worked like a charm. I have a test server 2.53 P4 with 1 gb of ram, loads up around 1.0 in modect with two axis cams at full resoultion.
I turn over detection to the cams and the load drops to .3, sweet!
EDIT: I now have it running on 1.22.3-9 in a production machine, many thanks again.
coke
Posts: 518
Joined: Wed Jan 30, 2008 5:53 pm
Location: St. Louis, MO, USA

zmtrigger.pl mods, axis 207

Post by coke »

I'm still working on getting my motion settings right, but in the mean time, I've found that you can put the + in the axis, you just have to go to a different setup page.

Setup
System Options
Advanced
Plain Config
Select group event, the message will be at the bottom.

well, ok, that doesn't really help with removing the quotes, though.
zeuss
Posts: 2
Joined: Sun Jun 08, 2008 3:42 pm

images from ftp

Post by zeuss »

doas zm can get images from ftp server when cameras having a motion detecion & send images to ftp serwer? how to do it?
W.
Posts: 108
Joined: Tue Apr 10, 2007 5:06 pm
Location: Latvia

Post by W. »

One can set two separate events one that is fired when motion starts and another when motion ends thus imitating behavior of ZM motion detection. In that case no + sign is needed.
if common sense is so uncommon, why is it called common then?
neb
Posts: 6
Joined: Wed Dec 17, 2008 12:21 am

some tweaks

Post by neb »

Thanks for the invaluable guide. It's been most helpful in getting my setup to work in the way I wanted. My zoneminder system also shares a dual role as a home threater pc, so it was critical offloading the motion detection work to the cameras where the processor cycles are free.

I did however found a few tweaks on top of the original guide and its subsequent comments that I felt made the offloading more robust and overall useful to me. First of all, the original guide sets up the axis camera to send the following message to zm via tcp:

1|on 25|5|cause|text|showtext

which causes zmtrigger.pl to turn on recording and then schedule a "cancel" event 25 second later. Thus, if your camera's pointing at a non-static environment, i.e. outdoors, you'd likely have to set the object size higher than the original poster did in the screen capture (i.e. to avoid triggering on leaves blowing across the sidewalk).

Unfortunately, there is no hysteresis in the axis motion detection algorithm, so you'll see frequent spikes in activity level even for large humans sized objects, and it's easy to cross the activity threshold back and forth as long as it's not set close to zero. Every time that happens, the axis camera sends a tcp message to zmtrigger. This is fine for the first 25 seconds, then the "cancel" scheduled from the first even starts interfering with subsequent "on" events, and you end up with a boatload of separate zm events for motion that continues >25 seconds.

I made the following code change to zmtrigger.pl to implement a hysteresis function so that each triggered "on" event stays on for at least a given amount of time before it may be canceled. The "on" command thus takes an optional 3rd input like the following:

on+60+5

The "60" schedules a cancel in 60 seconds (I'll explain later why it's so big), and the "5" blocks cancels for 5 seconds. A second "on+60+5" command arriving during the blocking period extends the interval back to 5 seconds, thus filtering any spikes within 5 seconds. Any cancels that occur during the block event get deferred until the end of the block interval.

Here's the code. Just look for the on|off portion of the handler and replace it with the following up until the end of the file:

Code: Select all

	elsif ( $action =~ /^(on|off)(?:\+(\d+))?(?:\+(\d+))?$/ )
	{
		next if ( !$monitor->{Enabled} );

		my $trigger = $1;
		my $delay = $2;
		my $block = $3;
		my $trigger_data;
		if ( $trigger eq "on" )
		{
			zmTriggerEventOn( $monitor, $score, $cause, $text );
		    zmTriggerShowtext( $monitor, $showtext ) if defined($showtext);
		    Info( "Trigger '$trigger' '$cause'\n" );
		}
		elsif ( $trigger eq "off" )
		{
            my $last_event = zmGetLastEvent( $monitor );
			zmTriggerEventOff( $monitor );
		    zmTriggerShowtext( $monitor, $showtext ) if defined($showtext);
		    Info( "Trigger '$trigger'\n" );
            # Wait til it's finished
            while( zmInAlarm( $monitor ) && ($last_event == zmGetLastEvent( $monitor )) )
            {
                # Tenth of a second
                usleep( 100000 );
            }
			zmTriggerEventCancel( $monitor );
		}
		else
		{
		    Info( "Trigger '$trigger'\n" );
			zmTriggerEventCancel( $monitor );
		}
		if ( $delay )
		{
			my $action_time = time()+$delay;
			#my $action_text = $id."|cancel|0|".$cause."|".$text;
			my $action_text = $id."|cancel";
			my $action_array = $actions{$action_time};
			if ( !$action_array )
			{
				$action_array = $actions{$action_time} = [];
			}
			push( @$action_array, { connection=>$connection, message=>$action_text } );
			Debug( "Added timed event '$action_text', expires at $action_time (+$delay secs)\n" );
		}
		if ( $block )
		{
			my $action_time = time()+$block;
			my $action_text = $id."|unblock";
			my $action_array = $actions{$action_time};
			if ( !$action_array )
			{
				$action_array = $actions{$action_time} = [];
			}
			push( @$action_array, { connection=>$connection, message=>$action_text } );
			Debug( "Added timed event '$action_text', expires at $action_time (+$block secs)\n" );
		}
	}
	elsif( $action eq "unblock" )
	{
		Info( "Unblocked event to allow future cancels\n" );
	}
	elsif( $action eq "cancel" || $action eq "unbcan" )
	{
		my $now = time();
		my $converted = 0;
		foreach my $action_time ( sort( grep { $_ > $now } keys( %actions ) ) )
		{
			for (my $i=0;$i<=$#{$actions{$action_time}};$i++)
			{
				my $action = ${$actions{$action_time}}[$i];
  				my $action_message = $action->{message};
				my ( $action_id, $action_action ) = split( /\|/, $action_message );
				if (($id eq $action_id) && ($action_action eq "unblock") && !$converted)
				{
					my $action_text = $id."|unbcan";
					${$actions{$action_time}}[$i]->{message} = $action_text;
					$converted = 1;
					Debug( "Converted unblock event to '$action_text', expires at $action_time\n" );
				}
				elsif (($id eq $action_id) && ($action_action eq "unbcan"))
				{
					$converted = 1;
                                        Debug( "Skipped a blocked event already marked for cancel at $action_time\n" );
				}
				elsif (($id eq $action_id) && ($action_action eq "cancel"))
				{
					my $action_text = $id."|nop";
					${$actions{$action_time}}[$i]->{message} = $action_text;
					Debug( "Converted cancel event to '$action_text', expires at $action_time\n" );
				}
			}
		}
		if (!$converted) {
			zmTriggerEventCancel( $monitor );
		  	zmTriggerShowtext( $monitor, $showtext ) if defined($showtext);
			if ( $action eq "cancel" )
			{
				Info( "Cancelled event\n" );
			}
			else
			{
				Info( "Unblocked and cancelled a previously marked event\n" );
			}
		}
	}
	elsif( $action eq "show" )
	{
		zmTriggerShowtext( $monitor, $showtext );
		Info( "Updated show text to '$showtext'\n" );
	}
	elsif( $action ne "nop" )
	{
		Error( "Unrecognised action '$action' in message '$message'\n" );
	}
}
The next tweak I did was in the Axis camera itself. Apart from the "on" command on motion start, I also created a separate "cancel" command to be sent on motion stop. The way to do it was via the plain config described in a previous post. Basically, you'd create one event along with its tcp action using the normal event interface (I'd start with the stop/cancel event first), then go to plain config, select event and write down the settings. Then go back to normal config and replace it with the start/on event, and then go to plain config again and select "add event type", which creates event "E1". You can then manually type in what you've written down previously. Note that there's no action setting "E1.A1". The Axis 207 has a hidden link to create it. Type in the following:

http://[camera IP]/axis-cgi/admin/param.cgi?action=add&group=Event.E1.Actions&template=tcpaction

and then go back to plain config and enter the actions. You can also enter the "+" in plain config as described in previous comments.

The 60 second cancel probably now makes sense to you. The point here is that I'm letting the axis cam send a separate cancel command on motion stop. However, if for some reason that there is no cancel (maybe somebody doing jumping jacks in front of your camera), then the 60 second cancel automatically kicks in. It's probably uninteresting after that anyways. In most instances, the axis cam sends the cancel almost immediately after the on, and in these cases the block extends them to at least 5 seconds. To prevent multiple cancels accumulating, each processed cancel converts future scheduled cancels to nop.

I found that the number of events recorded became dramatically fewer after making these changes, linking multiple triggering on the same physical event together as one.

Have fun and feel free to incorporate any of this in a release if you find it useful.

Ben
Last edited by neb on Mon Jan 05, 2009 6:39 pm, edited 1 time in total.
Athlon64
Posts: 4
Joined: Fri Jan 02, 2009 7:36 pm

Post by Athlon64 »

Thanks very much for posting all this very useful information. The info contained in this thread has certainly saved me from many hours of struggling.

In case anyone is struggling with the following URL...
  • http://[camera IP]/axis-cgi/admin/param.cgi?action=addgroup=Event.E1.Actions&template=tcpaction
...this one worked with my camera:
  • http://[camera IP]/axis-cgi/admin/param.cgi?action=add&group=Event.E1.Actions&template=tcpaction
Using the code for the zmtrigger.pl file which is contained in the first post, I managed to get the motion detection working. I'd quite like to get the functionality described in the post just above mine working though. I tried the code for the zmtrigger.pl file which is recommended in the post above mine though and it doesn't seem to work. I'm quite a Linux noob, but it seems that the zmtrigger.pl file doesn't run when it contains that code. I came to this conclusion when the connection test to the TCP server from my Axis camera failed. Placing the code from the first post into the zmtrigger.pl file again and rebooting, made things go back to normal again though. I have no perl programming knowledge but I had a look through the code in the post above mine anyway to look for any obvious errors. The only thing which I seemed like it could cause problems is the following statement:

Code: Select all

for (my $i=0;$i<actions>{message}; 
I think it should look as follows:

Code: Select all

for (my $i=0;$i<actions>{message}
{
I stand under correction though because I still had problems after making that change.

Can anyone with perl programming knowledge see a problem with the code in the post above mine? If not, any idea where my problem lies?

My setup:
Intel Core 2 Quad system
Ubuntu 8.10 x64
Zoneminder 1.23.3
Axis 209MFD-R camera
neb
Posts: 6
Joined: Wed Dec 17, 2008 12:21 am

updated my previous post

Post by neb »

I went back and diff'ed the posted code with my version and found some differences. After fixing them, the same wrong code appeared again in the post. This forum somehow strangely automatically changes the following and a few other lines from:

for (my $i=0;$i<=$#{$actions{$action_time}};$i++)

to:

for (my $i=0;$i<actions>{message};

If I select to disable HTML in the post, then everything looks correct. In any case, sorry about the code problems and please give the edited code in my previous post another try and let me know if it doesn't work.

Also, thanks for pointing out the mistake in the axis url link. That one was a typo and is corrected now.
Athlon64
Posts: 4
Joined: Fri Jan 02, 2009 7:36 pm

Post by Athlon64 »

Ah...thanks very much. It works great now! :) Thanks again for all the contributions made in this thread. I'd never have figured all this out on my own. :)
Athlon64
Posts: 4
Joined: Fri Jan 02, 2009 7:36 pm

Post by Athlon64 »

I've been testing my camera system with this new zmtrigger setup, and it works just as described. One thing which I don't like much though is the fact that, if motion continues past the 60 second mark and the alarm is automatically cancelled, the motion alarm is only triggered again after motion has stopped completely. In other words, if people are moving in front of the camera for 60 seconds, all movements after those 60 seconds have passed won't trigger an alarm before the camera detects a complete stop in motion.

With the standard zmtrigger setup, if there is still movement after the delay has ended, recording is started again, preventing any footage loss. Is there an easy way in which one can change the above code to make it work like this again (that is, while still keeping the other functionality provided with the last mentioned zmtrigger mod)? I'd give it a go myself, but my perl experience can be classified as sub-beginner :) so I'm hoping someone can help. Any help would be greatly appreciated.
jspinner
Posts: 1
Joined: Fri May 01, 2009 6:14 am

Post by jspinner »

Does anyone have the original images from the thread author? I would like to set this up for our cameras but I'm not sure exactly what settings I need. I can host them somewhere more permanent so that future users can benefit from this setup.
User avatar
kingofkya
Posts: 1110
Joined: Mon Mar 26, 2007 6:07 am
Location: Las Vegas, Nevada

Post by kingofkya »

I will make some new pics for it and prob put in the wiki and give credit to original author
Post Reply