转自:http://hi.baidu.com/belial/item/1cad73fb05819612fe3582dd
SACON是Hanzi Wang and David Suter合作完成的一篇论文《Background subtraction based on a robust consensu method>>里的算法。乍看之下,算法思路很简单,后来的大作VIBE算法似乎与其有异曲同工之妙。废话少说,下面来简单介绍下该算法:
(1)建立背景模型
文章中背景模型的建立比较简单,就是为each pixel 保存N个background samples,这个可以很容易的提取视频序列的前N帧实现。
(2)前景提取
该部分分为3阶段,其实每个阶段都是相互融合的:第一步利用邻域帧差法提取可能的前景掩膜(FMForeground Mask),即时间域内连续两帧相减,提取Possible moving pixels;第二部,针对第一步的得到的possible moving pixels以及background samples给SACON算法处理,主要的处理过程是按以下两个公式得到:
其中Tr为常量(constant),N是背景样本的数目(sample number),k表示通道数目(channel number), 在实现程序中默认取值为15. Tn的取值与N相关,其值可以表示为Tn~cNTr(C is a constant)。实现程序Tn默认取值为2N.
(3) 阴影去除(该文阴影去除算法是借鉴其他文章的,具体参考section 3.1)
(4) 空洞填充
考虑到提取的前景区域内部可能存在一些小的空洞,于是文章采用了空洞填充技术。在实现中,采用的是形态学的碰撞腐蚀(Dilate and erode)来进行填充的。
(5)考虑到背景的移出或前景的停止,文章介绍了TOM(TIme OF MAP)来对其处理,可以讲移出的背景留下的区域很快的内容到背景中,而对于停止在场景中的目标,也能很快的融入到背景中去。
下面是实现代码的.h文件
class SACON { public: SACON(); // 构造函数,其中iteration表示样本的数目,也即用于学习的帧数N SACON(IplImage *img,int iteration); /* Description: To implement the function to distinguish whether a pixel is foreground or background and then update the background model */ void Detect(IplImage *img); /* 设置当前像素是否为背景还是前景,即当前像素与背景样本比较,存在较大差异的数目 该值与样本的数目gIteration相关,默认为gIteration*2 */ void SetAlpha(double al){gAlpha=al;} // 设置两个像素值的差异,默认为15 void SetValDiff(int ival){valDiff=ival;} // 用于标记一个像素最多为 void SetTomTh(int Count){Th_Tom=Count;} IplImage* getForeground(){return pForeImg;} virtual ~SACON(); private: void InnerDetect(IplImage *img); // 填充前景区域的空洞,思想是填充, void ValidateForeground(IplImage *foreImg); IplImage **pBkImgArr; IplImage *pForeImg; IplImage *pPreImg; IplImage *pGray; IplImage *pDiffImg; int gHeight; int gWidth; CvSize gSize; int gIteration; int frmNum; int valDiff; // 两个像素值的差异 double gAlpha;//the value that determines a pixel whether a foreground or background int **Tom; //用来记录一个像素被连续判为前景的次数 int Th_Tom; // 用于标记一个像素最多为前景的次数,默认为100 };
下面是实现代码cpp文件:
SACON::SACON(IplImage *img,int iteration) { frmNum=0; Th_Tom=100; gHeight=img->height; gWidth=img->width; gIteration=iteration; gAlpha=gIteration*2; // 值的设置还有待商榷 gSize=cvGetSize(img); pForeImg=cvCreateImage(gSize,8,1); pGray=cvCreateImage(gSize,8,1); pPreImg=cvCreateImage(gSize,8,3); pDiffImg=cvCreateImage(gSize,8,3); int i=0,j=0; Tom=new int *[gHeight]; for (i=0;i<gHeight;i++) { Tom[i]=new int[gWidth]; for (j=0;j<gWidth;j++) { Tom[i][j]=0; } } pBkImgArr=new IplImage*[gIteration]; for (i=0;i<gIteration;i++) { pBkImgArr[i]=cvCreateImage(gSize,IPL_DEPTH_8U,3); } cvCopyImage(img,pBkImgArr[frmNum]); frmNum++; } // 实现前景检测,如果学习没完,则继续进行学习 void SACON::Detect(IplImage *img) { if (frmNum<gIteration) { cvCopyImage(img,pBkImgArr[frmNum]); frmNum++; if (frmNum==gIteration) { cvCopyImage(img,pPreImg); } } else { InnerDetect(img); ValidateForeground(pForeImg); } } // 实现前景检测,并进行背景更新 void SACON::InnerDetect(IplImage *img) { cvZero(pForeImg); cvZero(pGray); cvAbsDiff(img,pPreImg,pDiffImg); cvCvtColor(pDiffImg,pGray,CV_BGR2GRAY); cvThreshold(pGray,pGray,20,255,CV_THRESH_BINARY); cvCopyImage(img,pPreImg); int i=0,j=0,k=0,p=0; int cnt=0, iTom; CvScalar cs0,cs1,cs2,cs3; cs3.val[0]=255; for (i=0;i<gHeight;i++) { for (j=0;j<gWidth;j++) { cs0=cvGet2D(pGray,i,j); if (cs0.val[0]==255) { cs1=cvGet2D(img,i,j); cnt=0; for (k=0;k<gIteration;k++) { cs2=cvGet2D(pBkImgArr[k],i,j); for (p=0;p<3;p++) { if (fabs(cs1.val[p]-cs2.val[p])>valDiff) { cnt++; } } } //与背景比较完成 if (cnt>gAlpha) { iTom=Tom[i][j]; if (iTom>Th_Tom) // 如果当前像素为前景的次数超过Th_Tom,则判为背景 { Tom[i][j]=0; // 怎么更新背景 } else { Tom[i][j]++; cvSet2D(pForeImg,i,j,cs3); } } } else { continue; } } } } void SACON::ValidateForeground(IplImage *foreImg) { // 膨胀腐蚀的默认结构元素是3×3的长方形 cvDilate(foreImg,foreImg,NULL,1); cvErode(foreImg,foreImg,NULL,1); /* int i=0,j=0,p=0,q=0; int cnt; CvScalar cs0,cs; cs.val[0]=255; for (i=1;i<gHeight-1;i++) { for (j=1;j<gWidth-1;j++) { cnt=0; for (p=i-1;p<=i+1;p++) { for(q=j-1;q<=j+1;q++) { cs0=cvGet2D(foreImg,p,q); if (cs0.val[0]==255) { cnt++; } } } if (cnt>5) { cvSet2D(foreImg,i,j,cs); } } } */ }