OpenCV3学习(10.1)背景分离 (帧间差分法、背景差分法)

      背景提取是在视频图像序列中提取出背景,背景就是场景中静止不动的景物。因为摄像机不动,因此图像中的每个像素点都有一个对应的背景值,在一段时间内,这个背景值是比较固定的。背景提取的目标就是根据视频图像序列,找出图像中每一点的背景值。 背景提取有很多算法。针对静止摄像机的帧间差分法、高斯背景差分法,还有针对运动摄像机的光流法等。 

一. 帧间差分法

相邻帧间图像差分思想:检测出了相邻两帧图像中发生变化的区域。该方法是用图像序列中的连续两帧图像进行差分,然后二值化该灰度差分图像来提取运动信息。由帧间变化区域检测分割得到的图像,区分出背景区域和运动车辆区域,进而提取要检测的车辆目标。它是通过比较图像序列中前后两帧图像对应像素点灰度值的不同,通过两帧相减,如果灰度值很小,可以认为该点无运动物体经过;反之灰度变化很大,则认为有物体经过。
1. 算法原理

     帧间差分法是将视频流中相邻两帧或相隔几帧图像的两幅图像像素值相减,并对相减后的图像进行阈值化来提取图像中的运动区域。 
若相减两帧图像的帧数分别为第k帧, 第(k+1)帧,其帧图像分别为f_{k}(x,y)f_{k+1}(x,y)差分图像二值化阈值为T,差分图像用D(x, y)表示,则帧间差分法的公式如下: 

                                     

帧间差分法的优缺点如下:

  • 优点:算法简单,不易受环境光线影响
  • 缺点: 
    • 不能用于运动的摄像头中;
    • 无法识别静止或运动速度很慢的目标;
    • 运动目标表面有大面积灰度值相似区域的情况下,在做差分时图像会出现孔洞;

我们以年辆检测为例,车辆检测除了要检测出运动车辆.同时还要检测出暂时停止的车辆,在这个方面,此类方法无能为力。而且如果车辆的体积较大,那么车辆在前后帧中根容易产生重叠部分,尤其是大货车,这使得帧问差分的结果主要为车头和车尾。车辆中间部分的差分值相对报小.形成空洞,不利于检测。

实例:

#include
#include
using namespace std;
using namespace cv;

int main()//帧间差分法
{
VideoCapture capture("tree.avi");//获取视频
if (!capture.isOpened())
return -1;
double rate = capture.get(CV_CAP_PROP_FPS);//获取视频帧率
int delay = 10000 / rate;
Mat framepro, frame, dframe;
bool flag = false;
namedWindow("image");
namedWindow("test");
while (capture.read(frame))
{
if (false == flag)
{
framepro = frame.clone();//将第一帧图像拷贝给framePro
flag = true;
}
else
{
absdiff(frame, framepro, dframe);//帧间差分计算两幅图像各个通道的相对应元素的差的绝对值。
framepro = frame.clone();//将当前帧拷贝给framepro
threshold(dframe, dframe, 80, 255, CV_THRESH_BINARY);//阈值分割
imshow("image", frame);
imshow("test", dframe);
waitKey(delay);
}
}
waitKey();
return 0;
}

结果:

 OpenCV3学习(10.1)背景分离 (帧间差分法、背景差分法)_第1张图片 

 

二. 背景差分法

      为达到前景目标的识别,譬如,交通路口对车辆的识别、监控器对行人的识别,常用的且较为有效的方法就是背景差分法(还有其他的方法,比如光流场法,帧差法),背景差分法是一种对静止场景进行运动分割的通用方法,它将当前获取的图像帧与背景图像做差分运算,得到目标运动区域的灰度图,对灰度图进行阈值化提取运动区域,而且为避免环境光照变化影响,背景图像根据当前获取图像帧进行更新。 根据前景检测,背景维持和后处理方法,存在几种不同的背景差方法。如何获取一个“美好”的背景图,是背景差分法的关键和难点。此处介绍一种最为简单的获取背景的方法——平均背景法。

顾名思义,其基本思想就是,将所采集到的背景图片叠加求和,而后求取平均值作为要求的背景。

平均背景法使用四个OpenCV函数:

1.将图像视频的前几帧,利用opencv的cvAcc函数累加求和,并统计累加次数,求取平均值,记为A。(注意理解该平均值的意思是在(x,y)位置处,所有参与求和计算的图像在该点出的像素平均值);

