Opencv視訊分析---使用背景減法方法
阿新 • • 發佈:2018-12-16
理論
- 背景減法(BS)是用於通過使用靜態相機生成前景蒙版(即,包含屬於場景中的運動物件的畫素的二值影象)的常用且廣泛使用的技術。
- 顧名思義,BS計算前景蒙版,在當前幀和背景模型之間執行減法,包含場景的靜態部分,或者更一般地說,考慮到觀察場景的特徵,可以將所有內容視為背景。
- 背景建模包括兩個主要步驟:
- 背景初始化;
- 背景更新。
- 在第一步中,計算背景的初始模型,而在第二步中更新模型以適應場景中的可能變化。
程式碼
//opencv #include "opencv2/imgcodecs.hpp" #include "opencv2/imgproc.hpp" #include "opencv2/videoio.hpp" #include <opencv2/highgui.hpp> #include <opencv2/video.hpp> //C #include <stdio.h> //C++ #include <iostream> #include <sstream> using namespace cv; using namespace std; // Global variables Mat frame; //current frame Mat fgMaskMOG2; //fg mask fg mask generated by MOG2 method Ptr<BackgroundSubtractor> pMOG2; //MOG2 Background subtractor int keyboard; //input from keyboard void help(); void processVideo(char* videoFilename); void processImages(char* firstFrameFilename); void help() { cout << "--------------------------------------------------------------------------" << endl << "This program shows how to use background subtraction methods provided by " << endl << " OpenCV. You can process both videos (-vid) and images (-img)." << endl << endl << "Usage:" << endl << "./bs {-vid <video filename>|-img <image filename>}" << endl << "for example: ./bs -vid video.avi" << endl << "or: ./bs -img /data/images/1.png" << endl << "--------------------------------------------------------------------------" << endl << endl; } int main(int argc, char* argv[]) { //print help information help(); //check for the input parameter correctness if(argc != 3) { cerr <<"Incorret input list" << endl; cerr <<"exiting..." << endl; return EXIT_FAILURE; } //create GUI windows namedWindow("Frame"); namedWindow("FG Mask MOG 2"); //create Background Subtractor objects pMOG2 = createBackgroundSubtractorMOG2(); //MOG2 approach if(strcmp(argv[1], "-vid") == 0) { //input data coming from a video processVideo(argv[2]); } else if(strcmp(argv[1], "-img") == 0) { //input data coming from a sequence of images processImages(argv[2]); } else { //error in reading input parameters cerr <<"Please, check the input parameters." << endl; cerr <<"Exiting..." << endl; return EXIT_FAILURE; } //destroy GUI windows destroyAllWindows(); return EXIT_SUCCESS; } void processVideo(char* videoFilename) { //create the capture object VideoCapture capture(videoFilename); if(!capture.isOpened()){ //error in opening the video input cerr << "Unable to open video file: " << videoFilename << endl; exit(EXIT_FAILURE); } //read input data. ESC or 'q' for quitting while( (char)keyboard != 'q' && (char)keyboard != 27 ){ //read the current frame if(!capture.read(frame)) { cerr << "Unable to read next frame." << endl; cerr << "Exiting..." << endl; exit(EXIT_FAILURE); } //update the background model pMOG2->apply(frame, fgMaskMOG2); //get the frame number and write it on the current frame stringstream ss; rectangle(frame, cv::Point(10, 2), cv::Point(100,20), cv::Scalar(255,255,255), -1); ss << capture.get(CAP_PROP_POS_FRAMES); string frameNumberString = ss.str(); putText(frame, frameNumberString.c_str(), cv::Point(15, 15), FONT_HERSHEY_SIMPLEX, 0.5 , cv::Scalar(0,0,0)); //show the current frame and the fg masks imshow("Frame", frame); imshow("FG Mask MOG 2", fgMaskMOG2); //get the input from the keyboard keyboard = waitKey( 30 ); } //delete capture object capture.release(); } void processImages(char* fistFrameFilename) { //read the first file of the sequence frame = imread(fistFrameFilename); if(frame.empty()){ //error in opening the first image cerr << "Unable to open first image frame: " << fistFrameFilename << endl; exit(EXIT_FAILURE); } //current image filename string fn(fistFrameFilename); //read input data. ESC or 'q' for quitting while( (char)keyboard != 'q' && (char)keyboard != 27 ){ //update the background model pMOG2->apply(frame, fgMaskMOG2); //get the frame number and write it on the current frame size_t index = fn.find_last_of("/"); if(index == string::npos) { index = fn.find_last_of("\\"); } size_t index2 = fn.find_last_of("."); string prefix = fn.substr(0,index+1); string suffix = fn.substr(index2); string frameNumberString = fn.substr(index+1, index2-index-1); istringstream iss(frameNumberString); int frameNumber = 0; iss >> frameNumber; rectangle(frame, cv::Point(10, 2), cv::Point(100,20), cv::Scalar(255,255,255), -1); putText(frame, frameNumberString.c_str(), cv::Point(15, 15), FONT_HERSHEY_SIMPLEX, 0.5 , cv::Scalar(0,0,0)); //show the current frame and the fg masks imshow("Frame", frame); imshow("FG Mask MOG 2", fgMaskMOG2); //get the input from the keyboard keyboard = waitKey( 30 ); //search for the next image in the sequence ostringstream oss; oss << (frameNumber + 1); string nextFrameNumberString = oss.str(); string nextFrameFilename = prefix + nextFrameNumberString + suffix; //read the next frame frame = imread(nextFrameFilename); if(frame.empty()){ //error in opening the next image in the sequence cerr << "Unable to open image frame: " << nextFrameFilename << endl; exit(EXIT_FAILURE); } //update the path of the current frame fn.assign(nextFrameFilename); } }
解釋
- 首先,分配三個Mat物件來儲存當前幀和兩個前景掩碼,這兩個前景掩碼通過使用兩種不同的BS演算法獲得。
- 兩個cv :: BackgroundSubtractor物件將用於生成前景蒙版。 使用預設引數,但也可以在create函式中宣告特定引數。
- 分析命令列引數。 使用者可以選擇兩個選項:
- 視訊檔案(通過選擇-vid選項);
- 影象序列(通過選擇-img選項)。
- 假設您要處理視訊檔案。 讀取視訊直到到達終點或使用者按下按鈕“q”或按鈕“ESC”。
- 每個幀都用於計算前景蒙版和更新背景。 如果要更改用於更新背景模型的學習速率,可以通過將第三個引數傳遞給“apply”方法來設定特定的學習速率。
- 當前幀編號可以從cv :: VideoCapture物件中提取並標記在當前幀的左上角。 白色矩形用於突出顯示黑色框架編號。
- 我們準備顯示當前的輸入框架和結果。
- 可以使用一系列影象作為輸入來執行上面列出的相同操作。 呼叫processImage函式,而不是使用cv :: VideoCapture物件,在為下一幀讀取的正確路徑個別化之後,使用cv :: imread讀取影象。
效果
- 給定以下輸入引數:
- 程式的輸出如下所示:
- The video file Video_001.avi is part of the Background Models Challenge (BMC) data set and it can be downloaded from the following link
- 如果要處理一系列影象,則必須選擇“-img”選項:
- 程式的輸出如下所示:
- 為了儲存輸出影象,我們可以使用cv :: imwrite。 新增以下程式碼可以儲存前景蒙版。