به نام خدا : تو این مطلب یه سری از الگوریتم ها رو خواهیم دید که برا پیدا کردن تصویر پس زمینه ( background ) و پیش زمینه ( foreground ) در شرایطی که دوربین ثابت هستش ( و حرکتی نداره ) بکار میرن؛ سایت OpenCV توضیحات زیادی ( تقریبا هیچی ) درباره این الگوریتم ها نداده و فقط چند مثال قرار داده! ما هم فعلا به همین اکتفا میکنیم تا مطالب بعدی ببینیم چی پیش میاد ( یعنی اگه نیاز شد، این مطلب رو تکمیل میکنیم وگرنه که هیچی )؛ ماژول Background Subtraction، زیر مجموعه ای از ماژول Video Analysis هستش.
Background Subtraction
در این مطلب با الگوریتم های تفریق پس زمینه ( background subtraction algorithms = BGS ) آشنا خواهیم شد.
تفریق پس زمینه یک مرحله پیش پردازش اصلی در بسیاری از برنامه های کاربردی پردازش تصویر است؛ به عنوان مثال، دوربین ثابتی را در نظر بگیرید که تعداد بازدیدکنندگانی را که وارد اتاق می شوند یا از آن خارج می شوند را شمارش میکند، یا دوربین ترافیکی که اطلاعات مربوط به وسایل نقلیه و... را استخراج میکند؛ در تمام این موارد، ابتدا باید فرد یا وسایل نقلیه را به تنهایی استخراج کنید؛ برای اینکار، شما باید پیش زمینه متحرک ( moving foreground ) را از پس زمینه ثابت ( static background ) استخراج کنید.
اگر تصویری از background به تنهایی دارید، مانند تصویری از اتاق بدون بازدیدکننده، تصویری از جاده بدون وسایل نقلیه و...، کار آسان است!؛ فقط تصویر جدید را از background کم کنید ( برای این کار از تابع cv::subtract میتونید کمک بگیرید )؛ نمونه کد برای استفاده از تابع cv::subtract :
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 |
void motion_analysis_example1::BackgroundSubtraction_WhenWeHaveBackgroundImage() { cv::Mat background = cv::imread("C:/Users/Mahdi/Desktop/Background.jpg"); // create object -> open video cv::VideoCapture vc; if(!vc.open("C:/Users/Mahdi/Desktop/vtest.avi")) { qDebug() << "Cant open video file"; return; } // calculate the delay between each frame double fps = vc.get(cv::VideoCaptureProperties::CAP_PROP_FPS); double delay = 1000.0/fps; // read frames loop while(true) { // read frame cv::Mat frame; if( !vc.read(frame) ) break; cv::Mat mask, foreground; cv::subtract(background, frame, mask); // dst = background - frame; cv::cvtColor(mask, mask, cv::COLOR_BGR2GRAY); cv::threshold(mask, mask, 30, 255, cv::ThresholdTypes::THRESH_BINARY); cv::bitwise_and(frame, frame, foreground, mask); cv::imshow("1) frame", frame); cv::imshow("2) foreground", foreground); cv::waitKey(delay); } } |
اما در بیشتر موارد ممکن است تصویری از background نداشته باشیم، بنابراین باید background را از هر تصویری که داریم استخراج کنیم! ( که موضوع این مطلب هم همینه، معرفی توابع و کلاس ها -الگوریتم ها- یی برای انجام این کار )؛ وقتی سایههایی ( shadows ) از وسایل نقلیه ( و افراد و ... ) وجود داشته باشد ( که همیشه وجود دارد! )، وضعیت پیچیدهتر میشود!؛ از آنجایی که سایه ها نیز حرکت میکنند، تفریق ساده آن را نیز به عنوان پیش زمینه مشخص میکند و این، کار رو پیچیده میکند.
چندین الگوریتم برای این منظور معرفی شده؛ که خب یک سریاشون به بسته OpenCV راه پیدا کردن که در فایل background_segm.hpp قرار دارند؛ OpenCV یه صفحه جداگانه برای این قسمت با عنوان "Motion Analysis" ( تجزیه و تحلیل حرکت ) ایجاد کرده؛ فایل فوق، شامل کلاس های زیر هستش :
- cv::BackgroundSubtractor ( کلاس پایه هستش؛ تمامی الگوریتم های این مطلب، از این کلاس مشتق شدن )
- cv::BackgroundSubtractorKNN
- cv::BackgroundSubtractorMOG2
یک سری دیگه از الگوریتم ها هنوز به بسته OpenCV راه پیدا نکردن و هنوز در بسته Contrib Modules قرار دارند که در فایل bgsegm.hpp قرار دارند؛ فایل فوق، شامل کلاس های زیر هستش :
- cv::bgsegm::BackgroundSubtractorCNT
- cv::bgsegm::BackgroundSubtractorGMG
- cv::bgsegm::BackgroundSubtractorGSOC
- cv::bgsegm::BackgroundSubtractorLSBP
- cv::bgsegm::BackgroundSubtractorLSBPDesc ( الگوریتم نیست! )
- cv::bgsegm::BackgroundSubtractorMOG
- cv::bgsegm::SyntheticSequenceGenerator ( الگوریتم نیست! )
در عکس زیر لیست الگوریتم های این مطلب رو میبینید ( در این مطلب با الگوریتم های cuda کاری نداریم. ) :
برای ایجاد شیء از کلاس های مربوط به الگوریتم ها , مقدار دهی اولیه شون، به صورت زیر عمل میکنیم، که برای هر الگوریتمی 2 حالت وجود داره! که خب روش اول ساده تره :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
cv::Ptr<cv::BackgroundSubtractor> bsCNT1 = cv::bgsegm::createBackgroundSubtractorCNT(); cv::Ptr<cv::bgsegm::BackgroundSubtractorCNT> bsCNT2 = cv::bgsegm::createBackgroundSubtractorCNT(); cv::Ptr<cv::BackgroundSubtractor> bsGMG1 = cv::bgsegm::createBackgroundSubtractorGMG(); cv::Ptr<cv::bgsegm::BackgroundSubtractorGMG> bsGMG2 = cv::bgsegm::createBackgroundSubtractorGMG(); cv::Ptr<cv::BackgroundSubtractor> bsGSOC1 = cv::bgsegm::createBackgroundSubtractorGSOC(); cv::Ptr<cv::bgsegm::BackgroundSubtractorGSOC> bsGSOC2 = cv::bgsegm::createBackgroundSubtractorGSOC(); cv::Ptr<cv::BackgroundSubtractor> bsLSBP1 = cv::bgsegm::createBackgroundSubtractorLSBP(); cv::Ptr<cv::bgsegm::BackgroundSubtractorLSBP> bsLSBP2 = cv::bgsegm::createBackgroundSubtractorLSBP(); cv::Ptr<cv::BackgroundSubtractor> bsMOG_1 = cv::bgsegm::createBackgroundSubtractorMOG(); cv::Ptr<cv::bgsegm::BackgroundSubtractorMOG> bsMOG_2 = cv::bgsegm::createBackgroundSubtractorMOG(); cv::Ptr<cv::BackgroundSubtractor> bsKNN1 = cv::createBackgroundSubtractorKNN(); cv::Ptr<cv::BackgroundSubtractorKNN> bsKNN2 = cv::createBackgroundSubtractorKNN(); cv::Ptr<cv::BackgroundSubtractor> bsMOG2_1 = cv::createBackgroundSubtractorMOG2(); cv::Ptr<cv::BackgroundSubtractorMOG2> bsMOG2_2 = cv::createBackgroundSubtractorMOG2(); |
تابع apply : محاسبه foreground mask؛ این تابع توسط تمام الگوریتم ها پشتیبانی میشود.
1 |
CV_WRAP virtual void apply(InputArray image, OutputArray fgmask, double learningRate=-1) = 0; |
توضیح پارامترها :
- image : فریم بعدی ویدئو.
- fgmask : خروجی تصویر foreground mask با فرمت 8 بیتی باینری.
- learningRate : مقدار بین 0 و 1، نشان دهنده سرعت یادگیری مدل background است؛ مقادیر منفی باعث میشود که الگوریتم از نرخ یادگیری انتخابی خودکار استفاده کند؛ 0 به این معنی است که مدل background اصلا بهروزرسانی نمیشود؛ 1 به این معنی است که مدل background کاملاً از آخرین فریم دوباره مقداردهی اولیه شده است.
تابع getBackgroundImage : محاسبه تصویر background؛ این تابع توسط تمام الگوریتم ها ( بجزء GMG و MOG ) پشیبانی میشود.
1 |
CV_WRAP virtual void getBackgroundImage(OutputArray backgroundImage) const = 0; |
توضیح پارامترها :
backgroundImage : تصویر خروجی background، در این پارامتر ذخیره میشود.
توجه : تمامی الگوریتم ها، تابع apply رو پشتیبانی میکنند که foreground mask رو برای هر فریمی که بهشون بدیم رو محاسبه میکنند؛ به کمک این ماسک به راحتی میتونید پیش زمینه و پس زمینه هر فریم رو محاسبه کنید؛ اما تابع getBackgroundImage، میاد تصویر background رو تخمین میزنه با خوندن تعداد مشخصی از فریم ها؛ در ادامه مطلب ( در کد نمونه )، background های تولید شده الگوریتم های مختلف رو مقایسه میکنیم.
کد نمونه : در این کد یه فایل ویدئویی رو انتخاب میکنید، سپس الگوریتم رو انتخاب میکنید و در نهایت رو دکمه Start کلیک میکنید؛ برای تمامی الگوریتم ها تابع apply رو استفاده میکنیم و به کمکش foreground mask رو محاسبه میکنیم ( در زیر، عکسی از ماسک قرار ندادم دیگه، خودتون کد رو تست کنید و ببینید )، و تابع getBackgroundImage رو هم برای تمام الگوریتم ها ( بجزء GMG و MOG ) استفاده کردم که نتیجه خروجی این تابع رو در انتهای همین کد مشاهده میکنید. ( در زیر فقط کدهای فایل CPP رو مشاهده میکنید، کدهای کامل در انتهای مطلب برا دانلود گزاشتم )
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 |
void motion_analysis_example1::on_pushButton_OpenVideo_clicked() { fileName = QFileDialog::getOpenFileName(this, "Open Video", "", "Video Files (*.mp4 *.avi)"); } void motion_analysis_example1::on_pushButton_Start_clicked() { ProcessVideoFrames(); } void motion_analysis_example1::on_comboBox_BackgroundSubtractorClass_currentIndexChanged(int index) { // read selected algorithm and init variables and objects switch (ui->comboBox_BackgroundSubtractorClass->currentIndex()) { case 0: // BackgroundSubtractorCNT [ Can calculate background ] canDetectBackground = true; backgroundSubtractor = cv::bgsegm::createBackgroundSubtractorCNT(); break; case 1: // BackgroundSubtractorGMG [ Cant calculate background ] canDetectBackground = false; backgroundSubtractor = cv::bgsegm::createBackgroundSubtractorGMG(); break; case 2: // BackgroundSubtractorGSOC [ Can calculate background ] canDetectBackground = true; backgroundSubtractor = cv::bgsegm::createBackgroundSubtractorGSOC(); break; case 3: // BackgroundSubtractorKNN [ Can calculate background ] canDetectBackground = true; backgroundSubtractor = cv::createBackgroundSubtractorKNN(); break; case 4: // BackgroundSubtractorLSBP [ Can calculate background ] canDetectBackground = true; backgroundSubtractor = cv::bgsegm::createBackgroundSubtractorLSBP(); break; case 5: // BackgroundSubtractorMOG [ Cant calculate background ] canDetectBackground = false; backgroundSubtractor = cv::bgsegm::createBackgroundSubtractorMOG(); break; case 6: // BackgroundSubtractorMOG2 [ Can calculate background ] canDetectBackground = true; backgroundSubtractor = cv::createBackgroundSubtractorMOG2(); break; default: qDebug() << "class selected error!"; return; } } //--- void motion_analysis_example1::ProcessVideoFrames() { // create object -> open video if(fileName == NULL) { qDebug() << "first open video!"; return; } //--- cv::VideoCapture vc(fileName.toStdString()); if(!vc.isOpened()) { qDebug() << "Cant open video file"; return; } // define variables cv::Mat frame, foregroundMask, foregroundMask_Not, foregroundImage, backgroundImage; // read frames loop while(true) { // read frame if( !vc.read(frame) ) { vc.set(cv::CAP_PROP_POS_FRAMES, 0); // go to frame 0! if( !vc.read(frame) ) return; } // Computes a foreground mask [ frame : 3 channel, foregroundMask : 1 channel ] backgroundSubtractor->apply(frame, foregroundMask); // Create backgroundImage image if (canDetectBackground == true) backgroundSubtractor->getBackgroundImage(backgroundImage); // Create "foreground image" with "frame" & "foreground mask" cv::bitwise_not(foregroundMask, foregroundMask_Not); cv::cvtColor(foregroundMask_Not, foregroundMask_Not, cv::COLOR_GRAY2BGR); cv::subtract(frame, foregroundMask_Not, foregroundImage); // Show new data cv::imshow("1) frame", frame); cv::imshow("2) foregroundMask", foregroundMask ); cv::imshow("3) foregroundImage", foregroundImage); cv::imshow("4) backgroundImage", backgroundImage); // delay cv::waitKey( 1 ); } } |
نتیجه کد بالا :
تصاویر زیر، برا فریم 100 ام و الگوریتم هم MOG2 باید باشه ( فک کنم ) :
توجه : همین کد تو C++ و Console، در مسیر زیر قرار داره :
[opencv]/samples/cpp/tutorial_code/video/bg_sub.cpp
تکمیل شود
- mask چیست ؟ ( اصطلاحات مورد نیاز مطلب )
- فرق background و foreground ؟! ( اصطلاحات مورد نیاز مطلب )
- توضیح کلاس های الگوریتم ها و توابعشون
- توضیح مختصری درباره هر الگوریتم
- توضیح کامل الگوریتم ها
- مقایسه الگوریتم ها
- پروژه های بیشتر از الگوریتم ها
منابع :
- How to Use Background Subtraction Methods
- Tutorials for bgsegm module [ Background Subtraction ]
- Motion Analysis
- Improved Background-Foreground Segmentation Methods
مطالعه بیشتر :