版权声明:本文为博主原创文章,未经博主允许不得转载。

原文:http://blog.csdn.net/zcube/article/details/7353941

56帧时

63帧时

/**
比平均背景法性能更加良好的方法,codeBook模型实现背景减除

核心代码详细解析和实现 by zcube
*/

[cpp] view plaincopy
  1. /************************************************************************/
  2. /*          A few more thoughts on codebook models
  3. In general, the codebook method works quite well across a wide number of conditions,
  4. and it is relatively quick to train and to run. It doesn’t deal well with varying patterns of
  5. light — such as morning, noon, and evening sunshine — or with someone turning lights
  6. on or off indoors. This type of global variability can be taken into account by using
  7. several different codebook models, one for each condition, and then allowing the condition
  8. to control which model is active.                                       */
  9. /************************************************************************/
  10. #include "stdafx.h"
  11. #include <cv.h>
  12. #include <highgui.h>
  13. #include <cxcore.h>
  14. #define CHANNELS 3
  15. // 设置处理的图像通道数,要求小于等于图像本身的通道数
  16. ///
  17. // 下面为码本码元的数据结构
  18. // 处理图像时每个像素对应一个码本,每个码本中可有若干个码元
  19. // 当涉及一个新领域,通常会遇到一些奇怪的名词,不要被这些名词吓坏,其实思路都是简单的
  20. typedef struct ce {
  21. uchar   learnHigh[CHANNELS];    // High side threshold for learning
  22. // 此码元各通道的阀值上限(学习界限)
  23. uchar   learnLow[CHANNELS];     // Low side threshold for learning
  24. // 此码元各通道的阀值下限
  25. // 学习过程中如果一个新像素各通道值x[i],均有 learnLow[i]<=x[i]<=learnHigh[i],则该像素可合并于此码元
  26. uchar   max[CHANNELS];          // High side of box boundary
  27. // 属于此码元的像素中各通道的最大值
  28. uchar   min[CHANNELS];          // Low side of box boundary
  29. // 属于此码元的像素中各通道的最小值
  30. int     t_last_update;          // This is book keeping to allow us to kill stale entries
  31. // 此码元最后一次更新的时间,每一帧为一个单位时间,用于计算stale
  32. int     stale;                  // max negative run (biggest period of inactivity)
  33. // 此码元最长不更新时间,用于删除规定时间不更新的码元,精简码本
  34. } code_element;                     // 码元的数据结构
  35. typedef struct code_book {
  36. code_element    **cb;
  37. // 码元的二维指针,理解为指向码元指针数组的指针,使得添加码元时不需要来回复制码元,只需要简单的指针赋值即可
  38. int             numEntries;
  39. // 此码本中码元的数目
  40. int             t;              // count every access
  41. // 此码本现在的时间,一帧为一个时间单位
  42. } codeBook;                         // 码本的数据结构
  43. ///
  44. // int updateCodeBook(uchar *p, codeBook &c, unsigned cbBounds)
  45. // Updates the codebook entry with a new data point
  46. //
  47. // p            Pointer to a YUV pixel
  48. // c            Codebook for this pixel
  49. // cbBounds     Learning bounds for codebook (Rule of thumb: 10)
  50. // numChannels  Number of color channels we're learning
  51. //
  52. // NOTES:
  53. //      cvBounds must be of size cvBounds[numChannels]
  54. //
  55. // RETURN
  56. //  codebook index
  57. int cvupdateCodeBook(uchar *p, codeBook &c, unsigned *cbBounds, int numChannels)
  58. {
  59. if(c.numEntries == 0) c.t = 0;
  60. // 码本中码元为零时初始化时间为0
  61. c.t += 1;   // Record learning event
  62. // 每调用一次加一,即每一帧图像加一
  63. //SET HIGH AND LOW BOUNDS
  64. int n;
  65. unsigned int high[3],low[3];
  66. for (n=0; n<numChannels; n++)
  67. {
  68. high[n] = *(p+n) + *(cbBounds+n);
  69. // *(p+n) 和 p[n] 结果等价,经试验*(p+n) 速度更快
  70. if(high[n] > 255) high[n] = 255;
  71. low[n] = *(p+n)-*(cbBounds+n);
  72. if(low[n] < 0) low[n] = 0;
  73. // 用p 所指像素通道数据,加减cbBonds中数值,作为此像素阀值的上下限
  74. }
  75. //SEE IF THIS FITS AN EXISTING CODEWORD
  76. int matchChannel;
  77. int i;
  78. for (i=0; i<c.numEntries; i++)
  79. {
  80. // 遍历此码本每个码元,测试p像素是否满足其中之一
  81. matchChannel = 0;
  82. for (n=0; n<numChannels; n++)
  83. //遍历每个通道
  84. {
  85. if((c.cb[i]->learnLow[n] <= *(p+n)) && (*(p+n) <= c.cb[i]->learnHigh[n])) //Found an entry for this channel
  86. // 如果p 像素通道数据在该码元阀值上下限之间
  87. {
  88. matchChannel++;
  89. }
  90. }
  91. if (matchChannel == numChannels)        // If an entry was found over all channels
  92. // 如果p 像素各通道都满足上面条件
  93. {
  94. c.cb[i]->t_last_update = c.t;
  95. // 更新该码元时间为当前时间
  96. // adjust this codeword for the first channel
  97. for (n=0; n<numChannels; n++)
  98. //调整该码元各通道最大最小值
  99. {
  100. if (c.cb[i]->max[n] < *(p+n))
  101. c.cb[i]->max[n] = *(p+n);
  102. else if (c.cb[i]->min[n] > *(p+n))
  103. c.cb[i]->min[n] = *(p+n);
  104. }
  105. break;
  106. }
  107. }
  108. // ENTER A NEW CODE WORD IF NEEDED
  109. if(i == c.numEntries)  // No existing code word found, make a new one
  110. // p 像素不满足此码本中任何一个码元,下面创建一个新码元
  111. {
  112. code_element **foo = new code_element* [c.numEntries+1];
  113. // 申请c.numEntries+1 个指向码元的指针
  114. for(int ii=0; ii<c.numEntries; ii++)
  115. // 将前c.numEntries 个指针指向已存在的每个码元
  116. foo[ii] = c.cb[ii];
  117. foo[c.numEntries] = new code_element;
  118. // 申请一个新的码元
  119. if(c.numEntries) delete [] c.cb;
  120. // 删除c.cb 指针数组
  121. c.cb = foo;
  122. // 把foo 头指针赋给c.cb
  123. for(n=0; n<numChannels; n++)
  124. // 更新新码元各通道数据
  125. {
  126. c.cb[c.numEntries]->learnHigh[n] = high[n];
  127. c.cb[c.numEntries]->learnLow[n] = low[n];
  128. c.cb[c.numEntries]->max[n] = *(p+n);
  129. c.cb[c.numEntries]->min[n] = *(p+n);
  130. }
  131. c.cb[c.numEntries]->t_last_update = c.t;
  132. c.cb[c.numEntries]->stale = 0;
  133. c.numEntries += 1;
  134. }
  135. // OVERHEAD TO TRACK POTENTIAL STALE ENTRIES
  136. for(int s=0; s<c.numEntries; s++)
  137. {
  138. // This garbage is to track which codebook entries are going stale
  139. int negRun = c.t - c.cb[s]->t_last_update;
  140. // 计算该码元的不更新时间
  141. if(c.cb[s]->stale < negRun)
  142. c.cb[s]->stale = negRun;
  143. }
  144. // SLOWLY ADJUST LEARNING BOUNDS
  145. for(n=0; n<numChannels; n++)
  146. // 如果像素通道数据在高低阀值范围内,但在码元阀值之外,则缓慢调整此码元学习界限
  147. {
  148. if(c.cb[i]->learnHigh[n] < high[n])
  149. c.cb[i]->learnHigh[n] += 1;
  150. if(c.cb[i]->learnLow[n] > low[n])
  151. c.cb[i]->learnLow[n] -= 1;
  152. }
  153. return(i);
  154. }
  155. ///
  156. // uchar cvbackgroundDiff(uchar *p, codeBook &c, int minMod, int maxMod)
  157. // Given a pixel and a code book, determine if the pixel is covered by the codebook
  158. //
  159. // p        pixel pointer (YUV interleaved)
  160. // c        codebook reference
  161. // numChannels  Number of channels we are testing
  162. // maxMod   Add this (possibly negative) number onto max level when code_element determining if new pixel is foreground
  163. // minMod   Subract this (possible negative) number from min level code_element when determining if pixel is foreground
  164. //
  165. // NOTES:
  166. // minMod and maxMod must have length numChannels, e.g. 3 channels => minMod[3], maxMod[3].
  167. //
  168. // Return
  169. // 0 => background, 255 => foreground
  170. uchar cvbackgroundDiff(uchar *p, codeBook &c, int numChannels, int *minMod, int *maxMod)
  171. {
  172. // 下面步骤和背景学习中查找码元如出一辙
  173. int matchChannel;
  174. //SEE IF THIS FITS AN EXISTING CODEWORD
  175. int i;
  176. for (i=0; i<c.numEntries; i++)
  177. {
  178. matchChannel = 0;
  179. for (int n=0; n<numChannels; n++)
  180. {
  181. if ((c.cb[i]->min[n] - minMod[n] <= *(p+n)) && (*(p+n) <= c.cb[i]->max[n] + maxMod[n]))
  182. matchChannel++; //Found an entry for this channel
  183. else
  184. break;
  185. }
  186. if (matchChannel == numChannels)
  187. break; //Found an entry that matched all channels
  188. }
  189. if(i == c.numEntries)
  190. // p像素各通道值满足码本中其中一个码元,则返回白色
  191. return(255);
  192. return(0);
  193. }
  194. //UTILITES/
  195. /
  196. //int clearStaleEntries(codeBook &c)
  197. // After you've learned for some period of time, periodically call this to clear out stale codebook entries
  198. //
  199. //c     Codebook to clean up
  200. //
  201. // Return
  202. // number of entries cleared
  203. int cvclearStaleEntries(codeBook &c)
  204. {
  205. int staleThresh = c.t >> 1;           // 设定刷新时间
  206. int *keep = new int [c.numEntries]; // 申请一个标记数组
  207. int keepCnt = 0;                    // 记录不删除码元数目
  208. //SEE WHICH CODEBOOK ENTRIES ARE TOO STALE
  209. for (int i=0; i<c.numEntries; i++)
  210. // 遍历码本中每个码元
  211. {
  212. if (c.cb[i]->stale > staleThresh)
  213. // 如码元中的不更新时间大于设定的刷新时间,则标记为删除
  214. keep[i] = 0; //Mark for destruction
  215. else
  216. {
  217. keep[i] = 1; //Mark to keep
  218. keepCnt += 1;
  219. }
  220. }
  221. // KEEP ONLY THE GOOD
  222. c.t = 0;                        //Full reset on stale tracking
  223. // 码本时间清零
  224. code_element **foo = new code_element* [keepCnt];
  225. // 申请大小为keepCnt 的码元指针数组
  226. int k=0;
  227. for(int ii=0; ii<c.numEntries; ii++)
  228. {
  229. if(keep[ii])
  230. {
  231. foo[k] = c.cb[ii];
  232. foo[k]->stale = 0;       //We have to refresh these entries for next clearStale
  233. foo[k]->t_last_update = 0;
  234. k++;
  235. }
  236. }
  237. //CLEAN UP
  238. delete [] keep;
  239. delete [] c.cb;
  240. c.cb = foo;
  241. // 把foo 头指针地址赋给c.cb
  242. int numCleared = c.numEntries - keepCnt;
  243. // 被清理的码元个数
  244. c.numEntries = keepCnt;
  245. // 剩余的码元地址
  246. return(numCleared);
  247. }
  248. int main()
  249. {
  250. ///
  251. // 需要使用的变量
  252. CvCapture*  capture;
  253. IplImage*   rawImage;
  254. IplImage*   yuvImage;
  255. IplImage*   ImaskCodeBook;
  256. codeBook*   cB;
  257. unsigned    cbBounds[CHANNELS];
  258. uchar*      pColor; //YUV pointer
  259. int         imageLen;
  260. int         nChannels = CHANNELS;
  261. int         minMod[CHANNELS];
  262. int         maxMod[CHANNELS];
  263. //
  264. // 初始化各变量
  265. cvNamedWindow("Raw");
  266. cvNamedWindow("CodeBook");
  267. capture = cvCreateFileCapture("tree.avi");
  268. if (!capture)
  269. {
  270. printf("Couldn't open the capture!");
  271. return -1;
  272. }
  273. rawImage = cvQueryFrame(capture);
  274. yuvImage = cvCreateImage(cvGetSize(rawImage), 8, 3);
  275. // 给yuvImage 分配一个和rawImage 尺寸相同,8位3通道图像
  276. ImaskCodeBook = cvCreateImage(cvGetSize(rawImage), IPL_DEPTH_8U, 1);
  277. // 为ImaskCodeBook 分配一个和rawImage 尺寸相同,8位单通道图像
  278. cvSet(ImaskCodeBook, cvScalar(255));
  279. // 设置单通道数组所有元素为255,即初始化为白色图像
  280. imageLen = rawImage->width * rawImage->height;
  281. cB = new codeBook[imageLen];
  282. // 得到与图像像素数目长度一样的一组码本,以便对每个像素进行处理
  283. for (int i=0; i<imageLen; i++)
  284. // 初始化每个码元数目为0
  285. cB[i].numEntries = 0;
  286. for (int i=0; i<nChannels; i++)
  287. {
  288. cbBounds[i] = 10;   // 用于确定码元各通道的阀值
  289. minMod[i]   = 20;   // 用于背景差分函数中
  290. maxMod[i]   = 20;   // 调整其值以达到最好的分割
  291. }
  292. //
  293. // 开始处理视频每一帧图像
  294. for (int i=0;;i++)
  295. {
  296. cvCvtColor(rawImage, yuvImage, CV_BGR2YCrCb);
  297. // 色彩空间转换,将rawImage 转换到YUV色彩空间,输出到yuvImage
  298. // 即使不转换效果依然很好
  299. // yuvImage = cvCloneImage(rawImage);
  300. if (i <= 30)
  301. // 30帧内进行背景学习
  302. {
  303. pColor = (uchar *)(yuvImage->imageData);
  304. // 指向yuvImage 图像的通道数据
  305. for (int c=0; c<imageLen; c++)
  306. {
  307. cvupdateCodeBook(pColor, cB[c], cbBounds, nChannels);
  308. // 对每个像素,调用此函数,捕捉背景中相关变化图像
  309. pColor += 3;
  310. // 3 通道图像, 指向下一个像素通道数据
  311. }
  312. if (i == 30)
  313. // 到30 帧时调用下面函数,删除码本中陈旧的码元
  314. {
  315. for (int c=0; c<imageLen; c++)
  316. cvclearStaleEntries(cB[c]);
  317. }
  318. }
  319. else
  320. {
  321. uchar maskPixelCodeBook;
  322. pColor = (uchar *)((yuvImage)->imageData); //3 channel yuv image
  323. uchar *pMask = (uchar *)((ImaskCodeBook)->imageData); //1 channel image
  324. // 指向ImaskCodeBook 通道数据序列的首元素
  325. for(int c=0; c<imageLen; c++)
  326. {
  327. maskPixelCodeBook = cvbackgroundDiff(pColor, cB[c], nChannels, minMod, maxMod);
  328. // 我看到这儿时豁然开朗,开始理解了codeBook 呵呵
  329. *pMask++ = maskPixelCodeBook;
  330. pColor += 3;
  331. // pColor 指向的是3通道图像
  332. }
  333. }
  334. if (!(rawImage = cvQueryFrame(capture)))
  335. break;
  336. cvShowImage("Raw", rawImage);
  337. cvShowImage("CodeBook", ImaskCodeBook);
  338. if (cvWaitKey(30) == 27)
  339. break;
  340. if (i == 56 || i == 63)
  341. cvWaitKey();
  342. }
  343. cvReleaseCapture(&capture);
  344. if (yuvImage)
  345. cvReleaseImage(&yuvImage);
  346. if(ImaskCodeBook)
  347. cvReleaseImage(&ImaskCodeBook);
  348. cvDestroyAllWindows();
  349. delete [] cB;
  350. return 0;
  351. }

