帧差法可以用来检测运动目标,简单的背景模型建模是以图像序列均值以及差序列均值为基础的,优点是比较简单,缺点以均值为基础的背景模型无法涵括亮度跳度较大的周期背景(例如摇曳的树、转动的风扇、摆动窗帘等等)。

codebook用来描述一个像素位置的信息,是多个编码元(一个编码本),每个编码元主要包含一个box和两个学习区域。如下图可以理解为一个像素位置的两个编码元(编码元1相比编码元2来说对应更高的灰度区域)。


codebook同样需要一定背景图像序列学习,学习过程:

任意一个像素位置由一个编码本组成,一个编码本由多个编码元组成。每个编码元包含一个box,两块学习区域。当一个像素点灰度值在已有的任何一个box规定的范围内,我们直接判断其属于该编码元,跳过即可;当其不在box范围内,但在学习区域内,我们认为其属于该编码元,但需要进行box区域范围的更新(算是一种学习);若其同样不在学习区域内,则建立新的编码元。

codebook去除干扰:

每个编码元内包含一个t参数,是距离上次被查到属于该编码元时所过去的时间,用于排除不常使用的编码元。例如,可以设置一个时间阈值t=20帧,则将删除过去20帧内没有被访问到的编码元。

codebook前景检测:

与建立编码本过程类似,只是此时不再考虑学习区域,一个像素通过判断其是否属于该位置点的编码元,如果全都不属于,则认为是前景。

codebook主要的前景分割参数:

minMod[i]   = 10;   // 用于背景差分函数中
maxMod[i]   = 10;   // 调整其值以达到最好的分割

其含义为每个box的波动区域,值越大,被检测的像素则更可能在box中,即背景。
因此,如果原图噪声较大,可以设置大一些,去噪噪声,但也会导致一些目标信息的丢失,因此实际使用时需要不断实验得到最好的值。


使用CODEBOOK背景模型过程:
(1)使用函数update_codebook()在几秒或几分钟的时间内训练一个基本的背景模型。
(2)调用函数clear_stale_entries()清除stale索引。
(3)调整阈值minmod和maxmod对已知前景达到最好分割。
(4)保持一个更高级的场景模型
(5)通过函数background_diff()使用训练好的模型将前景从背景中分割出来。
(6)定期更新学习的背景像素。
(7)在一个频率较慢的情况下,用函数clear_stale_entries()定期清理stale的codebook索引


以下为随机截取的几帧实验图像


可以看出,codebook背景模型由于更加复杂,对运动目标的检测效果还是不错的,同时训练数据速度也比较快。但它不能很好处理不同模式的光(如早晨、中午和傍晚的阳光,室内开关灯等),因为某一场景会因长时间没被访问而删除,这种全局变化的类型可以考虑用几种不同条件下的codebook模型处理。

