【OpenCV】轮廓提取——findContours()

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

【OpenCV】轮廓提取——findContours()_第1张图片

图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 = 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<vector<cv::Point>> contours;
	vector<cv::Vec4i> 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

起初,用VS2013+OpenCV3.0.0编译运行上述代码,能够得到如图2所示的结果。但在退出运行时,触发了中断。于是,切换到VS2015 + OpenCV3.4.7上继续编译运行上述代码,得到的结果与图2所示结果一致,但在退出程序时,并未触发中断。

【OpenCV】轮廓提取——findContours()_第2张图片

图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张图片

图3 中断分析截图

若有同行遇到同样的问题,建议更换一个OpenCV版本,毕竟3.0.0是OpenCV3系列的首个版本。此外,在使用过程中,有人把vector<>> contours;改成了vector contours;,虽然也能编译运行,但个人不建议这样使用。在findContours函数的定义处,有明确说明contours参数的输出类型。我们在调用函数API时,应该使用规定的参数类型,从而减少不必要的调试。

参考文献:

  1. OpenCV官网
  2. 《学习OpenCV》

个人声明:
   以上内容,纯属个人观点,不喜勿喷。未经本人同意,不得私自转载。博客中出现的代码仅供学习参考,不得有其他用途。若文中存在纰漏,或读者有更好的建议,欢迎留言探讨。也可邮箱联系:[email protected]

你可能感兴趣的:(【图像处理】,opencv,debug)