图像处理之前景检测(三)之码本(codebook)(主要为代码升级)

article/2025/11/11 11:04:35
图像处理之前景检测(三)之码本(codebook)

      CodeBook算法的基本思想是得到每个像素的时间序列模型。这种模型能很好地处理时间起伏,缺点是需要消耗大量的内存。CodeBook算法为当前图像的每一个像素建立一个CodeBook(CB)结构,每个CodeBook结构又由多个CodeWord(CW)组成。

      具体原理讲解参考:
      野藤     opencv学习笔记(四)——codebook算法
     Bicelove    codebook算法(背景建模)的原理

基于C接口的代码实现与讲解:
       zcube    《学习openCV》例程解析 ex_9_3(codeBook模型实现背景减除)     

      这里将以上代码相应部分进行升级,变成C++接口。环境为VS2015+opencv3.2 contrib版。
        
      
/************************************************************************/
/*          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<iostream>
#include<opencv.hpp>using namespace std;
using namespace cv;#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 updateCodeBook(uchar *p, codeBook &c, unsigned *cbBounds, int numChannels)
{if (c.numEntries == 0) c.t = 0;// 码本中码元为零时初始化时间为0  c.t += 1;   // Record learning event  // 每调用一次加一,即每一帧图像加一  //SET HIGH AND LOW BOUNDS  int n;unsigned int high[3], low[3];for (n = 0; n<numChannels; n++){high[n] = *(p + n) + *(cbBounds + n);// *(p+n) 和 p[n] 结果等价,经试验*(p+n) 速度更快  if (high[n] > 255) high[n] = 255;low[n] = *(p + n) - *(cbBounds + n);if (low[n] < 0) low[n] = 0;// 用p 所指像素通道数据,加减cbBonds中数值,作为此像素阀值的上下限  }//SEE IF THIS FITS AN EXISTING CODEWORD  int matchChannel;int i;for (i = 0; i<c.numEntries; i++){// 遍历此码本每个码元,测试p像素是否满足其中之一  matchChannel = 0;for (n = 0; 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 = 0; 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 + 1];// 申请c.numEntries+1 个指向码元的指针  for (int ii = 0; 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 = 0; 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 = 0;c.numEntries += 1;}// OVERHEAD TO TRACK POTENTIAL STALE ENTRIES  for (int s = 0; 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 = 0; n<numChannels; n++)// 如果像素通道数据在高低阀值范围内,但在码元阀值之外,则缓慢调整此码元学习界限  {if (c.cb[i]->learnHigh[n] < high[n])c.cb[i]->learnHigh[n] += 1;if (c.cb[i]->learnLow[n] > low[n])c.cb[i]->learnLow[n] -= 1;}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 backgroundDiff(uchar *p, codeBook &c, int numChannels, int *minMod, int *maxMod)
{// 下面步骤和背景学习中查找码元如出一辙  int matchChannel;//SEE IF THIS FITS AN EXISTING CODEWORD  int i;for (i = 0; i<c.numEntries; i++){matchChannel = 0;for (int n = 0; 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  elsebreak;}if (matchChannel == numChannels)break; //Found an entry that matched all channels  }if (i == c.numEntries)// p像素各通道值满足码本中其中一个码元,则返回白色  return(255);return(0);
}//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 clearStaleEntries(codeBook &c)
{int staleThresh = c.t >> 1;           // 设定刷新时间  int *keep = new int[c.numEntries]; // 申请一个标记数组  int keepCnt = 0;                    // 记录不删除码元数目  //SEE WHICH CODEBOOK ENTRIES ARE TOO STALE  for (int i = 0; i<c.numEntries; i++)// 遍历码本中每个码元  {if (c.cb[i]->stale > staleThresh)// 如码元中的不更新时间大于设定的刷新时间,则标记为删除  keep[i] = 0; //Mark for destruction  else{keep[i] = 1; //Mark to keep  keepCnt += 1;}}// KEEP ONLY THE GOOD  c.t = 0;                        //Full reset on stale tracking  // 码本时间清零  code_element **foo = new code_element*[keepCnt];// 申请大小为keepCnt 的码元指针数组  int k = 0;for (int ii = 0; ii<c.numEntries; ii++){if (keep[ii]){foo[k] = c.cb[ii];foo[k]->stale = 0;       //We have to refresh these entries for next clearStale  foo[k]->t_last_update = 0;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()
{///  // 需要使用的变量  VideoCapture  capture("E://素材//2.mp4");Mat   rawImage;//Mat  yuvImage;//Mat   ImaskCodeBook;codeBook*   cB;unsigned    cbBounds[CHANNELS];uchar*      pColor; //YUV pointer  int         imageLen;int         nChannels = CHANNELS;int         minMod[CHANNELS];int         maxMod[CHANNELS];//  // 初始化各变量  namedWindow("Raw");namedWindow("CodeBook");capture >> rawImage;Mat yuvImage (rawImage.rows, rawImage.cols, 8, 3);// 给yuvImage 分配一个和rawImage 尺寸相同,8位3通道图像  Mat ImaskCodeBook(rawImage.rows, rawImage.cols, CV_8UC1, Scalar(255));// 为ImaskCodeBook 分配一个和rawImage 尺寸相同,8位单通道图像  // 设置单通道数组所有元素为255,即初始化为白色图像  imageLen = rawImage.cols * rawImage.rows;cB = new codeBook[imageLen];// 得到与图像像素数目长度一样的一组码本,以便对每个像素进行处理  for (int i = 0; i<imageLen; i++)// 初始化每个码元数目为0  cB[i].numEntries = 0;for (int i = 0; i<nChannels; i++){cbBounds[i] = 10;   // 用于确定码元各通道的阀值  minMod[i] = 20;   // 用于背景差分函数中  maxMod[i] = 20;   // 调整其值以达到最好的分割  }//  // 开始处理视频每一帧图像  for (int i = 0;; i++){capture >> rawImage;cvtColor(rawImage, yuvImage, CV_BGR2YCrCb);// 色彩空间转换,将rawImage 转换到YUV色彩空间,输出到yuvImage  // 即使不转换效果依然很好  // yuvImage = cvCloneImage(rawImage);  if (i <= 30)// 30帧内进行背景学习  {pColor = (uchar *)(yuvImage.ptr(0));// 指向yuvImage 图像的通道数据  for (int c = 0; c<imageLen; c++){updateCodeBook(pColor, cB[c], cbBounds, nChannels);// 对每个像素,调用此函数,捕捉背景中相关变化图像  pColor += 3;// 3 通道图像, 指向下一个像素通道数据  }if (i == 30)// 到30 帧时调用下面函数,删除码本中陈旧的码元  {for (int c = 0; c<imageLen; c++)clearStaleEntries(cB[c]);}}else{uchar maskPixelCodeBook;pColor = (uchar *)(yuvImage.ptr<uchar>(0)); //3 channel yuv image  uchar *pMask = (uchar *)(ImaskCodeBook.ptr(0)); //1 channel image  // 指向ImaskCodeBook 通道数据序列的首元素  for (int c = 0; c<imageLen; c++){maskPixelCodeBook = backgroundDiff(pColor, cB[c], nChannels, minMod, maxMod);// 我看到这儿时豁然开朗,开始理解了codeBook 呵呵  *pMask++ = maskPixelCodeBook;pColor += 3;// pColor 指向的是3通道图像  }}imshow("Raw", rawImage);imshow("CodeBook", ImaskCodeBook);waitKey(20);if (i ==450)waitKey(0);cout << "这是处理的第 " << i << " 帧图像数据\n\r";}delete[] cB;return 0;
}
         最终测试结果:

 源程序C接口:



 升级程序C++接口

    

    结果基本相同,没有差别。

http://chatgpt.dhexx.cn/article/DRGTnJ4G.shtml

相关文章

RIDCP: Revitalizing Real Image Dehazing via High-Quality Codebook Priors(CVPR2023)论文记录

通过高质量码本先验重塑真实图像去雾&#xff08;CVPR2023&#xff09; RIDCP: Revitalizing Real Image Dehazing via High-Quality Codebook Priors通过高质量码本先验重塑真实图像去雾&#xff08;CVPR2023&#xff09;摘要一、数据合成流程二、VQGAN 预训练&#xff1a;构建…

【AI人工智能绘画 CV】Towards Robust Blind Face Restoration with Codebook Lookup Transformer

Towards Robust Blind Face Restoration with Codebook Lookup Transformer (NeurIPS 2022) 使用 Codebook Lookup Transformer 实现稳健的盲脸修复 Blind face restoration is a highly ill-posed problem that often requires auxiliary guidance to 1) improve the mapping…

2021-11-10-Codebook理解

《Deep Visual-Semantic Quantization for Efficient Image Retrieval》中提到的codebook是什么意思&#xff1f; Specifically, each image embedding zn is quantized using a set of M codebooks C [C1, . . . , CM ], each codebook Cm contains K codewords Cm [Cm1, .…

《学习openCV》例程解析 ex_9_3(codeBook模型实现背景减除)

56帧时 63帧时 /** 比平均背景法性能更加良好的方法,codeBook模型实现背景减除 核心代码详细解析和实现 by zcube */ /************************************************************************/ /* A few more thoughts on codebook models In general, the codebook met…

“Requesting Java AST from selection“. ‘boolean com.ibm.icu.text.UTF16.isSurrogate(char)‘

记录一次问题。自从安装了spring tool&#xff08;sts&#xff09;插件后&#xff0c;在使用eclipse过程中总是弹出An internal error occurred during: "Requesting Java AST from selection". boolean com.ibm.icu.text.UTF16.isSurrogate(char)这种错误&#xff0…

详解Unicode与UTF-8、UTF-16、UTF-32.

计算机起源于美国&#xff0c;上个世纪&#xff0c;他们对英语字符与二进制位之间的关系做了统一规定&#xff0c;并制定了一套字符编码规则&#xff0c;这套编码规则被称为ASCII编码 ASCII 编码一共定义了128个字符的编码规则&#xff0c;用七位二进制表示 ( 0x00 - 0x7F ), …

解决eclipse boolean com.ibm.icu.text.UTF16.isSurrogate(char)

window > preferences > java > Editor > mark occurrences and desable : mark occurrences ...

字符集编码 Unicode UTF8 UTF16 UTF32 和BOM(Byte Order Mark)

BOM(Byte Order Mark) 在分析unicode之前,先把bom(byte order mark)说一下。 bom是unicode字符顺序的标识符号,一般以魔数(magic code)的形式出现在以Unicode字符编码的文件的开始的头部,作为该文件的编码标识。 来举个很简单的例子,在windows下新建一个文本文件,并另…

utf8与utf16转换

1.UTF8与UTF16编码转换 std::string ConvertFromUtf16ToUtf8(const std::wstring& wstr) {std::string convertedString;int requiredSize WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, 0, 0, 0, 0);if(requiredSize > 0){std::vector<char> buffer(requ…

探究乱码问题的本源:GBK,UTF8,UTF16,UTF8BOM,ASN1之间的关联

文章目录 前言一、字符集和字符编码的区别和联系二、字符集编码的发展1.单字节2.双字节3.多字节(UNICODE字符集)&#xff08;1&#xff09;UTF-8&#xff08;2&#xff09;UTF-16&#xff08;3&#xff09;UTF-32&#xff08;4&#xff09;UTF BOM 三、不同编码方式的对比分析四…

oracle字符集utf16,oracle字符集AL16UTF16改为ZHS16GBK

AL16UTF16一个汉字占3个字节 ZHS16GBK一个汉字占2个字节 sql> conn / as sysdba; sql> shutdown immediate; database closed. database dismounted. oracle instance shut down. sql> startup mount; oracle instance started. total system global area 135337420 …

字符编码的概念(UTF-8、UTF-16、UTF-32都是什么鬼)

字符集为每个字符分配了一个唯一的编号&#xff0c;通过这个编号就能找到对应的字符。在编程过程中我们经常会使用字符&#xff0c;而使用字符的前提就是把字符放入内存中&#xff0c;毫无疑问&#xff0c;放入内存中的仅仅是字符的编号&#xff0c;而不是真正的字符实体。 这就…

UTF-8 与 UTF-16编码详解

目录 一、UTF-8编码 1、UTF-8介绍 2、UTF-8是如何编码的&#xff1f; 3、上述Unicode码点值范围中十进制值127、2047、65535、2097151这几个临界值是怎么来的呢&#xff1f; 二、UTF-16编码 1、UTF-16介绍 2、UTF-16编码方式 1&#xff09;设计思路 2&#xff09;具体…

了解一下UTF-16

1)先啰嗦一下 UTF-16是一种编码格式。啥是编码格式&#xff1f;就是怎么存储&#xff0c;也就是存储的方式。 存储啥&#xff1f;存二进制数字。为啥要存二进制数字&#xff1f; 因为Unicode字符集里面把二进制数字和字符一一对应了&#xff0c;存二进制数字就相当于存了二进制…

字符编码--UTF-16

2019独角兽企业重金招聘Python工程师标准>>> 第4节 UTF-16 UTF-16是Unicode字符编码五层次模型的第三层&#xff1a;字符编码表&#xff08;Character Encoding Form&#xff0c;也称为"storage format"&#xff09;的一种实现方式。即把Unicode字符集的抽…

蔡勒公式、三角函数

1.蔡勒公式 2.三角函数

蔡勒(Zeller)公式及其推导:快速将任意日期转换为星期数

0. 本文的初衷及蔡勒公式的用处 前一段时间&#xff0c;我在准备北邮计算机考研复试的时候&#xff0c;做了几道与日期计算相关的题目&#xff0c;在这个过程中我接触到了蔡勒公式。先简单的介绍一下蔡勒公式是干什么用的。 我们有时候会遇到这样的问题&#xff1a;看到一个日期…

1185.一周中的几天 四种解法(java),主要新学一下蔡勒公式

题目 给你一个日期&#xff0c;请你设计一个算法来判断它是对应一周中的哪一天。 输入为三个整数&#xff1a;day、month 和 year&#xff0c;分别表示日、月、年。 您返回的结果必须是这几个值中的一个 {“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”…

给定日期(年月日)求星期几(蔡勒公式?没那么简单!)

前言 前几日做到一个机试题&#xff0c;给出一个日期&#xff0c;让你输出那天是星期几&#xff0c;这种题无疑两种思路&#xff1a;一是从今天&#xff08;前提是知道今天日期及周几&#xff09;开始推算&#xff0c;计算今天与目标日期差的天数再取模运算&#xff0c;考虑到…

欧拉计划题-19 (蔡勒公式)

欧拉计划题19 前言一 题目描述二 题解分析1.暴力求解&#xff08;低配版解法&#xff09;2.蔡勒公式&#xff08;公式法&#xff09; 三 题解代码 前言 欧拉计划是学习数学、数论选手遨游的海洋&#xff0c;700道题让你我越来越强。 打卡网址链接: link. 一 题目描述 题目链接…