#include <cv.h>
#include <highgui.h>
#include <cxcore.h>#define CHANNELS 3
// 设置处理的图像通道数,要求小于等于图像本身的通道数///////////////////////////////////////////////////////////////////////////
// 下面为码本码元的数据结构
// 处理图像时每个像素对应一个码本,每个码本中可有若干个码元
// 当涉及一个新领域,通常会遇到一些奇怪的名词,不要被这些名词吓坏,其实思路都是简单的
typedef struct ce {uchar   learnHigh[CHANNELS];    // High side threshold for learning// 此码元各通道的阀值上限(学习界限)uchar   learnLow[CHANNELS];     // Low side threshold for learning// 此码元各通道的阀值下限// 学习过程中如果一个新像素各通道值x[i],均有 learnLow[i]<=x[i]<=learnHigh[i],则该像素可合并于此码元uchar   max[CHANNELS];          // High side of box boundary// 属于此码元的像素中各通道的最大值uchar   min[CHANNELS];          // Low side of box boundary// 属于此码元的像素中各通道的最小值int     t_last_update;          // This is book keeping to allow us to kill stale entries// 此码元最后一次更新的时间,每一帧为一个单位时间,用于计算staleint     stale;                  // max negative run (biggest period of inactivity)// 此码元最长不更新时间,用于删除规定时间不更新的码元,精简码本
} code_element;                     // 码元的数据结构typedef struct code_book {code_element    **cb;// 码元的二维指针,理解为指向码元指针数组的指针,使得添加码元时不需要来回复制码元,只需要简单的指针赋值即可int             numEntries;// 此码本中码元的数目int             t;              // count every access// 此码本现在的时间,一帧为一个时间单位
} codeBook;                         // 码本的数据结构///////////////////////////////////////////////////////////////////////////////////
// int updateCodeBook(uchar *p, codeBook &c, unsigned cbBounds)
// Updates the codebook entry with a new data point
//
// p            Pointer to a YUV pixel
// c            Codebook for this pixel
// cbBounds     Learning bounds for codebook (Rule of thumb: 10)
// numChannels  Number of color channels we're learning
//
// NOTES:
//      cvBounds must be of size cvBounds[numChannels]
//
// RETURN
//  codebook index
int cvupdateCodeBook(uchar *p, codeBook &c, unsigned *cbBounds, int numChannels)
{if(c.numEntries == 0) c.t = 0;// 码本中码元为零时初始化时间为0c.t += 1;   // Record learning event// 每调用一次加一,即每一帧图像加一//SET HIGH AND LOW BOUNDSint n;unsigned int high[3],low[3];for (n=0; n<numChannels; n++){high[n] = *(p+n) + *(cbBounds+n);// *(p+n) 和 p[n] 结果等价,经试验*(p+n) 速度更快if(high[n] > 255) high[n] = 255;low[n] = *(p+n)-*(cbBounds+n);if(low[n] < 0) low[n] = 0;// 用p 所指像素通道数据,加减cbBonds中数值,作为此像素阀值的上下限}//SEE IF THIS FITS AN EXISTING CODEWORDint matchChannel;   int i;for (i=0; i<c.numEntries; i++){// 遍历此码本每个码元,测试p像素是否满足其中之一matchChannel = 0;for (n=0; n<numChannels; n++)//遍历每个通道{if((c.cb[i]->learnLow[n] <= *(p+n)) && (*(p+n) <= c.cb[i]->learnHigh[n])) //Found an entry for this channel// 如果p 像素通道数据在该码元阀值上下限之间{   matchChannel++;}}if (matchChannel == numChannels)        // If an entry was found over all channels// 如果p 像素各通道都满足上面条件{c.cb[i]->t_last_update = c.t;// 更新该码元时间为当前时间// adjust this codeword for the first channelfor (n=0; n<numChannels; n++)//调整该码元各通道最大最小值{if (c.cb[i]->max[n] < *(p+n))c.cb[i]->max[n] = *(p+n);else if (c.cb[i]->min[n] > *(p+n))c.cb[i]->min[n] = *(p+n);}break;}}// ENTER A NEW CODE WORD IF NEEDEDif(i == c.numEntries)  // No existing code word found, make a new one// p 像素不满足此码本中任何一个码元,下面创建一个新码元{code_element **foo = new code_element* [c.numEntries+1];// 申请c.numEntries+1 个指向码元的指针for(int ii=0; ii<c.numEntries; ii++)// 将前c.numEntries 个指针指向已存在的每个码元foo[ii] = c.cb[ii];foo[c.numEntries] = new code_element;// 申请一个新的码元if(c.numEntries) delete [] c.cb;// 删除c.cb 指针数组c.cb = foo;// 把foo 头指针赋给c.cbfor(n=0; n<numChannels; n++)// 更新新码元各通道数据{c.cb[c.numEntries]->learnHigh[n] = high[n];c.cb[c.numEntries]->learnLow[n] = low[n];c.cb[c.numEntries]->max[n] = *(p+n);c.cb[c.numEntries]->min[n] = *(p+n);}c.cb[c.numEntries]->t_last_update = c.t;c.cb[c.numEntries]->stale = 0;c.numEntries += 1;}// OVERHEAD TO TRACK POTENTIAL STALE ENTRIESfor(int s=0; s<c.numEntries; s++){// This garbage is to track which codebook entries are going staleint negRun = c.t - c.cb[s]->t_last_update;// 计算该码元的不更新时间if(c.cb[s]->stale < negRun) c.cb[s]->stale = negRun;}// SLOWLY ADJUST LEARNING BOUNDSfor(n=0; n<numChannels; n++)// 如果像素通道数据在高低阀值范围内,但在码元阀值之外,则缓慢调整此码元学习界限{if(c.cb[i]->learnHigh[n] < high[n]) c.cb[i]->learnHigh[n] += 1;if(c.cb[i]->learnLow[n] > low[n]) c.cb[i]->learnLow[n] -= 1;}return(i);
}///////////////////////////////////////////////////////////////////////////////////
// uchar cvbackgroundDiff(uchar *p, codeBook &c, int minMod, int maxMod)
// Given a pixel and a code book, determine if the pixel is covered by the codebook
//
// p        pixel pointer (YUV interleaved)
// c        codebook reference
// numChannels  Number of channels we are testing
// maxMod   Add this (possibly negative) number onto max level when code_element determining if new pixel is foreground
// minMod   Subract this (possible negative) number from min level code_element when determining if pixel is foreground
//
// NOTES:
// minMod and maxMod must have length numChannels, e.g. 3 channels => minMod[3], maxMod[3].
//
// Return
// 0 => background, 255 => foreground
uchar cvbackgroundDiff(uchar *p, codeBook &c, int numChannels, int *minMod, int *maxMod)
{// 下面步骤和背景学习中查找码元如出一辙int matchChannel;//SEE IF THIS FITS AN EXISTING CODEWORDint i;for (i=0; i<c.numEntries; i++){matchChannel = 0;for (int n=0; n<numChannels; n++){if ((c.cb[i]->min[n] - minMod[n] <= *(p+n)) && (*(p+n) <= c.cb[i]->max[n] + maxMod[n]))matchChannel++; //Found an entry for this channelelsebreak;}if (matchChannel == numChannels)break; //Found an entry that matched all channels}if(i == c.numEntries) // p像素各通道值满足码本中其中一个码元,则返回白色return(255);return(0);
}//UTILITES/
/////////////////////////////////////////////////////////////////////////////////
//int clearStaleEntries(codeBook &c)
// After you've learned for some period of time, periodically call this to clear out stale codebook entries
//
//c     Codebook to clean up
//
// Return
// number of entries cleared
int cvclearStaleEntries(codeBook &c)
{int staleThresh = c.t >> 1;         // 设定刷新时间int *keep = new int [c.numEntries]; // 申请一个标记数组int keepCnt = 0;                    // 记录不删除码元数目//SEE WHICH CODEBOOK ENTRIES ARE TOO STALEfor (int i=0; i<c.numEntries; i++)// 遍历码本中每个码元{if (c.cb[i]->stale > staleThresh)   // 如码元中的不更新时间大于设定的刷新时间,则标记为删除keep[i] = 0; //Mark for destructionelse{keep[i] = 1; //Mark to keepkeepCnt += 1;}}// KEEP ONLY THE GOODc.t = 0;                        //Full reset on stale tracking// 码本时间清零code_element **foo = new code_element* [keepCnt];// 申请大小为keepCnt 的码元指针数组int k=0;for(int ii=0; ii<c.numEntries; ii++){if(keep[ii]){foo[k] = c.cb[ii];foo[k]->stale = 0;      //We have to refresh these entries for next clearStalefoo[k]->t_last_update = 0;k++;}}//CLEAN UPdelete [] keep;delete [] c.cb;c.cb = foo;// 把foo 头指针地址赋给c.cb int numCleared = c.numEntries - keepCnt;// 被清理的码元个数c.numEntries = keepCnt;// 剩余的码元地址return(numCleared);
}// 寻找掩模轮廓
void find_connected_componts(IplImage *raw,IplImage *mask,int ploy_hull0 = 1,float perimScale = 4,CvRect *bbs = NULL,CvPoint *centers = NULL)
{static CvMemStorage* mem_storage = NULL;static CvSeq* contours = NULL;//为寻找轮廓定义存储空间/*cvMorphologyEx(mask,mask,0,0,CV_MOP_OPEN,1); cvMorphologyEx(mask,mask,0,0,CV_MOP_CLOSE,1); *///形态学去除噪声if(mem_storage==NULL){mem_storage = cvCreateMemStorage(0);}else{cvClearMemStorage(mem_storage);}CvContourScanner scanner = cvStartFindContours(mask,mem_storage,sizeof(CvContour),CV_RETR_EXTERNAL,CV_CHAIN_APPROX_SIMPLE);//寻找外轮廓,保存在scanner中CvSeq *c;int numCont = 0;while((c=cvFindNextContour(scanner))!=NULL){double len = cvContourPerimeter(c);  //该轮廓的长度double q = (mask->height+mask->width)/perimScale;//所需要轮廓的长度,小于则删除该轮廓if (len<q){cvSubstituteContour(scanner,NULL);}else{CvSeq *c_new;c_new = cvApproxPoly(c,sizeof(CvContour),mem_storage,CV_POLY_APPROX_DP,0,0);//多边形近似,减少无用点(貌似还可以用凸优化近似)cvSubstituteContour(scanner,c_new);//替换原轮廓numCont++;}}contours = cvEndFindContours(&scanner);CvScalar CVX_WHITE = CV_RGB(0xff,0xff,0xff);CvScalar CVX_BLACK = CV_RGB(0x00,0x00,0x00);//定义绘制轮廓颜色cvZero(0);//在新的掩模绘制CvRect rect;int i=0;for(i=0,c=contours;c!=NULL&i<10;c=c->h_next,i++){bbs[i] = cvBoundingRect(c);rect = bbs[i];cvDrawContours(mask,c,CVX_WHITE,CVX_WHITE,-1,2,8);cvRectangle(raw,cvPoint(rect.x,rect.y),cvPoint(rect.x+rect.width,rect.y+rect.height),CVX_WHITE,1,8,0);}cvNamedWindow("Raw");cvShowImage("Raw", raw);cvNamedWindow("CodeBook");cvShowImage("CodeBook",mask);cvWaitKey(30);
}int main()
{///////////////////////////////////////// 需要使用的变量CvCapture*  capture;IplImage*   rawImage;IplImage*   yuvImage;IplImage*   ImaskCodeBook;codeBook*   cB;CvRect      bbs[20];unsigned    cbBounds[CHANNELS];uchar*      pColor; //YUV pointerint         imageLen;int         nChannels = CHANNELS;int         minMod[CHANNELS];int         maxMod[CHANNELS];//////////////////////////////////////////////////////////////////////////// 初始化各变量cvNamedWindow("Raw");cvNamedWindow("CodeBook");capture = cvCreateFileCapture("Walk2.mpg");if (!capture){printf("Couldn't open the capture!");return -1;}rawImage = cvQueryFrame(capture);yuvImage = cvCreateImage(cvGetSize(rawImage), 8, 3);    // 给yuvImage 分配一个和rawImage 尺寸相同,8位3通道图像ImaskCodeBook = cvCreateImage(cvGetSize(rawImage), IPL_DEPTH_8U, 1);// 为ImaskCodeBook 分配一个和rawImage 尺寸相同,8位单通道图像cvSet(ImaskCodeBook, cvScalar(255));// 设置单通道数组所有元素为255,即初始化为白色图像imageLen = rawImage->width * rawImage->height;cB = new codeBook[imageLen];// 得到与图像像素数目长度一样的一组码本,以便对每个像素进行处理for (int i=0; i<imageLen; i++)// 初始化每个码元数目为0cB[i].numEntries = 0;for (int i=0; i<nChannels; i++){cbBounds[i] = 10;   // 用于确定码元各通道的阀值minMod[i]   = 10;   // 用于背景差分函数中maxMod[i]   = 10;   // 调整其值以达到最好的分割}//////////////////////////////////////////////////////////////////////////// 开始处理视频每一帧图像for (int i=0;;i++){cvCvtColor(rawImage, yuvImage, CV_BGR2YCrCb);// 色彩空间转换,将rawImage 转换到YUV色彩空间,输出到yuvImage// 即使不转换效果依然很好// yuvImage = cvCloneImage(rawImage);if (i <= 100)// 100帧内进行背景学习{pColor = (uchar *)(yuvImage->imageData);// 指向yuvImage 图像的通道数据for (int c=0; c<imageLen; c++){cvupdateCodeBook(pColor, cB[c], cbBounds, nChannels);// 对每个像素,调用此函数,捕捉背景中相关变化图像pColor += 3;// 3 通道图像, 指向下一个像素通道数据}if (i == 100)// 到100 帧时调用下面函数,删除码本中陈旧的码元{for (int c=0; c<imageLen; c++)cvclearStaleEntries(cB[c]);}}else{uchar maskPixelCodeBook;pColor = (uchar *)((yuvImage)->imageData); //3 channel yuv imageuchar *pMask = (uchar *)((ImaskCodeBook)->imageData); //1 channel image// 指向ImaskCodeBook 通道数据序列的首元素for(int c=0; c<imageLen; c++){maskPixelCodeBook = cvbackgroundDiff(pColor, cB[c], nChannels, minMod, maxMod);*pMask++ = maskPixelCodeBook;pColor += 3;// pColor 指向的是3通道图像}}find_connected_componts(rawImage,ImaskCodeBook,1,4.0,bbs);if (!(rawImage = cvQueryFrame(capture)))break;}   cvReleaseCapture(&capture);if (yuvImage)cvReleaseImage(&yuvImage);if(ImaskCodeBook) cvReleaseImage(&ImaskCodeBook);cvDestroyAllWindows();delete [] cB;return 0;
}

