OpenCV : Motion Tracking with Recording using WebCam (Easy Way) aka Motion Activated Security Camera
Hello Everybody, Pankaj here !!
Today we are going to be going over another OpenCV Tutotirla using C++, more importantly, we are going to use our new found knowledge of Image Processing to build our very own motion-activated surveillance camera.
Here is a little preview of what the program is going to look like when all is said and done:
You can see that once, there is motion detected in the camera’s field of vision, the program then starts recording and it will save to a video file on the hard drive and you’ll be able to open it up at the end of the day if you run it for the whole day and view all the activity that’s happened on your camera.
Some of the features that you’ll notice is that the camera only records when there is a motion on the screen. Also, we have added a little data and time stamp for your convinience just like the CCTV camera or a closed-circuit camera that’s used for survelliance.
So let’s look at the complete code and pay attention to the comments with each line of code to understand what every line is meant to do.
Let’s get started!
Open up visual studio and creat an empty project and then create a new motion.cpp file and paste the below code in it. Ensure that your build is correctly set and you have linked the opencv libraries and includes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 | //http://pythonopencv.com #include <opencv\cv.h> #include <opencv\highgui.h> #include <time.h> using namespace std; using namespace cv; //our sensitivity value to be used in the absdiff() function //for higher sensitivity, use a lower value const static int SENSITIVITY_VALUE = 40; //size of blur used to smooth the intensity image output from absdiff() function const static int BLUR_SIZE = 10; //these two can be toggled by pressing 'd' or 't' bool debugMode; bool trackingEnabled; //int to string helper function string intToString(int number){ //this function has a number input and string output std::stringstream ss; ss << number; return ss.str(); } string getDateTime(){ //get the system time SYSTEMTIME theTime; GetLocalTime(&theTime); //create string to store the date and time string dateTime; //convert year to string string year = intToString(theTime.wYear); //use stringstream to add a leading '0' to the month (ie. 3 -> 03) //we use 'setw(2)' so that we force the string 2 characters wide with a zero in front of it. //if the month is '10' then it will remain '10' std::stringstream m; m<<std::setfill('0')<<std::setw(2)<< theTime.wMonth; string month = m.str(); //day std::stringstream d; d<<std::setfill('0')<<std::setw(2)<< theTime.wDay; string day = d.str(); //hour std::stringstream hr; hr<<setfill('0')<<std::setw(2)<<theTime.wHour; string hour = hr.str(); //minute std::stringstream min; min<<setfill('0')<<std::setw(2)<<theTime.wMinute; string minute = min.str(); //second std::stringstream sec; sec<<setfill('0')<<std::setw(2)<<theTime.wSecond; string second = sec.str(); //here we use the year, month, day, hour, minute info to create a custom string //this will be displayed in the bottom left corner of our video feed. dateTime = year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second; return dateTime; } string getDateTimeForFile(){ //this function does the exact same as "getDateTime()" //however it returns a string that can be used as a filename SYSTEMTIME theTime; GetLocalTime(&theTime); string dateTime; string year = intToString(theTime.wYear); std::stringstream m; m<<std::setfill('0')<<std::setw(2)<< theTime.wMonth; string month = m.str(); std::stringstream d; d<<std::setfill('0')<<std::setw(2)<< theTime.wDay; string day = d.str(); std::stringstream hr; hr<<setfill('0')<<std::setw(2)<<theTime.wHour; string hour = hr.str(); std::stringstream min; min<<setfill('0')<<std::setw(2)<<theTime.wMinute; string minute = min.str(); std::stringstream sec; sec<<setfill('0')<<std::setw(2)<<theTime.wSecond; string second = sec.str(); //here we use "_" instead of "-" and ":" //if we try to save a filename with "-" or ":" in it we will get an error. dateTime = year + "_" + month + "_" + day + "_" + hour + "h" + minute + "m" + second + "s"; return dateTime; } bool detectMotion(Mat thresholdImage, Mat &cameraFeed){ //create motionDetected variable. bool motionDetected = false; //create temp Mat for threshold image Mat temp; thresholdImage.copyTo(temp); //these two vectors needed for output of findContours vector< vector<Point> > contours; vector<Vec4i> hierarchy; //find contours of filtered image using openCV findContours function //findContours(temp,contours,hierarchy,CV_RETR_CCOMP,CV_CHAIN_APPROX_SIMPLE );// retrieves all contours findContours(temp,contours,hierarchy,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_SIMPLE );// retrieves external contours //if contours vector is not empty, we have found some objects //we can simply say that if the vector is not empty, motion in the video feed has been detected. if(contours.size()>0)motionDetected=true; else motionDetected = false; return motionDetected; } int main(){ //set recording and startNewRecording initially to false. bool recording = false; bool startNewRecording = false; int inc=0; bool firstRun = true; //if motion is detected in the video feed, we will know to start recording. bool motionDetected = false; //pause and resume code (if needed) bool pause = false; //set debug mode and trackingenabled initially to false //these can be toggled using 'd' and 't' debugMode = false; trackingEnabled = true; //set up the matrices that we will need //the two frames we will be comparing Mat frame1,frame2; //their grayscale images (needed for absdiff() function) Mat grayImage1,grayImage2; //resulting difference image Mat differenceImage; //thresholded difference image (for use in findContours() function) Mat thresholdImage; //video capture object. VideoCapture capture; capture.open(0); VideoWriter oVideoWriter;//create videoWriter object, not initialized yet double dWidth = capture.get(CV_CAP_PROP_FRAME_WIDTH); //get the width of frames of the video double dHeight = capture.get(CV_CAP_PROP_FRAME_HEIGHT); //get the height of frames of the video //set framesize for use with videoWriter Size frameSize(static_cast<int>(dWidth), static_cast<int>(dHeight)); if(!capture.isOpened()){ cout<<"ERROR ACQUIRING VIDEO FEED\n"; getchar(); return -1; } while(1){ //read first frame capture.read(frame1); //convert frame1 to gray scale for frame differencing cv::cvtColor(frame1,grayImage1,COLOR_BGR2GRAY); //copy second frame capture.read(frame2); //convert frame2 to gray scale for frame differencing cv::cvtColor(frame2,grayImage2,COLOR_BGR2GRAY); //perform frame differencing with the sequential images. This will output an "intensity image" //do not confuse this with a threshold image, we will need to perform thresholding afterwards. cv::absdiff(grayImage1,grayImage2,differenceImage); //threshold intensity image at a given sensitivity value cv::threshold(differenceImage,thresholdImage,SENSITIVITY_VALUE,255,THRESH_BINARY); if(debugMode==true){ //show the difference image and threshold image cv::imshow("Difference Image",differenceImage); cv::imshow("Threshold Image", thresholdImage); }else{ //if not in debug mode, destroy the windows so we don't see them anymore cv::destroyWindow("Difference Image"); cv::destroyWindow("Threshold Image"); } //blur the image to get rid of the noise. This will output an intensity image cv::blur(thresholdImage,thresholdImage,cv::Size(BLUR_SIZE,BLUR_SIZE)); //threshold again to obtain binary image from blur output cv::threshold(thresholdImage,thresholdImage,SENSITIVITY_VALUE,255,THRESH_BINARY); if(debugMode==true){ //show the threshold image after it's been "blurred" imshow("Final Threshold Image",thresholdImage); } else { //if not in debug mode, destroy the windows so we don't see them anymore cv::destroyWindow("Final Threshold Image"); } //if tracking enabled, search for Motion if(trackingEnabled){ //check for motion in the video feed //the detectMotion function will return true if motion is detected, else it will return false. //set motionDetected boolean to the returned value. motionDetected = detectMotion(thresholdImage,frame1); }else{ //reset our variables if tracking is disabled motionDetected = false; } ////////////**STEP 1**////////////////////////////////////////////////////////////////////////////////////////////////////////////// //draw time stamp to video in bottom left corner. We draw it before we write so that it is written on the video file. //if we're in recording mode, write to file if(recording){ //check if it's our first time running the program so that we don't create a new video file over and over again. //we use the same boolean check to create a new recording if we want. if(firstRun == true || startNewRecording == true){ //////////**STEP 3**/////////////////////////////////////////////////////////////////////////////////////////////////////////////// //Create a unique filename for each video based on the date and time the recording has started string videoFileName = "D:/MyVideo"+intToString(inc)+".avi"; cout << "File has been opened for writing: " << videoFileName<<endl; cout << "Frame Size = " << dWidth << "x" << dHeight << endl; oVideoWriter = VideoWriter(videoFileName, CV_FOURCC('D', 'I', 'V', '3'), 20, frameSize, true); if ( !oVideoWriter.isOpened() ) { cout << "ERROR: Failed to initialize video writing" << endl; getchar(); return -1; } //reset our variables to false. firstRun = false; startNewRecording = false; } oVideoWriter.write(frame1); //show "REC" in top left corner in red //be sure to do this AFTER you write to the file so that "REC" doesn't show up on the recorded video file. //Cut and paste the following line above "oVideoWriter.write(frame1)" to see what I'm talking about. putText(frame1,"REC",Point(0,60),2,2,Scalar(0,0,255),2); } //check if motion is detected in the video feed. if(motionDetected){ //show "MOTION DETECTED" in bottom left corner in green putText(frame1,"MOTION DETECTED",cv::Point(0,420),2,2,cv::Scalar(0,255,0)); //////////**STEP 2**/////////////////////////////////////////////////////////////////////////////////////////////////////////////// //set recording to true since there is motion in the video feed. //else recording should be false. } //show our captured frame imshow("Frame1",frame1); //check to see if a button has been pressed. //the 30ms delay is necessary for proper operation of this program //if removed, frames will not have enough time to referesh and a blank image will appear. switch(waitKey(30)){ case 27: //'esc' key has been pressed, exit program. return 0; case 116: //'t' has been pressed. this will toggle tracking (disabled for security cam) /*trackingEnabled = !trackingEnabled; if(trackingEnabled == false) cout<<"Tracking disabled."<<endl; else cout<<"Tracking enabled."<<endl;*/ break; case 100: //'d' has been pressed. this will debug mode debugMode = !debugMode; if(debugMode == false) cout<<"Debug mode disabled."<<endl; else cout<<"Debug mode enabled."<<endl; break; case 112: //'p' has been pressed. this will pause/resume the code. pause = !pause; if(pause == true){ cout<<"Code paused, press 'p' again to resume"<<endl; while (pause == true){ //stay in this loop until switch (waitKey()){ //a switch statement inside a switch statement? Mind blown. case 112: //change pause back to false pause = false; cout<<"Code Resumed"<<endl; break; } } } case 114: //'r' has been pressed. //toggle recording mode recording =!recording; if (!recording)cout << "Recording Stopped" << endl; else cout << "Recording Started" << endl; break; case 110: //'n' has been pressed //start new video file startNewRecording = true; recording = true; cout << "New Recording Started" << endl; //increment video file name inc+=1; break; } } return 0; } |