2.前一帧图像和当前参与累加的图像求差的绝对值,利用cvAbsDiff函数,而后该绝对值图像也进行累加求平均值,记为D。(同理这是某点的差值平均值)它描述的是某点像素值得波动幅度。

3.那么有1和2便可以认为,图像某点的像素值P若满足A-D < P < A+D,则认为此点属于背景,然而由于前景目标的加入,可能对背景点的亮度有一定的影响,故对波动幅度D进行一定的放缩,若 A -KD < p < A + KD,便认为该点为背景点。自然在该范围外的点,便是我们需要的前景点。那么用cvThreshold函数完成二值化。我们的目的就达到了。
此为背景建模最简单的方法,在室内等干扰较少的环境下使用时,效果较好,一旦有些许干扰则效果严重下降.

from :https://blog.csdn.net/q6541oijhk/article/details/41850129

实例:

#include
#include
using namespace std;
using namespace cv;
Mat IavgF, IdiffF, IprevF, IhiF, IlowF;//平均,差分,前景,判断是否前景的高低阈值,在之间表示是背景(变化小)
Mat tmp, tmp2;//作为中间变量,传递变量
Mat Imaskt;
float Icount;
vector Igray(3);
vector Ilow(3);//将高阈值按通道分别存放
vector Ihi(3);//将低阈值按通道分别存放

void AllocateImages(Mat& I);
void accumulateBackground(Mat& I);
void creatModelsfromStats();
void setHighThreshold(float scale);
void setLowThreshold(float scale);
void backgroundDiff(Mat& I, Mat& Imask);
int main() {
	Mat image;
	VideoCapture capture("tree.avi");
	double rate = capture.get(CV_CAP_PROP_FPS);//获取视频帧率
	int delay = 10000 / rate;
	capture >> image;
	if (image.empty())//如果某帧为空则退出循环
		cout << "read fail" << endl;
	AllocateImages(image);
	if (!capture.isOpened())
		return -1;
	int num = 1;
	while (num==50) {//这一个是学习背景模型,也可以设置为前50帧用来学习,不一定非要学习整个视频
		accumulateBackground(image);
		capture >> image;
		if (image.empty())
			break;
		imshow("intput", image);
		waitKey(delay);
		num++;
	}
	destroyAllWindows();

	creatModelsfromStats();//获得高低阈值
	Mat mask;
	while (1) {
		capture >> image;
		if (image.empty())
			break;
		backgroundDiff(image, mask);//mask输出二值化图像,前景为255
		imshow("ed", mask);
		split(image, Igray);
		Igray[2] = max(mask, Igray[2]);//用红色标记前景
	    merge(Igray, image);
		imshow("标记", image);
		waitKey(delay);
	}
	destroyAllWindows();//防止异常退出
}
void AllocateImages(Mat& I) {
	Size sz = I.size();
	IavgF = Mat::zeros(sz, CV_32FC3);
	IdiffF = Mat::zeros(sz, CV_32FC3);
	IprevF = Mat::zeros(sz, CV_32FC3);
	IhiF = Mat::zeros(sz, CV_32FC3);
	IlowF = Mat::zeros(sz, CV_32FC3);
	Icount = 0.00001f;//防止除以0
	tmp = Mat::zeros(sz, CV_32FC3);
	tmp2 = Mat::zeros(sz, CV_32FC3);
	Imaskt = Mat(sz, CV_32FC3);
}
void accumulateBackground(Mat& I) {
	static int flag = 1;
	I.convertTo(tmp, CV_32F);//类型转换
	if (!flag) {
		IavgF += tmp;
		absdiff(tmp, IprevF, tmp2);
		IdiffF += tmp2;
		Icount += 1;
	}
	flag = 0;
	IprevF = tmp;
}
void creatModelsfromStats() {//得到背景模型
	IavgF *= (1 / Icount);
	IdiffF *= (1 / Icount);
	IdiffF += Scalar(1.0, 1.0, 1.0);
	setHighThreshold(12.0);//参数指 倍数
	setLowThreshold(13.0);
}
void setHighThreshold(float scale) {//高低阈值的设定
	IhiF = IavgF + (IdiffF*scale);
	split(IhiF, Ihi);
}
void setLowThreshold(float scale) {
	IlowF = IavgF - (IdiffF*scale);
	split(IlowF, Ilow);
}
void backgroundDiff(Mat& I, Mat& Imask) {
	I.convertTo(tmp, CV_32F);//类型转换
	split(tmp, Igray);
	//对三个通道依次比较是否在阈值区间内
	inRange(Igray[0], Ilow[0], Ihi[0], Imask);//输出二值图像Imask
	inRange(Igray[1], Ilow[1], Ihi[1], Imaskt);
	Imask = min(Imask, Imaskt);//与运算
	inRange(Igray[2], Ilow[2], Ihi[2], Imaskt);
	Imask = min(Imask, Imaskt);
	Imask = 255 - Imask;//在阈值内的置255,表示背景,所以做翻转,使背景标为0,前景为255.
}

