【图像处理】|【OpenCV】轮廓提取——findContours()

轮廓是图像的重要特征之一,有些时候,很容易将其和边缘混淆。因此查阅了关于轮廓和边缘的词条,以此加深对轮廓和边缘这两个概念的认识和理解。边缘是图像差异变化比较显著的地方,而轮廓则是构成图形和物体的边缘线条,属于边缘的一部分。对于形状单一的二值图像而言,物体轮廓和边缘是重合的。在OpenCV中,我们可以通过findContours函数提取图像的轮廓信息,其具体声明在imgproc.hpp文件内,如图1所示。
【图像处理】|【OpenCV】轮廓提取——findContours()
文章图片

图1 findContours函数声明截图 通过图1所示内容可知,findContours函数在imgproc.hpp文件中声明了两次,区别在于前者有输出轮廓层次hierarchy,而后者没有。故以第一种完整声明为例,简要说明findContours()函数的参数含义。

  1. image,输入图像,通常为8通道二值图像。在OpenCV3.2版本后,轮廓提取方式是RETR_CCOMP或者RECT_FLOODFILL时,也可以输入32位单通道整形图像(CV_32SC1)。
  2. contours,输出找到的轮廓。
  3. hierarchy,可选项,输出所有轮廓的树结构。
  4. mode,轮廓提取方式。
  5. method,轮廓近似方式。
  6. offset,可选项,返回的轮廓中所有点都会根据设置的参数值发生偏移。
轮廓提取方式mode
  • RETR_EXTERNAL,只检索最外层轮廓。
  • RETR_LIST,检测所有轮廓并保存到列表中。
  • RETR_CCMOP,检测所有轮廓,并将它们组织成双层结构,顶层是所有成分的外部边界,第二层是内部空的孔的边界。
  • RETR_TREE,检测所有轮库并重新建立网状轮廓结构。
  • RECT_FLOODFILL,尚未在官网上查到解释。
轮廓近似方式method
  • CHAIN_APPROX_NONE,将轮廓编码中的所有点转换为点。
  • CHAIN_APPROX_SIMPLE,压缩水平、垂直、倾斜部分,只保留最后一个点。
  • CHAIN_APPROX_TC89_L1CHAIN_APPROX_TC89_L1,使用Teh-Chin链逼近算法中的一个。
了解findContours函数的各个参数和参数特性,是熟练掌握并使用该函数的前提。结合drawContours函数,能够直观地查看轮廓提取状况。因此,我们可以通过如下所示的代码测试上述两个函数功能。
//: findcontours_operate.cpp #include #include #include using namespace cv; using namespace std; int main(int argv, char **argc) { Mat src, dst; src = https://www.it610.com/article/imread("Fig1001.tif"); if (!src.data) { cout << "Could not loaded image..." << endl; return -1; }// convert binary image Mat grayImg, binImg; cvtColor(src, grayImg, COLOR_BGR2GRAY); threshold(grayImg, binImg, 100, 255, THRESH_BINARY | THRESH_OTSU); // find contours vector> contours; vector hierarchy; findContours(binImg, contours, hierarchy, RETR_LIST, CHAIN_APPROX_SIMPLE); // draw find result src.copyTo(dst); for (size_t i = 0; i < contours.size(); i++) { Scalar color = Scalar(0, 0, 255); drawContours(dst, contours, (int)i, color, 1, 8, hierarchy, 0); }// show find result namedWindow("Find Result", WINDOW_AUTOSIZE); imshow("Find Result", dst); waitKey(0); return 0; } //:~ findcontours_operate.cpp

【【图像处理】|【OpenCV】轮廓提取——findContours()】起初,用VS2013+OpenCV3.0.0编译运行上述代码,能够得到如图2所示的结果。但在退出运行时,触发了中断。于是,切换到VS2015 + OpenCV3.4.7上继续编译运行上述代码,得到的结果与图2所示结果一致,但在退出程序时,并未触发中断。
【图像处理】|【OpenCV】轮廓提取——findContours()
文章图片

图2 轮廓提取前后对比图 对于测试遇到的状况,感到有些莫名其妙,便以“findContours”为词条进行检索,发现很多人也遇到了类似的问题。于是想要偷个懒,直接用别人的解决方案解决自己的问题。然而事与愿违,看了几篇博客后,发现解决方案有很多,不乏有更改配置属性、修改contours的存储结构等等。再经过一系列试验后发现,诸多博客中提出的解决方案没有一种能够解决自己遇到的问题。因此,只能回过头,老老实实地分析触发中断的原因。
当视线再次回到VS2013后,发现"输出"中有报出:HEAP[findcontours_operate.exe]: Invalid address specified to RtlValidateHeap( 000001ECB8F10000, 000001ECBAC76A30 )。于是,调出了“调用堆栈”窗口,通过该窗口显示的内容,发现参数hierarchy是由mscvr120d.dll分配,由findcontours_operate.exe释放(如图3所示),中断很明显因为malloc和free不匹配导致。因此,怀疑是cv::OutputArray设计存在缺陷。后来在3.4.7版本的imgproc.hpp文件中发现,findContours函数从3.2版本后便没有在修改。便对比了3.0.0和3.4.7两个版本的contours.cpp内容,发现后续版本并未修改contours和hierarchy的写出方式,从侧面印证了自己的怀疑。
【图像处理】|【OpenCV】轮廓提取——findContours()
文章图片

图3 中断分析截图 若有同行遇到同样的问题,建议更换一个OpenCV版本,毕竟3.0.0是OpenCV3系列的首个版本。此外,在使用过程中,有人把vector<>> contours; 改成了vector contours; ,虽然也能编译运行,但个人不建议这样使用。在findContours函数的定义处,有明确说明contours参数的输出类型。我们在调用函数API时,应该使用规定的参数类型,从而减少不必要的调试。
参考文献:
  1. OpenCV官网
  2. 《学习OpenCV》
个人声明:
?? 以上内容,纯属个人观点,不喜勿喷。未经本人同意,不得私自转载。博客中出现的代码仅供学习参考,不得有其他用途。若文中存在纰漏,或读者有更好的建议,欢迎留言探讨。也可邮箱联系:yxyx_0212@163.com

    推荐阅读