codebook背景建模

摘要  codebook的建模效果比平均背景法好很多,建模过程中可以适应运动。CodeBook算法的基本思想是得到每个像素的时间序列模型。这种模型能很好地处理时间起伏,缺点是需要消耗大量的内存。

导读

《Learning OpenCV》一书当中介绍的第二种背景建模方法是codebook。直接通过书本来理解codebook算法有点困难,可以按照下面的顺序来理解codebook算法,首先看看百度百科上对这个算法的基本原理的阐述,我认为百度百科上的描述已经比较直观,但当中有很多细节的东西还需要看具体的代码,所以可以通过细读下面转载的代码来理解codebook算法,理解代码的过程需要有点耐心,先看main函数,理解程序大致的流程,再仔细看看cvupdateCodeBook()、cvclearStaleEntries()、cvbackgroundDiff()这三个函数,看懂了代码之后就应该能够理解这个算法了 。下面阐述的基本原理部分来自于百度百科,已经阐述得比较直观。代码来自于网友的博文http://blog.csdn.net/zcube/article/details/7353941

基本原理

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),否则结束。

参考代码

代码来自于网友的博文http://blog.csdn.net/zcube/article/details/7353941

001 /************************************************************************/
002 /*          A few more thoughts on codebook models
003 In general, the codebook method works quite well across a wide number of conditions,
004 and it is relatively quick to train and to run. It doesn’t deal well with varying patterns of
005 light — such as morning, noon, and evening sunshine — or with someone turning lights
006 on or off indoors. This type of global variability can be taken into account by using
007 several different codebook models, one for each condition, and then allowing the condition
008 to control which model is active.                                       */
009 /************************************************************************/
010  
011 #include "stdafx.h"
012 #include <cv.h>          
013 #include <highgui.h>
014 #include <cxcore.h>
015  
016 #define CHANNELS 3     
017 // 设置处理的图像通道数,要求小于等于图像本身的通道数
018  
019 ///
020 // 下面为码本码元的数据结构
021 // 处理图像时每个像素对应一个码本,每个码本中可有若干个码元
022 // 当涉及一个新领域,通常会遇到一些奇怪的名词,不要被这些名词吓坏,其实思路都是简单的
023 typedef struct ce {
024     uchar   learnHigh[CHANNELS];    // High side threshold for learning
025     // 此码元各通道的阀值上限(学习界限)
026     uchar   learnLow[CHANNELS];     // Low side threshold for learning
027     // 此码元各通道的阀值下限
028     // 学习过程中如果一个新像素各通道值x[i],均有 learnLow[i]<=x[i]<=learnHigh[i],则该像素可合并于此码元
029     uchar   max[CHANNELS];          // High side of box boundary
030     // 属于此码元的像素中各通道的最大值
031     uchar   min[CHANNELS];          // Low side of box boundary
032     // 属于此码元的像素中各通道的最小值
033     int     t_last_update;          // This is book keeping to allow us to kill stale entries
034     // 此码元最后一次更新的时间,每一帧为一个单位时间,用于计算stale
035     int     stale;                  // max negative run (biggest period of inactivity)
036     // 此码元最长不更新时间,用于删除规定时间不更新的码元,精简码本
037 } code_element;                     // 码元的数据结构
038  
039 typedef struct code_book {
040     code_element    **cb;
041     // 码元的二维指针,理解为指向码元指针数组的指针,使得添加码元时不需要来回复制码元,只需要简单的指针赋值即可
042     int             numEntries;
043     // 此码本中码元的数目
044     int             t;              // count every access
045     // 此码本现在的时间,一帧为一个时间单位
046 } codeBook;                         // 码本的数据结构
047  
048  
049 ///
050 // int updateCodeBook(uchar *p, codeBook &c, unsigned cbBounds)
051 // Updates the codebook entry with a new data point
052 //
053 // p            Pointer to a YUV pixel
054 // c            Codebook for this pixel
055 // cbBounds     Learning bounds for codebook (Rule of thumb: 10)
056 // numChannels  Number of color channels we're learning
057 //
058 // NOTES:
059 //      cvBounds must be of size cvBounds[numChannels]
060 //
061 // RETURN
062 //  codebook index
063 int cvupdateCodeBook(uchar *p, codeBook &c, unsigned *cbBounds, int numChannels)
064 {
065     if(c.numEntries == 0) c.t = 0;
066     // 码本中码元为零时初始化时间为0
067     c.t += 1;   // Record learning event
068     // 每调用一次加一,即每一帧图像加一
069      
070     //SET HIGH AND LOW BOUNDS
071     int n;
072     unsigned int high[3],low[3];
073     for (n=0; n<numChannels; n++)
074     {
075         high[n] = *(p+n) + *(cbBounds+n);
076         // *(p+n) 和 p[n] 结果等价,经试验*(p+n) 速度更快
077         if(high[n] > 255) high[n] = 255;
078         low[n] = *(p+n)-*(cbBounds+n);
079         if(low[n] < 0) low[n] = 0;
080         // 用p 所指像素通道数据,加减cbBonds中数值,作为此像素阀值的上下限
081     }
082  
083     //SEE IF THIS FITS AN EXISTING CODEWORD
084     int matchChannel;  
085     int i;
086     for (i=0; i<c.numEntries; i++)
087     {
088         // 遍历此码本每个码元,测试p像素是否满足其中之一
089         matchChannel = 0;
090         for (n=0; n<numChannels; n++)
091             //遍历每个通道
092         {
093             if((c.cb[i]->learnLow[n] <= *(p+n)) && (*(p+n) <= c.cb[i]->learnHigh[n])) //Found an entry for this channel
094             // 如果p 像素通道数据在该码元阀值上下限之间
095             {  
096                 matchChannel++;
097             }
098         }
099         if (matchChannel == numChannels)        // If an entry was found over all channels
100             // 如果p 像素各通道都满足上面条件
101         {
102             c.cb[i]->t_last_update = c.t;
103             // 更新该码元时间为当前时间
104             // adjust this codeword for the first channel
105             for (n=0; n<numChannels; n++)
106                 //调整该码元各通道最大最小值
107             {
108                 if (c.cb[i]->max[n] < *(p+n))
109                     c.cb[i]->max[n] = *(p+n);
110                 else if (c.cb[i]->min[n] > *(p+n))
111                     c.cb[i]->min[n] = *(p+n);
112             }
113             break;
114         }
115     }
116  
117     // ENTER A NEW CODE WORD IF NEEDED
118     if(i == c.numEntries)  // No existing code word found, make a new one
119     // p 像素不满足此码本中任何一个码元,下面创建一个新码元
120     {
121         code_element **foo = new code_element* [c.numEntries+1];
122         // 申请c.numEntries+1 个指向码元的指针
123         for(int ii=0; ii<c.numEntries; ii++)
124             // 将前c.numEntries 个指针指向已存在的每个码元
125             foo[ii] = c.cb[ii];
126          
127         foo[c.numEntries] = new code_element;
128         // 申请一个新的码元
129         if(c.numEntries) delete [] c.cb;
130         // 删除c.cb 指针数组
131         c.cb = foo;
132         // 把foo 头指针赋给c.cb
133         for(n=0; n<numChannels; n++)
134             // 更新新码元各通道数据
135         {
136             c.cb[c.numEntries]->learnHigh[n] = high[n];
137             c.cb[c.numEntries]->learnLow[n] = low[n];
138             c.cb[c.numEntries]->max[n] = *(p+n);
139             c.cb[c.numEntries]->min[n] = *(p+n);
140         }
141         c.cb[c.numEntries]->t_last_update = c.t;
142         c.cb[c.numEntries]->stale = 0;
143         c.numEntries += 1;
144     }
145  
146     // OVERHEAD TO TRACK POTENTIAL STALE ENTRIES
147     for(int s=0; s<c.numEntries; s++)
148     {
149         // This garbage is to track which codebook entries are going stale
150         int negRun = c.t - c.cb[s]->t_last_update;
151         // 计算该码元的不更新时间
152         if(c.cb[s]->stale < negRun)
153             c.cb[s]->stale = negRun;
154     }
155  
156     // SLOWLY ADJUST LEARNING BOUNDS
157     for(n=0; n<numChannels; n++)
158         // 如果像素通道数据在高低阀值范围内,但在码元阀值之外,则缓慢调整此码元学习界限
159     {
160         if(c.cb[i]->learnHigh[n] < high[n])
161             c.cb[i]->learnHigh[n] += 1;
162         if(c.cb[i]->learnLow[n] > low[n])
163             c.cb[i]->learnLow[n] -= 1;
164     }
165  
166     return(i);
167 }
168  
169 ///
170 // uchar cvbackgroundDiff(uchar *p, codeBook &c, int minMod, int maxMod)
171 // Given a pixel and a code book, determine if the pixel is covered by the codebook
172 //
173 // p        pixel pointer (YUV interleaved)
174 // c        codebook reference
175 // numChannels  Number of channels we are testing
176 // maxMod   Add this (possibly negative) number onto max level when code_element determining if new pixel is foreground
177 // minMod   Subract this (possible negative) number from min level code_element when determining if pixel is foreground
178 //
179 // NOTES:
180 // minMod and maxMod must have length numChannels, e.g. 3 channels => minMod[3], maxMod[3].
181 //
182 // Return
183 // 0 => background, 255 => foreground
184 uchar cvbackgroundDiff(uchar *p, codeBook &c, int numChannels, int *minMod, int *maxMod)
185 {
186     // 下面步骤和背景学习中查找码元如出一辙
187     int matchChannel;
188     //SEE IF THIS FITS AN EXISTING CODEWORD
189     int i;
190     for (i=0; i<c.numEntries; i++)
191     {
192         matchChannel = 0;
193         for (int n=0; n<numChannels; n++)
194         {
195             if ((c.cb[i]->min[n] - minMod[n] <= *(p+n)) && (*(p+n) <= c.cb[i]->max[n] + maxMod[n]))
196                 matchChannel++; //Found an entry for this channel
197             else
198                 break;
199         }
200         if (matchChannel == numChannels)
201             break//Found an entry that matched all channels
202     }
203     if(i == c.numEntries)
204         return(255);
205         //p像素各通道值满足码本中其中一个码元,则返回黑色
206     return(0);
207 }
208  
209  
210 //UTILITES/
211 /
212 //int clearStaleEntries(codeBook &c)
213 // After you've learned for some period of time, periodically call this to clear out stale codebook entries
214 //
215 //c     Codebook to clean up
216 //
217 // Return
218 // number of entries cleared
219 int cvclearStaleEntries(codeBook &c)
220 {
221     int staleThresh = c.t >> 1;           // 设定刷新时间
222     int *keep = new int [c.numEntries]; // 申请一个标记数组
223     int keepCnt = 0;                    // 记录不删除码元数目
224     //SEE WHICH CODEBOOK ENTRIES ARE TOO STALE
225     for (int i=0; i<c.numEntries; i++)
226         // 遍历码本中每个码元
227     {
228         if (c.cb[i]->stale > staleThresh)
229             // 如码元中的不更新时间大于设定的刷新时间,则标记为删除
230             keep[i] = 0; //Mark for destruction
231         else
232         {
233             keep[i] = 1; //Mark to keep
234             keepCnt += 1;
235         }
236     }
237  
238     // KEEP ONLY THE GOOD
239     c.t = 0;                        //Full reset on stale tracking
240     // 码本时间清零
241     code_element **foo = new code_element* [keepCnt];
242     // 申请大小为keepCnt 的码元指针数组
243     int k=0;
244     for(int ii=0; ii<c.numEntries; ii++)
245     {
246         if(keep[ii])
247         {
248             foo[k] = c.cb[ii];
249             foo[k]->stale = 0;       //We have to refresh these entries for next clearStale
250             foo[k]->t_last_update = 0;
251             k++;
252         }
253     }
254     //CLEAN UP
255     delete [] keep;
256     delete [] c.cb;
257     c.cb = foo;
258     // 把foo 头指针地址赋给c.cb
259     int numCleared = c.numEntries - keepCnt;
260     // 被清理的码元个数
261     c.numEntries = keepCnt;
262     // 剩余的码元地址
263     return(numCleared);
264 }
265  
266  
267  
268 int main()
269 {
270     ///
271     // 需要使用的变量
272     CvCapture*  capture;
273     IplImage*   rawImage;
274     IplImage*   yuvImage;
275     IplImage*   ImaskCodeBook;
276     codeBook*   cB;
277     unsigned    cbBounds[CHANNELS];
278     uchar*      pColor; //YUV pointer
279     int         imageLen;
280     int         nChannels = CHANNELS;
281     int         minMod[CHANNELS];
282     int         maxMod[CHANNELS];
283      
284     //
285     // 初始化各变量
286     cvNamedWindow("Raw");
287     cvNamedWindow("CodeBook");
288  
289     capture = cvCreateFileCapture("tree.avi");
290     if (!capture)
291     {
292         printf("Couldn't open the capture!");
293         return -1;
294     }
295  
296     rawImage = cvQueryFrame(capture);
297     yuvImage = cvCreateImage(cvGetSize(rawImage), 8, 3);   
298     // 给yuvImage 分配一个和rawImage 尺寸相同,8位3通道图像
299     ImaskCodeBook = cvCreateImage(cvGetSize(rawImage), IPL_DEPTH_8U, 1);
300     // 为ImaskCodeBook 分配一个和rawImage 尺寸相同,8位单通道图像
301     cvSet(ImaskCodeBook, cvScalar(255));
302     // 设置单通道数组所有元素为255,即初始化为白色图像
303      
304     imageLen = rawImage->width * rawImage->height;
305     cB = new codeBook[imageLen];
306     // 得到与图像像素数目长度一样的一组码本,以便对每个像素进行处理
307      
308     for (int i=0; i<imageLen; i++)
309         // 初始化每个码元数目为0
310         cB[i].numEntries = 0;
311     for (int i=0; i<nChannels; i++)
312     {
313         cbBounds[i] = 10;   // 用于确定码元各通道的阀值
314  
315         minMod[i]   = 20;   // 用于背景差分函数中
316         maxMod[i]   = 20;   // 调整其值以达到最好的分割
317     }
318          
319      
320     //
321     // 开始处理视频每一帧图像
322     for (int i=0;;i++)
323     {
324         cvCvtColor(rawImage, yuvImage, CV_BGR2YCrCb);
325         // 色彩空间转换,将rawImage 转换到YUV色彩空间,输出到yuvImage
326         // 即使不转换效果依然很好
327         // yuvImage = cvCloneImage(rawImage);
328  
329         if (i <= 30)
330             // 30帧内进行背景学习
331         {
332             pColor = (uchar *)(yuvImage->imageData);
333             // 指向yuvImage 图像的通道数据
334             for (int c=0; c<imageLen; c++)
335             {
336                 cvupdateCodeBook(pColor, cB[c], cbBounds, nChannels);
337                 // 对每个像素,调用此函数,捕捉背景中相关变化图像
338                 pColor += 3;
339                 // 3 通道图像, 指向下一个像素通道数据
340             }
341             if (i == 30)
342                 // 到30 帧时调用下面函数,删除码本中陈旧的码元
343             {
344                 for (int c=0; c<imageLen; c++)
345                     cvclearStaleEntries(cB[c]);
346             }
347         }
348         else
349         {
350             uchar maskPixelCodeBook;
351             pColor = (uchar *)((yuvImage)->imageData); //3 channel yuv image
352             uchar *pMask = (uchar *)((ImaskCodeBook)->imageData); //1 channel image
353             // 指向ImaskCodeBook 通道数据序列的首元素
354             for(int c=0; c<imageLen; c++)
355             {
356                 maskPixelCodeBook = cvbackgroundDiff(pColor, cB[c], nChannels, minMod, maxMod);
357                 // 我看到这儿时豁然开朗,开始理解了codeBook 呵呵
358                 *pMask++ = maskPixelCodeBook;
359                 pColor += 3;
360                 // pColor 指向的是3通道图像
361             }
362         }
363         if (!(rawImage = cvQueryFrame(capture)))
364             break;
365         cvShowImage("Raw", rawImage);
366         cvShowImage("CodeBook", ImaskCodeBook);
367  
368         if (cvWaitKey(30) == 27)
369             break;
370         if (i == 56 || i == 63)
371             cvWaitKey();
372     }  
373      
374     cvReleaseCapture(&capture);
375     if (yuvImage)
376         cvReleaseImage(&yuvImage);
377     if(ImaskCodeBook)
378         cvReleaseImage(&ImaskCodeBook);
379     cvDestroyAllWindows();
380     delete [] cB;
381  
382     return 0;
383 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值