结果:

 OpenCV3学习(10.1)背景分离 (帧间差分法、背景差分法)_第2张图片 

程序可以运行,但我使用opencv例程里的tree.avi做检测,但结果失败了,未能检测出手掌,求各位大佬能指出算法哪地方出了问题? 

下面是网上普遍流传的c版本的平均背景法,亲测有效,效果一般。

#define _CRT_SECURE_NO_WARNINGS
#include 
#include
#include

//float 3-channel iamges
IplImage *IavgF, *IdiffF, *IprevF, *IhiF, *IlowF;

IplImage *Iscratch, *Iscratch2;

//float 1-channel iamges
IplImage *Igray1, *Igray2, *Igray3;
IplImage *Ilow1, *Ilow2, *Ilow3;
IplImage *Ihi1, *Ihi2, *Ihi3;

//byte  1-channel image
IplImage *Imaskt;

//counts number of images learned for averaging later
float Icount;

void  AllocateImages(IplImage* I);
void  accumulateBackground(IplImage* I);
void  createModelsfromStats();
void  setHighThreshold(float scale);
void  setLowThreshold(float scale);
void  backgroundDiff(IplImage *I, IplImage * Imask);
void  DeallocateImage();

int main(int argc, char** argv)
{
	cvNamedWindow("intput", CV_WINDOW_AUTOSIZE);                    //创建输入显示窗口
	cvNamedWindow("output", CV_WINDOW_AUTOSIZE);                    //创建输出显示窗口
	CvCapture* capture = cvCreateFileCapture(argv[1]);          //返回一个capture指针,指向视频

	IplImage* img = cvQueryFrame(capture);                          //从视频中取出的图片

	IplImage* Imask = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 1);//创建输出图片,这里不能去掉cvCreateImage(cvGetSize(Img),IPL_DEPTH_8U,1),虽然我看例程里省略了

	AllocateImages(img);//给图像分配内存

						/*累积图像,只取了前30帧图片*/
	while (Icount<30) {
		accumulateBackground(img);                                   //调用累积图像的函数,循环30次                               
		img = cvQueryFrame(capture);								 //从视频中取出的图片
		cvShowImage("intput", img);
		cvWaitKey(20);
	}


	createModelsfromStats();                                        //背景建模

	while (1)
	{
		img = cvQueryFrame(capture);
		if (!img) break;

		backgroundDiff(img, Imask);                                 //根据模型分割前景

		cvShowImage("output", Imask);                               //显示图像,视频是一张一张图片连续播放的结果
		cvShowImage("intput", img);
		char c = cvWaitKey(33);                                    //当前帧被显示后,等待33ms再读取下一张图片
		if (c == 27) break;                                           //等待期间按下esc键,ASCII码为27,则循环退出
	}
	cvReleaseCapture(&capture);
	cvDestroyWindow("output");
	cvDestroyWindow("intput");
	DeallocateImage();
}




//给需要的所有临时图像分配内存
void  AllocateImages(IplImage* I) {
	CvSize sz = cvGetSize(I);
	IavgF = cvCreateImage(sz, IPL_DEPTH_32F, 3); cvZero(IavgF);
	IdiffF = cvCreateImage(sz, IPL_DEPTH_32F, 3); cvZero(IdiffF);
	IprevF = cvCreateImage(sz, IPL_DEPTH_32F, 3); cvZero(IprevF);
	IhiF = cvCreateImage(sz, IPL_DEPTH_32F, 3); cvZero(IhiF);
	IlowF = cvCreateImage(sz, IPL_DEPTH_32F, 3); cvZero(IlowF);
	Ilow1 = cvCreateImage(sz, IPL_DEPTH_32F, 1);
	Ilow2 = cvCreateImage(sz, IPL_DEPTH_32F, 1);
	Ilow3 = cvCreateImage(sz, IPL_DEPTH_32F, 1);
	Ihi1 = cvCreateImage(sz, IPL_DEPTH_32F, 1);
	Ihi2 = cvCreateImage(sz, IPL_DEPTH_32F, 1);
	Ihi3 = cvCreateImage(sz, IPL_DEPTH_32F, 1);
	Icount = 0.00001;//protect against divide by zero

	Iscratch = cvCreateImage(sz, IPL_DEPTH_32F, 3);
	Iscratch2 = cvCreateImage(sz, IPL_DEPTH_32F, 3);
	Igray1 = cvCreateImage(sz, IPL_DEPTH_32F, 1);
	Igray2 = cvCreateImage(sz, IPL_DEPTH_32F, 1);
	Igray3 = cvCreateImage(sz, IPL_DEPTH_32F, 1);
	Imaskt = cvCreateImage(sz, IPL_DEPTH_8U, 1);
	cvZero(Iscratch);
	cvZero(Iscratch2);
}
/*积累背景图像和每一帧图像差值的绝对值
/函数调用需要通道为3,深度为8的彩色图像
*/
void  accumulateBackground(IplImage* I) {
	static int first = 1;
	cvCvtScale(I, Iscratch, 1, 0);//convert to float,3-channel
	if (!first) {
		cvAcc(Iscratch, IavgF);
		cvAbsDiff(Iscratch, IprevF, Iscratch2);
		cvAcc(Iscratch2, IdiffF);
		Icount += 1.0;
	}
	first = 0;
	cvCopy(Iscratch, IprevF);
}

//计算每一个像素的均值和方差观测(平均绝对差分)
void createModelsfromStats() {
	cvConvertScale(IavgF, IavgF, (double)(1.0 / Icount));
	cvConvertScale(IdiffF, IdiffF, (double)(1.0 / Icount));

	//make sure diff is always something
	cvAddS(IdiffF, cvScalar(1.0, 1.0, 1.0), IdiffF);
	setHighThreshold(7.0);
	setLowThreshold(6.0);
}

void setHighThreshold(float scale) {
	cvConvertScale(IdiffF, Iscratch, scale);
	cvAdd(Iscratch, IavgF, IhiF);
	cvSplit(IhiF, Ihi1, Ihi2, Ihi3, 0);
}

void setLowThreshold(float scale) {
	cvConvertScale(IdiffF, Iscratch, scale);
	cvAdd(IavgF, Iscratch, IlowF);
	cvSplit(IlowF, Ilow1, Ilow2, Ilow3, 0);
}

//图像分割
void backgroundDiff(IplImage *I, IplImage *Imask) {
	cvConvertScale(I, Iscratch, 1, 0);//to float
	cvSplit(Iscratch, Igray1, Igray2, Igray3, 0);
	//channel-1
	cvInRange(Igray1, Ilow1, Ihi1, Imask);
	//channel-2
	cvInRange(Igray2, Ilow2, Ihi2, Imaskt);
	cvOr(Imask, Imaskt, Imask);
	//channel-3
	cvInRange(Igray3, Ilow3, Ihi3, Imaskt);
	cvOr(Imask, Imaskt, Imask);
	cvSubRS(Imask, cvScalar(255), Imask);
}

//内存释放
void DeallocateImage() {
	cvReleaseImage(&IavgF);
	cvReleaseImage(&IdiffF);
	cvReleaseImage(&IprevF);
	cvReleaseImage(&IhiF);
	cvReleaseImage(&IlowF);
	cvReleaseImage(&Ilow1);
	cvReleaseImage(&Ilow2);
	cvReleaseImage(&Ilow3);
	cvReleaseImage(&Ihi1);
	cvReleaseImage(&Ihi2);
	cvReleaseImage(&Ihi3);
	cvReleaseImage(&Iscratch);
	cvReleaseImage(&Iscratch2);
	cvReleaseImage(&Igray1);
	cvReleaseImage(&Igray2);
	cvReleaseImage(&Igray3);
	cvReleaseImage(&Imaskt);
}

关于int argc, char** argv的参数传递和调试时如何使用请参见:

https://blog.csdn.net/qq_30815237/article/details/87006586 

 

你可能感兴趣的:(OpenCV3学习(10.1)背景分离 (帧间差分法、背景差分法))