原文地址:http://hi.baidu.com/lin65505578/item/05c9d83ae80aa343033edcae

一、理论资料

codebook背景建模方法,opencv库中还没有对应的函数。在《learning opencv》中相应的代码,但是不完善甚至,有错误。

http://underthehood.blog.51cto.com/2531780/484191有对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),否则结束。

还有几篇经典的文章上网可以搜到。

在opencv 中文官网上(http://www.opencv.org.cn/forum/viewtopic.php?f=1&t=4648&start=25)有一段貌似调试过的代码,

但自己在vs2008平台在调试这段程序出现几个问题。

首先是函数中i未定义,这可能在vc06平台下可以运行,但在vs2008不可以。

把这个错误改正后,做了下试验,结果还是问题。TcodeBook[j].numEntries都为1,这显然不对。另外,帖子中lz说这段程序是实时更新的背景模型,个人不认同。

最简单的判断方法是,帖子中的程序做实验结果很差,lz应该没有做过实验。

二、实验

1、修改后的代码,没有实时更新,个人对codebook实时更新的原理不了解。

//codebook BGS method
//adapt from book of learning opencv by mikewolf
//2008-11-25
#include <cv.h>
#include <highgui.h>
#include <stdio.h>

#define CHANNELS 3
typedef struct ce {
 uchar learnHigh[CHANNELS]; //High side threshold for learning
 uchar learnLow[CHANNELS]; //Low side threshold for learning
 uchar max[CHANNELS]; //High side of box boundary
 uchar min[CHANNELS]; //Low side of box boundary
 int t_last_update; //Allow us to kill stale entries
 int stale; //max negative run (longest period of inactivity)
} code_element;

//码书结构
typedef struct code_book {
 code_element **cb; //指向码字的指针
 int numEntries;   //码书包含的码字数量
 int t; //count every access
} codeBook;

codeBook* TcodeBook;//包括所有像素的码书集合

int update_codebook(
     uchar* p,
     codeBook& c,
     unsigned* cbBounds,
     int numChannels
     )
{
 //unsigned int high[3],low[3];
 int high[3],low[3];
 int n;
 if(c.numEntries==0) c.t=0;
 c.t=c.t+1;//自己添加
 for(n=0; n<numChannels; n++)
 {
  high[n] = *(p+n)+*(cbBounds+n);
  if(high[n] > 255) high[n] = 255;
  low[n] = *(p+n)-*(cbBounds+n);
  if(low[n] < 0)
   low[n] = 0;
 }
 int matchChannel;
 // SEE IF THIS FITS AN EXISTING CODEWORD
 //
 int i;
 //for(int i=0; i<c.numEntries; i++)
 for(i=0; i<c.numEntries; i++)
 {
  matchChannel = 0;
  for(n=0; n<numChannels; n++)
  {
   if((c.cb[i]->learnLow[n] <= *(p+n)) &&
    //Found an entry for this channel
    (*(p+n) <= c.cb[i]->learnHigh[n]))
   {
    matchChannel++;
   }
  }
  if(matchChannel == numChannels) //If an entry was found
  {
   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;
  }
 }
 // OVERHEAD TO TRACK POTENTIAL STALE ENTRIES
 //
 for(int s=0; s<c.numEntries; s++)
 {
  // 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;
 }
 // ENTER A NEW CODEWORD IF NEEDED
 //
 if(i == c.numEntries) //if no existing codeword found, make one
 {
  code_element **foo = new code_element* [c.numEntries+1];
  for(int ii=0; ii<c.numEntries; ii++)
  {
   foo[ii] = c.cb[ii];
  }
  foo[c.numEntries] = new code_element;
  if(c.numEntries) delete [] c.cb;
  c.cb = foo;
  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;
 }
 // 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);
}

///
//int clear_stale_entries(codeBook &c)
// During learning, 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 clear_stale_entries(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];
 int k=0;
 for(int ii=0; ii<c.numEntries; ii++){
  if(keep[ii])
  {
   foo[k] = c.cb[ii];
   //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;
 int numCleared = c.numEntries - keepCnt;
 c.numEntries = keepCnt;
 return(numCleared);
}

// uchar background_diff( uchar *p, codeBook &c,
// int minMod, int maxMod)
// Given a pixel and a codebook, 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 determining if new pixel is foreground
// minMod Subract this (possibly negative) number from
// min level when determining if new pixel is foreground
//
// NOTES:
// minMod and maxMod must have length numChannels,
// e.g. 3 channels => minMod[3], maxMod[3]. There is one min and
// one max threshold per channel.
//
// Return
// 0 => background, 255 => foreground
//
uchar background_diff(
       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
   } else {
    break;
   }
  }
  if(matchChannel == numChannels) {
   break; //Found an entry that matched all channels
  }
 }
 if(i >= c.numEntries) return(255);
 return(0);
}

