1,CodeBook算法流程介绍

  CodeBook算法的基本思想是得到每个像素的时间序列模型。这种模型能很好地处理时间起伏,缺点是需要消耗大量的内存。CodeBook算法为当前图像的每一个像素建立一个CodeBook(CB)结构,每个CodeBook结构又由多个CodeWord(CW)组成。
  CB和CW的形式如下:
  CB={CW1,CW2,…CWn,t}
  CW={lHigh,lLow,max,min,t_last,stale}
  其中n为一个CB中所包含的CW的数目,当n太小时,退化为简单背景,当n较大时可以对复杂背景进行建模;t为CB更新的次数。CW是一个6元组,其中IHigh和ILow作为更新时的学习上下界,max和min记录当前像素的最大值和最小值。上次更新的时间t_last和陈旧时间stale(记录该CW多久未被访问)用来删除很少使用的CodeWord。
假设当前训练图像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,若当前像素值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。
  使用已建立好的CB进行运动目标检测的方法很简单,记判断前景的范围上下界为minMod和maxMod,对于当前待检测图像上的某一像素I(x,y),遍历它对应像素背景模型CB中的每一个码字CW,若存在一个CW,使得I(x,y) < max + maxMod并且I(x,y) > min – minMod,则I(x,y)被判断为背景,否则被判断为前景。
  在实际使用CodeBook进行运动检测时,除了要隔一定的时间对CB进行更新的同时,需要对CB进行一个时间滤波,目的是去除很少被访问到的CW,其方法是访问每个CW的stale,若stale大于一个阈值(通常设置为总更新次数的一半),移除该CW。
  综上所述,CodeBook算法检测运动目标的流程如下:
  (1) 选择一帧到多帧使用更新算法建立CodeBook背景模型;
  (2) 按上面所述方法检测前景(运动目标);
  (3) 间隔一定时间使用更新算法更新CodeBook模型,并对CodeBook进行时间滤波;
  (4) 若检测继续,转(2),否则结束。

2,基于OpenCV2.x的代码

  codebookWithOpenCV2.h

#ifndef ___CODEBOOKWITHOPENCV2_H___
#define ___CODEBOOKWITHOPENCV2_H___ #include <iostream>
#include <list>
#include <opencv2/opencv.hpp> #define CHANNELS 3 using namespace std;
using namespace cv; class CodeWord
{
public:
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)
// 此码元最长不更新时间,用于删除规定时间不更新的码元,精简码本 }; class CodeBook
{
public:
list<CodeWord> codeElement;
//考虑到方便的删除旧码元节点,同时访问码元的时候只需要顺序遍历,选用STL中的List,避免用指针。
int numEntries;
// 此码本中码元的数目
int t; // count every access
// 此码本现在的时间,一帧为一个时间单位 // 码本的数据结构
}; class BackgroundSubtractorCodeBook
{
public: BackgroundSubtractorCodeBook();
void updateCodeBook(const Mat &inputImage);//背景建模
void initialize(const Mat &inputImagee,Mat &outputImage);//获取第一帧,进行初始化
void clearStaleEntries();//清除旧的码元
void backgroudDiff(const Mat &inputImage,Mat &outputImage);//背景前景判断
~BackgroundSubtractorCodeBook(); private: Mat yuvImage;
Mat maskImage;
CodeBook* codebookVec; //本来想用Vector,不用数组指针,但是codebook定长,不用增减,vector也不好初始化。
unsigned cbBounds[CHANNELS]; //背景建模时,用于建立学习的上下界限
uchar* pColor; //yuvImage pointer 通道指针
uchar* pMask;// maskImage pointer
int imageSize;
int nChannels;
int minMod[CHANNELS]; //判断背景或者前景所用调节阈值
int maxMod[CHANNELS]; uchar maskPixelCodeBook;
void _updateCodeBookPerPixel(int pixelIndex);
void _clearStaleEntriesPerPixel(int pixelIndex);
uchar _backgroundDiff(int pixelIndex);
}; #endif

  codebookWithOpenCV2.cpp

#include "codebookWithOpenCV2.h"

BackgroundSubtractorCodeBook::BackgroundSubtractorCodeBook()
{
  nChannels=CHANNELS;
} //初始化maskImage,codebook数组,将每个像素的codebook的码元个数置0,初始化建模阈值和分割阈值
void BackgroundSubtractorCodeBook::initialize(const Mat &inputRGBImage,Mat &outputImage)
{
if (inputRGBImage.empty())
{
return ;
}
if (yuvImage.empty())
{
yuvImage.create(inputRGBImage.size(),inputRGBImage.type());
}
if (maskImage.empty())
{
maskImage.create(inputRGBImage.size(),CV_8UC1);
Mat temp(inputRGBImage.rows,inputRGBImage.cols,CV_8UC1,Scalar::all());
maskImage=temp; }
imageSize=inputRGBImage.cols*inputRGBImage.rows; codebookVec=new CodeBook[imageSize];
for (int i=;i<imageSize;++i)
{
codebookVec[i].numEntries=;
}//初始化码元个数
for (int i=; i<nChannels; i++)
{
cbBounds[i] = ; // 用于确定码元各通道的建模阀值
minMod[i] = ; // 用于背景差分函数中
maxMod[i] = ; // 调整其值以达到最好的分割
}
outputImage=maskImage;
} //逐个像素建模
void BackgroundSubtractorCodeBook::updateCodeBook(const Mat &inputImage)
{
cvtColor(inputImage,yuvImage,CV_RGB2YCrCb);
pColor=yuvImage.data;
for (int c=;c<imageSize;++c)
{
_updateCodeBookPerPixel(c);
pColor+=;
}
} //单个像素建模实现函数,遍历所有码元,分三通道匹配,若满足,则更新该码元的时间,最大,最小值
//若不匹配,则创建新的码元。
void BackgroundSubtractorCodeBook::_updateCodeBookPerPixel(int pixelIndex)
{
if (codebookVec[pixelIndex].numEntries==)
{
codebookVec[pixelIndex].t=;
}
codebookVec[pixelIndex].t+=; int n;
unsigned int high[],low[];
for (n=; n<nChannels; n++) //处理三通道的三个像素
{
high[n] = *(pColor+n) + *(cbBounds+n);
// *(p+n) 和 p[n] 结果等价,经试验*(p+n) 速度更快
if(high[n] > ) high[n] = ;
low[n] = *(pColor+n)-*(cbBounds+n);
if(low[n] < ) low[n] = ;
// 用p 所指像素通道数据,加减cbBonds中数值,作为此像素阀值的上下限
} int matchChannel; list<CodeWord>::iterator jList;
list<CodeWord>::iterator jListAfterPush; for (jList=codebookVec[pixelIndex].codeElement.begin();jList!=codebookVec[pixelIndex].codeElement.end();++jList)
{
// 遍历此码本每个码元,测试p像素是否满足其中之一
matchChannel = ;
for (n=; n<nChannels; n++)
//遍历每个通道
{
if(((*jList).learnLow[n]<= *(pColor+n))
&& (*(pColor+n) <= (*jList).learnHigh[n])) //Found an entry for this channel
// 如果p 像素通道数据在该码元阀值上下限之间
{
matchChannel++;
}
}
if (matchChannel == nChannels) // If an entry was found over all channels
// 如果p 像素各通道都满足上面条件
{
(*jList).t_last_update = codebookVec[pixelIndex].t;
// 更新该码元时间为当前时间
// adjust this codeword for the first channel
for (n=; n<nChannels; n++)
//调整该码元各通道最大最小值
{
if ((*jList).max[n] < *(pColor+n))
(*jList).max[n] = *(pColor+n);
else if ((*jList).min[n] > *(pColor+n))
(*jList).min[n] = *(pColor+n);
}
break;//如果满足其中一个码元,则退出循环。
}
} // ENTER A NEW CODE WORD IF NEEDED
if(jList==codebookVec[pixelIndex].codeElement.end()) // No existing code word found, make a new one
// p 像素不满足此码本中任何一个码元,下面创建一个新码元
{
CodeWord newElement;
for(n=; n<nChannels; n++)
// 更新新码元各通道数据
{
newElement.learnHigh[n] = high[n];
newElement.learnLow[n] = low[n];
newElement.max[n] = *(pColor+n);
newElement.min[n] = *(pColor+n);
}
newElement.t_last_update = codebookVec[pixelIndex].t;
newElement.stale = ;
codebookVec[pixelIndex].numEntries += ;
codebookVec[pixelIndex].codeElement.push_back(newElement);//新的码元加入链表的最后。
} // OVERHEAD TO TRACK POTENTIAL STALE ENTRIES
for (jListAfterPush=codebookVec[pixelIndex].codeElement.begin();
jListAfterPush!=codebookVec[pixelIndex].codeElement.end();++jListAfterPush)
{
// This garbage is to track which codebook entries are going stale
int negRun = codebookVec[pixelIndex].t - (*jListAfterPush).t_last_update;
// 计算该码元的不更新时间
if((*jListAfterPush).stale < negRun)
(*jListAfterPush).stale = negRun;
} // SLOWLY ADJUST LEARNING BOUNDS
//对符合的码元进行更新,刚建立的码元肯定不满足条件,不用考虑
for(n=; n<nChannels; n++)
// 如果像素通道数据在高低阀值范围内,但在码元阀值之外,则缓慢调整此码元学习界限
{
if((*jList).learnHigh[n] < high[n])
(*jList).learnHigh[n] += ;//+1是什么意思?缓慢调整?
if((*jList).learnLow[n] > low[n])
(*jList).learnLow[n] -= ;
} return;
} void BackgroundSubtractorCodeBook::clearStaleEntries()
{
for (int i=;i<imageSize;++i)
{
_clearStaleEntriesPerPixel(i);
}
} void BackgroundSubtractorCodeBook::_clearStaleEntriesPerPixel(int pixelIndex)
{
int staleThresh=codebookVec[pixelIndex].t;
for (list<CodeWord>::iterator itor=codebookVec[pixelIndex].codeElement.begin();
itor!=codebookVec[pixelIndex].codeElement.end();)
{
if ((*itor).stale>staleThresh)
{
itor=codebookVec[pixelIndex].codeElement.erase(itor);//erase之后返回被删的下一个元素的位置
}
else
{
(*itor).stale=;
(*itor).t_last_update=;
itor++;
}
}
codebookVec[pixelIndex].t=;//码本时间清零; codebookVec[pixelIndex].numEntries=(int)codebookVec[pixelIndex].codeElement.size(); return; } void BackgroundSubtractorCodeBook:: backgroudDiff(const Mat &inputImage,Mat &outputImage)
{
cvtColor(inputImage,yuvImage,CV_RGB2YCrCb);
pColor=yuvImage.data;
pMask =maskImage.data;
for(int c=; c<imageSize; c++)
{
maskPixelCodeBook = _backgroundDiff(c);
*pMask++ = maskPixelCodeBook;
pColor += ;
// pColor 指向的是3通道图像
}
outputImage=maskImage.clone();
} uchar BackgroundSubtractorCodeBook::_backgroundDiff(int pixelIndex)
{
int matchChannels;
list<CodeWord>::iterator itor;
for (itor=codebookVec[pixelIndex].codeElement.begin();
itor!=codebookVec[pixelIndex].codeElement.end();++itor)
{
matchChannels=;
for (int n=;n<nChannels;++n)
{ if (((*itor).min[n] - minMod[n] <= *(pColor+n)) && (*(pColor+n) <=(*itor).max[n] + maxMod[n]))
//相对于背景学习,这里是与码元中的最大最小值比较,并加入了余量minMod,maxMod;
matchChannels++; //Found an entry for this channel
else
break;//一个通道没匹配,直接退出
}
if (matchChannels == nChannels)
break; //Found an entry that matched all channels,确定是背景像素 返回0 黑色
}
if (itor==codebookVec[pixelIndex].codeElement.end())
{
return();
}
return ();
} BackgroundSubtractorCodeBook::~BackgroundSubtractorCodeBook()
{
delete [] codebookVec;
}

  codebookTest.cpp

#include "codebookWithOpenCV2.h"

int main()
{
VideoCapture cap;
cap.open("people.avi");
if( !cap.isOpened() )
{
printf("can not open video file\n");
return -;
} namedWindow("image", WINDOW_NORMAL);
namedWindow("foreground mask", WINDOW_NORMAL); BackgroundSubtractorCodeBook bgcbModel;
Mat inputImage,outputMaskCodebook; for(int i=;;++i)
{
cap>>inputImage;
if( inputImage.empty() )
break;
if(i==)
{
bgcbModel.initialize(inputImage,outputMaskCodebook); }
else if (i<=&&i>)
{
bgcbModel.updateCodeBook(inputImage);
if (i==)
{
bgcbModel.clearStaleEntries();
}
}
else
{
bgcbModel.backgroudDiff(inputImage,outputMaskCodebook);
}
imshow("image",inputImage);
imshow("foreground mask",outputMaskCodebook); int c = waitKey();
if (c == 'q' || c == 'Q' || (c & ) == )
break;
} return ;
}

  代码是参考如下翻译过来的。

/************************************************************************/
/* A few more thoughts on codebook models
In general, the codebook method works quite well across a wide number of conditions,
and it is relatively quick to train and to run. It doesn’t deal well with varying patterns of
light — such as morning, noon, and evening sunshine — or with someone turning lights
on or off indoors. This type of global variability can be taken into account by using
several different codebook models, one for each condition, and then allowing the condition
to control which model is active. */
/************************************************************************/ #include "stdafx.h"
#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 index
int cvupdateCodeBook(uchar *p, codeBook &c, unsigned *cbBounds, int numChannels)
{
if(c.numEntries == ) c.t = ;
// 码本中码元为零时初始化时间为0
c.t += ; // Record learning event
// 每调用一次加一,即每一帧图像加一 //SET HIGH AND LOW BOUNDS
int n;
unsigned int high[],low[];
for (n=; n<numChannels; n++)
{
high[n] = *(p+n) + *(cbBounds+n);
// *(p+n) 和 p[n] 结果等价,经试验*(p+n) 速度更快
if(high[n] > ) high[n] = ;
low[n] = *(p+n)-*(cbBounds+n);
if(low[n] < ) low[n] = ;
// 用p 所指像素通道数据,加减cbBonds中数值,作为此像素阀值的上下限
} //SEE IF THIS FITS AN EXISTING CODEWORD
int matchChannel;
int i;
for (i=; i<c.numEntries; i++)
{
// 遍历此码本每个码元,测试p像素是否满足其中之一
matchChannel = ;
for (n=; 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=; 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+];
// 申请c.numEntries+1 个指向码元的指针
for(int ii=; 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=; 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 = ;
c.numEntries += ;
} // OVERHEAD TO TRACK POTENTIAL STALE ENTRIES
for(int s=; 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=; n<numChannels; n++)
// 如果像素通道数据在高低阀值范围内,但在码元阀值之外,则缓慢调整此码元学习界限
{
if(c.cb[i]->learnHigh[n] < high[n])
c.cb[i]->learnHigh[n] += ;
if(c.cb[i]->learnLow[n] > low[n])
c.cb[i]->learnLow[n] -= ;
} 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 CODEWORD
int i;
for (i=; i<c.numEntries; i++)
{
matchChannel = ;
for (int n=; 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(); return();
} //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 >> ; // 设定刷新时间
int *keep = new int [c.numEntries]; // 申请一个标记数组
int keepCnt = ; // 记录不删除码元数目
//SEE WHICH CODEBOOK ENTRIES ARE TOO STALE
for (int i=; i<c.numEntries; i++)
// 遍历码本中每个码元
{
if (c.cb[i]->stale > staleThresh)
// 如码元中的不更新时间大于设定的刷新时间,则标记为删除
keep[i] = ; //Mark for destruction
else
{
keep[i] = ; //Mark to keep
keepCnt += ;
}
} // KEEP ONLY THE GOOD
c.t = ; //Full reset on stale tracking
// 码本时间清零
code_element **foo = new code_element* [keepCnt];
// 申请大小为keepCnt 的码元指针数组
int k=;
for(int ii=; ii<c.numEntries; ii++)
{
if(keep[ii])
{
foo[k] = c.cb[ii];
foo[k]->stale = ; //We have to refresh these entries for next clearStale
foo[k]->t_last_update = ;
k++;
}
}
//CLEAN UP
delete [] keep;
delete [] c.cb;
c.cb = foo;
// 把foo 头指针地址赋给c.cb
int numCleared = c.numEntries - keepCnt;
// 被清理的码元个数
c.numEntries = keepCnt;
// 剩余的码元地址
return(numCleared);
} int main()
{
///////////////////////////////////////
// 需要使用的变量
CvCapture* capture;
IplImage* rawImage;
IplImage* yuvImage;
IplImage* ImaskCodeBook;
codeBook* cB;
unsigned cbBounds[CHANNELS];
uchar* pColor; //YUV pointer
int imageLen;
int nChannels = CHANNELS;
int minMod[CHANNELS];
int maxMod[CHANNELS]; //////////////////////////////////////////////////////////////////////////
// 初始化各变量
cvNamedWindow("Raw");
cvNamedWindow("CodeBook"); capture = cvCreateFileCapture("tree.avi");
if (!capture)
{
printf("Couldn't open the capture!");
return -;
} rawImage = cvQueryFrame(capture);
yuvImage = cvCreateImage(cvGetSize(rawImage), , );
// 给yuvImage 分配一个和rawImage 尺寸相同,8位3通道图像
ImaskCodeBook = cvCreateImage(cvGetSize(rawImage), IPL_DEPTH_8U, );
// 为ImaskCodeBook 分配一个和rawImage 尺寸相同,8位单通道图像
cvSet(ImaskCodeBook, cvScalar());
// 设置单通道数组所有元素为255,即初始化为白色图像 imageLen = rawImage->width * rawImage->height;
cB = new codeBook[imageLen];
// 得到与图像像素数目长度一样的一组码本,以便对每个像素进行处理 for (int i=; i<imageLen; i++)
// 初始化每个码元数目为0
cB[i].numEntries = ;
for (int i=; i<nChannels; i++)
{
cbBounds[i] = ; // 用于确定码元各通道的阀值 minMod[i] = ; // 用于背景差分函数中
maxMod[i] = ; // 调整其值以达到最好的分割
} //////////////////////////////////////////////////////////////////////////
// 开始处理视频每一帧图像
for (int i=;;i++)
{
cvCvtColor(rawImage, yuvImage, CV_BGR2YCrCb);
// 色彩空间转换,将rawImage 转换到YUV色彩空间,输出到yuvImage
// 即使不转换效果依然很好
// yuvImage = cvCloneImage(rawImage); if (i <= )
// 30帧内进行背景学习
{
pColor = (uchar *)(yuvImage->imageData);
// 指向yuvImage 图像的通道数据
for (int c=; c<imageLen; c++)
{
cvupdateCodeBook(pColor, cB[c], cbBounds, nChannels);
// 对每个像素,调用此函数,捕捉背景中相关变化图像
pColor += ;
// 3 通道图像, 指向下一个像素通道数据
}
if (i == )
// 到30 帧时调用下面函数,删除码本中陈旧的码元
{
for (int c=; 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=; c<imageLen; c++)
{
maskPixelCodeBook = cvbackgroundDiff(pColor, cB[c], nChannels, minMod, maxMod);
// 我看到这儿时豁然开朗,开始理解了codeBook 呵呵
*pMask++ = maskPixelCodeBook;
pColor += ;
// pColor 指向的是3通道图像
}
}
if (!(rawImage = cvQueryFrame(capture)))
break;
cvShowImage("Raw", rawImage);
cvShowImage("CodeBook", ImaskCodeBook); if (cvWaitKey() == )
break;
if (i == || i == )
cvWaitKey();
} cvReleaseCapture(&capture);
if (yuvImage)
cvReleaseImage(&yuvImage);
if(ImaskCodeBook)
cvReleaseImage(&ImaskCodeBook);
cvDestroyAllWindows();
delete [] cB; return ;
}

  另外,在stackoverflow上找到的代码,未测试,可参考。

  bgfg_cb.h

#ifndef __bgfg_cb_h__
#define __bgfg_cb_h__ //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.148.9778&rep=rep1&type=pdf
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <time.h>
using namespace cv;
using namespace std; struct codeword {
float min;
float max;
float f;
float l;
int first;
int last;
bool isStale;
};
extern int alpha ;
extern float beta ;
extern int Tdel ,Tadd , Th; void initializeCodebook(int w,int h);
void update_cb(Mat& frame);
void fg_cb(Mat& frame,Mat& fg);
#endif

bgfg_cb.cpp

#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <time.h>
#include "bgfg_cb.h"
using namespace cv;
using namespace std; vector<codeword> **cbMain;
vector<codeword> **cbCache;
int t=;
int alpha = ;//knob
float beta =;
int Tdel = ,Tadd = , Th= ;//knobs void initializeCodebook(int w,int h)
{
cbMain = new vector<codeword>*[w];
for(int i = ; i < w; ++i)
cbMain[i] = new vector<codeword>[h]; cbCache = new vector<codeword>*[w];
for(int i = ; i < w; ++i)
cbCache[i] = new vector<codeword>[h];
} void update_cb(Mat& frame)
{
if(t>) return;
for(int i=;i<frame.rows;i++)
{
for(int j=;j<frame.cols;j++)
{
int pix = frame.at<uchar>(i,j);
vector<codeword>& cm =cbMain[i][j];
bool found = false;
for(int k=;k<cm.size();k++)
{
if(cm[k].min<=pix && pix<=cm[k].max && !found)
{
found=true;
cm[k].min = ((pix-alpha)+(cm[k].f*cm[k].min))/(cm[k].f+);
cm[k].max = ((pix+alpha)+(cm[k].f*cm[k].max))/(cm[k].f+);
cm[k].l=;
cm[k].last=t;
cm[k].f++;
}else
{
cm[k].l++;
}
}
if(!found)
{
codeword n={};
n.min=max(,pix-alpha);
n.max=min(,pix+alpha);
n.f=;
n.l=;
n.first=t;
n.last=t;
cm.push_back(n);
}
}
}
t++;
} void fg_cb(Mat& frame,Mat& fg)
{
fg=Mat::zeros(frame.size(),CV_8UC1);
if(cbMain==) initializeCodebook(frame.rows,frame.cols);
if(t<)
{
update_cb(frame);
return;
} for(int i=;i<frame.rows;i++)
{
for(int j=;j<frame.cols;j++)
{
int pix = frame.at<uchar>(i,j);
vector<codeword>& cm = cbMain[i][j];
bool found = false;
for(int k=;k<cm.size();k++)
{
if(cm[k].min<=pix && pix<=cm[k].max && !found)
{
cm[k].min = ((-beta)*(pix-alpha)) + (beta*cm[k].min);
cm[k].max = ((-beta)*(pix+alpha)) + (beta*cm[k].max);
cm[k].l=;
cm[k].first=t;
cm[k].f++;
found=true;
}else
{
cm[k].l++;
}
}
cm.erase( remove_if(cm.begin(), cm.end(), [](codeword& c) { return c.l>=Tdel;} ), cm.end() );
fg.at<uchar>(i,j) = found?:;
if(found) continue;
found = false;
vector<codeword>& cc = cbCache[i][j];
for(int k=;k<cc.size();k++)
{
if(cc[k].min<=pix && pix<=cc[k].max && !found)
{
cc[k].min = ((-beta)*(pix-alpha)) + (beta*cc[k].min);
cc[k].max = ((-beta)*(pix+alpha)) + (beta*cc[k].max);
cc[k].l=;
cc[k].first=t;
cc[k].f++;
found=true;
}else
{
cc[k].l++;
}
} if(!found)
{
codeword n={};
n.min=max(,pix-alpha);
n.max=min(,pix+alpha);
n.f=;
n.l=;
n.first=t;
n.last=t;
cc.push_back(n);
} cc.erase( remove_if(cc.begin(), cc.end(), [](codeword& c) { return c.l>=Th;} ), cc.end() );
for(vector<codeword>::iterator it=cc.begin();it!=cc.end();it++)
{
if(it->f>Tadd)
{
cm.push_back(*it);
}
} cc.erase( remove_if(cc.begin(), cc.end(), [](codeword& c) { return c.f>Tadd;} ), cc.end() );
}
}
}

main.cpp

#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "bgfg_cb.h"
#include <iostream>
using namespace cv;
using namespace std;
void proc()
{
Mat frame,fg,gray;
VideoCapture cap("C:/Downloads/S2_L1.tar/S2_L1/Crowd_PETS09/S2/L1/Time_12-34/View_001/frame_%04d.jpg");
cap >> frame;
initializeCodebook(frame.rows,frame.cols);
for(;;)
{
cap>>frame;
cvtColor(frame,gray,CV_BGR2GRAY);
fg_cb(gray,fg);
Mat cc;
imshow("fg",fg);
waitKey();
}
} int main(int argc, char ** argv)
{
proc();
cin.ignore();
return ;
}

OpenCV ——背景建模之CodeBook(1)的更多相关文章

  1. OpenCV ——背景建模之CodeBook(2)

    1,CodeBook的来源 先考虑平均背景的建模方法.该方法是针对每一个像素,累积若干帧的像素值,然后计算平均值和方差,以此来建立背景模型,相当于模型的每一个像素含有两个特征值,这两个特征值只是单纯的 ...

  2. OpenCV4Android背景建模(MOG、MOG2)

    本文为作者原创,转载请注明出处(http://www.cnblogs.com/mar-q/)by 负赑屃     很久以前的笔记了,分享给大家吧...OpenCV4Android中用于背景建模的类主要 ...

  3. OpenCV笔记(6)(harris角点检测、背景建模)

    一.Harris角点 如上图所示,红色框AB都是平面,蓝色框CD都是边缘,而绿色框EF就是角点. 平面:框往X或Y抽移动,变化都很小. 边缘:框沿X或Y轴移动,其中一个变化很小,而另外一个变化比较大. ...

  4. [MOC062066]背景建模资料收集整理

    一.相关博客 背景建模相关资料收集,各个链接都已给出. 资料,不可能非常完整,以后不定期更新. -----------------切割线----------------- 这个哥们总结的非常好啊,看完 ...

  5. 背景建模技术(七):预处理(PreProcessor)模块

    预处理(PreProcessor)模块是BgsLibrary中一个必选的模块,是真正进入背景建模算法的“预处理”过程,其主要功能包括‘去模糊’.‘获得灰度图’.'应用Canny算子‘等可选模块. 下面 ...

  6. 背景建模技术(三):背景减法库(BGS Library)的基本框架与入口函数main()的功能

    背景减法库(BGS Library = background subtraction library)包含了37种背景建模算法,也是目前国际上关于背景建模技术研究最全也最权威的资料.本文将更加详细的介 ...

  7. 【背景建模】SOBS

    SOBS(self-Organizing through artificial neural networks)是一种基于自组织神经网络的背景差分算法,主要是借鉴神经网络的特性,一个网络输入节点,对应 ...

  8. 【背景建模】PbModel

    PbModel是基于概率模型的背景差分算法,其基本思想是像素点会因光照变化.运动物体经过产生多种颜色值,但是一段时间内,像素点处于静止状态的时间会比处于运动状态的时间长.因而一段时间内,像素点某个颜色 ...

  9. 【背景建模】VIBE

    ViBe是一种像素级的背景建模.前景检测算法,该算法主要不同之处是背景模型的更新策略,随机选择需要替换的像素的样本,随机选择邻域像素进行更新.在无法确定像素变化的模型时,随机的更新策略,在一定程度上可 ...

随机推荐

  1. sublime text 设置

    https://packagecontrol.io/installation#Simple 下载 php 自动补全 点击菜单栏的:Preferences: 选择:Setting-User项: 然后在大 ...

  2. CachedRowSet的用法

    String sql="select item_code from xt_dictionary_item where type_id='32' and parent_itemid='0' o ...

  3. 二十八、oracle 视图

    一.介绍视图是一张虚拟表,其内容由查询定义,同真实的表一样,视图包含一系列带有名称的列和行数据.但是,视图并不在数据库中以存储的数据值集形式存在.行和列数据来自由定义视图的查询所引用的表,并且在引用视 ...

  4. j2ee tomcat 部署学习

    J2EE基础实例demo http://www.cnblogs.com/javabin/p/3809954.html J2EE 数据库JDBC(Java Data Base Connectivity, ...

  5. vs2015下载及预览与发布

    VS2015 RC发布下载,通用Windows平台必备神器! 几个月前微软发布了Visual Studio 2015的技术预览版本,之后又先后发布了6个更新版本.如今,微软已在其官方页面中公布了最新开 ...

  6. hadoop中,combine、partition、shuffle作用分别是什么?

    combine和partition都是函数,中间的步骤应该只有shuffle! combine分为map端和reduce端,作用是把同一个key的键值对合并在一起,可以自定义的.combine函数把一 ...

  7. GOPS 2016全球运维大会 • 北京站概况

    GOPS 2016全球运维大会上海站已圆满落幕,错过上海站的朋友或许会感到一些遗憾,但是不用担心,在12月16日,GOPS 2016全球运维大会 • 北京站将隆重召开,错过上海在的朋友可以赶上北京站哦 ...

  8. Adobe Acrobat Pro 9破解

    (转载,Window8.1/64bit系统亲测可用) 1.删除C:\Program Files\Common Files\Adobe\Adobe PCD\cache\cache.db和C:\Docum ...

  9. IOS 类似于网易新闻首页新闻轮播的组件

    一.需求分析 1.可横向循环滚动新闻图片 2.滚动到对应图片时显示新闻标题 3.每张新闻图片可点击 4.有pageControl提示 5.具有控件的扩展能力 二.设计实现 1.显示图片使用SDWebI ...

  10. Mysql innodb 间隙锁

    前段时间系统老是出现insert死锁,很是纠结.经过排查发现是间隙锁!间隙锁是innodb中行锁的一种, 但是这种锁锁住的却不止一行数据,他锁住的是多行,是一个数据范围.间隙锁的主要作用是为了防止出现 ...