理论
- 背景减法(BS)是用于通过使用静态相机生成前景蒙版(即,包含属于场景中的运动对象的像素的二值图像)的常用且广泛使用的技术。
- 顾名思义,BS计算前景蒙版,在当前帧和背景模型之间执行减法,包含场景的静态部分,或者更一般地说,考虑到观察场景的特征,可以将所有内容视为背景。
![](https://img-blog.csdn.net/20181021160607953?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xZS3lteQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
- 背景建模包括两个主要步骤:
- 背景初始化;
- 背景更新。
- 在第一步中,计算背景的初始模型,而在第二步中更新模型以适应场景中的可能变化。
代码
//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算法获得。
![](https://img-blog.csdn.net/20181021161016351?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xZS3lteQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
- 两个cv :: BackgroundSubtractor对象将用于生成前景蒙版。 使用默认参数,但也可以在create函数中声明特定参数。
![](https://img-blog.csdn.net/20181021161110630?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xZS3lteQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
- 分析命令行参数。 用户可以选择两个选项:
- 视频文件(通过选择-vid选项);
- 图像序列(通过选择-img选项)。
![](https://img-blog.csdn.net/20181021161226174?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xZS3lteQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
- 假设您要处理视频文件。 读取视频直到到达终点或用户按下按钮“q”或按钮“ESC”。
![](https://img-blog.csdn.net/20181021161245757?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xZS3lteQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
- 每个帧都用于计算前景蒙版和更新背景。 如果要更改用于更新背景模型的学习速率,可以通过将第三个参数传递给“apply”方法来设置特定的学习速率。
![](https://img-blog.csdn.net/2018102116131751?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xZS3lteQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
- 当前帧编号可以从cv :: VideoCapture对象中提取并标记在当前帧的左上角。 白色矩形用于突出显示黑色框架编号。
![](https://img-blog.csdn.net/20181021161341467?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xZS3lteQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
- 我们准备显示当前的输入框架和结果。
![](https://img-blog.csdn.net/20181021161400224?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xZS3lteQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
- 可以使用一系列图像作为输入来执行上面列出的相同操作。 调用processImage函数,而不是使用cv :: VideoCapture对象,在为下一帧读取的正确路径个别化之后,使用cv :: imread读取图像。
![](https://img-blog.csdn.net/20181021161435717?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xZS3lteQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
效果
- 给定以下输入参数:
![](https://img-blog.csdn.net/20181021161458561?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xZS3lteQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
- 程序的输出如下所示:
![](https://img-blog.csdn.net/2018102116151599?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xZS3lteQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
- 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 Video_001 (about 32 MB).
- 如果要处理一系列图像,则必须选择“-img”选项:
![](https://img-blog.csdn.net/20181021161545461?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xZS3lteQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
- 程序的输出如下所示:
![](https://img-blog.csdn.net/20181021161602474?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xZS3lteQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
- 为了保存输出图像,我们可以使用cv :: imwrite。 添加以下代码可以保存前景蒙版。
![](https://img-blog.csdn.net/20181021161709827?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xZS3lteQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)