版权声明:本文为博主原创文章,未经博主允许不得转载。

56帧时

63帧时

/**
比平均背景法性能更加良好的方法,codeBook模型实现背景减除

核心代码详细解析和实现 by zcube
*/

[cpp] view plaincopy
  1. /************************************************************************/
  2. /*          A few more thoughts on codebook models
  3. In general, the codebook method works quite well across a wide number of conditions,
  4. and it is relatively quick to train and to run. It doesn’t deal well with varying patterns of
  5. light — such as morning, noon, and evening sunshine — or with someone turning lights
  6. on or off indoors. This type of global variability can be taken into account by using
  7. several different codebook models, one for each condition, and then allowing the condition
  8. to control which model is active.                                       */
  9. /************************************************************************/
  10. #include "stdafx.h"
  11. #include <cv.h>
  12. #include <highgui.h>
  13. #include <cxcore.h>
  14. #define CHANNELS 3
  15. // 设置处理的图像通道数,要求小于等于图像本身的通道数
  16. ///
  17. // 下面为码本码元的数据结构
  18. // 处理图像时每个像素对应一个码本,每个码本中可有若干个码元
  19. // 当涉及一个新领域,通常会遇到一些奇怪的名词,不要被这些名词吓坏,其实思路都是简单的
  20. typedef struct ce {
  21. uchar   learnHigh[CHANNELS];    // High side threshold for learning
  22. // 此码元各通道的阀值上限(学习界限)
  23. uchar   learnLow[CHANNELS];     // Low side threshold for learning
  24. // 此码元各通道的阀值下限
  25. // 学习过程中如果一个新像素各通道值x[i],均有 learnLow[i]<=x[i]<=learnHigh[i],则该像素可合并于此码元
  26. uchar   max[CHANNELS];          // High side of box boundary
  27. // 属于此码元的像素中各通道的最大值
  28. uchar   min[CHANNELS];          // Low side of box boundary
  29. // 属于此码元的像素中各通道的最小值
  30. int     t_last_update;          // This is book keeping to allow us to kill stale entries
  31. // 此码元最后一次更新的时间,每一帧为一个单位时间,用于计算stale
  32. int     stale;                  // max negative run (biggest period of inactivity)
  33. // 此码元最长不更新时间,用于删除规定时间不更新的码元,精简码本
  34. } code_element;                     // 码元的数据结构
  35. typedef struct code_book {
  36. code_element    **cb;
  37. // 码元的二维指针,理解为指向码元指针数组的指针,使得添加码元时不需要来回复制码元,只需要简单的指针赋值即可
  38. int             numEntries;
  39. // 此码本中码元的数目
  40. int             t;              // count every access
  41. // 此码本现在的时间,一帧为一个时间单位
  42. } codeBook;                         // 码本的数据结构
  43. ///
  44. // int updateCodeBook(uchar *p, codeBook &c, unsigned cbBounds)
  45. // Updates the codebook entry with a new data point
  46. //
  47. // p            Pointer to a YUV pixel
  48. // c            Codebook for this pixel
  49. // cbBounds     Learning bounds for codebook (Rule of thumb: 10)
  50. // numChannels  Number of color channels we're learning
  51. //
  52. // NOTES:
  53. //      cvBounds must be of size cvBounds[numChannels]
  54. //
  55. // RETURN
  56. //  codebook index
  57. int cvupdateCodeBook(uchar *p, codeBook &c, unsigned *cbBounds, int numChannels)
  58. {
  59. if(c.numEntries == 0) c.t = 0;
  60. // 码本中码元为零时初始化时间为0
  61. c.t += 1;   // Record learning event
  62. // 每调用一次加一,即每一帧图像加一
  63. //SET HIGH AND LOW BOUNDS
  64. int n;
  65. unsigned int high[3],low[3];
  66. for (n=0; n<numChannels; n++)
  67. {
  68. high[n] = *(p+n) + *(cbBounds+n);
  69. // *(p+n) 和 p[n] 结果等价,经试验*(p+n) 速度更快
  70. if(high[n] > 255) high[n] = 255;
  71. low[n] = *(p+n)-*(cbBounds+n);
  72. if(low[n] < 0) low[n] = 0;
  73. // 用p 所指像素通道数据,加减cbBonds中数值,作为此像素阀值的上下限
  74. }
  75. //SEE IF THIS FITS AN EXISTING CODEWORD
  76. int matchChannel;
  77. int i;
  78. for (i=0; i<c.numEntries; i++)
  79. {
  80. // 遍历此码本每个码元,测试p像素是否满足其中之一
  81. matchChannel = 0;
  82. for (n=0; n<numChannels; n++)
  83. //遍历每个通道
  84. {
  85. if((c.cb[i]->learnLow[n] <= *(p+n)) && (*(p+n) <= c.cb[i]->learnHigh[n])) //Found an entry for this channel
  86. // 如果p 像素通道数据在该码元阀值上下限之间
  87. {
  88. matchChannel++;
  89. }
  90. }
  91. if (matchChannel == numChannels)        // If an entry was found over all channels
  92. // 如果p 像素各通道都满足上面条件
  93. {
  94. c.cb[i]->t_last_update = c.t;
  95. // 更新该码元时间为当前时间
  96. // adjust this codeword for the first channel
  97. for (n=0; n<numChannels; n++)
  98. //调整该码元各通道最大最小值
  99. {
  100. if (c.cb[i]->max[n] < *(p+n))
  101. c.cb[i]->max[n] = *(p+n);
  102. else if (c.cb[i]->min[n] > *(p+n))
  103. c.cb[i]->min[n] = *(p+n);
  104. }
  105. break;
  106. }
  107. }
  108. // ENTER A NEW CODE WORD IF NEEDED
  109. if(i == c.numEntries)  // No existing code word found, make a new one
  110. // p 像素不满足此码本中任何一个码元,下面创建一个新码元
  111. {
  112. code_element **foo = new code_element* [c.numEntries+1];
  113. // 申请c.numEntries+1 个指向码元的指针
  114. for(int ii=0; ii<c.numEntries; ii++)
  115. // 将前c.numEntries 个指针指向已存在的每个码元
  116. foo[ii] = c.cb[ii];
  117. foo[c.numEntries] = new code_element;
  118. // 申请一个新的码元
  119. if(c.numEntries) delete [] c.cb;
  120. // 删除c.cb 指针数组
  121. c.cb = foo;
  122. // 把foo 头指针赋给c.cb
  123. for(n=0; n<numChannels; n++)
  124. // 更新新码元各通道数据
  125. {
  126. c.cb[c.numEntries]->learnHigh[n] = high[n];
  127. c.cb[c.numEntries]->learnLow[n] = low[n];
  128. c.cb[c.numEntries]->max[n] = *(p+n);
  129. c.cb[c.numEntries]->min[n] = *(p+n);
  130. }
  131. c.cb[c.numEntries]->t_last_update = c.t;
  132. c.cb[c.numEntries]->stale = 0;
  133. c.numEntries += 1;
  134. }
  135. // OVERHEAD TO TRACK POTENTIAL STALE ENTRIES
  136. for(int s=0; s<c.numEntries; s++)
  137. {
  138. // This garbage is to track which codebook entries are going stale
  139. int negRun = c.t - c.cb[s]->t_last_update;
  140. // 计算该码元的不更新时间
  141. if(c.cb[s]->stale < negRun)
  142. c.cb[s]->stale = negRun;
  143. }
  144. // SLOWLY ADJUST LEARNING BOUNDS
  145. for(n=0; n<numChannels; n++)
  146. // 如果像素通道数据在高低阀值范围内,但在码元阀值之外,则缓慢调整此码元学习界限
  147. {
  148. if(c.cb[i]->learnHigh[n] < high[n])
  149. c.cb[i]->learnHigh[n] += 1;
  150. if(c.cb[i]->learnLow[n] > low[n])
  151. c.cb[i]->learnLow[n] -= 1;
  152. }
  153. return(i);
  154. }
  155. ///
  156. // uchar cvbackgroundDiff(uchar *p, codeBook &c, int minMod, int maxMod)
  157. // Given a pixel and a code book, determine if the pixel is covered by the codebook
  158. //
  159. // p        pixel pointer (YUV interleaved)
  160. // c        codebook reference
  161. // numChannels  Number of channels we are testing
  162. // maxMod   Add this (possibly negative) number onto max level when code_element determining if new pixel is foreground
  163. // minMod   Subract this (possible negative) number from min level code_element when determining if pixel is foreground
  164. //
  165. // NOTES:
  166. // minMod and maxMod must have length numChannels, e.g. 3 channels => minMod[3], maxMod[3].
  167. //
  168. // Return
  169. // 0 => background, 255 => foreground
  170. uchar cvbackgroundDiff(uchar *p, codeBook &c, int numChannels, int *minMod, int *maxMod)
  171. {
  172. // 下面步骤和背景学习中查找码元如出一辙
  173. int matchChannel;
  174. //SEE IF THIS FITS AN EXISTING CODEWORD
  175. int i;
  176. for (i=0; i<c.numEntries; i++)
  177. {
  178. matchChannel = 0;
  179. for (int n=0; n<numChannels; n++)
  180. {
  181. if ((c.cb[i]->min[n] - minMod[n] <= *(p+n)) && (*(p+n) <= c.cb[i]->max[n] + maxMod[n]))
  182. matchChannel++; //Found an entry for this channel
  183. else
  184. break;
  185. }
  186. if (matchChannel == numChannels)
  187. break; //Found an entry that matched all channels
  188. }
  189. if(i == c.numEntries)
  190. // p像素各通道值满足码本中其中一个码元,则返回白色
  191. return(255);
  192. return(0);
  193. }
  194. //UTILITES/
  195. /
  196. //int clearStaleEntries(codeBook &c)
  197. // After you've learned for some period of time, periodically call this to clear out stale codebook entries
  198. //
  199. //c     Codebook to clean up
  200. //
  201. // Return
  202. // number of entries cleared
  203. int cvclearStaleEntries(codeBook &c)
  204. {
  205. int staleThresh = c.t >> 1;           // 设定刷新时间
  206. int *keep = new int [c.numEntries]; // 申请一个标记数组
  207. int keepCnt = 0;                    // 记录不删除码元数目
  208. //SEE WHICH CODEBOOK ENTRIES ARE TOO STALE
  209. for (int i=0; i<c.numEntries; i++)
  210. // 遍历码本中每个码元
  211. {
  212. if (c.cb[i]->stale > staleThresh)
  213. // 如码元中的不更新时间大于设定的刷新时间,则标记为删除
  214. keep[i] = 0; //Mark for destruction
  215. else
  216. {
  217. keep[i] = 1; //Mark to keep
  218. keepCnt += 1;
  219. }
  220. }
  221. // KEEP ONLY THE GOOD
  222. c.t = 0;                        //Full reset on stale tracking
  223. // 码本时间清零
  224. code_element **foo = new code_element* [keepCnt];
  225. // 申请大小为keepCnt 的码元指针数组
  226. int k=0;
  227. for(int ii=0; ii<c.numEntries; ii++)
  228. {
  229. if(keep[ii])
  230. {
  231. foo[k] = c.cb[ii];
  232. foo[k]->stale = 0;       //We have to refresh these entries for next clearStale
  233. foo[k]->t_last_update = 0;
  234. k++;
  235. }
  236. }
  237. //CLEAN UP
  238. delete [] keep;
  239. delete [] c.cb;
  240. c.cb = foo;
  241. // 把foo 头指针地址赋给c.cb
  242. int numCleared = c.numEntries - keepCnt;
  243. // 被清理的码元个数
  244. c.numEntries = keepCnt;
  245. // 剩余的码元地址
  246. return(numCleared);
  247. }
  248. int main()
  249. {
  250. ///
  251. // 需要使用的变量
  252. CvCapture*  capture;
  253. IplImage*   rawImage;
  254. IplImage*   yuvImage;
  255. IplImage*   ImaskCodeBook;
  256. codeBook*   cB;
  257. unsigned    cbBounds[CHANNELS];
  258. uchar*      pColor; //YUV pointer
  259. int         imageLen;
  260. int         nChannels = CHANNELS;
  261. int         minMod[CHANNELS];
  262. int         maxMod[CHANNELS];
  263. //
  264. // 初始化各变量
  265. cvNamedWindow("Raw");
  266. cvNamedWindow("CodeBook");
  267. capture = cvCreateFileCapture("tree.avi");
  268. if (!capture)
  269. {
  270. printf("Couldn't open the capture!");
  271. return -1;
  272. }
  273. rawImage = cvQueryFrame(capture);
  274. yuvImage = cvCreateImage(cvGetSize(rawImage), 8, 3);
  275. // 给yuvImage 分配一个和rawImage 尺寸相同,8位3通道图像
  276. ImaskCodeBook = cvCreateImage(cvGetSize(rawImage), IPL_DEPTH_8U, 1);
  277. // 为ImaskCodeBook 分配一个和rawImage 尺寸相同,8位单通道图像
  278. cvSet(ImaskCodeBook, cvScalar(255));
  279. // 设置单通道数组所有元素为255,即初始化为白色图像
  280. imageLen = rawImage->width * rawImage->height;
  281. cB = new codeBook[imageLen];
  282. // 得到与图像像素数目长度一样的一组码本,以便对每个像素进行处理
  283. for (int i=0; i<imageLen; i++)
  284. // 初始化每个码元数目为0
  285. cB[i].numEntries = 0;
  286. for (int i=0; i<nChannels; i++)
  287. {
  288. cbBounds[i] = 10;   // 用于确定码元各通道的阀值
  289. minMod[i]   = 20;   // 用于背景差分函数中
  290. maxMod[i]   = 20;   // 调整其值以达到最好的分割
  291. }
  292. //
  293. // 开始处理视频每一帧图像
  294. for (int i=0;;i++)
  295. {
  296. cvCvtColor(rawImage, yuvImage, CV_BGR2YCrCb);
  297. // 色彩空间转换,将rawImage 转换到YUV色彩空间,输出到yuvImage
  298. // 即使不转换效果依然很好
  299. // yuvImage = cvCloneImage(rawImage);
  300. if (i <= 30)
  301. // 30帧内进行背景学习
  302. {
  303. pColor = (uchar *)(yuvImage->imageData);
  304. // 指向yuvImage 图像的通道数据
  305. for (int c=0; c<imageLen; c++)
  306. {
  307. cvupdateCodeBook(pColor, cB[c], cbBounds, nChannels);
  308. // 对每个像素,调用此函数,捕捉背景中相关变化图像
  309. pColor += 3;
  310. // 3 通道图像, 指向下一个像素通道数据
  311. }
  312. if (i == 30)
  313. // 到30 帧时调用下面函数,删除码本中陈旧的码元
  314. {
  315. for (int c=0; c<imageLen; c++)
  316. cvclearStaleEntries(cB[c]);
  317. }
  318. }
  319. else
  320. {
  321. uchar maskPixelCodeBook;
  322. pColor = (uchar *)((yuvImage)->imageData); //3 channel yuv image
  323. uchar *pMask = (uchar *)((ImaskCodeBook)->imageData); //1 channel image
  324. // 指向ImaskCodeBook 通道数据序列的首元素
  325. for(int c=0; c<imageLen; c++)
  326. {
  327. maskPixelCodeBook = cvbackgroundDiff(pColor, cB[c], nChannels, minMod, maxMod);
  328. // 我看到这儿时豁然开朗,开始理解了codeBook 呵呵
  329. *pMask++ = maskPixelCodeBook;
  330. pColor += 3;
  331. // pColor 指向的是3通道图像
  332. }
  333. }
  334. if (!(rawImage = cvQueryFrame(capture)))
  335. break;
  336. cvShowImage("Raw", rawImage);
  337. cvShowImage("CodeBook", ImaskCodeBook);
  338. if (cvWaitKey(30) == 27)
  339. break;
  340. if (i == 56 || i == 63)
  341. cvWaitKey();
  342. }
  343. cvReleaseCapture(&capture);
  344. if (yuvImage)
  345. cvReleaseImage(&yuvImage);
  346. if(ImaskCodeBook)
  347. cvReleaseImage(&ImaskCodeBook);
  348. cvDestroyAllWindows();
  349. delete [] cB;
  350. return 0;
  351. }

