Click here to Skip to main content
15,897,519 members
Articles / Multimedia / GDI+

Motion Detection Algorithms

Rate me:
Please Sign up or sign in to vote.
4.95/5 (683 votes)
27 Mar 2007GPL39 min read 9.1M   128K   1.4K  
Some approaches to detect motion in a video stream.
// Motion Detector
//
// Copyright � Andrew Kirillov, 2005
// andrew.kirillov@gmail.com
//
namespace motion
{
	using System;
	using System.Drawing;
	using System.Drawing.Imaging;

	using AForge.Imaging;
	using AForge.Imaging.Filters;

	/// <summary>
	/// MotionDetector3Optimized
	/// </summary>
	public class MotionDetector3Optimized : IMotionDetector
	{
		private byte[]	backgroundFrame = null;
		private byte[]	currentFrame = null;
		private byte[]	currentFrameDilatated = null;

		private int		counter = 0;

		private bool	calculateMotionLevel = false;
		private int		width;	// image width
		private int		height;	// image height
		private int		pixelsChanged;

		// Motion level calculation - calculate or not motion level
		public bool MotionLevelCalculation
		{
			get { return calculateMotionLevel; }
			set { calculateMotionLevel = value; }
		}

		// Motion level - amount of changes in percents
		public double MotionLevel
		{
			get { return (double) pixelsChanged / ( width * height ); }
		}

		// Constructor
		public MotionDetector3Optimized( )
		{
		}

		// Reset detector to initial state
		public void Reset( )
		{
			backgroundFrame = null;
			currentFrame = null;
			currentFrameDilatated = null;
			counter = 0;
		}

		// Process new frame
		public void ProcessFrame( ref Bitmap image )
		{
			// get image dimension
			width	= image.Width;
			height	= image.Height;

			int fW = ( ( ( width - 1 ) / 8 ) + 1 );
			int fH = ( ( ( height - 1 ) / 8 ) + 1 );
			int len = fW * fH;

			if ( backgroundFrame == null )
			{
				// alloc memory for a backgound image and for current image
				backgroundFrame = new byte[len];
				currentFrame = new byte[len];
				currentFrameDilatated = new byte[len];

				// lock image
				BitmapData imgData = image.LockBits(
					new Rectangle( 0, 0, width, height ),
					ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb );

				// create initial backgroung image
				PreprocessInputImage( imgData, width, height, backgroundFrame );

				// unlock the image
				image.UnlockBits( imgData );

				// just return for the first time
				return;
			}

			// lock image
			BitmapData data = image.LockBits(
				new Rectangle( 0, 0, width, height ),
				ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb );

			// preprocess input image
			PreprocessInputImage( data, width, height, currentFrame );

			if ( ++counter == 2 )
			{
				counter = 0;

				// move background towards current frame
				for ( int i = 0; i < len; i++ )
				{
					int t = currentFrame[i] - backgroundFrame[i];
					if ( t > 0 )
						backgroundFrame[i]++;
					else if ( t < 0 )
						backgroundFrame[i]--;
				}
			}

			// difference and thresholding
			pixelsChanged = 0;
			for ( int i = 0; i < len; i++ )
			{
				int t = currentFrame[i] - backgroundFrame[i];
				if ( t < 0 )
					t = -t;

				if ( t >= 15 )
				{
					pixelsChanged++;
					currentFrame[i] = (byte) 255;
				}
				else
				{
					currentFrame[i] = (byte) 0;
				}
			}
			if ( calculateMotionLevel )
				pixelsChanged *= 64;
			else
				pixelsChanged = 0;

			// dilatation analogue for borders extending
			// it can be skipped
			for ( int i = 0; i < fH; i++ )
			{
				for ( int j = 0; j < fW; j++ )
				{
					int k = i * fW + j;
					int v = currentFrame[k];

					// left pixels
					if ( j > 0 )
					{
						v += currentFrame[k - 1];

						if ( i > 0 )
						{
							v += currentFrame[k - fW - 1];
						}
						if ( i < fH - 1 )
						{
							v += currentFrame[k + fW - 1];
						}
					}
					// right pixels
					if ( j < fW - 1 )
					{
						v += currentFrame[k + 1];

						if ( i > 0 )
						{
							v += currentFrame[k - fW + 1];
						}
						if ( i < fH - 1 )
						{
							v += currentFrame[k + fW + 1];
						}
					}
					// top pixel
					if ( i > 0 )
					{
						v += currentFrame[k - fW];
					}
					// right pixel
					if ( i < fH - 1 )
					{
						v += currentFrame[k + fW];
					}

					currentFrameDilatated[k] = (v != 0) ? (byte) 255 : (byte) 0;
				}
			}

			// postprocess the input image
			PostprocessInputImage( data, width, height, currentFrameDilatated );

			// unlock the image
			image.UnlockBits( data );
		}

		// Preprocess input image
		private void PreprocessInputImage( BitmapData data, int width, int height, byte[] buf )
		{
			int stride = data.Stride;
			int offset = stride - width * 3;
			int len = (int)( ( width - 1 ) / 8 ) + 1;
			int rem = ( ( width - 1 ) % 8 ) + 1;
			int[] tmp = new int[len];
			int i, j, t1, t2, k = 0;

			unsafe
			{
				byte * src = (byte *) data.Scan0.ToPointer( );

				for ( int y = 0; y < height; )
				{
					// collect pixels
					Array.Clear( tmp, 0, len );

					// calculate
					for ( i = 0; ( i < 8 ) && ( y < height ); i++, y++ )
					{
						// for each pixel
						for ( int x = 0; x < width; x++, src += 3 )
						{
							// grayscale value using BT709
							tmp[(int) ( x / 8 )] += (int)( 0.2125f * src[RGB.R] + 0.7154f * src[RGB.G] + 0.0721f * src[RGB.B] );
						}
						src += offset;
					}

					// get average values
					t1 = i * 8;
					t2 = i * rem;

					for ( j = 0; j < len - 1; j++, k++ )
						buf[k] = (byte)( tmp[j] / t1 );
					buf[k++] = (byte)( tmp[j] / t2 );
				}
			}
		}

		// Postprocess input image
		private void PostprocessInputImage( BitmapData data, int width, int height, byte[] buf )
		{
			int stride = data.Stride;
			int offset = stride - width * 3;
			int len = (int)( ( width - 1 ) / 8 ) + 1;
			int lenWM1 = len - 1;
			int lenHM1 = (int)( ( height - 1 ) / 8);
			int rem = ( ( width - 1 ) % 8 ) + 1;

			int i, j, k;
	
			unsafe
			{
				byte * src = (byte *) data.Scan0.ToPointer( );

				// for each line
				for ( int y = 0; y < height; y++ )
				{
					i = (y / 8);

					// for each pixel
					for ( int x = 0; x < width; x++, src += 3 )
					{
						j = x / 8;	
						k = i * len + j;

						// check if we need to highlight moving object
						if (buf[k] == 255)
						{
							// check for border
							if (
								( ( x % 8 == 0 ) && ( ( j == 0 ) || ( buf[k - 1] == 0 ) ) ) ||
								( ( x % 8 == 7 ) && ( ( j == lenWM1 ) || ( buf[k + 1] == 0 ) ) ) ||
								( ( y % 8 == 0 ) && ( ( i == 0 ) || ( buf[k - len] == 0 ) ) ) ||
								( ( y % 8 == 7 ) && ( ( i == lenHM1 ) || ( buf[k + len] == 0 ) ) )
								)
							{
								src[RGB.R] = 255;
							}
						}
					}
					src += offset;
				}
			}
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Software Developer IBM
United Kingdom United Kingdom
Started software development at about 15 years old and it seems like now it lasts most part of my life. Fortunately did not spend too much time with Z80 and BK0010 and switched to 8086 and further. Similar with programming languages – luckily managed to get away from BASIC and Pascal to things like Assembler, C, C++ and then C#. Apart from daily programming for food, do it also for hobby, where mostly enjoy areas like Computer Vision, Robotics and AI. This led to some open source stuff like AForge.NET, Computer Vision Sandbox, cam2web, ANNT, etc.

Comments and Discussions