本文非原创,理论部分转载于:http://www.cnblogs.com/dwdxdy/p/3530862.html
SACON(SAmple CONsensus)算法是基于样本一致性的运动目标检测算法。该算法通过对每个像素进行样本一致性判断来判定像素是否为背景。
算法框架图
由上图可知,该算法主要分为四个主要部分,分别是邻域差分、SACON算法核心处理、空洞填充后处理、TOM(Time Out Map),其中TOM(Time Out Map)主要用于背景模型更新,其他部分属于前景目标检测。
背景模型建立
SACON算法建立背景模型的方法是直接取视频序列的前N帧作为背景模型。
对于每个像素而言,其背景模型可以表示为:C(c1,c2,...,cN),对于彩色图像,ci=(r,g,b),对于灰度图像,则对应于灰度值。
前景目标检测
1.邻域帧间差分
为了减少计算量,加快计算速度,采用邻域帧间差分法提取可能的运动像素。
2.SACON算法核心
对于新像素,将其与对应的背景模型比较,判断新像素是否满足背景模型样本的一致性。判定方法如下:
其中,两个公式分别计算新像素与背景模型的样本是否距离相近和统计距离相近的样本数目,当距离相近的样本数目大于阈值时,则判定新像素为背景,Bt(m) = 1。
公式涉及的阈值主要有Tr和Tn,当背景模型的样本集合的数目N越大,距离阈值Tr越大,则近似样本数目阈值Tn越大,因而Tn正比于N和Tr,Tn = a * N * Tr,a为比例因子。
3.后处理
后处理主要包括两个操作:阴影去除和空洞填充。
阴影去除:采用进一步的验证规则,去除阴影区域,验证规则如下:
其中,{r,g,I}是归一化的色彩空间,r = R/(R+G+B), g = G/(R+G+B),I = (R+G+B)/3,具体细节请参考论文资料。
空洞填充:对于检测到的前景,采用连通性分析,填充前景区域的内部空洞,主要通过形态学滤波实现。
背景模型更新
考虑到背景的移出或前景的停止,SACON算法采用TOM(Time of Map)进行处理,将背景的移出和运动目标的停止而引起的变化很快的融入到背景中,具体的更新方法分为Pixel-level和Blob-level。
1.Pixel-level
该方法主要统计像素m连续分为前景的次数,TOMt(m)。
如果TOMt(m)的值超过一个阈值,则更新像素m对应的背景模型。
2.Blob-level
如果一个目标(a blob)处于静止状态,则该目标所包含的所有像素的TOM都加1;
如果不处于静止状态,则该目标所包含的所有像素的TOM都置为0;
如果目标的TOM值大于阈值时,则该目标所包含的所有像素更新为背景像素。
Blob-level的更新方法,主要是弥补Pixel-level的不足,当一个目标包含运动的部分和不运动的部分,不运动的部分会持续被判定会前景,从而会被Pixel-level更新为背景;而运动的部分会时而被判定为前景,时而判定为背景,不会被Pixel-level更新为背景;最终的更新后结果是一个目标被分割,部分被检测为前景,部分被检测为背景,不合理。
Blob-level通过整体判断目标是否处于运动或静止,从而整体更新目标,保证了目标的完整性。
如上图所示,描述一个人走入房间,坐下并打电话,打电话过程中,人大部分区域处于静止状态,只有部分区域处于轻微运动状态。
(a)是原始图片,(b)是利用Pixel-level更新方法后的检测结果,(c)是结合Pixel-level和Blob-level更新方法的检测结果。
由显示结果可以看出,Pixel-level将处于静止状态的区域更新为背景,而Blob-level方法判定人整体处于运动状态,因而人未被更新为背景。
注意:
1.背景更新时,背景模型样本集合的替换策略未说明,是先进先出,还是随机替换,每次替换的数目也未说明。
2.如何判定一个目标(a blob)是处于运动状态,还是处于静止状态,文中只是提到可以利用目标的中心和像素的数目判定,但具体的策略也未说明。
参考资料:
http://blog.csdn.net/kezunhai/article/details/9500637
http://www.dotblogs.com.tw/dragon229/archive/2012/01/13/65615.aspx
Background Subtraction Based on a Robust Consensus Method
SACON: A Consensus Based Model for Background Subtraction
代码学习:(转载于:http://blog.csdn.net/stellar0/article/details/8740594)
头文件.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
- };
- 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);
- }
- }
- }
- */
- }