codebook 背景减除相关推荐

  1. [转载]背景减除法综述

    原文地址:背景减除法综述 作者:高大鹏 文献[1]和专利[2]描述了一种算法叫VIBE,将在后面详述. 背景差分图像解决问题的方法是:通过用现在的图像去对比已知的观察图像(背景图像),该观察图像不含有 ...

  2. 背景减除(Background Segment)

    原文:https://blog.csdn.net/Anderson_Y/article/details/82082095 背景减除(Background Segment) 写在前面 1. 高斯背景建模 ...

  3. 运动检测 背景减除(Background Segment)

    背景减除(Background Segment) https://blog.csdn.net/Anderson_Y/article/details/82082095 https://blog.csdn ...

  4. 目标检测与转自背景减除

    背景减除 对背景建模,然后进行背景减除剩下前景视作所求的目标,也是目标检测的一类方法.背景模型的巨大变化即意味着目标移动. 帧间差分是背景减除中的一个经典算法.Wren等人提出用3D高斯函数对固定背景 ...

  5. 【OpenCV】视频/图像背景减除方法

      背景减除法(Background subtraction)常用于通过静态摄像头生成一个前景掩码,即场景中移动物体的二进制图像. 代码示例 #include "opencv2/imgcod ...

  6. 目标检测、追踪梳理:帧差法、光流法、背景减除法

    (0)总览 运动目标检测是指在序列图像中检测出变化区域并将运动目标从背景图像(背景层)中提取出来.通常情况下,目标分类.跟踪和行为理解等后处理过程仅仅考虑图像中对应于运动目标的像素区域(前景层),因此 ...

  7. 基于opencv的BackgroundSubtractorMOG2和BackgroundSubtractorKNN通过背景减除来实现目标追踪

    背景减除(Background Subtraction)是许多基于计算机视觉的任务中的主要预处理步骤.如果我们有完整的静止的背景帧,那么我们可以通过帧差法来计算像素差从而获取到前景对象.但是在大多数情 ...

  8. 背景减除算法之K-Nearest(KNN)和Mixture of Gaussians(MOG2)

    Python版本:3.5.2,Opencv版本:3.2.0,网上安装教程很多,在此不再赘述 MOG2算法,即高斯混合模型分离算法,是MOG的改进算法.它基于Z.Zivkovic发布的两篇论文,即200 ...

  9. 视频处理:帧差法、光流法和背景减除法的视频目标识别

    视频处理:帧差法.光流法和背景减除法的视频目标识别视频处理:帧差法.光流法和背景减除法的视频目标识别视频处理:帧差法.光流法和背景减除法的视频目标识别 1.调用摄像头 函数1:cv2.VideoCap ...

  10. 【计算机视觉】基于自组织背景减除的运动目标检测算法

    本文根据M. Lucia等人的论文"A self-Organizing approach to background subtraction for visual surveillance ...

