#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; }
程序运行结果: