OpenCV如何使用背景減法方法

2018-10-14 11:12 更新
  • 背景減除(BS)是通過(guò)使用靜態(tài)攝像機(jī)來(lái)生成前景掩碼(即,包含屬于場(chǎng)景中的移動(dòng)對(duì)象的像素的二進(jìn)制圖像)的常用和廣泛使用的技術(shù)。
  • 顧名思義,BS計(jì)算當(dāng)前幀和背景模型之間的減法的前景掩碼,該背景模型包含場(chǎng)景的靜態(tài)部分,或者更一般地,在給定觀察到的場(chǎng)景的特征的情況下,可以將其視為背景的所有內(nèi)容。

OpenCV如何使用背景減法方法

  • 背景建模包括兩個(gè)主要步驟:
  1. 后臺(tái)初始化
  2. 背景更新。

在第一步中,計(jì)算背景的初始模型,而在第二步中,更新模型以適應(yīng)場(chǎng)景中的可能變化。

目標(biāo)

在本教程中,您將學(xué)習(xí)如何:

  1. 通過(guò)使用cv :: VideoCapture或圖像序列通過(guò)使用cv :: imread從視頻讀取數(shù)據(jù);
  2. 使用cv :: BackgroundSubtractor類創(chuàng)建和更新背景模型;
  3. 使用cv :: imshow獲取并顯示前景蒙版 ;
  4. 使用cv :: imwrite保存輸出以定量評(píng)估結(jié)果。

Code

在下面你可以找到源代碼。我們將讓用戶選擇處理視頻文件或一系列圖像。

使用兩種不同的方法來(lái)生成兩個(gè)前景蒙版:

  1. CV :: bgsegm :: BackgroundSubtractorMOG
  2. CV :: BackgroundSubtractorMOG2

結(jié)果以及輸入數(shù)據(jù)顯示在屏幕上。源文件可以在這里下載。


//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);
    }
}

說(shuō)明

我們討論上面代碼的主要部分:

  • 首先,分配三個(gè)Mat對(duì)象來(lái)存儲(chǔ)當(dāng)前幀和兩個(gè)前臺(tái)掩碼,通過(guò)使用兩種不同的BS算法獲得。
Mat frame; //current frame
Mat fgMaskMOG; //fg mask generated by MOG method
Mat fgMaskMOG2; //fg mask fg mask generated by MOG2 method
  • 將使用兩個(gè)cv :: BackgroundSubtractor對(duì)象來(lái)生成前景蒙版。在此示例中,使用默認(rèn)參數(shù),但也可以在create函數(shù)中聲明特定參數(shù)。
Ptr<BackgroundSubtractor> pMOG; //MOG Background subtractor
Ptr<BackgroundSubtractor> pMOG2; //MOG2 Background subtractor
...
//create Background Subtractor objects
pMOG = createBackgroundSubtractorMOG(); //MOG approach
pMOG2 = createBackgroundSubtractorMOG2(); //MOG2 approach
  • 分析命令行參數(shù)。用戶可以選擇兩種選擇:
  1. 視頻文件(通過(guò)選擇-vid選項(xiàng));
  2. 圖像序列(通過(guò)選擇選項(xiàng)-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]);
}
  • 假設(shè)你想處理一個(gè)視頻文件。讀取視頻直到到達(dá)結(jié)束或用戶按下按鈕“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);
  }
  • 每個(gè)幀都用于計(jì)算前景蒙版和更新背景。如果要更改用于更新背景模型的學(xué)習(xí)率,可以通過(guò)將第三個(gè)參數(shù)傳遞給“apply”方法來(lái)設(shè)置特定的學(xué)習(xí)率。
//update the background model
pMOG->apply(frame, fgMaskMOG);
pMOG2->apply(frame, fgMaskMOG2);
  • 可以從cv :: VideoCapture對(duì)象中提取當(dāng)前幀號(hào),并將其標(biāo)記在當(dāng)前幀的左上角。使用白色矩形來(lái)突出顯示黑色的幀數(shù)。
//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));
  • 我們準(zhǔn)備顯示當(dāng)前的輸入框架和結(jié)果。
//show the current frame and the fg masks
imshow("Frame", frame);
imshow("FG Mask MOG", fgMaskMOG);
imshow("FG Mask MOG 2", fgMaskMOG2);
  • 可以使用圖像序列作為輸入來(lái)執(zhí)行上述相同的操作。processImage函數(shù)被調(diào)用,而不是使用cv :: VideoCapture對(duì)象,通過(guò)使用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);

請(qǐng)注意,此示例僅適用于文件名格式為<n> .png的圖像序列,其中n是幀號(hào)(例如,7.png)。

結(jié)果

  • 給出以下輸入?yún)?shù):
-vid Video_001.avi

程序的輸出將如下所示:

OpenCV如何使用背景減法方法

  • 視頻文件Video_001.avi是背景模型挑戰(zhàn)(BMC)數(shù)據(jù)集的一部分,可以從以下鏈接Video_001(約32 MB)下載。
  • 如果要處理圖像序列,則必須選擇“-img”選項(xiàng):
-img 111_png / input / 1.png

程序的輸出將如下所示:

OpenCV如何使用背景減法方法

  • 該示例中使用的圖像序列是背景模型挑戰(zhàn)(BMC)數(shù)據(jù)集的一部分,并且可以從以下鏈接序列111(大約708 MB)下載。請(qǐng)注意,此示例僅適用于文件名格式為<n> .png的序列,其中n為幀號(hào)(例如,7.png)。

評(píng)估

為了定量評(píng)估所得結(jié)果,我們需要:

  • 保存輸出圖像;
  • 擁有所選序列的地面真實(shí)圖像。

為了保存輸出圖像,我們可以使用cv :: imwrite。添加以下代碼可以保存前景蒙版。

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

一旦我們收集了結(jié)果圖像,我們可以將它們與地面真實(shí)數(shù)據(jù)進(jìn)行比較。存在幾個(gè)公開(kāi)可用的背景減法序列,其中包含地面真值數(shù)據(jù)。如果您決定使用背景模型挑戰(zhàn)(BMC),則可以將結(jié)果圖像用作BMC向?qū)У?/a>輸入。向?qū)Э梢詫?duì)結(jié)果的準(zhǔn)確性計(jì)算不同的度量。

參考

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)