最新文章

  1. jQuery 1.9 移除了 $.browser 的替代方法
  2. Bio-protocol与Cell Research达成合作:共同提升科研的可重复性
  3. python 线性回归 统计检验 p值_SPSS 25 数学统计分析工具
  4. 裸机篇 -- S5PV210的中断体系
  5. 《机器学习》 第 5 章 神经网络
  6. CryptoAPI与openssl RSA非对称加密解密(PKCS1 PADDING)交互
  7. Python istitle() 方法
  8. OpenGL 几何着色器Geometry Shader
  9. Airbnb数据科学团队进化论:如何由内而外实现数据驱动
  10. HTML5 Web Worker
  11. 基于气动人工肌肉的双足机器人关节设计
  12. 设置UIImage的边框和圆角大小以及颜色
  13. Visual Studio Code的设置及插件同步
  14. 报表软件FineReport如何连接SAP HANA
  15. 一种使用Python计算可达矩阵的简单方法
  16. 零基础小白入行3D建模,首先!你要了解什么叫建模!
  17. Method called after release()
  18. windows 通过快捷键来复制粘贴当前日期时间到剪贴板
  19. 针式PKM,不是简单的“文档管理器”
  20. Python笔记 No.1 - Python函数及装饰器

热门文章

  1. 卡巴斯基实验室解析勒索软件的发展与攻防
  2. 计算机常见故障判断方法,电脑故障判断-计算机常见故障判断方法
  3. html chm如何打开方式,解答chm文件如何打开
  4. JavaScript替换字符串中括号里的内容
  5. 如何在Nintendo交换机上设置家长控制
  6. 左程云 Java 笔记--图
  7. 闸机常用通讯协议(韦根,485等),或者开关量输出(继电器)直接控制
  8. 界面画好了如何开发软件_如何做儿童类APP?来看英语流利说的实战经验总结!...
  9. Python解决同一台如何调用多台usb打印机
  10. 谷歌浏览器导入插件教程