阅读(144) (9)

OpenCV如何使用背景减法方法

2017-10-14 10:45:24 更新
  • 背景减除(BS)是通过使用静态摄像机来生成前景掩码(即,包含属于场景中的移动对象的像素的二进制图像)的常用和广泛使用的技术。
  • 顾名思义,BS计算当前帧和背景模型之间的减法的前景掩码,该背景模型包含场景的静态部分,或者更一般地,在给定观察到的场景的特征的情况下,可以将其视为背景的所有内容。

OpenCV如何使用背景减法方法

  • 背景建模包括两个主要步骤:
  1. 后台初始化
  2. 背景更新。

在第一步中,计算背景的初始模型,而在第二步中,更新模型以适应场景中的可能变化。

目标

在本教程中,您将学习如何:

  1. 通过使用cv :: VideoCapture或图像序列通过使用cv :: imread从视频读取数据;
  2. 使用cv :: BackgroundSubtractor类创建和更新背景模型;
  3. 使用cv :: imshow获取并显示前景蒙版 ;
  4. 使用cv :: imwrite保存输出以定量评估结果。

Code

在下面你可以找到源代码。我们将让用户选择处理视频文件或一系列图像。

使用两种不同的方法来生成两个前景蒙版:

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

说明

我们讨论上面代码的主要部分:

  • 首先,分配三个Mat对象来存储当前帧和两个前台掩码,通过使用两种不同的BS算法获得。
Mat frame; //current frame
Mat fgMaskMOG; //fg mask generated by MOG method
Mat fgMaskMOG2; //fg mask fg mask generated by MOG2 method
  • 将使用两个cv :: BackgroundSubtractor对象来生成前景蒙版。在此示例中,使用默认参数,但也可以在create函数中声明特定参数。
Ptr<BackgroundSubtractor> pMOG; //MOG Background subtractor
Ptr<BackgroundSubtractor> pMOG2; //MOG2 Background subtractor
...
//create Background Subtractor objects
pMOG = createBackgroundSubtractorMOG(); //MOG approach
pMOG2 = createBackgroundSubtractorMOG2(); //MOG2 approach
  • 分析命令行参数。用户可以选择两种选择:
  1. 视频文件(通过选择-vid选项);
  2. 图像序列(通过选择选项-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]);
}
  • 假设你想处理一个视频文件。读取视频直到到达结束或用户按下按钮“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);
  }
  • 每个帧都用于计算前景蒙版和更新背景。如果要更改用于更新背景模型的学习率,可以通过将第三个参数传递给“apply”方法来设置特定的学习率。
//update the background model
pMOG->apply(frame, fgMaskMOG);
pMOG2->apply(frame, fgMaskMOG2);
  • 可以从cv :: VideoCapture对象中提取当前帧号,并将其标记在当前帧的左上角。使用白色矩形来突出显示黑色的帧数。
//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", fgMaskMOG);
imshow("FG Mask MOG 2", fgMaskMOG2);
  • 可以使用图像序列作为输入来执行上述相同的操作。processImage函数被调用,而不是使用cv :: VideoCapture对象,通过使用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);

请注意,此示例仅适用于文件名格式为<n> .png的图像序列,其中n是帧号(例如,7.png)。

结果

  • 给出以下输入参数:
-vid Video_001.avi

程序的输出将如下所示:

OpenCV如何使用背景减法方法

  • 视频文件Video_001.avi是背景模型挑战(BMC)数据集的一部分,可以从以下链接Video_001(约32 MB)下载。
  • 如果要处理图像序列,则必须选择“-img”选项:
-img 111_png / input / 1.png

程序的输出将如下所示:

OpenCV如何使用背景减法方法

  • 该示例中使用的图像序列是背景模型挑战(BMC)数据集的一部分,并且可以从以下链接序列111(大约708 MB)下载。请注意,此示例仅适用于文件名格式为<n> .png的序列,其中n为帧号(例如,7.png)。

评估

为了定量评估所得结果,我们需要:

  • 保存输出图像;
  • 拥有所选序列的地面真实图像。

为了保存输出图像,我们可以使用cv :: imwrite。添加以下代码可以保存前景蒙版。

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

一旦我们收集了结果图像,我们可以将它们与地面真实数据进行比较。存在几个公开可用的背景减法序列,其中包含地面真值数据。如果您决定使用背景模型挑战(BMC),则可以将结果图像用作BMC向导的输入。向导可以对结果的准确性计算不同的度量。

参考