IplImage* pFrame = NULL;
IplImage* pFrameHSV = NULL;
IplImage* pFrImg = NULL;
CvCapture* pCapture = NULL;
int nFrmNum = 0;
//IplImage* pFrImg = NULL;
//IplImage* pBkImg = NULL;

unsigned cbBounds[3] = {10,10,10};

int height,width;
int nchannels;
int minMod[3]={35,8,8}, maxMod[3]={25,8,8};//和这两个值的选择有联系

int main(int argc, char * argv[])
{
 //创建窗口
 cvNamedWindow("video", 1);
 cvNamedWindow("HSV空间图像",1);
 cvNamedWindow("foreground",1);
 //使窗口有序排列
 cvMoveWindow("video", 30, 0);
 cvMoveWindow("HSV空间图像", 360, 0);
 cvMoveWindow("foreground", 690, 0);
 //打开视频文件,
 if( !(pCapture = cvCaptureFromFile("intelligentroom_raw.avi")))
 {
  fprintf(stderr, "Can not open video file %s\n");
  return -2;
 }

int j;
 //逐帧读取视频
 while(pFrame = cvQueryFrame( pCapture ))
 {
  cvSmooth(pFrame,pFrame,CV_GAUSSIAN,3,3);//高斯滤波
  nFrmNum++;
  printf("第几%d帧\n",nFrmNum);
  cvShowImage("video", pFrame);

if (nFrmNum == 1)
  {
   height =  pFrame->height;
   width = pFrame->width;
   nchannels = pFrame->nChannels;
   pFrameHSV = cvCreateImage(cvSize(pFrame->width, pFrame->height), IPL_DEPTH_8U,3);
   pFrImg = cvCreateImage(cvSize(pFrame->width, pFrame->height), IPL_DEPTH_8U,1);
   //cvCvtColor(pFrame, pFrameHSV, CV_BGR2HSV);//色彩空间转化
   TcodeBook = new codeBook[width*height];

for(j = 0; j < width*height; j++)
   {
    TcodeBook[j].numEntries = 0;
    TcodeBook[j].t = 0;
   }

}

if (nFrmNum<50)
  {
   cvCvtColor(pFrame, pFrameHSV, CV_BGR2YCrCb);//色彩空间转化
   //学习背景
   for(j = 0; j < width*height; j++)
   {
    update_codebook((uchar*)pFrameHSV->imageData+j*nchannels, TcodeBook[j],cbBounds,3);
   }

}
  else
  {
   cvCvtColor(pFrame, pFrameHSV, CV_BGR2YCrCb);//色彩空间转化
   if( nFrmNum == 50)
   {

// for(j = 0; j < width*height; j++)
   //  update_codebook((uchar*)pFrameHSV->imageData+j*nchannels, TcodeBook[j],cbBounds,3);
    for(j = 0; j < width*height; j++)
     clear_stale_entries(TcodeBook[j]);
   }

//if(nFrmNum%30 == 0)
   //{
   // for(j = 0; j < width*height; j++)
   //  update_codebook((uchar*)pFrameHSV->imageData+j*nchannels, TcodeBook[j],cbBounds,3);
   //}
   //if(nFrmNum%60 == 0)
   //{
   // for(j = 0; j < width*height; j++)
   //  clear_stale_entries(TcodeBook[j]);
   //}
   for(j = 0; j < width*height; j++)
   {
    if(background_diff((uchar*)pFrameHSV->imageData+j*nchannels, TcodeBook[j],3,minMod,maxMod))
    {
     pFrImg->imageData[j] = 255;
    }
    else
    {
     pFrImg->imageData[j] = 0;
    }
   }
   cvShowImage("foreground", pFrImg);
   cvShowImage("HSV空间图像", pFrameHSV);
  }
  if( cvWaitKey(2) >= 0 )
   break;

} // end of while-loop

for(j = 0; j < width*height; j++)
 {
  if (!TcodeBook[j].cb)
   delete [] TcodeBook[j].cb;
 }
 if (!TcodeBook)
  delete [] TcodeBook;
 //销毁窗口
 cvDestroyWindow("video");
 cvDestroyWindow("HSV空间图像");
 cvDestroyWindow("foreground");
 return 0;
}

2、说明

平台:opencv2.1 vs2008

主要是update_codebook_model函数做了较多的改动,另外选择的颜色空间为YCrCb,不是HSV。

minMod[3]={35,8,8}, maxMod[3]={25,8,8};这两个值对结果会有影响。

图像事先经过高斯滤波这样出来的效果更好。

3、实验结果

背景图

场景中有人走入室内,人的图像被分割出来

4、问题

对与codebook的实时更新原理不怎么了解,可能以后用到会仔细的研究;

觉得opencv  中cvQueryFrame函数比较奇怪,在opencv1.0中cvQueryFrame是一帧一帧读,第一帧为1,读得速度和视频播放速度一样。但是在在opencv2.1中cvQueryFrame有些视频时是一帧一帧读,有些跳着读,第一帧为0,读得速度和视频播放速度明显要快,不知道是什么原因。

三、备注

网上摘录的

在使用opencv显示图像时会出现图像倒立的情况,IplImage的origin属性有关系。origin为0表示顶左结构,即图像的原点是左上角,如果为1为左下角。

一般从硬盘读入的图片或者通过cvCreateImage方法创建的IplImage图片默认的origin为0,即显示的时候都是正的。

而由摄像头或者视频文件获取的帧图像origin为1,此时显示的时候扫描顺序是从下到上,显示也是正的(opencv显示的时候是根据origin的值显示的,如果origin=1,则从下到上显示,否则反之)。

但是如果你自己创建了一个IplImage格式的图像img,且从帧图像中copy或者截取一部分区域进行显示的时候就会出现倒立情况。这是因为cvCreateImage方法得到的img的origin是0,而帧图像的origin为1,它会将帧图像的第i行赋值给img的第height-i行,因此就出现了倒立.解决办法是:在创建之后将origin调整为与帧图像的origin一致即可。

IplImage* face=cvCreateImage(cvSize(width,height),copy_Frame->depth,copy_Frame->nChannels);

//因为IplImage的origin=0,所以要先将face->origin改为1
        face->origin=copy_Frame->origin;//1

也是说,origin只负责显示。“摄像头或者视频文件获取的帧图像origin为1”这句话在opencv2.1中不适用。

