#include<opencv2/opencv.hpp>
#include<iostream>
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;
codeBook* TcodeBook; //所有像素的码书集合
//在视频的前50帧用来更新码本
int update_codebook(uchar* p, codeBook& c,unsigned* cbBounds, int numChannels )
{
int high[3],low[3];
int n;
if(c.numEntries==0) c.t=0; //设置当前访问时间为0
c.t=c.t+1; //每次更新 时间加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;
int i;
//将当前像素各个通道上的像素亮度*(p+n)同每个码字的相应通道上的像素亮度记录进行匹配
for(i=0; i<c.numEntries; i++)
{
matchChannel = 0;
for(n=0; n<numChannels; n++)
{
if((c.cb[i]->learnLow[n] <= *(p+n)) &&(*(p+n) <= c.cb[i]->learnHigh[n]))
{
matchChannel++;//匹配成功
}
}
if(matchChannel == numChannels) //如果三个通道都匹配成功
{
c.cb[i]->t_last_update = c.t;//更新最后访问时间
//更新匹配码字的每个通道上的最大值和最小值
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;
}
}
//更新每个码字的最长未访问时间
for(int s=0; s<c.numEntries; s++)
{
int negRun = c.t - c.cb[s]->t_last_update;
if(c.cb[s]->stale < negRun) c.cb[s]->stale = negRun;
}
if(i == c.numEntries) //如果没有匹配的码字 添加一个新的码字
{
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;
}
//对于匹配的码字或新添加的码字 更新每个通道上的亮度阈值 每次增加一度
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)
{
int staleThresh = c.t>>1;
int *keep = new int [c.numEntries];
int keepCnt = 0;
for(int i=0; i<c.numEntries; i++)
{
if(c.cb[i]->stale > staleThresh)
keep[i] = 0; //标识用来清除
else
{
keep[i] = 1; //标识用来保存
keepCnt += 1;
}
}
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++;
}
}
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 numChannels, int* minMod, int* maxMod )
{
int matchChannel;
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; //找到匹配的码字 则为背景像素
}
}
if(i >= c.numEntries) return(255);
return(0);
}
IplImage* pFrame = NULL;
IplImage* pFrameHSV = NULL;
IplImage* pFrImg = NULL;
CvCapture* pCapture = NULL;
int nFrmNum = 0;
unsigned cbBounds[3] = {10,10,10};
int height,width;
int nchannels;
//用训练好的背景模型进行前景检测时用到,小于max[n] + maxMod[n]
//并且大于min[n]-minMod[n])的像素点才被认为是背景像素
int minMod[3]={35,8,8}, maxMod[3]={25,8,8};//和这两个值的选择有联系
int main()
{
cvNamedWindow("video", 1);
cvNamedWindow("HSV空间图像",1);
cvNamedWindow("foreground",1);
//使窗口有序排列
cvMoveWindow("video", 30, 0);
cvMoveWindow("HSV空间图像", 360, 0);
cvMoveWindow("foreground", 690, 0);
//打开视频文件
pCapture = cvCaptureFromFile("video.avi");
int j;
while(pFrame = cvQueryFrame( pCapture ))
{
cvSmooth(pFrame,pFrame,CV_GAUSSIAN,3,3);//高斯滤波
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);
//初始化码书
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++)
clear_stale_entries(TcodeBook[j]);
}
for(j = 0; j < width*height; j++)
{
//如果background_diff返回值不为NULL 则为前景像素
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(22) >= 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;
}
程序运行结果: