1 Nov 2010

opencv轮廓检测函数

收藏到CSDN网摘



matlab用久了,人会变懒惰.一直没有读opencv的代码,苦苦寻找bwlabel和bwareaopen的类似函数而不得,肤色检测结果却必须经过去噪这一关,没辙,自己写吧.google到一篇博文,博主很牛,分析了matlab的bwlabe代码,参考博主的分析,我实现了我的bwareaopen第一版.



其实也就是参考下面的步骤: labeling -> compute areas -> erasing small regions. 标记过所有区域之后,矩阵的元素已经是它所对应的区域的标号,然后用循环判断面积,最后根据面积删除不需要的区域内的区域即可.函数运行后大概需要5.3s左右.这可是5s多啊,我的整个人脸检测matlab代码在几千*几千像素的大图才不超过15s,这号称高效的c++竟然一个去噪就要5s多?不行,继续考虑.

一个群的群主用c写了个类似的函数,输入二值图,和面积阀值,输出去噪后的图像,经测试该函数比较有效,对于输入二值图像只需250ms左右.不知道是我函数实现有误还是c++的变长数组效率低下(群主的那个实现,变长数组是绕不开的,而且中间存在递归调用),竟然c++改写后去噪需要50多s,呜呼哀哉,继续google.

终于找到了一个函数,cvFindContour好像可以找到所有边缘,而且其参数可选;cvDrawContour函数可以在图上画边缘,但是需要指定参数方可填充内部.实现后,阀值选择得当,去噪效果非常好,而且运行时间基本在30ms以内,记录一下,免得以后忘记.

经验教训:一个成熟的库能存在这么久,已经有很多前辈为他流血流汗了,你绝对不会是第一个碰到类似问题的,如果问题比较普遍,那么库一定有对应的解决方法,关键就在于你能否找到这个方法.想起了学习matlab一开始的时候,几乎每天都有新的函数发现,每天都有收获.学无止境啊.

下面是部分代码及注释:
如果你有更好的实现方法,请联系.
// ******************************************
// 类matlab函数bwareaopen的opencv实现
// 31/10/2010
// ******************************************
IplImage* bwaeraopen(IplImage *pSrc, double areaThreshold)
{
 // 这里有2个图像,为什么定义2个呢?
 // 由于cvDrawContour这个函数external_color和hole_colr必须制定RGB格式
 // 问题来了:为了在后续操作区分不同区域,我可不想再来findContour一次了
 // pDst这个图像可以直接给drawContour画,只是B分量的值为当前label的值
 // G和R则随机生成,因为不关心;最后用cvSplit分开后只把B分量返回
 // 即可取得类似matlab中bwlabel的效果.这是我目前找到的办法
 // 如果你需要返回彩色图像,修改return pMark即可
 IplImage *pDst = cvCreateImage(cvGetSize(pSrc), IPL_DEPTH_8U, 3);
 IplImage *pMark = cvCreateImage(cvGetSize(pSrc),IPL_DEPTH_8U, 1);
 cvSetZero(pDst);
 
 // 分配内存
 CvMemStorage* storage = cvCreateMemStorage(0);
 CvSeq* contour = 0;
 
 // 模式: CV_RETR_CCOMP 检测外侧和内测边缘
 //    CV_RETR_EXTERNAL只检测外部边缘
 // 第二个mode下drawContour后就相当于matlab的imfill
 // 填充后,大的区域中间无孔洞
 //int mode = CV_RETR_CCOMP; 
 int mode = CV_RETR_EXTERNAL;
 
 // 检测边缘
 cvFindContours(pSrc, storage, &contour, sizeof(CvContour), mode, CV_CHAIN_APPROX_SIMPLE);
 
 // 标记,并擦除小区域
 int i;
 double maxArea=0,minArea=0;
 for(i=1; contour != 0; contour = contour->h_next)
 {
  // 得到当前区域面积
  double area=fabs(cvContourArea(contour,CV_WHOLE_SEQ));
  if(area>=areaThreshold)
  {
   //maxArea = max(area,maxArea); // 最大区域面积
   //minArea = min(area,minArea); // 最小区域面积

   // 修改CV_FILLED为1可以只画边缘
   // 这个color的cvscalar创建是label的关键
   // 网上很多都是做3个rand()&255,这样生成的结果类似
   // matlab的label2image这个函数
   CvScalar color = CV_RGB(i, rand()&255, rand()&255);
   cvDrawContours(pDst, contour, color, color, -1, CV_FILLED, 8 );
   i++; // label递增
  }
 }
 
 // 分离得到label矩阵,
// 注意这里,我第一次写错了,写成了 cvSplit(pDst,pMark,NULL,NULL,NULL);
// 为什么呢?因为opencv里面彩色图像默认是BGR存储,上面作出来的color确是CV_RGB顺序
// 因此,分离的时候为了得到你需要的label矩阵,就需要调整通道顺序,改成下面即可
 cvSplit(pDst,NULL,NULL,pMark,NULL);
 
 // 释放内存
 cvReleaseMemStorage(&storage);
 cvReleaseImage(&pDst);

 return pMark;
}
其效果如下:
原二值图-label图-伪彩色图

这个函数的参数说明:
CvScalar color = CV_RGB( rand()&255, rand()&255, rand()&255 );           /* 用1替代 CV_FILLED  所指示的轮廓外形 */
cvDrawContours( dst, contour, color, color, -1, CV_FILLED, 8 );
参数说明:
在图像中绘制外部和内部的轮廓。

void cvDrawContours( CvArr *img, CvSeq* contour, CvScalar external_color, CvScalar hole_color, int max_level, int thickness=1, int line_type=8, CvPoint offset=cvPoint(0,0) );

img 用以绘制轮廓的图像。和其他绘图函数一样,边界图像被感兴趣区域(ROI)所剪切。

contour 指针指向第一个轮廓。 external_color 外层轮廓的颜色。 hole_color 内层轮廓的颜色。 max_level 绘制轮廓的最大等级。

如果等级为0,绘制单独的轮廓。如果为1,绘制轮廓及在其后的相同的级别下轮廓。如果值为2,所有的轮廓。如果等级为2,绘制所有同级轮廓及所有低一级轮廓,

诸此种种。如果值为负数,函数不绘制同级轮廓,但会升序绘制直到级别为abs(max_level)-1的子轮廓。

thickness 绘制轮廓时所使用的线条的粗细度。如果值为负(e.g. =CV_FILLED),绘制内层轮廓。 line_type 线条的类型。

参考cvLine. offset 按照给出的偏移量移动每一个轮廓点坐标.当轮廓是从某些感兴趣区域(ROI)中提取的然后需要在运算中考虑ROI偏移量时,将会用到这个参数。 当thickness>=0,函数cvDrawContours在图像中绘制轮廓,或者当thickness<0时,填充轮廓所限制的区域。
另一个函数:
cvFindContours(img,storage,&contour,
        sizeof(CvContour),mode,CV_CHAIN_APPROX_NONE);
参数说明:
提取模式.
CV_RETR_EXTERNAL - 只提取最外层的轮廓
CV_RETR_LIST - 提取所有轮廓,并且放置在 list 中
CV_RETR_CCOMP - 提取所有轮廓,并且将其组织为两层的 hierarchy: 顶层为连通域的外围边界,次层为洞的内层边界。
CV_RETR_TREE - 提取所有轮廓,并且重构嵌套轮廓的全部 hierarchy method
那位牛人分析matlab的bwlabel的代码可参考:这里

No comments :

Post a Comment