opencv codebook相关推荐

  1. 图像处理和图像识别中常用的OpenCV函数

    1.   cvLoadImage:将图像文件加载至内存: 2.   cvNamedWindow:在屏幕上创建一个窗口: 3.   cvDestroyWindow:销毁显示图像文件的窗口: 4.   c ...

  2. 前景检测算法_4(opencv自带GMM)

    参考:https://www.cnblogs.com/tornadomeet/archive/2012/06/02/2531705.html 前景检测算法_4(opencv自带GMM) 前面已经有3篇 ...

  3. 前景检测算法_1(codebook和平均背景法)

    前景检测算法_1(codebook和平均背景法) 前景分割中一个非常重要的研究方向就是背景减图法,因为背景减图的方法简单,原理容易被想到,且在智能视频监控领域中,摄像机很多情况下是固定的,且背景也是基 ...

  4. opencv如何把一个矩阵不同列分离开_学习OPEN_CV

    OpenCv中文论坛精华地址 http://www.opencv.org.cn/index.php/User:Ollydbg23 http://sivp.sourceforge.net/(sivp) ...

  5. OpenCV3学习(10.2)codebook码书法实现背景剔除

    一.概述 码本模型是 Kim 等提出的一种新颖的背景建模方法.该模型针对彩色视频图像序列,根据像素点的连续采样值的颜色失真程度及其亮度范围,将背景像素用码本表示,然后利用背景差分法思想对新输入像素值与 ...

  6. OpenCV精进之路(二十三):实例——Bag of Features(BoF)图像分类实践

    在深度学习在图像识别任务上大放异彩之前,词袋模型Bag of Features一直是各类比赛的首选方法.首先我们先来回顾一下PASCAL VOC竞赛历年来的最好成绩来介绍物体分类算法的发展. 从上表我 ...

  7. 前景检测算法(二)--codebook和平均背景法

     原文:http://www.cnblogs.com/tornadomeet/archive/2012/04/08/2438158.html 前景检测算法_1(codebook和平均背景法) 前景 ...

  8. OpenCV系统学习(基本了解完)

    1.下载安装.http://blog.csdn.net/poem_qianmo/article/details/19809337 2.框架架构http://blog.csdn.net/poem_qia ...

  9. Python OpenCV 3.x 示例:6~11

    原文:OpenCV 3.x with Python By Example 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自[ApacheCN 计算机视觉 译文集],采用译后编辑(MTPE)流 ...

  10. Python+OpenCV实用案例应用教程:建立自定义物体检测器

    本章将深入探讨物体检测的概念,这是计算机视觉中最常见的挑 战之一.既然在这本教程中已经讲了很多内容了,读到这里,你也许会 想,什么时候才能把计算机视觉应用实践中呢.你是否想过建立一个 系统来检测车辆和 ...

最新文章

  1. php常用的搜索引擎,常用搜索引擎高级命令有哪些
  2. 如何对phpcms v9的首页列表进行分页?
  3. python 数据去重_python 对数据常用的几种去重方式
  4. 惠斯通电桥信号调理芯片_瑞萨推出集成LIN输出接口的传感器信号调理芯片,适用于电动/混动汽车HVAC系统...
  5. 并发(concurrency)和并行(parallellism)
  6. Ubuntu 每日技巧- 自动备份Ubuntu 14.04到Box云存储上
  7. 如何知道linux的ssh秘钥是否匹配,SSH密钥验证
  8. 谷歌浏览器安装apizza
  9. 中国的北斗,世界的北斗|hightopo卫星发射demo
  10. 湖南省委短信平台改造方案
  11. 【debug】googlecode 使用代理svn下载代码
  12. 联想微型计算机开机黑屏什么原因,联想笔记本电源键亮但黑屏怎么办
  13. CSS3------ 按钮闪光划过效果
  14. stm32实现毫秒ms微秒us级延时
  15. git 移除项目版本控制_Git - 关于版本控制
  16. 山威SDUWH高性能计算课程HPL HPCC软件快速安装脚本
  17. iOS App Singer 重签名工具的使用简介
  18. 超级表格迎来深度用户:苏河汇,一个天使投资机构
  19. 工作中的完美主义 感悟_如何克服设计中的完美主义
  20. 9.物体的几何表示——分形几何

热门文章

  1. [渝粤教育] 南京审计大学 审计学基础 参考 资料
  2. 转:中文汉字占二个字节还是三个字节长度
  3. ie tab chrome_将IE Tab集成添加到Google Chrome
  4. 武汉大学计算机国家网络安全学院怎么样,武汉大学国家网络安全学院怎么样?...
  5. 测试用例设计方法之选择原则
  6. Autodesk如何炸开增加属性块
  7. spring boot 2 整合 j2Cache
  8. 一种简单的不净观(女人)方法,帮助看破色欲
  9. 服务器主板的无线驱动,驱动天空 - 品牌主板 - 服务器主板 SERVER
  10. 项目组最重要的三个角色