Click here to Skip to main content
Click here to Skip to main content

MicroDVR - Simple Video Security Using Webcams

By , 2 Mar 2012
 

Introduction

This is a simple application that allows the user to use the available VideoCaptureDevices (Webcams) as security cameras. The interface allows the user to record the videocapture of a specific camera to a video file (.avi) to local storage (The recording path also can be set via the interface). The user can at anytime set the focus on any camera. For each camera, we can enable the Motion Detection, Beep on motion and Automatic recording on motion.

Background

The basic idea of this application is to create a low cost/cost free video security program for a rather small place (a small store in my case) that covers the basic DVR functionalities (Motion detection, recording).

Code Explanation

a - The CameraMonitor Class

The "CameraMonitor" class is the essencial core of this application, as it provides the interface to control the "VideoCaptureDevice" as well as the other functions like Video Recording and motion detection.

here's the class's public members and properties :

The way we can create "CameraMonitor" object is through its constructor:
new CameraMonitor(
		PictureBox display, // a PictureBox where we will be displaying the incoming frames
		string monikerString, // the monikerSring is the WMI Object Path of our VideoCaptureDevice
		String  cameraName // a camera name (used for naming the video files recorded)
		);
The CameraMonitor first creates the "VideoCaptureDevice" using the monikerString passed to it, then sets the event handler for new incoming frames and finally it starts the Video capture.
cam = new VideoCaptureDevice(monikerString);
cam.NewFrame += new NewFrameEventHandler(cam_NewFrame); // the method "cam_NewFrame" is called when a new frame arrives
cam.Start(); // starts the videoCapture 
It also creates the MotionDetector:
md = new MotionDetector(new TwoFramesDifferenceDetector(), new MotionAreaHighlighting()); // creates the motion detector
Now when a NewFrameEvent is detected the "cam_NewFrame" method is ready to handle it, first it gets the BitMap from the new frame , then it displays it on the PictureBox for the user.
Bitmap bit = (Bitmap)eventArgs.Frame.Clone(); // get a copy of the BitMap from the VideoCaptureDevice
this.display.Image = (Bitmap)bit.Clone(); // displays the current frame on the main form

The Motion Detection Algorithm

If the motion detection is activated by the user, "cam_NewFrame" executes the code below:
if (this.MotionDetection && !this.motionDetected)
{
    // if motion detection is enabled and there werent any previous motion detected
    Bitmap bit2 = (Bitmap)bit.Clone(); // clone the bits from the current frame

    if (md.ProcessFrame(bit2) > 0.001) // feed the bits to the MD 
    {
        if (this.calibrateAndResume > 3)
        {
            // if motion was detected in 3 subsequent frames
            Thread th = new Thread(MotionReaction);
            th.Start(); // start the motion reaction thread
        }
        else this.calibrateAndResume++;
    }
}
Note : The "calibrateAndResume" counter is added to make sure that there is a real motion going on (withing 3 subsequent frames). Before, the motion detector would trigger even for a small change in the room lighting.

If Motion is detected, a new Thread is launched to do whatever needs to done (display a message and/or beep and/or start recording).

private void MotionReaction()
        {
            this.motionDetected = true; // pauses motion detection for a while
            if (this.RecordOnMotion)
            {
                this.StartRecording(); // record if Autorecord is toggled
            }
            if (this.BeepOnMotion)
            {
                // beep if BeepOnMotion is toggeled
                System.Console.Beep(400, 500);
                System.Console.Beep(800, 500);
            }
            
            Thread.Sleep(10000); // the user is notified for 10 seconds
            calibrateAndResume = 0;
            this.motionDetected = false; // resumes motion detection
            Thread.Sleep(3000);
            // the thread waits 3 seconds if there is no motion detected we stop the AutoRecord
            if (!this.forceRecord && this.motionDetected == false)
            {	
				// if the motion has totally stopped we stop AutoRecording
                this.StopRecording();
            }

        }

Video Recording

