基于codebook背景建模的运动目标检测
帧差法可以用来检测运动目标,简单的背景模型建模是以图像序列均值以及差序列均值为基础的,优点是比较简单,缺点以均值为基础的背景模型无法涵括亮度跳度较大的周期背景(例如摇曳的树、转动的风扇、摆动窗帘等等)。
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背景建模的运动目标检测相关推荐
- 基于平均背景建模的运动目标检测
建立一个场景模型,包含图像灰度均值.帧间平均差值. 对于输入的后续视频,当灰度在区间内时,认为是静止区域,反之为运动目标.判断静止灰度区间为: diff实际上可认为是为了抑制一定的噪声,当图像序列噪声 ...
- Python与OpenCV(二)——基于背景差分法的运动目标检测程序分析
背景差分法是传统运动目标检测算法中最常用的方法.其基本原理如图所示. 从图中可知,背景差分法是通过建立背景模型,比较当前帧与背景模型对应像素的差异点来检测运动目标的方法. 背景模型的建立主要通过两种方 ...
- 背景建模与前景检测3(Background Generation And Foreground Detection Phase 3)
背景建模与前景检测之三(Background Generation And Foreground Detection Phase 3) 作者:王先荣 在上一篇文章里,我尝试翻译了<Nonpara ...
- 背景建模与前景检测2(Background Generation And Foreground Detection Phase 2)
背景建模与前景检测2(Background Generation And Foreground Detection Phase 2) 作者:王先荣 本文尝试对<学习OpenCV>中推荐的论 ...
- 基于用户行为建模和异常检测算法的内部威胁检测
Insider Threat Detection Based on User Behavior Modeling and Anomaly Detection Algorithms 内部威胁是授权用户的 ...
- 运动背景下的运动目标检测
from:运动背景下的运动目标检测 各种目标检测方法介绍(懒人可以直接略过) 目标检测是一个老话题了,在很多算法当中都有它的身影.目标检测要做的就两件事:检测当前图片中有没有目标?如果有的话,在哪?按 ...
- 背景建模与前景检测1(Background Generation And Foreground Detection)
背景建模与前景检测(Background Generation And Foreground Detection) 作者:王先荣 前言 在很多情况下,我们需要从一段视频或者一系列图片中找到感兴 ...
- ViBe(Visual Background extractor)背景建模或前景检测
ViBe算法:ViBe - a powerful technique for background detection and subtraction in video sequences 算法官网: ...
- 背景建模或前景检测(Background Generation And Foreground Detection) 二
转自:http://www.cnblogs.com/xrwang/archive/2010/03/27/BackgroundGenerationAndForegroundDetectionPhase2 ...
- Python与OpenCV(一)——基于帧差法的运动目标检测程序分析
OpenCV提供了强大的图像处理功能,与Python的结合堪称完美... 这一次,我们试一下用帧差法来完成对运动目标的检测与跟踪. 帧差法的原理是这样的:由于摄像机采集的视频序列具有连续性的特点,所以 ...
最新文章
- python中国大学排名爬虫写明详细步骤-Python爬虫--2019大学排名数据抓取
- 渗透测试---数据库安全: sql注入数据库原理详解
- Centos7.5.1804永久生效修改主机名
- 王侠对话农民丰收节交易会 万祥军:解读供销社服务平台
- 运营总监训练营本周六开营,B612、神策数据等运营总监倾囊相授运营方法论
- ux和ui_UI和UX设计师的10种软技能
- leetcode674. 最长连续递增序列
- 第4阶段——制作根文件系统之分析init_post()如何启动第1个程序(1)
- Android自定义属性:format选项之reference
- 蓝牙 查询码 android,android bluetooth UUID蓝牙查询表
- oracle 函数怎么个写法,Oracle表值函数的两种写法
- jclasslib安装
- 透明OLED显示器的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
- js迷宫生成与迷宫求解算法
- 明翰Java入门教学系列之IO篇
- plsql 连接 虚拟机Linux下的Oracle数据库 失败
- 设计分享|单片机直流电机转速控制(汇编)
- Python 的 .py 与 Cython 的 .pxd .pyx .pyd 文件格式之间的主要区别
- C++库和C库的区别
- 如何创建全球(全局)公司代码 (Global Company Code )
热门文章
- 如何自制条形码扫描器
- 浪曦 ASP.net AJAX系列 视频下载
- android课设会议室预约系统,教室及会议室预约系统(C#小程序,课堂作业)
- 烂笔头笔记:macOS卸载Adobe产品后,在Launchpad上遗留空文件夹的解决方法
- 毕业设计实战:单片机智能温控风扇设计 带智能调速人体感应 论文仿真 源码 原理图
- 看点视频解析去水印原理分析过程及源码,rowkey的秘密
- 高级终端termux下载不了Python_基于Termux打造Android手机渗透工具
- 工厂物资管理E-R图
- Weblogic安装时闪退问题
- java长连接_java如何实现http长连接