&emsp 运动目标检测是指在序列图像中检测出变化区域并将运动目标从背景图像中提取出来。通常情况下,目标分类、跟踪和行为理解等后处理过程仅仅考虑图像中对应于运动目标的像素区域,因此运动目标的正确检测与分割对于后期处理非常重要然而,由于场景的动态变化,如天气、光照、阴影及杂乱背景干扰等的影响,使得运动目标的检测与分割变得相当困难。根据摄像头是否保持静止,运动检测分为静态背景和运运动目标检测是指在序列图像中检测出变化区域并将运动目标从背景图像中提取出来。通常情况下,目标分类、跟踪和行为理解等后处理过程仅仅考虑图像中对应于运动目标的像素区域,因此运动目标的正确检测与分割对于后期处理非常重要然而,由于场景的动态变化,如天气、光照、阴影及杂乱背景干扰等的影响,使得运动目标的检测与分割变得相当困难。根据摄像头是否保持静止,运动检测分为静态背景和运动背景两类。大多数视频监控系统是摄像头固定的,因此静态背景下运动目标检测算法受到广泛关注,常用的方法有帧差法、光流法、背景减除法等。
帧差法是最为常用的运动目标检测和分割方法之一,基本原理就是在图像序列相邻两帧或三帧间采用基于像素的时间差分通过闭值化来提取出图像中的运动区域。首先,将相邻帧图像对应像素值相减得到差分图像,然后对差分图像二值化,在环境亮度变化不大的情况下,如果对应像素值变化小于事先确定的阂值时,可以认为此处为背景像素:如果图像区域的像素值变化很大,可以认为这是由于图像中运动物体引起的,将这些区域标记为前景像素,利用标记的像素区域可以确定运动目标在图像中的位置。由于相邻两帧间的时间间隔非常短,用前一帧图像作为当前帧的背景模型具有较好的实时性,其背景不积累,且更新速度快、算法简单、计算量小。算法的不足在于对环境噪声较为敏感,闽值的选择相当关键,选择过低不足以抑制图像中的噪声,过高则忽略了图像中有用的变化。对于比较大的、颜色一致的运动目标,有可能在目标内部产生空洞,无法完整地提取运动目标。它仅仅适应于相机静止的情况。
//运动物体检测——帧差法
#include
#include
#include
#include
cv::Mat MoveDetect(cv::Mat temp, cv::Mat frame)
{
cv::Mat result = frame.clone();
//1.将background和frame转为灰度图
cv::Mat gray1, gray2;
cv::cvtColor(temp, gray1, CV_BGR2GRAY);
cv::cvtColor(frame, gray2, CV_BGR2GRAY);
//2.将background和frame做差
cv::Mat diff;
cv::absdiff(gray1, gray2, diff);
cv::imshow("diff", diff);
//3.对差值图diff_thresh进行阈值化处理
cv::Mat diff_thresh;
cv::threshold(diff, diff_thresh, 50, 255, CV_THRESH_BINARY);
cv::imshow("diff_thresh", diff_thresh);
//4.腐蚀
cv::Mat kernel_erode = getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
cv::Mat kernel_dilate = getStructuringElement(cv::MORPH_RECT, cv::Size(18, 18));
cv::erode(diff_thresh, diff_thresh, kernel_erode);
cv::imshow("erode", diff_thresh);
//5.膨胀
cv::dilate(diff_thresh, diff_thresh, kernel_dilate);
cv::imshow("dilate", diff_thresh);
//6.查找轮廓并绘制轮廓
std::vector<std::vector > contours;
cv::findContours(diff_thresh, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
cv::drawContours(result, contours, -1, cv::Scalar(0, 0, 255), 2);//在result上绘制轮廓
//7.查找正外接矩形
std::vector boundRect(contours.size());
for (int i = 0; i < contours.size(); i++)
{
boundRect[i] = boundingRect(contours[i]);
rectangle(result, boundRect[i], cv::Scalar(0, 255, 0), 2);//在result上绘制正外接矩形
}
return result;//返回result
}
int main() {
cv::VideoCapture video("F:/test/openCV/forestfireAsms/forestfireAsms/data/forestFireHouse.mp4");
if (!video.isOpened())
return -1;
int frameCount = video.get(CV_CAP_PROP_FRAME_COUNT);//获取帧数
double FPS = video.get(CV_CAP_PROP_FPS);//获取FPS
cv::Mat frame;//存储帧
cv::Mat temp;//存储前一帧图像
cv::Mat result;//存储结果图像
for (int i = 0; i < frameCount; i++)
{
if (!video.read(frame))
break;
cv::imshow("frame", frame);
if (frame.empty())//对帧进行异常检测
{
cout << "frame is empty!" << endl;
break;
}
if (i == 0)//如果为第一帧(temp还为空)
{
result = MoveDetect(frame, frame);//调用MoveDetect()进行运动物体检测,返回值存入result
}
else//若不是第一帧(temp有值了)
{
result = MoveDetect(temp, frame);//调用MoveDetect()进行运动物体检测,返回值存入result
}
cv::imshow("result", result);
if (cv::waitKey(1000.0 / FPS) == 27)//按原FPS显示
{
cout << "ESC退出!" << endl;
break;
}
temp = frame.clone();
}
return 0;
}
在非固定摄像头下的视频中,背景也是动态改变的,这种情况下对运动目标的检测就不能采用以上两种方法,而是采用光流法。由于光流包含了目标运动的信息,所以,光流场近似于运动场。通常,视频序列中背景的光流是一致,与运动目标的光流不同,因此,根据光流的不同就可以提取运动目标和背景区域。
如果在 t t 时刻图像点 (x,y) ( x , y ) 的亮度值为 I(x,y,t) I ( x , y , t ) ,而光流 w=(u,v) w = ( u , v ) 在x与y轴的分量 u u 和 v v 的表达式如下式(2-4)所示,那么,经dt时间后图像点运动到 (x+dx,y+dy) ( x + d x , y + d y ) ,当 dt d t 趋向于0时, I I 值是不变,可得到式(2-5)。
背景减除法是一种有效的运动对象检测算法,基本思想是利用背景的参数模型来近似背景图像的像素值,将当前帧与背景图像进行差分比较实现对运动区域的检测,其中区别较大的像素区域被认为是运动区域,而区别较小的像素区域被认为是背景区域。背景减除法必须要有背景图像,并且背景图像必须是随着光照或外部环境的变化而实时更新的,因此背景减除法的关键是背景建模及其更新。针对如何建立对于不同场景的动态变化均具有自适应性的背景模型,减少动态场景变化对运动分割的影响,研究人员已提出了许多背景建模算法,但总的来讲可以概括为非回归递推和回归递推两类。非回归背景建模算法是动态的利用从某一时刻开始到当前一段时间内存储的新近观测数据作为样本来进行背景建模。非回归背景建模方法有最简单的帧间差分、中值滤波方法、Toyama等利用缓存的样本像素来估计背景模型的线性滤波器、Elg~al等提出的利用一段时间的历史数据来计算背景像素密度的非参数模型等。回归算法在背景估计中无需维持保存背景估计帧的缓冲区,它们是通过回归的方式基于输入的每一帧图像来更新某个时刻的背景模型。这类方法包括广泛应用的线性卡尔曼滤波法、Stauffe:与Grimson提出的混合高斯模型等。它仅仅适应于相机静止的情况。
一般而言,背景减除都包含了以下步骤:
(1)背景初始化
背景初始化是指从视频开始的前N帧图像中“训练”出不包含任何运动目标的背景图像的过程。与背景更新不同的是,背景初始化仅仅完成从视频第一帧到第N帧的背景建立工作。通常,使用一些相对“干净”的视频帧(不包含运动前景)来“训练”出高质量的背景。然而,实际环境中,视频帧往往包含了很多干扰的运动前景。
(2)背景更新
随着时间的推移,往往会发生光照变化、运动的物体停在背景中,而这些变化都会使背景发生变化。所以,背景更新是至关重要的。不同的算法都有着不同的背景更新方法,但这一步骤通常涉及到以下几个问题:
I、更新机制
目前,有全更新、选择更新,以及模糊自适更新三种背景更新方法。全更新是指更新所有的像素,如下式所示:
//运动物体检测——背景减除法
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
int main()
{
cv::Mat frame; //frame image
cv::Mat frImg; //foreground image
cv::Mat bkImg; //background image
cv::Mat frameMat; //frame Mat
cv::Mat frMat; //foreground image
cv::Mat bkMat; //foreground image
int nFrmNum = 0;
//创建窗口
namedWindow("video", 1);
namedWindow("background", 1);
namedWindow("foreground", 1);
//使窗口有序排列
cvMoveWindow("video", 30, 0);
cvMoveWindow("background", 360, 0);
cvMoveWindow("foreground", 690, 0);
cv::VideoCapture video("F:/test/openCV/forestfireAsms/forestfireAsms/data/forestFireHouse.mp4");
video = cv::VideoCapture("F:/test/img/data/forestFire/web/FireClips/controlled2.avi");
if (!video.isOpened())
return -1;
//逐帧读取视频
while (1)
{
if (!video.read(frame))
break;
nFrmNum++;
//如果是第一帧,需要申请内存,并初始化
if (nFrmNum == 1)
{
bkImg =cv::Mat(frame.size(),CV_8UC1);
frImg = cv::Mat(frame.size(), CV_8UC1);
frameMat = cv::Mat(frame.size(), CV_32FC1);
bkMat = cv::Mat(frame.size(), CV_32FC1);
frMat = cv::Mat(frame.size(), CV_32FC1);
//convert frame into the grayscale image
cvtColor(frame, bkImg, CV_BGR2GRAY);
cvtColor(frame, frImg, CV_BGR2GRAY);
frImg.convertTo(frameMat, CV_32FC1);
frImg.convertTo(frMat, CV_32FC1);
frImg.convertTo(frImg, CV_32FC1);
}
else
{
cvtColor(frame, frImg, CV_BGR2GRAY);
frImg.convertTo(frameMat, CV_32FC1);
//先做高斯滤波,以平滑图像
GaussianBlur(frameMat, frameMat, cv::Size(3,3),3, 3, 0);
//当前帧跟背景图相减
absdiff(frameMat, bkMat, frMat);
//二值化前景图
threshold(frMat, frImg, 20, 255.0, CV_THRESH_BINARY);
//进行形态学滤波,去掉噪音
cv::Mat kernel_erode = getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
erode(frImg, frImg, kernel_erode);
dilate(frImg, frImg, kernel_erode);
//update the background
cv::addWeighted(frameMat, 1-0.03, bkMat, 0.03,0, bkMat);
bkMat.convertTo(bkImg, CV_8UC1);
cv::imshow("video", frame);
cv::imshow("background", bkImg);
cv::imshow("foreground", frImg);
//如果有按键事件,则跳出循环
if (waitKey(2) >= 0)
break;
} // end of if-else
} // end of while-loop
return 0;
}
目前,虽然有大量的运动目标检测算法,但由于实际环境的复杂多变,所以这些算法并不都是十分的健壮。面临的问题与抢占可以归纳总结为以下几个方面:
(1)模型初始化问题:在背景初始化训练时期,由于还没有获得高质量的背景模型,故常常导致运动目标的误检;
(2)伪装现象:一些运动目标可能与背景极其相似,从而导致运动目标无法正确地与背景区分开;
(3)光照变化:分为光线的突变和渐变。背景模型要能够适应白天室外环境中光线的逐渐变化;相应的,背景模型也能够适应突然打开灯光的室内环境。总之,光线的变化将强烈影响背景模型,极有可能导致错误的检测;
(4)前景空洞现象:当运动目标有大量颜色一致的区域时,这些区域的内在变化可能将导致检测不准确,使得前景的一些内部区域被错误判断为背景;
(5)动态背景:最常见的就是树叶的抖动,当然还有水面涟漪、小目标抖动;
(6)突然停滞的运动目标:有些运动物体进入场景后,停在了场景中。显然,这种情况下的运动目标应该被识别为背景;
(7)阴影:能够检测出运动目标的阴影以及背景区域原有的阴影;
(8)噪声干扰:这种情况基本上属于由网络摄像头传输或压缩后的视频图像而引起的数据质量不高;
(9)相机抖动:在一些条件下,风会引起摄像机的抖动;
(10)相机自调节:目前,很多摄像头都具有自动控制的功能,如光照控制、白平衡以及放大缩小等功能。
高斯混合模型(Gaussian Mixed Model)指的是多个高斯分布函数的线性组合,理论上GMM可以拟合出任意类型的分布,通常用于解决同一集合下的数据包含多个不同的分布的情况(或者是同一类分布但参数不一样,或者是不同类型的分布,比如正态分布和伯努利分布)。
基于混合高斯模型的背景建模将图像中的每个像素看成是从混合高斯分布样本中采样得到的随机变量,在这些高斯分布中,一些高斯分布表示背景,另一些表示前景(运动物体),根据算法估计出每个像素点属于哪一个高斯模型,进而判断该像素是前景或背景 ; K K 的选择由计算效率等因素决定,通常情况下, K K 的取值范围为3~7;设特定像素在时刻t的亮度值为 gt g t ,用 K K 个高斯分布表示像素 gt g t 的特征,观测概率
第三步:前景判断:对于任意像素,比较是否满足各个高斯模型
第四步:模型更新:如果没有找到匹配的高斯模型,将第 K K 个(最后一个)高斯模型用一个新的高斯函数替换,新的高斯函数具有像素点的均值、一个较大的方差和较小的权重
总结如下:
算法:基于混合高斯模型的背景减除
初始化:选择高斯函数数目和学习率 η η
repeat
until 所有帧都被处理