平均背景法
为达到前景目标的识别,譬如,交通路口对车辆的识别、监控器对行人的识别,常用的且较为有效的方法就是背景差分法(还有其他的方法,比如光流场法,帧差法),即用一张有要识别目标的图像减去相应的背景图像,那么所得的结果便是我们所要的目标。
然而,如何获取一个“美好”的背景图,是背景差分法的关键和难点。此处介绍一种最为简单的获取背景的方法——平均背景法。
顾名思义,其基本思想就是,将所采集到的背景图片叠加求和,而后求取平均值作为要求的背景。其大致算法流程如下:
1.将采集到的部分图片,利用OpenCV的cvAcc函数累加求和,并统计累加次数,累加完成后在用cvConvertScale函数求取平均值,记为A。(注意理解该平均值的意思是在(x,y)位置处,所有参与求和计算的图像在该点出的像素平均值,每一点的像素平均值是不同的)
2.前一帧图像和当前参与累加的图像求差的绝对值,利用cvAbsDiff函数,而后该绝对值图像也进行累加求平均值,记为D。(同理这是某点的差值平均值)它描述的是某点像素值得波动幅度。
3.那么有1和2便可以认为,图像某点的像素值P若满足A-D < P < A+D,则认为此点属于背景,然而由于前景目标的加入,可能对背景点的亮度有一定的影响,故对波动幅度D进行一定的放缩,若 A -KD < p < A + KD,便认为该点为背景点。自然在该范围外的点,便是我们需要的前景点。那么用cvThreshold函数完成二值化。我们的目的就达到了。
#include "highgui.h"
#include "cv.h"
#include "cxcore.h"
/*为不同临时图像和统计属性图像创建指针*/
//三通道float图像
IplImage *IavgF, *IdiffF, *IprevF, *IhiF, *IlowF;//平均 不同 先前 高 低
IplImage *Iscratch, *Iscratch2;
//单通道float图像
IplImage *Igray1, *Igray2, *Igray3;
IplImage *Ilow1, *Ilow2, *Ilow3;
IplImage *Ihi1, *Ihi2, *Ihi3;
//单通道Byte图像
IplImage *Imaskt;
//自己定义的图像
//IplImage *mypic;
//计算背景建模使用图片的张数
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 DeallocateImages();
int main(int argc, char** argv)
{
cvNamedWindow("intput", CV_WINDOW_AUTOSIZE); //创建输入显示窗口
cvNamedWindow("output", CV_WINDOW_AUTOSIZE); //创建输出显示窗口
//返回一个capture指针,指向视频
CvCapture* capture = cvCreateFileCapture("C:/Users/Administrator/Desktop/OPENCV/MV版.avi");
IplImage*Img = cvQueryFrame(capture); //从视频中取出的图片
//创建输出图片,这里不能去掉cvCreateImage(cvGetSize(Img),IPL_DEPTH_8U,1),虽然我看例程里省略了
IplImage * Imask = 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");
DeallocateImages();
}
/*给需要的所有临时图像分配内存。为了方便,传递一副图像作为大小参考来分配临时图像*/
void AllocateImages(IplImage* I){
CvSize sz = cvGetSize(I); //这个比较好 值得一学
IavgF = cvCreateImage(sz, IPL_DEPTH_32F, 3); //IPL_DEPTH_32F 单精度浮点数
IdiffF = cvCreateImage(sz, IPL_DEPTH_32F, 3);
IprevF = cvCreateImage(sz, IPL_DEPTH_32F, 3);
IhiF = cvCreateImage(sz, IPL_DEPTH_32F, 3);
IlowF = cvCreateImage(sz, IPL_DEPTH_32F, 3);
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);
//归零
cvZero(IavgF);
cvZero(IdiffF);
cvZero(IprevF);
cvZero(IhiF);
cvZero(IlowF);
Icount = 0.0001;
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); //IPL_DEPTH_8U - 无符号8位整型
cvZero(Iscratch);
cvZero(Iscratch2);
// mypic = cvCreateImage(sz,IPL_DEPTH_8U,1);
}
/*累积背景图像和每一帧图像差值的绝对值*/
void accumulateBackground(IplImage *I){
static int first = 1;
cvCvtScale(I, Iscratch, 1, 0);
if (!first){
cvAcc(Iscratch, IavgF); //累加图像
cvAbsDiff(Iscratch, IprevF, Iscratch2); //计算一定时间内的每帧图像之差
cvAcc(Iscratch2, IdiffF);
Icount += 1.0; //累加
}
first = 0;
cvCopy(Iscratch, IprevF);
}
/*累积背景图像与每一帧图像差值的绝对值后,建立一个背景的统计模型*/
/*先定义setHighThreshold与setLowThreshold*/
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);
cvSub(IavgF, Iscratch, IlowF);
cvSplit(IlowF, Ilow1, Ilow2, Ilow3, 0);
}
/*建立模型*/
void createModelsfromStats(){
cvConvertScale(IavgF, IavgF, (double)(1.0 / Icount)); //IgvgF = IgvgF *(1.0/Icount) + 0 求取平均值
cvConvertScale(IdiffF, IdiffF, (double)(1.0 / Icount));
cvAddS(IdiffF, cvScalar(1.0, 1.0, 1.0), IdiffF);
setHighThreshold(7.0); //函数设置一个阈值,使得对于每一帧图像的绝对差大于平局值7倍的像素都认为是前景
setLowThreshold(6.0);
}
/*建立模型同时有了高低阈值,我们就能把图像分割成前景(不能被背景模型“解释”的图像部分)*/
void backgroundDiff(
IplImage *I,
IplImage *Imask
){
cvCvtScale(I, Iscratch, 1, 0);
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);
//cvCvtScale(Imask,mypic,1,0);
cvSubRS(Imask, cvScalar(255), Imask); //这里书上例程写cvSubRS(Imask,255,Imask);但是这样编译的时候会报错,必须先转换成cvScalar型
}
/*释放内存*/
void DeallocateImages()
{
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);
}