基于codebook背景建模的运动目标检测
来源:互联网 发布:开淘宝网店发货图片 编辑:程序博客网 时间:2024/05/16 12:46
帧差法可以用来检测运动目标,简单的背景模型建模是以图像序列均值以及差序列均值为基础的,优点是比较简单,缺点以均值为基础的背景模型无法涵括亮度跳度较大的周期背景(例如摇曳的树、转动的风扇、摆动窗帘等等)。
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 // 此码元最后一次更新的时间,每一帧为一个单位时间,用于计算stale int 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 indexint cvupdateCodeBook(uchar *p, codeBook &c, unsigned *cbBounds, int numChannels){ if(c.numEntries == 0) c.t = 0; // 码本中码元为零时初始化时间为0 c.t += 1; // Record learning event // 每调用一次加一,即每一帧图像加一 //SET HIGH AND LOW BOUNDS int 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 CODEWORD int 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 channel for (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 NEEDED if(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.cb for(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 ENTRIES for(int s=0; s<c.numEntries; s++) { // This garbage is to track which codebook entries are going stale int negRun = c.t - c.cb[s]->t_last_update; // 计算该码元的不更新时间 if(c.cb[s]->stale < negRun) c.cb[s]->stale = negRun; } // SLOWLY ADJUST LEARNING BOUNDS for(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 => foregrounduchar cvbackgroundDiff(uchar *p, codeBook &c, int numChannels, int *minMod, int *maxMod){ // 下面步骤和背景学习中查找码元如出一辙 int matchChannel; //SEE IF THIS FITS AN EXISTING CODEWORD int 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 channel else break; } 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 clearedint cvclearStaleEntries(codeBook &c){ int staleThresh = c.t >> 1; // 设定刷新时间 int *keep = new int [c.numEntries]; // 申请一个标记数组 int keepCnt = 0; // 记录不删除码元数目 //SEE WHICH CODEBOOK ENTRIES ARE TOO STALE for (int i=0; i<c.numEntries; i++) // 遍历码本中每个码元 { if (c.cb[i]->stale > staleThresh) // 如码元中的不更新时间大于设定的刷新时间,则标记为删除 keep[i] = 0; //Mark for destruction else { keep[i] = 1; //Mark to keep keepCnt += 1; } } // KEEP ONLY THE GOOD c.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 clearStale foo[k]->t_last_update = 0; k++; } } //CLEAN UP delete [] 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 pointer int 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++) // 初始化每个码元数目为0 cB[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 image uchar *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背景建模的运动目标检测
- 基于平均背景建模的运动目标检测
- 基于平均背景建模的运动目标检测(二)
- 混合高斯背景建模-视频的运动目标检测
- 运动目标检测_单高斯背景建模
- 基于codebook model的运动目标检测—整理opencv源码
- 对codebook model模型运动目标检测的理解
- OpenCV_基于自适应背景更新的运动目标检测
- 基于自组织背景减除的运动目标检测算法
- OpenCV_基于自适应背景更新的运动目标检测
- 【OPENCV】基于背景差法的运动目标检测
- 运动背景下的运动目标检测
- 目标检测——CodeBook背景建模(原理+Opencv实现代码)
- 运动目标检测_混合高斯背景建模
- 动态背景下的运动目标检测
- 运动目标检测--改进的背景减法
- 运动目标检测--改进的背景减法
- codebook算法(背景建模)的原理
- string 转int
- PAT(basic level) 1003 我要通过
- JSP新闻系统之五 增加操作
- 安卓项目,信号检测总结
- caffe学习(2)前后传播,loss,solver
- 基于codebook背景建模的运动目标检测
- java 所谓的值传递和引用传递
- 线性表的应用——约瑟夫环
- windows下开启远程连接Mysql
- 滤波器设计(4):自适应滤波器之最小均方误差(LMS)滤波器的设计
- NavigationView的实例应用
- Java并发---- Executor并发框架--ThreadToolExecutor类详解(execute方法)
- 表单
- Android系列之UI组件----Menu菜单