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