运动物体检测与跟踪中的帧差分法,除了相邻帧差分法和三帧差分法外,还有一种差分方法,可以通过建立不含前景的背景模型,用当前帧和背景模型做差,差值就可以体现运动物体大概的位置和大小信息。
相比相邻帧差分法和三帧差分法,背景模型做差法可以较为完整的体现运动物体的整体轮廓,运动物体的双重轮廓、“鬼影”、空洞现象改善明显,下文的对比效果可以看到这一点。
但背景模型的选取和建立的要求条件也更为苛刻:
1. 背景模型中不能包含前景物体,如果包含前景物体,则在之后检测到的运动物体结果中,会一直保留有前景运动物体初始的轮廓。
例如下边这个例子,选取了第一帧作为背景模型,不幸的是,第一帧中含有运动物体,用红色框标示出:
检测效果:
正如前边所说的,在之后检测到的运动物体结果中,3个前景运动物体初始的轮廓一直存在,即使运动物体早已经不在初始的位置,用红色框标示出。
若使用完全不包含前景物体的背景图像作为背景模型,上述情况就不会出现了,检测效果也比较好。
比如使用下边这个图像作为背景模型:
由于这个视频没有完全不包含前景物体的背景图像,我的方法是求所有视频帧序列和的平均值作为背景模型,在一定程度上可以代表不含前景的背景图,部分位置处移动物体停留的事件比较久,所以有一些黑影出现,如图像中部区域。
检测效果中不再有之前的3个“伪影像”,并且可以看到,相比相邻帧差分法和三帧差分法,背景模型做差法可以较为完整的体现运动物体的整体轮廓,运动物体的双重轮廓、“鬼影”、空洞现象改善明显。
2. 背景模型对环境的变化非常敏感,比如光照的变化、背景模型建立之后监控中加入的静止的物体、或背景模型中原本的物体位置被挪动等等,这些因素都会造成静止的物体被当做运动物体检测出来。造成误检,
还以上一个视频为例,当背景模型中原本静止的物体位置被挪动:
建立背景模型时视频中彩带的位置在下图用蓝色线段标示出,之后由于一些原因彩带的位置发生了改变,用红色线段标示,位置改变后,即使彩带一直保持静止,在检测结果中仍然会错误的检测出静止的彩带。
现实情况中,想要消除以上两点影响,一次性建立一个理想的背景模型,几乎是不可能的。基于此,我们可以建立一个动态的背景模型,这个动态模型实现以下两个功能:
1. 如果初始建立的背景模型中包含有前景物体,动态模型应该能够快速将前景物体的影响降低或消除掉;
2. 对于背景模型中静止的物体位置改变或者新加入视频画面中静止的物体,动态模型应该能够快速觉察到这种变化, 并把这种改变纳入到下一轮的背景模型构建中。
基于这两个基本的要求,构建动态背景模型的步骤如下:
1. 以初始第一帧作为第一个背景模型
2. 检测第二帧中运动物体,得到前景图像
3. 把第二帧图像抠除检测到的前景物体后,以一定比例系数累加到上一轮构建的背景模型中
4. 更新背景模型,在随后帧上,重复1,2,3
Opencv中,accumulateWeighted方法可以实现以上构建动态模型的要求。
方法原型:
void accumulateWeighted( InputArray src, InputOutputArray dst,
double alpha, InputArray mask=noArray() );
第一个参数:src,新加入的构建背景模型的图像矩阵;
第二个参数:dst,累计新元素src后生成的新的背景模型;
第三个参数:alpha,新加入原型src的系数,公式表述为:
dst = dst*(1-alpha) + src*alpha;
即alpha越大,当前新元素对构建动态模型的影响越大。如下,当alpha取值为0.9时,背景模型为:
此时新加入背景模型的新元素占比较大,对新的背景模型的影响也大,从上图可以看到,除有少许拖 影外 ,基本跟上一帧图像特征一致。
当alpha取值为0.2时,背景模型为:
此时,新加入背景模型的元素占比较小,意味着之前加入的元素比重相应较大,累计的背景模型有很 重的“鬼影”,每一个虚影代表了最近新加入背景模型的一个元素。
第四个参数:mask,英文释义“面具”,顾名思义,指在背景模型中需要减去的,不予考虑的部分,可以使用在当前背 景模型下检出的前景物体作为mask,进一步减少对背景模型的干扰。可以为空。
累积权重构建背景模型代码实现:
#include "core/core.hpp"
#include "highgui/highgui.hpp"
#include "imgproc/imgproc.hpp"
#include "iostream"
using namespace std;
using namespace cv;
int main(int argc,char *argv[])
{
VideoCapture videoCap(argv[1]);
if(!videoCap.isOpened())
{
return -1;
}
Mat image;
Mat imageBackground; //动态背景模型
Mat imageFront; //前景
double videoFPS=videoCap.get(CV_CAP_PROP_FPS); //获取帧率
double videoPause=1000/videoFPS;
videoCap>>imageBackground; //第一帧作为初始背景模型
cvtColor(imageBackground,imageBackground,CV_RGB2GRAY);
Mat element=getStructuringElement(0,Size(3,3)); //腐蚀核
while(true)
{
videoCap>>image;
if(image.empty()||waitKey(videoPause)==27) //视频播放完成,或Esc键退出
{
break;
}
Mat image1;
cvtColor(image,image1,CV_RGB2GRAY);
absdiff(image1,imageBackground,imageFront);
imageBackground.convertTo(imageBackground,CV_32FC1); //扩展至32位做运算
accumulateWeighted(image1,imageBackground,0.6,imageFront);
imageBackground.convertTo(imageBackground,CV_8UC1); //转换回8位
threshold(imageFront,imageFront,0,255,CV_THRESH_OTSU); //阈值分割
morphologyEx(imageFront,imageFront,CV_MOP_OPEN,element); //消除孤立的点
//膨胀操作,消除孔洞
dilate(imageFront,imageFront,element);
dilate(imageFront,imageFront,element);
dilate(imageFront,imageFront,element);
dilate(imageFront,imageFront,element);
dilate(imageFront,imageFront,element);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(imageFront,contours,hierarchy,RETR_EXTERNAL,CHAIN_APPROX_NONE,Point());
for(int i=0;i<contours.size();i++)
{
//绘制轮廓的最小外结矩形
RotatedRect rect=minAreaRect(contours[i]);
Point2f P[4];
rect.points(P);
for(int j=0;j<=3;j++)
{
line(image,P[j],P[(j+1)%4],Scalar(0,0,255),2);
}
}
imshow("Video",image);
imshow("Detection",imageFront);
imshow("背景模型",imageBackground);
}
return 0;
}
效果2:
效果3: