1. 程式人生 > >背景差分法示例

背景差分法示例

背景差分法


背景差分法是一種很常用而且廣泛感測的技術,主要用於背景不動的情況下提取前景。它主要的原理是在當前幀和背景做減法,然後使用threshold進行二值化得到前景掩碼。下面是背景減法的示意圖。
這裡寫圖片描述
背景差分法主要包含以下兩個步驟:
1.背景的建立
2.背景的更新
兩個關鍵點,第一個就是如何選擇背景,第二個就是什麼時候更新背景,這樣可以適應背景變化的情況,更新背景的快慢也很重要,如果更新慢了,快速運動的物體如果沒有重疊就認為是兩個物體了,如果更新快了,慢速運動的物體會被認為是背景,這樣就造成漏檢。
這裡學習一下在opencv中如何使用背景差分法。

程式碼

使用了兩個方法來產生前景掩碼:
1. cv::bgsegm::BackgroundSubtractorMog
2. cv::BackgroundSubtractorMog2

//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 char 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 << "./bg_sub {-vid <video filename>|-img <image filename>}" << endl << "for example: ./bg_sub -vid video.avi" << endl << "or: ./bg_sub -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 keyboard = 0; while( keyboard != 'q' && 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 = (char)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 keyboard = 0; while( keyboard != 'q' && 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 = (char)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); } }

關鍵程式碼說明

  1. 建立三個全域性變數,frame用於儲存當前影象,fgMaskMog用於儲存前景掩碼,fgMaskMog2是第二種方法產生的前景掩碼。
Mat frame; //current frame
Mat fgMaskMOG; //fg mask generated by MOG method
Mat fgMaskMOG2; //fg mask fg mask generated by MOG2 method
  1. 使用兩個方法產生前景掩碼,cv::BackgroundSubtractor。在本例中,使用預設的引數,你也可以嘗試其它引數。
Ptr<BackgroundSubtractor> pMOG; //MOG Background subtractor
Ptr<BackgroundSubtractor> pMOG2; //MOG2 Background subtractor
...
//create Background Subtractor objects
pMOG = createBackgroundSubtractorMOG(); //MOG approach
  1. 傳入視訊還是單張影象,使用-vid引數傳入視訊 使用-img引數傳入影象
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]);
}
  1. 建議使用視訊進行測試。如果要停止,輸入“q”或者esc。
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);
  }
  1. 每一幀影象都用於計算前景掩碼和更新背景,可以通過傳入引數來修改更新速度。
//update the background model
pMOG->apply(frame, fgMaskMOG);
pMOG2->apply(frame, fgMaskMOG2);
  1. 在在上角顯示當前幀號。
//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));
  1. 然後顯示輸入影象和結果
//show the current frame and the fg masks
imshow("Frame", frame);
imshow("FG Mask MOG", fgMaskMOG);
imshow("FG Mask MOG 2", fgMaskMOG2);
  1. 如果選擇圖片進行處理,需要輸入幾張影象。使用cv::imread進行處理。
//read the first file of the sequence
frame = imread(fistFrameFilename);
if(!frame.data){
  //error in opening the first image
  cerr << "Unable to open first image frame: " << fistFrameFilename << endl;
  exit(EXIT_FAILURE);
}
...
//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.data){
  //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);

結果顯示:

結果顯示如果人停下了,那麼就檢測不到了。
這裡寫圖片描述

儲存

儲存使用cv::imread就可以了,程式碼實現很簡單

string imageToSave = "output_MOG_" + frameNumberString + ".png";
bool saved = imwrite(imageToSave, fgMaskMOG);
if(!saved) {
  cerr << "Unable to save " << imageToSave << endl;
}