基于codebook背景建模的运动目标检测相关推荐

  1. 基于平均背景建模的运动目标检测

    建立一个场景模型,包含图像灰度均值.帧间平均差值. 对于输入的后续视频,当灰度在区间内时,认为是静止区域,反之为运动目标.判断静止灰度区间为: diff实际上可认为是为了抑制一定的噪声,当图像序列噪声 ...

  2. Python与OpenCV(二)——基于背景差分法的运动目标检测程序分析

    背景差分法是传统运动目标检测算法中最常用的方法.其基本原理如图所示. 从图中可知,背景差分法是通过建立背景模型,比较当前帧与背景模型对应像素的差异点来检测运动目标的方法. 背景模型的建立主要通过两种方 ...

  3. 背景建模与前景检测3(Background Generation And Foreground Detection Phase 3)

    背景建模与前景检测之三(Background Generation And Foreground Detection Phase 3) 作者:王先荣 在上一篇文章里,我尝试翻译了<Nonpara ...

  4. 背景建模与前景检测2(Background Generation And Foreground Detection Phase 2)

    背景建模与前景检测2(Background Generation And Foreground Detection Phase 2) 作者:王先荣 本文尝试对<学习OpenCV>中推荐的论 ...

  5. 基于用户行为建模和异常检测算法的内部威胁检测

    Insider Threat Detection Based on User Behavior Modeling and Anomaly Detection Algorithms 内部威胁是授权用户的 ...

  6. 运动背景下的运动目标检测

    from:运动背景下的运动目标检测 各种目标检测方法介绍(懒人可以直接略过) 目标检测是一个老话题了,在很多算法当中都有它的身影.目标检测要做的就两件事:检测当前图片中有没有目标?如果有的话,在哪?按 ...

  7. 背景建模与前景检测1(Background Generation And Foreground Detection)

    背景建模与前景检测(Background Generation And Foreground Detection) 作者:王先荣 前言     在很多情况下,我们需要从一段视频或者一系列图片中找到感兴 ...

  8. ViBe(Visual Background extractor)背景建模或前景检测

    ViBe算法:ViBe - a powerful technique for background detection and subtraction in video sequences 算法官网: ...

  9. 背景建模或前景检测(Background Generation And Foreground Detection) 二

    转自:http://www.cnblogs.com/xrwang/archive/2010/03/27/BackgroundGenerationAndForegroundDetectionPhase2 ...

  10. Python与OpenCV(一)——基于帧差法的运动目标检测程序分析

    OpenCV提供了强大的图像处理功能,与Python的结合堪称完美... 这一次,我们试一下用帧差法来完成对运动目标的检测与跟踪. 帧差法的原理是这样的:由于摄像机采集的视频序列具有连续性的特点,所以 ...

最新文章

  1. python中国大学排名爬虫写明详细步骤-Python爬虫--2019大学排名数据抓取
  2. 渗透测试---数据库安全: sql注入数据库原理详解
  3. Centos7.5.1804永久生效修改主机名
  4. 王侠对话农民丰收节交易会 万祥军:解读供销社服务平台
  5. 运营总监训练营本周六开营,B612、神策数据等运营总监倾囊相授运营方法论
  6. ux和ui_UI和UX设计师的10种软技能
  7. leetcode674. 最长连续递增序列
  8. 第4阶段——制作根文件系统之分析init_post()如何启动第1个程序(1)
  9. Android自定义属性:format选项之reference
  10. 蓝牙 查询码 android,android bluetooth UUID蓝牙查询表
  11. oracle 函数怎么个写法,Oracle表值函数的两种写法
  12. jclasslib安装
  13. 透明OLED显示器的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  14. js迷宫生成与迷宫求解算法
  15. 明翰Java入门教学系列之IO篇
  16. plsql 连接 虚拟机Linux下的Oracle数据库 失败
  17. 设计分享|单片机直流电机转速控制(汇编)
  18. Python 的 .py 与 Cython 的 .pxd .pyx .pyd 文件格式之间的主要区别
  19. C++库和C库的区别
  20. 如何创建全球(全局)公司代码 (Global Company Code )

热门文章

  1. 如何自制条形码扫描器
  2. 浪曦 ASP.net AJAX系列 视频下载
  3. android课设会议室预约系统,教室及会议室预约系统(C#小程序,课堂作业)
  4. 烂笔头笔记:macOS卸载Adobe产品后,在Launchpad上遗留空文件夹的解决方法
  5. 毕业设计实战:单片机智能温控风扇设计 带智能调速人体感应 论文仿真 源码 原理图
  6. 看点视频解析去水印原理分析过程及源码,rowkey的秘密
  7. 高级终端termux下载不了Python_基于Termux打造Android手机渗透工具
  8. 工厂物资管理E-R图
  9. Weblogic安装时闪退问题
  10. java长连接_java如何实现http长连接