作者:封鹏 | 来源:互联网 | 2023-09-17 09:07
9.2-9.16:codebook
【一】 基本原理
CodeBook算法的基本思想是为每一个像素点建立一个codebook,每个codebook包含一个或者多个码元code_elements,并且随着背景像素值波动情况的不同,不同像素点所包含的码元数目不一定相同。
CodeBook算法为当前图像的每一个像素建立一个码本CodeBook(CB)结构,每个CodeBook由多个码元code_element(ce)组成。CB和ce的形式如下:
CB={**cb,numEntries,t }
ce={learnHigh, learnLow,max,min,t_last_update,stale}
其中numEntries为一个CB中所包含的ce的数目,当n太小时,退化为简单背景,当n较大时可以对复杂背景进行建模;t为CB更新的次数。ce中learnHigh和learnLow作为更新时的学习上下界,max和min记录当前像素的最大值和最小值。t_last_update表示此码元最后一次更新的时间,stale表示此码元最长不更新时间(即该ce多久未被访问),用来删除很少使用的码元,精简码本。
【二】 步骤
1 模型训练
假设当前训练图像I中某一像素 (x,y),该像素的CB的更新算法如下,另外记背景阈值的增长判定阈值为Bounds:
(1) CB的访问次数加1;
(2) 遍历CB中的每个ce,如果存在一个ce中的learnHigh,learnLow满足learnLow≤I(x,y)≤learnHigh,则转(4);
(3) 创建一个新的码字new ce加入到CB中, new ce的max与min赋值为I(x,y)&#xff0c;learnHigh <- I(x,y) &#43; Bounds&#xff0c;learnLow <- I(x,y) – Bounds&#xff0c;并且转(6)&#xff1b;
(4) 更新该码字的t_last_update&#xff0c;若当前像素值I(x,y)大于该码字的max&#xff0c;则max <- I(x,y)&#xff0c;若I(x,y)小于该码字的min&#xff0c;则min <- I(x,y)&#xff1b;
(5) 更新该码字的学习上下界&#xff0c;以增加背景模型对于复杂背景的适应能力&#xff0c;具体做法是&#xff1a;若learnHigh I(x,y) – Bounds&#xff0c;则learnLow减少1&#xff1b;
(6) 更新CB中每个ce的stale。设定刷新时间&#xff0c;遍历每个码元&#xff0c;若码元中的不更新时间大于设定的刷新时间&#xff0c;则删除&#xff1b;在刷新时间内则保持。
2 背景检测
使用已建立好的CB进行运动目标检测&#xff0c;记判断前景的范围上下界为minMod和maxMod&#xff0c;对于当前待检测图像上的某一像素I(x,y)&#xff0c;遍历它对应像素背景模型CB中的每一个码字ce&#xff0c;若存在一个ce&#xff0c;使得I(x,y) min – minMod&#xff0c;则I(x,y)被判断为背景&#xff0c;否则被判断为前景。
【三】 实验程序
个人认为《Learning opencv3》中codebook示例程序可读性差&#xff0c;网上的大多数示例程序是基于较低版本opencv的&#xff0c;找了很久找到适合的示例程序&#xff0c;做了少许修改。
#include
#include
using namespace cv;
using namespace std;
#define CHANNELS 3//码元
typedef struct ce {uchar learnHigh[CHANNELS];uchar learnLow[CHANNELS];uchar max[CHANNELS];uchar min[CHANNELS];int t_last_update;int stale;
}code_element;//码本
typedef struct code_book {code_element **cb;int numEntries;int t;
}codeBook;//每一帧每个像素调用以更新码本
int updateCodeBook(uchar *p, codeBook&c, unsigned*cbBounds, int numChannels)
{if (c.numEntries &#61;&#61; 0)c.t &#61; 0;//初始化时间c.t &#43;&#61; 1;int n;unsigned int high[3], low[3];for (n &#61; 0; n 255){high[n] &#61; 255;}low[n]&#61; *(p &#43; n) - *(cbBounds &#43; n);if (low[n]<0){low[n] &#61; 0;}}//该像素是否在码本范围之内&#xff0c;是的话更新选中码元int matchChannel;int i;for ( i &#61; 0; i learnLow[n]<&#61;*(p&#43;n)&&(*(p &#43; n)<&#61;c.cb[i]->learnHigh[n])){matchChannel&#43;&#43;;}}if (matchChannel&#61;&#61;numChannels){c.cb[i]->t_last_update &#61; c.t;for (n &#61; 0; n max[n] <*(p &#43; n)){c.cb[i]->max[n] &#61; *(p &#43; n);}else if (c.cb[i]->min[n] > *(p &#43; n)){c.cb[i]->min[n] &#61; *(p &#43; n);}}break;}}//该像素不满足任何一码元&#xff0c;创建一个新码元if (i&#61;&#61;c.numEntries){code_element **foo &#61; new code_element*[c.numEntries &#43; 1];for (int j &#61; 0; j learnHigh[n] &#61; high[n];c.cb[c.numEntries]->learnLow[n] &#61; low[n]; c.cb[c.numEntries]->max[n] &#61; *(p &#43; n);c.cb[c.numEntries]->min[n] &#61; *(p &#43; n);}c.cb[c.numEntries]->t_last_update &#61; c.t;c.cb[c.numEntries]->stale &#61; 0;c.numEntries &#43;&#61; 1;}//更新码元中不更新时间for (int j &#61; 0; j t_last_update;if (tmp>c.cb[j]->stale){c.cb[j]->stale &#61; tmp;}}//调整学习域for ( n &#61; 0; n learnHigh[n] learnHigh[n] &#43;&#61; 1;if (c.cb[i]->learnLow[n] > low[n])c.cb[i]->learnLow[n] -&#61; 1;}return (i);
}//区分背景
uchar backgroundDiff(uchar *p, codeBook&c, int numChannels, int *minMod, int *maxMod)
{int matchChannel;int i;for ( i &#61; 0; i min[n]-minMod[n]<&#61;*(p&#43;n))&&(*(p&#43;n)<&#61;c.cb[i]->max[n]&#43;maxMod[n])){matchChannel&#43;&#43;;}else break;}if (matchChannel &#61;&#61; numChannels)break;}//无码元匹配&#xff0c;则判断为前景&#xff0c;返回255白色&#xff0c;否则返回0黑色if (i &#61;&#61; c.numEntries)return(255);return (0);
}//清除长时间未更新码元
int clearStaleEntries(codeBook&c)
{int staleThresh &#61; c.t >> 1;int *keep &#61; new int[c.numEntries];int keepCnt &#61; 0;for (int i &#61; 0; i stale>staleThresh){keep[i] &#61; 0;}else{keep[i] &#61; 1;keepCnt&#43;&#43;;}}c.t &#61; 0;code_element **foo &#61; new code_element*[keepCnt];int k &#61; 0;for (int j &#61; 0; j stale &#61; 0;foo[k]->t_last_update &#61; 0;k&#43;&#43;;}}delete[]keep;delete[]c.cb;c.cb &#61; foo;int numCleared &#61; c.numEntries - keepCnt;c.numEntries &#61; keepCnt;return(numCleared);
}int main()
{VideoCapture capture;capture.open("tree.avi");Mat rawImage;capture >> rawImage;Mat yuvImage(rawImage.rows, rawImage.cols, CV_8UC3);Mat ImaskCodeBook(rawImage.rows, rawImage.cols, CV_8UC1, Scalar(255));uchar *pColor;int imageLen&#61;rawImage.rows*rawImage.cols;codeBook*cB&#61;new codeBook[imageLen];int nChannel &#61; CHANNELS;unsigned cbBound[CHANNELS];int minMod[CHANNELS];int maxMod[CHANNELS];//定义像素个码本for (int i &#61; 0; i > rawImage;if (rawImage.empty()){break;}cvtColor(rawImage, yuvImage, COLOR_BGR2YCrCb);if (i <&#61; 30){pColor &#61; (uchar *)(yuvImage.ptr(0));for (int c &#61; 0; c (0));uchar *pMask &#61; (uchar*)(ImaskCodeBook.ptr(0));for (int c &#61; 0; c }
小博客现在只有FLAG和油炸火同学在看。还是附上微信&#xff0c;万一以后有人看呢。hh
欢迎交流&#xff1a;wechat&#xff1a;l18090123402