To record videos we're using a Bitmap Queue to store the frames coming from the camera, then the recorder thread will dequeue frame by frame and write it to a file using a "VideoFileWriter"
private void DoRecord()
        {
            // we set our VideoFileWriter as well as the file name, resolution and fps
            VideoFileWriter writer = new VideoFileWriter();
            writer.Open(RecordingPath+"\\" + this.cameraName +String.Format("{0:_dd-M-yyyy_hh-mm-ss}",DateTime.Now) +".avi", this.Width, this.Height, 30);
            
            // as long as we're recording 
            // we dequeue the BitMaps waiting in the Queue and write them to the file
            while (IsRecording)
            {
                if (frames.Count > 0)
                {
                    Bitmap bmp = frames.Dequeue();
                    writer.WriteVideoFrame(bmp);
                }
            }
            writer.Close();
        }
Note: we can always set the recording path via the public property "RecordingPath". Note: here the FrameRate is not properly set as it must be retrieved from the camera FrameRate, this will be noticed in the output videofile (speed not accurate).

b - The user interface (MainForm Class)

Using the CameraMonitor

When the form loads, it fetches the FilterInfoCollection array which contain the monikerString strings required to start the VideoCaptureDevice in the CameraMonitor. At this point the available cameras will be working and displaying video on the PictureBox controls we passed to them.
webcam = new FilterInfoCollection(FilterCategory.VideoInputDevice);
            // we create our CameraMonitors
            for (int i = 0; i < webcam.Count && i<4; i++)
            {
                this.CamMonitor[i] = new CameraMonitor(this.DisplayReference[i],webcam[i].MonikerString,"Camera"+(i+1));
                // Enable the user controls coressponding to the CameraMonitor
                this.camPanels[i].Enabled = true;
                this.camOptions[i].Enabled = true;
            }

Saving and Loading User Options

The last part is making the application remember stuff like the recording path or which cam was detecting motion etc., so when the user runs the application, he would not have to RESET all options manually. To do this, I've created a Config DataSet that stors associations ( KEY => VALUE ) where we'll save user option upon aplication exit, and reload them at application run using the DataSet.WriteXml() and DataSet.ReadXml()

Example: fetching options from the user interface and saving them to an XML file:

		try
            {
                // we try to get the option record by key
                DataRow r = this.config.Options.Select("Key = 'Camera1'")[0];
                // then we retrieve the value from the user control
                r[1] = ((!this.MotionDetection1.Checked) ? "0" : "1") +
                    ((!this.AutoRecord1.Checked) ? "0" : "1") +
                    ((!this.BeepOnMotionCheck1.Checked) ? "0" : "1");
            }
            catch (Exception ex) // if somthing goes wrong (ie. Option key is not found)
            {
                // we create a new Option record
                this.config.Options.AddOptionsRow("Camera1",
                    ((!this.MotionDetection1.Checked) ? "0" : "1") +
                    ((!this.AutoRecord1.Checked) ? "0" : "1") +
                    ((!this.BeepOnMotionCheck1.Checked) ? "0" : "1"));
            }
		// finally we write everyting to an xml file
            this.config.WriteXml("config.xml");
Example: fetching options from XML File and applying them to the user interface:
this.config.ReadXml("config.xml");
	try
            {
                // we try to get the option by its Key
                DataRow r = this.config.Options.Select("Key = 'Camera1'")[0];
                string option = r[1].ToString();
                // we apply changes to the user interface
                this.MotionDetection1.Checked = (option[0] == '0') ? false : true;
                this.AutoRecord1.Checked = (option[1] == '0') ? false : true;
                this.BeepOnMotionCheck1.Checked = (option[2] == '0') ? false : true;
            }
            catch (Exception ex) { }

Points of Interest

As a programmer, this application enabled me to scratch the surface to working with Video using AForge, writing video files (FFMpeg),playing with some image processing, so all in all, its good programming experience!

History

First Release : Feb 2012.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Osman Kalache
Software Developer Smart Solutions Médéa
Algeria Algeria
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionDisplay properties for camerasmemberBoringPortlander8 Apr '13 - 16:28 
Loving your solution, I was wondering if your version will support getting camera settings. We use a variety of sources in a current solution and I use a x264 codec for post processing. I was looking at VideoCaptureDevice.DisplayPropertyPage Method from the aforge website but its a bit vague on implementation. Any suggestions are appreciated. I would really like to use your sample as a base for my next version but being able to set codec view source settings is crucial.
AnswerRe: Display properties for camerasmemberOsman Kalache8 Apr '13 - 23:32 
Hello there, thank you for the feedback, and for considering improving my solution.
 
displaying the PropertyPage would be easy to integrate, as its a method that resides in the VideoCaptureDevice (which is being wrapped by my class CameraMonitor)
 
this is not a tested code but this is basicly what you should do :
 
1 - Add the DisplayPropertyPage() wrapper method in the CameraMonitor Class
public void DisplayPropertyPage(IntPtr parent){
     this.cam.DisplayPropertyPage(parent);
     // cam is the instance of (VideoCaptureDevice)
}
 
2 - Make the call for the wrapper method inside the mainform (you would use that in a buttonClick event or anything, when you want to display the PropertyPage.
     this.cameraMonitor[i].DisplayPropertyPage(this);
     // i is the index of camera, and we pass a refrence of the mainForm because AForge somehow needs it.
 
i hope this is helpful to you. feel free to ask anything you want.
GeneralRe: Display properties for camerasmemberBoringPortlander9 Apr '13 - 10:41 
I will have a look at this, I needed to see some proper syntax because I was confusing myself sideways since I was previously using a directshow.net library from 2008. I hope this works out, Thanks!
GeneralRe: Display properties for cameras [modified]memberBoringPortlander9 Apr '13 - 11:36 
Got this when adding the button click function:
Error	17	'WebcamSecurity.MainForm' does not contain a definition for 'camMonitor' and no extension method 'camMonitor' accepting a first argument of type 'WebcamSecurity.MainForm' could be found (are you missing a using directive or an assembly reference?)	C:\Users\gwilkins\Desktop\Article_src\MainForm.cs	570	18	WebcamSecurity
 
CamerMonitor throws same error, I can see it in your code however in the form load call. What am I missing? forgive me, I am trying to reprogram my head for C# not VB.NET so I am trying to avoid sloppy habits.
 
EDIT: Looks like I figured that portion out. I used the following to get it working:
this.CamMonitor[1].DisplayPropertyPage(IntPtr.Zero);
Now, my second boggle was making a dropdown to choose from available codecs on the system similar to directshow.net. The Aforge website does not directly point me to anywhere except the displaypropertypage which is what we just covered.
 
Once again thanks for the help!

modified 9 Apr '13 - 18:09.

QuestioncalibrateAndResume questionmemberJacob Pachansky4 Nov '12 - 11:12 
First of all - great article, thank you!
One thing I'm not certain about, however, is how calibrateAndResume works... I think the MotionReaction method would trigger after any [not necessarily subsequent] 3 frames. (unless is reset back to 0 each time motion isn't detected). Or am I missing something?
AnswerRe: calibrateAndResume questionmemberOsman Kalache28 Nov '12 - 15:26 
Thanks for your feedback,
well, the calibrateAndResume test's purpose is to avoid trigering MotionReactions to small changes between two frames ie. we wait until we have 3 different frames to launch our MotionReaction.
At first, i only relied on the md.ProcessFrame() and ajusting the threshold in the test but still it didn't seem efficient, it was either not detecting slow motions or detecting all small changes in the lighing for example.
QuestionIs it possible to save MPEG4?membergxdata23 Aug '12 - 22:08 
The FFMPEG DLL when decompiled with Reflector shows BMP captures being saved into the AVI. It seems to me that replaying the resultant AVI file, it runs much too fast.
Is it possible to save in WMV or MP4?
AnswerRe: Is it possible to save MPEG4?memberOsman Kalache18 Sep '12 - 1:17 
Well in that case you only have to use a different library which enables writing in WMV or MP4 formats.
Here, the resulting AVI file seems to be running somehow "FAST" because i didn't bother to set the FPS rate when writing the video file, otherwise if you manage to set it, the speed of the video should be accurate.
AnswerRe: Is it possible to save MPEG4?memberMember 930431025 Jan '13 - 15:29 
yes its possible you must change frame rate while we storing video i use ff as .mkv it need 6 number of frame you change frame rate it run slowly .
Questionmy vote of 5membermuhamad yousef4 Jun '12 - 22:35 
thanks for your work
Smile | :)

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 2 Mar 2012
Article Copyright 2012 by Osman Kalache
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid