├── Background_subtraction_report.pdf ├── README.md └── bg_subtration.cpp /Background_subtraction_report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkm97/Motion-Detection-and-Tracking-For-Moving-Object-Background-Subtraction-/HEAD/Background_subtraction_report.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Motion-Detection-and-Tracking-For-Moving-Object-Background-Subtraction 2 | 3 | 4 | A simple system that used OpenCV library coded in C++ that detect and track moving object by implementing background subtraction technique. 5 | Solved dynamic background problem. 6 | -------------------------------------------------------------------------------- /bg_subtration.cpp: -------------------------------------------------------------------------------- 1 | /*------------------- Team 12 ------------------- 2 | | CONTENTS | 3 | | 1. Libraries | 4 | | 2. Global Variables | 5 | | a. You can change video filename here | 6 | | 3. Object Classes | 7 | | a. Color | 8 | | b. ColorBox | 9 | | 4. Functions Headers | 10 | | 5. Main Function | 11 | | 6. Functions | 12 | | a. ROI | 13 | | b. Training Phase | 14 | | c. Testing Phase | 15 | -------------------------------------------------*/ 16 | 17 | /** 1. Libraries **/ 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | using namespace cv; 26 | using namespace std; 27 | 28 | /** 2. Global Variables **/ 29 | bool ldown = false; // Left Mouse Button Down (is clicking) 30 | bool lup = false; // Left Mouse Button Up (not clicking) 31 | Point corner1; // Use to store Point A (x1, y1) 32 | Point corner2; // Use to store Point B (x2, y2) 33 | Mat ROI; // Use to store ROI image frame 34 | Rect box; //Rectangle of ROI 35 | bool doneCropping = false; 36 | const int NBIN = 16; 37 | const int BINSIZE = 256 / NBIN; 38 | const int THRES = 50; 39 | 40 | string filename = "parking.mp4"; 41 | //string filename = "contrysideTraffic.mp4"; 42 | //string filename = "MAQ00626.MP4"; 43 | //string filename = "Home_Intrusion.mp4"; 44 | //string filename = "WavingTrees.avi"; 45 | //string filename = "highway1.avi"; 46 | //string filename = "fountain01.avi"; 47 | 48 | VideoCapture video(filename); 49 | 50 | /** 3. Object Classes **/ 51 | class Color 52 | { 53 | private: 54 | int colorIndex; 55 | int quantity; 56 | double weightage; 57 | 58 | public: 59 | Color(int colorIndex) 60 | { 61 | this->colorIndex = colorIndex; 62 | this->quantity = 1; 63 | this->weightage = 0.0; 64 | } 65 | 66 | int getColorIndex() 67 | { 68 | return colorIndex; 69 | } 70 | 71 | Vec3b getColor() 72 | { 73 | int r = colorIndex % (NBIN * NBIN) % (NBIN)* BINSIZE + BINSIZE / 2; 74 | int g = colorIndex % (NBIN * NBIN) / NBIN * BINSIZE + BINSIZE / 2; 75 | int b = colorIndex / (NBIN * NBIN) * BINSIZE + BINSIZE / 2; 76 | 77 | return Vec3b(b, g, r); 78 | } 79 | 80 | int getQuantity() 81 | { 82 | return quantity; 83 | } 84 | 85 | double getWeightage() 86 | { 87 | return weightage; 88 | } 89 | 90 | void increaseQuantity() 91 | { 92 | quantity++; 93 | } 94 | 95 | void setWeightage(int& total_train_frame) 96 | { 97 | weightage = quantity / (double)total_train_frame; 98 | } 99 | 100 | bool operator< (Color& c) 101 | { 102 | return c.quantity < this->quantity; 103 | } 104 | }; 105 | 106 | class ColorBox 107 | { 108 | private: 109 | vector colorList; 110 | 111 | public: 112 | ColorBox() {} 113 | 114 | vector& getColorList() 115 | { 116 | return colorList; 117 | } 118 | 119 | int findIndex(int index) 120 | { 121 | for (int i = 0; i < colorList.size(); i++) 122 | { 123 | if (colorList[i].getColorIndex() == index) 124 | { 125 | return i; 126 | } 127 | } 128 | return -1; 129 | } 130 | 131 | void addColor(Vec3b color) 132 | { 133 | int index = 0; 134 | 135 | index += (color[0] / BINSIZE) * NBIN * NBIN; 136 | index += color[1] / BINSIZE * NBIN; 137 | index += color[2] / BINSIZE; 138 | 139 | int found = findIndex(index); 140 | 141 | if (found == -1) 142 | { 143 | Color newColor(index); 144 | colorList.push_back(newColor); 145 | } 146 | else 147 | { 148 | colorList[found].increaseQuantity(); 149 | } 150 | } 151 | 152 | void sortColorList() 153 | { 154 | sort(colorList.begin(), colorList.end()); 155 | } 156 | 157 | Vec3b getDominantColor() 158 | { 159 | return colorList[0].getColor(); 160 | } 161 | }; 162 | 163 | struct ForegroundObject 164 | { 165 | Point center; 166 | int area; 167 | int x; 168 | int y; 169 | int height; 170 | int width; 171 | 172 | ForegroundObject(Point center, int area, int x, int y, int h, int w) 173 | { 174 | this->center = center; 175 | this->area = area; 176 | this->x = x; 177 | this->y = y; 178 | this->height = h; 179 | this->width = w; 180 | } 181 | 182 | void draw(Mat& frame) 183 | { 184 | rectangle(frame, Point(x, y), Point(x + width, y + height), Scalar(0, 0, 255), 1); 185 | circle(frame, center, 2, Scalar(255, 0, 0), -1); 186 | } 187 | }; 188 | 189 | /** 4. Function Headers **/ 190 | //-- a. ROI 191 | static void mouse_callback(int event, int x, int y, int, void *); 192 | //-- b. Training Phase 193 | Mat training(double percentage, vector& background); 194 | void put_frame_to_background(Mat& training_frame, vector& background); 195 | Mat get_background_model(vector& background); 196 | //-- c. Testing Phase 197 | void testing(vector& background); 198 | Mat get_foreground_mask(Mat& training_frame, vector& background); 199 | bool compare_color(Vec3b a, vector& colorList); 200 | bool matchColor(Vec3b a, Vec3b b); 201 | 202 | /** 5. Main Functions **/ 203 | int main(void) 204 | { 205 | system("Color F0"); // Set white background, black font 206 | 207 | //-- 1. Check whether video exist or not 208 | // If not, then exit the program 209 | if (!video.isOpened()) 210 | { 211 | cout << "Can't find video file named - " << filename << "." << endl; 212 | system("PAUSE"); 213 | return -1; 214 | } 215 | 216 | int total_frame = (int)video.get(CV_CAP_PROP_FRAME_COUNT); 217 | int video_height = (int)video.get(CV_CAP_PROP_FRAME_HEIGHT); 218 | int video_width = (int)video.get(CV_CAP_PROP_FRAME_WIDTH); 219 | 220 | //-- 2. Show the properties of the Video 221 | cout << "-----// Video Information //-----" << endl; 222 | cout << "Total Frame: " << total_frame << endl; 223 | cout << "Video Height: " << video_height << "px" << endl; 224 | cout << "Video Width: " << video_width << "px" << endl << endl; 225 | 226 | cout << "Press and hold mouse left button to select." << endl; 227 | cout << "\tor" << endl; 228 | cout << "Press 'q' to exit" << endl << endl; 229 | 230 | video.read(ROI); 231 | imshow("Select ROI", ROI); 232 | setMouseCallback("Select ROI", mouse_callback); 233 | while (char(waitKey(1)) != 'q' && !doneCropping) {/*Critical Section*/ } 234 | 235 | //-- 3. Training Phase 236 | vector background(box.area(), ColorBox()); 237 | Mat background_model = training(0.3, background); // train only 30% of the frame 238 | 239 | //-- 4. Testing Phase 240 | video.open(filename); 241 | testing(background); 242 | 243 | cout << "\nDone" << endl; 244 | system("PAUSE"); 245 | return 0; 246 | } 247 | 248 | /** 6. Functions **/ 249 | //-- a. ROI 250 | void mouse_callback(int event, int x, int y, int, void *) 251 | { 252 | if (event == EVENT_LBUTTONDOWN) 253 | { 254 | ldown = true; 255 | corner1.x = x; 256 | corner1.y = y; 257 | cout << "Corner 1 at " << corner1 << endl; 258 | } 259 | 260 | if (event == EVENT_LBUTTONUP) 261 | { 262 | if (abs(x - corner1.x) > 10 && abs(y - corner1.y) > 10) 263 | { 264 | lup = true; 265 | 266 | corner2.x = x; 267 | corner2.y = y; 268 | 269 | if (y > ROI.size().height) corner2.y = ROI.size().height; 270 | if (x > ROI.size().width) corner2.x = ROI.size().width; 271 | 272 | if (y < 0) corner2.y = 0; 273 | if (x < 0) corner2.x = 0; 274 | 275 | cout << "Corner 2 at " << corner2 << endl; 276 | } 277 | else 278 | { 279 | cout << "Please select a bigger region" << endl; 280 | ldown = false; 281 | } 282 | } 283 | 284 | if (ldown == true && lup == false) 285 | { 286 | Point pt; 287 | pt.x = x; 288 | pt.y = y; 289 | 290 | Mat locale_img = ROI.clone(); 291 | 292 | if (y > ROI.size().height) corner2.y = ROI.size().height; 293 | if (x > ROI.size().width) corner2.x = ROI.size().width; 294 | 295 | if (y < 0) pt.y = 0; 296 | if (x < 0) pt.x = 0; 297 | 298 | rectangle(locale_img, corner1, pt, Scalar(0, 0, 255), 2); 299 | 300 | imshow("Select ROI", locale_img); 301 | } 302 | 303 | if (ldown == true && lup == true) 304 | { 305 | ldown = false; 306 | lup = false; 307 | 308 | box.width = abs(corner1.x - corner2.x); 309 | box.height = abs(corner1.y - corner2.y); 310 | 311 | box.x = min(corner1.x, corner2.x); 312 | box.y = min(corner1.y, corner2.y); 313 | 314 | doneCropping = true; 315 | } 316 | } 317 | 318 | //-- b. Training Phase 319 | Mat training(double percentage, vector& background) 320 | { 321 | if (percentage <= 0.0 || percentage > 1.0) 322 | { 323 | percentage = 0.3; 324 | } 325 | 326 | Mat training_frame; 327 | int total_frame = (int)video.get(CV_CAP_PROP_FRAME_COUNT); 328 | int total_train_frame = int(total_frame * percentage); 329 | 330 | cout << "\n-----// Training Phase //-----" << endl; 331 | cout << "Train " << percentage * 100 << "% of total " << total_frame << " frames." << endl; 332 | cout << "Learning Frame 1 to Frame " << total_train_frame - 1 << "." << endl; 333 | 334 | for (int i = 1; i <= total_train_frame - 1; i++) 335 | { 336 | video.read(training_frame); 337 | training_frame = training_frame(box); 338 | imshow("Learning Progress", training_frame); 339 | waitKey(1); 340 | 341 | put_frame_to_background(training_frame, background); 342 | 343 | cout << "\r" << (i * 100 / total_train_frame) + 1 << "%"; 344 | } 345 | Mat background_model = get_background_model(background); 346 | imshow("Background Trained", background_model); 347 | waitKey(1); 348 | 349 | for (int i = 0; i < background.size(); i++) 350 | { 351 | for (int j = 0; j < background[i].getColorList().size(); j++) 352 | { 353 | background[i].getColorList().at(j).setWeightage(total_train_frame); 354 | 355 | } 356 | } 357 | 358 | cout << endl; 359 | Sleep(1000); // Sleep for 1 second 360 | destroyWindow("Select ROI"); 361 | destroyWindow("Learning Progress"); 362 | return background_model; 363 | } 364 | 365 | void put_frame_to_background(Mat& training_frame, vector& background) 366 | { 367 | int cols = box.width; 368 | int rows = box.height; 369 | 370 | for (int i = 0; i < rows; i++) 371 | { 372 | for (int j = 0; j < cols; j++) 373 | { 374 | background[i*cols + j].addColor(training_frame.at(i, j)); 375 | background[i*cols + j].sortColorList(); 376 | } 377 | } 378 | } 379 | 380 | Mat get_background_model(vector& background) 381 | { 382 | int cols = box.width; 383 | int rows = box.height; 384 | 385 | Mat background_model(rows, cols, CV_8UC3); 386 | for (int i = 0; i < rows; i++) 387 | { 388 | for (int j = 0; j < cols; j++) 389 | { 390 | background_model.at(i, j) = background[i*cols + j].getDominantColor(); 391 | } 392 | } 393 | return background_model; 394 | } 395 | 396 | //-- c. Testing Phase 397 | void testing(vector& background) 398 | { 399 | moveWindow("Background Trained", 100, 100); 400 | 401 | Mat testing_frame; 402 | Mat foreground_mask, display; 403 | int frame_counter = 1; 404 | int total_frame = (int)video.get(CV_CAP_PROP_FRAME_COUNT); 405 | 406 | cout << "\n-----// Testing Phase //-----" << endl; 407 | cout << "Testing " << total_frame << "frames." << endl; 408 | 409 | for (int i = 1; i <= total_frame - 1; i++) 410 | { 411 | video.read(testing_frame); 412 | testing_frame = testing_frame(box); 413 | imshow("Original Video", testing_frame); 414 | moveWindow("Original Video", 300, 100); 415 | 416 | foreground_mask = get_foreground_mask(testing_frame, background); 417 | bitwise_not(foreground_mask, display); 418 | imshow("Foreground Mask", display); 419 | moveWindow("Foreground Mask", 100, 300); 420 | 421 | Mat structElement = getStructuringElement(MORPH_ELLIPSE, Size(2, 2)); 422 | morphologyEx(foreground_mask, foreground_mask, MORPH_OPEN, structElement); 423 | structElement = getStructuringElement(MORPH_ELLIPSE, Size(5, 5)); 424 | morphologyEx(foreground_mask, foreground_mask, MORPH_CLOSE, structElement); 425 | //morphologyEx(foreground_mask, foreground_mask, MORPH_CLOSE, structElement); 426 | bitwise_not(foreground_mask, display); 427 | imshow("Morphology Transformations", display); 428 | moveWindow("Morphology Transformations", 300, 300); 429 | 430 | Mat labels, stats, centroids; 431 | int nLabels = connectedComponentsWithStats(foreground_mask, labels, stats, centroids); 432 | vector f_objects; 433 | for (int i = 0; i < nLabels; i++) 434 | { 435 | if (stats.at(i, CC_STAT_AREA) < 80) 436 | { 437 | continue; 438 | } 439 | int x = stats.at(i, CC_STAT_LEFT); 440 | int y = stats.at(i, CC_STAT_TOP); 441 | int height = stats.at(i, CC_STAT_HEIGHT); 442 | int width = stats.at(i, CC_STAT_WIDTH); 443 | 444 | ForegroundObject current_object(Point(centroids.at(i, 0), centroids.at(i, 1)), stats.at(i, CC_STAT_AREA), x, y, height, width); 445 | f_objects.push_back(current_object); 446 | } 447 | 448 | for (int i = 0; i < f_objects.size(); i++) 449 | { 450 | f_objects[i].draw(testing_frame); 451 | } 452 | 453 | imshow("Testing Result", testing_frame); 454 | moveWindow("Testing Result", 500, 300); 455 | 456 | cout << "\r" << (i * 100 / total_frame) + 1 << "%"; 457 | waitKey(1); 458 | } 459 | 460 | cout << endl; 461 | } 462 | 463 | Mat get_foreground_mask(Mat& testing_frame, vector& background) 464 | { 465 | int cols = box.width; 466 | int rows = box.height; 467 | 468 | Mat foreground_mask(rows, cols, CV_8UC1); 469 | for (int i = 0; i < rows; i++) 470 | { 471 | for (int j = 0; j < cols; j++) 472 | { 473 | foreground_mask.at(i, j) = 255 * 474 | !compare_color(testing_frame.at(i, j), background[i*cols + j].getColorList()); 475 | } 476 | } 477 | return foreground_mask; 478 | } 479 | 480 | bool compare_color(Vec3b a, vector& colorList) 481 | { 482 | double accumulate = 0; 483 | for (int i = 0; accumulate < 0.75; i++) { 484 | 485 | if (matchColor(a, colorList[i].getColor())) 486 | { 487 | return true; 488 | } 489 | accumulate += colorList[i].getWeightage(); 490 | } 491 | return false; 492 | } 493 | 494 | bool matchColor(Vec3b a, Vec3b b) 495 | { 496 | for (int i = 0; i < 3; i++) 497 | { 498 | if (abs(a.val[i] - b.val[i]) > THRES) 499 | { 500 | return false; 501 | } 502 | } 503 | return true; 504 | } --------------------------------------------------------------------------------