利用codebook法训练得到背景模型后,对背景差分得到的掩模图像去噪声并找到较大连通域。相对于平均背景法,它的分割效果更好些。当然,分割效果和背景模型训练的帧数有很大关系,适当调整一些参数会得到更好的效果。
1 #include "stdafx.h" 2 #include "cv.h" 3 #include "highgui.h" 4 5 #define CHANNELS 3 6 typedef struct ce{ 7 uchar learnHigh[CHANNELS]; 8 uchar learnLow[CHANNELS]; 9 uchar max[CHANNELS]; 10 uchar min[CHANNELS]; 11 int t_last_update; 12 int stale; 13 }code_element; 14 15 typedef struct code_book{ 16 code_element **cb; 17 int numEntries; 18 int t; 19 }codeBook; 20 21 #define CVCONTOUR_APPROX_LEVEL 2 // Approx.threshold - the bigger it is, the simpler is the boundary 22 #define CVCLOSE_ITR 1 // How many iterations of erosion and/or dialation there should be 23 24 #define CV_CVX_WHITE CV_RGB(0xff,0xff,0xff) 25 #define CV_CVX_BLACK CV_RGB(0x00,0x00,0x00) 26 27 /////////////////////////////////////////////////////////////////////////////////// 28 // int updateCodeBook(uchar *p, codeBook &c, unsigned cbBounds) 29 // Updates the codebook entry with a new data point 30 // 31 // p Pointer to a YUV pixel 32 // c Codebook for this pixel 33 // cbBounds Learning bounds for codebook (Rule of thumb: 10) 34 // numChannels Number of color channels we're learning 35 // 36 // NOTES: 37 // cvBounds must be of size cvBounds[numChannels] 38 // 39 // RETURN 40 // codebook index 更新码本 41 int update_codebook(uchar* p,codeBook &c, unsigned* cbBounds, int numChannels) 42 { 43 if(c.numEntries==0)c.t=0; //码本中码元数为0初始化时间为0 44 c.t+=1; //每调用一次时间数加1 45 46 int n; 47 unsigned int high[3],low[3]; 48 for(n=0;n<numChannels;n++) 49 { 50 //加减cbBonds作为此像素阈值上下界 51 high[n]=*(p+n) + *(cbBounds+n); //直接使用指针操作更快 52 if(high[n]>255) high[n]=255; 53 low[n]=*(p+n)-*(cbBounds+n); 54 if(low[n]<0) low[n]=0; 55 } 56 57 int matchChannel; 58 int i; 59 for(i=0;i<c.numEntries;i++) 60 { 61 matchChannel=0; 62 for(n=0;n<numChannels;n++) 63 { 64 if((c.cb[i]->learnLow[n]<=*(p+n)) && (*(p+n)<=c.cb[i]->learnHigh[n])) 65 { 66 matchChannel++; 67 } 68 } 69 if(matchChannel==numChannels) 70 { 71 c.cb[i]->t_last_update=c.t; //更新码元时间 72 for(n=0;n<numChannels;n++) //调整码元各通道最大最小值 73 { 74 if(c.cb[i]->max[n]<*(p+n)) 75 c.cb[i]->max[n]=*(p+n); 76 else if(c.cb[i]->min[n]>*(p+n)) 77 c.cb[i]->min[n]=*(p+n); 78 } 79 break; 80 } 81 } 82 83 //像素p不满足码本中任何一个码元,创建一个新码元 84 if(i == c.numEntries) 85 { 86 code_element **foo=new code_element*[c.numEntries+1]; 87 for(int ii=0;ii<c.numEntries;ii++) 88 foo[ii]=c.cb[ii]; 89 foo[c.numEntries]=new code_element; 90 if(c.numEntries)delete[]c.cb; 91 c.cb=foo; 92 for(n=0;n<numChannels;n++) 93 { 94 c.cb[c.numEntries]->learnHigh[n]=high[n]; 95 c.cb[c.numEntries]->learnLow[n]=low[n]; 96 c.cb[c.numEntries]->max[n]=*(p+n); 97 c.cb[c.numEntries]->min[n]=*(p+n); 98 } 99 c.cb[c.numEntries]->t_last_update = c.t; 100 c.cb[c.numEntries]->stale = 0; 101 c.numEntries += 1; 102 } 103 104 //计算码元上次更新到现在的时间 105 for(int s=0; s<c.numEntries; s++) 106 { 107 int negRun=c.t-c.cb[s]->t_last_update; 108 if(c.cb[s]->stale < negRun) 109 c.cb[s]->stale = negRun; 110 } 111 112 //如果像素通道值在高低阈值内,但在码元阈值之外,则缓慢调整此码元学习界限(max,min相当于外墙,粗调;learnHigh,learnLow相当于内墙,细调) 113 for(n=0; n<numChannels; n++) 114 { 115 if(c.cb[i]->learnHigh[n]<high[n]) 116 c.cb[i]->learnHigh[n]+=1; 117 if(c.cb[i]->learnLow[n]>low[n]) 118 c.cb[i]->learnLow[n]-=1; 119 } 120 121 return i; 122 } 123 124 // 删除一定时间内未访问的码元,避免学习噪声的codebook 125 int cvclearStaleEntries(codeBook &c) 126 { 127 int staleThresh=c.t>>1; //设定刷新时间 128 int *keep=new int[c.numEntries]; 129 int keepCnt=0; //记录不删除码元码元数目 130 for(int i=0; i<c.numEntries; i++) 131 { 132 if(c.cb[i]->stale > staleThresh) 133 keep[i]=0; //保留标志符 134 else 135 { 136 keep[i]=1; //删除标志符 137 keepCnt+=1; 138 } 139 } 140 141 c.t=0; 142 code_element **foo=new code_element*[keepCnt]; 143 int k=0; 144 for(int ii=0; ii<c.numEntries; ii++) 145 { 146 if(keep[ii]) 147 { 148 foo[k]=c.cb[ii]; 149 foo[k]->stale=0; //We have to refresh these entries for next clearStale 150 foo[k]->t_last_update=0; 151 k++; 152 } 153 } 154 155 delete[] keep; 156 delete[] c.cb; 157 c.cb=foo; 158 int numCleared=c.numEntries-keepCnt; 159 c.numEntries=keepCnt; 160 return numCleared; //返回删除的码元 161 } 162 163 /////////////////////////////////////////////////////////////////////////////////// 164 // uchar cvbackgroundDiff(uchar *p, codeBook &c, int minMod, int maxMod) 165 // Given a pixel and a code book, determine if the pixel is covered by the codebook 166 // 167 // p pixel pointer (YUV interleaved) 168 // c codebook reference 169 // numChannels Number of channels we are testing 170 // maxMod Add this (possibly negative) number onto max level when code_element determining if new pixel is foreground 171 // minMod Subract this (possible negative) number from min level code_element when determining if pixel is foreground 172 // 173 // NOTES: 174 // minMod and maxMod must have length numChannels, e.g. 3 channels => minMod[3], maxMod[3]. 175 // 176 // Return 177 // 0 => background, 255 => foreground 背景差分,寻找前景目标 178 uchar cvbackgroundDiff(uchar *p, codeBook &c, int numChannels, int *minMod, int *maxMod) 179 { 180 int matchChannel; 181 int i; 182 for(i=0; i<c.numEntries; i++) 183 { 184 matchChannel=0; 185 for(int n=0; n<numChannels; n++) 186 { 187 if((c.cb[i]->min[n]-minMod[n]<=*(p+n)) && (*(p+n)<=c.cb[i]->max[n]+maxMod[n])) 188 matchChannel++; 189 else 190 break; 191 } 192 if(matchChannel==numChannels) 193 break; 194 } 195 if(i==c.numEntries) //像素p各通道值不满足所有的码元,则为前景,返回白色 196 return 255; 197 return 0; //匹配到一个码元时,则为背景,返回黑色 198 } 199 200 /////////////////////////////////////////////////////////////////////////////////////////// 201 //void cvconnectedComponents(IplImage *mask, int poly1_hull0, float perimScale, int *num, CvRect *bbs, CvPoint *centers) 202 // This cleans up the foreground segmentation mask derived from calls to cvbackgroundDiff 203 // 204 // mask Is a grayscale (8 bit depth) "raw" mask image which will be cleaned up 205 // 206 // OPTIONAL PARAMETERS: 207 // poly1_hull0 If set, approximate connected component by (DEFAULT) polygon, or else convex hull (0) 208 // perimScale Len = image (width+height)/perimScale. If contour len < this, delete that contour (DEFAULT: 4) 209 void cvconnectedComponents(IplImage *mask, int poly1_hull0, float perimScale) 210 { 211 static CvMemStorage* mem_storage=NULL; 212 static CvSeq* contours=NULL; 213 cvMorphologyEx( mask, mask, NULL, NULL, CV_MOP_OPEN, CVCLOSE_ITR); 214 cvMorphologyEx( mask, mask, NULL, NULL, CV_MOP_CLOSE, CVCLOSE_ITR); 215 216 if(mem_storage==NULL) 217 mem_storage=cvCreateMemStorage(0); 218 else 219 cvClearMemStorage(mem_storage); 220 221 CvContourScanner scanner=cvStartFindContours(mask,mem_storage,sizeof(CvContour),CV_RETR_EXTERNAL); 222 CvSeq* c; 223 int numCont=0; //轮廓数 224 while((c=cvFindNextContour(scanner))!=NULL) 225 { 226 double len=cvContourPerimeter(c); 227 double q=(mask->height+mask->width)/perimScale; //轮廓长度阀值设定 228 if(len<q) 229 cvSubstituteContour(scanner,NULL); //删除太短轮廓 230 else 231 { 232 CvSeq* c_new; 233 if(poly1_hull0) //用多边形拟合轮廓 234 c_new = cvApproxPoly(c, sizeof(CvContour), mem_storage, 235 CV_POLY_APPROX_DP, CVCONTOUR_APPROX_LEVEL); 236 else //计算轮廓Hu矩 237 c_new = cvConvexHull2(c,mem_storage, CV_CLOCKWISE, 1); 238 239 cvSubstituteContour(scanner,c_new); //替换拟合后的多边形轮廓 240 numCont++; 241 } 242 } 243 contours = cvEndFindContours(&scanner); //结束扫描,并返回最高层的第一个轮廓指针 244 245 cvZero(mask); 246 for(c=contours; c!=NULL; c=c->h_next) 247 cvDrawContours(mask,c,CV_CVX_WHITE, CV_CVX_BLACK,-1,CV_FILLED,8); 248 } 249 250 int main() 251 { 252 /////////////////////////////////////// 253 // 需要使用的变量 254 CvCapture* capture=NULL; 255 IplImage* rawImage=NULL; //视频的每一帧原图像 256 IplImage* yuvImage=NULL; //比经验角度看绝大部分背景中的变化倾向于沿亮度轴,而不是颜色轴,故YUV颜色空间效果更好 257 IplImage* ImaskCodeBook=NULL; //掩模图像 258 IplImage* ImaskCodeBookCC=NULL; //清除噪声后并采用多边形法拟合轮廓连通域的掩模图像 259 260 codeBook* cB=NULL; 261 unsigned cbBounds[CHANNELS]; 262 uchar* pColor=NULL; //yuvImage像素指针 263 int imageLen=0; 264 int nChannels=CHANNELS; 265 int minMod[CHANNELS]; 266 int maxMod[CHANNELS]; 267 268 ////////////////////////////////////////////////////////////////////////// 269 // 初始化各变量 270 cvNamedWindow("原图"); 271 cvNamedWindow("掩模图像"); 272 cvNamedWindow("连通域掩模图像"); 273 274 //capture = cvCreateFileCapture("C:/Users/shark/Desktop/eagle.flv"); 275 capture=cvCreateCameraCapture(0); 276 if(!capture) 277 { 278 printf("Couldn't open the capture!"); 279 return -1; 280 } 281 282 rawImage=cvQueryFrame(capture); 283 int width=(int)cvGetCaptureProperty(capture,CV_CAP_PROP_FRAME_WIDTH); 284 int height=(int)cvGetCaptureProperty(capture,CV_CAP_PROP_FRAME_HEIGHT); 285 CvSize size=cvSize(width,height); 286 yuvImage=cvCreateImage(size,8,3); 287 ImaskCodeBook = cvCreateImage(size, IPL_DEPTH_8U, 1); 288 ImaskCodeBookCC = cvCreateImage(size, IPL_DEPTH_8U, 1); 289 cvSet(ImaskCodeBook,cvScalar(255)); 290 291 imageLen=width*height; 292 cB=new codeBook[imageLen]; //得到与图像像素数目长度一样的一组码本,以便对每个像素进行处理 293 294 for(int i=0;i<imageLen;i++) 295 cB[i].numEntries=0; 296 for(int i=0;i<nChannels;i++) 297 { 298 cbBounds[i]=10; 299 minMod[i]=20; //用于背景差分函数中 300 maxMod[i]=20; //调整其值以达到最好的分割 301 } 302 303 ////////////////////////////////////////////////////////////////////////// 304 // 开始处理视频每一帧图像 305 for(int i=0;;i++) 306 { 307 if(!(rawImage=cvQueryFrame(capture))) 308 break; 309 cvCvtColor(rawImage, yuvImage, CV_BGR2YCrCb); 310 // 色彩空间转换,将rawImage 转换到YUV色彩空间,输出到yuvImage 311 // 即使不转换效果依然很好 312 // yuvImage = cvCloneImage(rawImage); 313 314 if(i<=30) //前30帧进行背景学习 315 { 316 pColor=(uchar*)yuvImage->imageData; 317 for(int c=0; c<imageLen; c++) 318 { 319 update_codebook(pColor, cB[c], cbBounds, nChannels); //对每个像素调用此函数 320 pColor+=3; 321 } 322 if(i==30) 323 { 324 for(int c=0;c<imageLen;c++) 325 { 326 cvclearStaleEntries(cB[c]); //第30时帧时,删除每个像素码本中陈旧的码元 327 } 328 } 329 } 330 else 331 { 332 uchar maskPixel; 333 pColor=(uchar*)yuvImage->imageData; 334 uchar* pMask=(uchar*)ImaskCodeBook->imageData; 335 for(int c=0;c<imageLen;c++) 336 { 337 maskPixel=cvbackgroundDiff(pColor,cB[c],nChannels,minMod,maxMod); 338 *pMask++=maskPixel; 339 pColor+=3; 340 } 341 cvCopy(ImaskCodeBook,ImaskCodeBookCC); 342 cvconnectedComponents(ImaskCodeBookCC,1,4.0); 343 cvShowImage("掩模图像",ImaskCodeBook); 344 cvShowImage("连通域掩模图像",ImaskCodeBookCC); 345 } 346 cvShowImage("原图",rawImage); 347 if (cvWaitKey(30) == 27) 348 break; 349 } 350 351 cvReleaseCapture(&capture); 352 if (yuvImage) 353 cvReleaseImage(&yuvImage); 354 if(ImaskCodeBook) 355 cvReleaseImage(&ImaskCodeBook); 356 cvDestroyAllWindows(); 357 delete [] cB; 358 359 return 0; 360 361 }