第一次尝试写博客,就当做写一次随笔吧!
最近在做一个小项目,需要检测目标轮廓,而且只需要内轮廓,OpenCV自带的轮廓检测函数findContours使用起来特别方便,利用轮廓之间层级关系即可实现内轮廓查找。
findContours函数的具体使用方法自行查看相关教程和手册,下面仅对本算法相关的关键点进行一些强调和补充,如有错误,还请批评指正。
findContours( InputOutputArray image, OutputArrayOfArrays contours,
OutputArray hierarchy, int mode,
int method, Point offset=Point());
参数列表中有个数据结构参数:hierarchy(译层次结构),hierarchy是一个向量,其元素个数与查找到的轮廓总数相同,每一个元素中包含4个int类型数据hierarchy[i][0]~hierarchy[i][3]。
分别表示:
(1)表示同一级轮廓的下个轮廓的编号,如果这一级轮廓没有下一个轮廓,则为-1。
(2)表示同一级轮廓的上个轮廓的编号,如果这一级轮廓没有上一个轮廓,则为-1。
(3)表示该轮廓包含的下一级轮廓的第一个的编号,假如没有,则为-1。
(4)表示该轮廓的上一级轮廓的编号,假如没有上一级,则为-1。
具体参考博客:findContour函数详解
参数:int mode:定义轮廓的检索模式,mode不同,轮廓之间的拓扑结构就不同,具体参数定义可参考其他博客。
本文在仅检测内轮廓时使用了mode:CV_RETR_CCOMP 其检测所有的轮廓,但所有轮廓只建立两个等级关系,即外轮廓和内轮廓,如果内轮廓中还含有其他轮廓,则内轮廓内的所有轮廓均归属于外轮廓,以此类推。
程序中所用测试图像如下:
(在hsv颜色空间下通过红色阈值分割得到二值化图像对应代码段中find_color()函数)
代码如下,很简单:
int main()
{
Mat dstImage, SrcImage, drawImage;
string path = "F:\\Image_design\\circle4.png";
SrcImage = imread(path); //source image
if (!SrcImage.data) {
std::cout << "Could not open or find the image" << std::endl;
return -1;
}
find_color(SrcImage, dstImage); //hsv颜色空间提取红色部分
Mat markers = dstImage.clone();
drawImage = cv::Mat::zeros(SrcImage.size(), SrcImage.type());
std::vector< std::vector<cv::Point> > contours;
std::vector<cv::Vec4i> hierarchy;
contours.clear();
cv::findContours(markers, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE); //层级关系只选择两层
for (int i = 0; i < contours.size(); i++)
{
if (hierarchy[i][3] != -1 && contourArea(contours[i]) > 50)
{
drawContours(SrcImage, contours, i, Scalar(0, 255, 0), 2);
drawContours(drawImage, contours, i, Scalar(0, 255, 0), 1);
cout << "向量hierarchy的第" << to_string(i) << " 个元素内容为:" << endl << hierarchy[i] << endl << endl;
}
}
imshow("contours", SrcImage);
waitKey(0);
return 0;
}
总结:其实只要理解了mode:CV_RETR_CCOMP 和hierarchy的实际意义,算法的关键点就是一句话:
if (hierarchy[i][3] != -1 ) 就保存该轮廓;
(选用CV_RETR_CCOMP时,上文测试图的层级结构数据)
因为层级关系就两层,当向量hierarchy元素中最后一位数据不为-1时,表示该轮廓存在外轮廓,那么此轮廓就是内轮廓了。
(学习OpenCV中,第一次写博客,如有错误,敬请谅解!)