您的位置:首页 > 编程语言

目标检测——CodeBook背景建模(原理+Opencv实现代码)

2015-10-08 23:28 996 查看
CodeBook算法的基本思想是为每一个像素点建立一个codebook,每个codebook包含一个或者多个boxes,并且随着背景像素值波动情况的不同,不同像素点所包含的boxes数目不一定相同。

CodeBook算法为当前图像的每一个像素建立一个码本CodeBook(CB)结构,每个CodeBook由多个码元CodeWord(CW)组成。CB和CW的形式如下:

CB={CW1,CW2,…CWn,t}

CW={lHigh,lLow,max,min,t_last_update,stale}

其中n为一个CB中所包含的CW的数目,当n太小时,退化为简单背景,当n较大时可以对复杂背景进行建模;t为CB更新的次数。CW是一个6元组,其中IHigh和ILow作为更新时的学习上下界,max和min记录当前像素的最大值和最小值。t_last_update表示此码元最后一次更新的时间,stale表示此码元最长不更新时间(即该CW多久未被访问),用来删除很少使用的CodeWord,精简码本。

1 模型训练

假设当前训练图像I中某一像素为I(x,y),该像素的CB的更新算法如下,另外记背景阈值的增长判定阈值为Bounds:

(1) CB的访问次数加1;

(2) 遍历CB中的每个CW,如果存在一个CW中的IHigh,ILow满足ILow≤I(x,y)≤IHigh,则转(4);

(3) 创建一个新的码字CWnew加入到CB中, CWnew的max与min都赋值为I(x,y),IHigh <- I(x,y) + Bounds,ILow <- I(x,y) – Bounds,并且转(6);

(4) 更新该码字的t_last_update,若当前像素值I(x,y)大于该码字的max,则max <- I(x,y),若I(x,y)小于该码字的min,则min <- I(x,y);

(5) 更新该码字的学习上下界,以增加背景模型对于复杂背景的适应能力,具体做法是:若IHigh < I(x,y) + Bounds,则IHigh 增长1,若ILow > I(x,y) – Bounds,则ILow减少1;

(6) 更新CB中每个CW的stale。设定刷新时间,遍历每个码元,若码元中的不更新时间大于设定的刷新时间,则删除;在刷新时间内则保持。

2 背景检测

使用已建立好的CB进行运动目标检测,记判断前景的范围上下界为minMod和maxMod,对于当前待检测图像上的某一像素I(x,y),遍历它对应像素背景模型CB中的每一个码字CW,若存在一个CW,使得I(x,y) < max + maxMod并且I(x,y) > min – minMod,则I(x,y)被判断为背景,否则被判断为前景。

Opencv库

(1)legacy.hpp

typedef struct CvBGCodeBookElem
{
struct CvBGCodeBookElem* next;
int tLastUpdate; //码元最后一次更新时间
int stale;       // 码元最长更新时间
uchar boxMin[3]; // 码元中包含像素的最小值
uchar boxMax[3];// 码元中包含像素的最大值
uchar learnMin[3];// 码元学习下界
uchar learnMax[3];// 码元学习上界
} CvBGCodeBookElem;

typedef struct CvBGCodeBookModel
{
CvSize size;
int t;
uchar cbBounds[3]; //背景阈值判定
uchar modMin[3]; //前景判定下界
uchar modMax[3];//前景判定上界
CvBGCodeBookElem** cbmap;
CvMemStorage* storage;
CvBGCodeBookElem* freeList;
} CvBGCodeBookModel;


(2)bgfg_codebook.cpp

主要包含三个函数 : 模型更新、码元精简、背景检测

1)模型更新

void cvBGCodeBookUpdate( CvBGCodeBookModel* model, const CvArr* _image,
CvRect roi, const CvArr* _mask )
{
CV_FUNCNAME( "cvBGCodeBookUpdate" );

__BEGIN__;

CvMat stub, *image = cvGetMat( _image, &stub );
CvMat mstub, *mask = _mask ? cvGetMat( _mask, &mstub ) : 0;
int i, x, y, T;
int nblocks;
uchar cb0, cb1, cb2;
CvBGCodeBookElem* freeList;

CV_ASSERT( model && CV_MAT_TYPE(image->type) == CV_8UC3 &&
(!mask || (CV_IS_MASK_ARR(mask) && CV_ARE_SIZES_EQ(image, mask))) );

if( roi.x == 0 && roi.y == 0 && roi.width == 0 && roi.height == 0 )
{
roi.width = image->cols;
roi.height = image->rows;
}   //默认感兴趣区域 CvRect roi CV_DEFAULT(cvRect(0,0,0,0)
else
CV_ASSERT( (unsigned)roi.x < (unsigned)image->cols &&
(unsigned)roi.y < (unsigned)image->rows &&
roi.width >= 0 && roi.height >= 0 &&
roi.x + roi.width <= image->cols &&
roi.y + roi.height <= image->rows );

if( image->cols != model->size.width || image->rows != model->size.height )
{
cvClearMemStorage( model->storage );
model->freeList = 0;
cvFree( &model->cbmap );
int bufSz = image->cols*image->rows*sizeof(model->cbmap[0]);
model->cbmap = (CvBGCodeBookElem**)cvAlloc(bufSz);
memset( model->cbmap, 0, bufSz );
model->size = cvSize(image->cols, image->rows);
}

icvInitSatTab();

cb0 = model->cbBounds[0];
cb1 = model->cbBounds[1];
cb2 = model->cbBounds[2];

T = ++model->t;//码元更新时间即使用次数
freeList = model->freeList;
nblocks = (int)((model->storage->block_size - sizeof(CvMemBlock))/sizeof(*freeList));
nblocks = MIN( nblocks, 1024 );
CV_ASSERT( nblocks > 0 );
//遍历一副图像中所有像素点
for( y = 0; y < roi.height; y++ )
{
const uchar* p = image->data.ptr + image->step*(y + roi.y) + roi.x*3;
const uchar* m = mask ? mask->data.ptr + mask->step*(y + roi.y) + roi.x : 0;
CvBGCodeBookElem** cb = model->cbmap + image->cols*(y + roi.y) + roi.x;

for( x = 0; x < roi.width; x++, p += 3, cb++ )
{
CvBGCodeBookElem *e, *found = 0;
uchar p0, p1, p2, l0, l1, l2, h0, h1, h2;
int negRun;

if( m && m[x] == 0 )
continue;

p0 = p[0]; p1 = p[1]; p2 = p[2];
l0 = SAT_8U(p0 - cb0); l1 = SAT_8U(p1 - cb1); l2 = SAT_8U(p2 - cb2);
h0 = SAT_8U(p0 + cb0); h1 = SAT_8U(p1 + cb1); h2 = SAT_8U(p2 + cb2);

for( e = *cb; e != 0; e = e->next )
{  //  如果满足ILow≤I(x,y)≤IHigh(介于学习上下界之间)
if( e->learnMin[0] <= p0 && p0 <= e->learnMax[0] &&
e->learnMin[1] <= p1 && p1 <= e->learnMax[1] &&
e->learnMin[2] <= p2 && p2 <= e->learnMax[2] )
{//上述原理描述(4)部分
e->tLastUpdate = T;
e->boxMin[0] = MIN(e->boxMin[0], p0);
e->boxMax[0] = MAX(e->boxMax[0], p0);
e->boxMin[1] = MIN(e->boxMin[1], p1);
e->boxMax[1] = MAX(e->boxMax[1], p1);
e->boxMin[2] = MIN(e->boxMin[2], p2);
e->boxMax[2] = MAX(e->boxMax[2], p2);

// no need to use SAT_8U for updated learnMin[i] & learnMax[i] here,
// as the bounding li & hi are already within 0..255.
//算法原理描述步骤(5),更新学习上下界
if( e->learnMin[0] > l0 ) e->learnMin[0]--;
if( e->learnMax[0] < h0 ) e->learnMax[0]++;
if( e->learnMin[1] > l1 ) e->learnMin[1]--;
if( e->learnMax[1] < h1 ) e->learnMax[1]++;
if( e->learnMin[2] > l2 ) e->learnMin[2]--;
if( e->learnMax[2] < h2 ) e->learnMax[2]++;

found = e;
break;
}
//记录不更新时间
negRun = T - e->tLastUpdate;
e->stale = MAX( e->stale, negRun );
}

for( ; e != 0; e = e->next )
{
negRun = T - e->tLastUpdate;
e->stale = MAX( e->stale, negRun );
}
//若无匹配,创建新码元(原理描述步骤(3))
if( !found )
{
if( !freeList )
{
freeList = (CvBGCodeBookElem*)cvMemStorageAlloc(model->storage,
nblocks*sizeof(*freeList));
for( i = 0; i < nblocks-1; i++ )
freeList[i].next = &freeList[i+1];
freeList[nblocks-1].next = 0;
}
e = freeList;
freeList = freeList->next;

e->learnMin[0] = l0; e->learnMax[0] = h0;
e->learnMin[1] = l1; e->learnMax[1] = h1;
e->learnMin[2] = l2; e->learnMax[2] = h2;
e->boxMin[0] = e->boxMax[0] = p0;
e->boxMin[1] = e->boxMax[1] = p1;
e->boxMin[2] = e->boxMax[2] = p2;
e->tLastUpdate = T;
e->stale = 0;
e->next = *cb;
*cb = e;
}
}
}

model->freeList = freeList;

__END__;
}


2)码元精简

void cvBGCodeBookClearStale( CvBGCodeBookModel* model, int staleThresh,
CvRect roi, const CvArr* _mask )
{
CV_FUNCNAME( "cvBGCodeBookClearStale" );

__BEGIN__;

CvMat mstub, *mask = _mask ? cvGetMat( _mask, &mstub ) : 0;
int x, y, T;
CvBGCodeBookElem* freeList;

CV_ASSERT( model && (!mask || (CV_IS_MASK_ARR(mask) &&
mask->cols == model->size.width && mask->rows == model->size.height)) );

if( roi.x == 0 && roi.y == 0 && roi.width == 0 && roi.height == 0 )
{
roi.width = model->size.width;
roi.height = model->size.height;
}
else
CV_ASSERT( (unsigned)roi.x < (unsigned)mask->cols &&
(unsigned)roi.y < (unsigned)mask->rows &&
roi.width >= 0 && roi.height >= 0 &&
roi.x + roi.width <= mask->cols &&
roi.y + roi.height <= mask->rows );

icvInitSatTab();
freeList = model->freeList;
T = model->t;

for( y = 0; y < roi.height; y++ )
{
const uchar* m = mask ? mask->data.ptr + mask->step*(y + roi.y) + roi.x : 0;
CvBGCodeBookElem** cb = model->cbmap + model->size.width*(y + roi.y) + roi.x;

for( x = 0; x < roi.width; x++, cb++ )
{
CvBGCodeBookElem *e, first, *prev = &first;

if( m && m[x] == 0 )
continue;
//遍历所有像素点的码本模型
for( first.next = e = *cb; e != 0; e = prev->next )
{    //超出更新时间阈值
if( e->stale > staleThresh )
{
prev->next = e->next;//下一个码元
e->next = freeList;
freeList = e;//清空该码元
}
else
{
e->stale = 0;
e->tLastUpdate = T;
prev = e;
}
}

*cb = first.next;
}
}

model->freeList = freeList;

__END__;
}


3)背景检测

int cvBGCodeBookDiff( const CvBGCodeBookModel* model, const CvArr* _image,
CvArr* _fgmask, CvRect roi )
{
int maskCount = -1;

CV_FUNCNAME( "cvBGCodeBookDiff" );

__BEGIN__;

CvMat stub, *image = cvGetMat( _image, &stub );
CvMat mstub, *mask = cvGetMat( _fgmask, &mstub );
int x, y;
uchar m0, m1, m2, M0, M1, M2;

CV_ASSERT( model && CV_MAT_TYPE(image->type) == CV_8UC3 &&
image->cols == model->size.width && image->rows == model->size.height &&
CV_IS_MASK_ARR(mask) && CV_ARE_SIZES_EQ(image, mask) );

if( roi.x == 0 && roi.y == 0 && roi.width == 0 && roi.height == 0 )
{
roi.width = image->cols;
roi.height = image->rows;
}
else
CV_ASSERT( (unsigned)roi.x < (unsigned)image->cols &&
(unsigned)roi.y < (unsigned)image->rows &&
roi.width >= 0 && roi.height >= 0 &&
roi.x + roi.width <= image->cols &&
roi.y + roi.height <= image->rows );
//检测前景的上下界
m0 = model->modMin[0]; M0 = model->modMax[0];
m1 = model->modMin[1]; M1 = model->modMax[1];
m2 = model->modMin[2]; M2 = model->modMax[2];

maskCount = roi.height*roi.width;
for( y = 0; y < roi.height; y++ )
{
const uchar* p = image->data.ptr + image->step*(y + roi.y) + roi.x*3;
uchar* m = mask->data.ptr + mask->step*(y + roi.y) + roi.x;
CvBGCodeBookElem** cb = model->cbmap + image->cols*(y + roi.y) + roi.x;

for( x = 0; x < roi.width; x++, p += 3, cb++ )
{
CvBGCodeBookElem *e;
uchar p0 = p[0], p1 = p[1], p2 = p[2];
int l0 = p0 + m0, l1 = p1 + m1, l2 = p2 + m2;
int h0 = p0 - M0, h1 = p1 - M1, h2 = p2 - M2;
m[x] = (uchar)255;

for( e = *cb; e != 0; e = e->next )
{   //背景检测原理描述,存在码本模型满足得I(x,y) < max + maxMod && I(x,y) > min – minMod
if( e->boxMin[0] <= l0 && h0 <= e->boxMax[0] &&
e->boxMin[1] <= l1 && h1 <= e->boxMax[1] &&
e->boxMin[2] <= l2 && h2 <= e->boxMax[2] )
{
//掩码标记为0,判断为背景
m[x] = 0;
maskCount--;
break;
}
}
}
}

__END__;

return maskCount;
}


备注:对算法的理解是一回事,算法实现过程中也会遇到一些问题,平时用matlab较多,现在努力学习C++,学习开源库中优秀的算法编写能力。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: