第一个问题:

问题:假如我有如下一张图,我要把边上两个小点去除,又要保留大轮廓内部的空洞,怎么办?

Fig 1.1

函数原型:

C++: void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point())
C++: void findContours(InputOutputArray image, OutputArrayOfArrays contours, int mode, int method, Point offset=Point())

这两个重载函数只有一个hierarchy的差别。说明如下:

测试:   用以下代码测试一下 contours 和 hierarchy 是怎么对轮廓进行标号的?选择mode = CV_RETR_CCOMP 双层结构。

#include <opencv2\opencv.hpp>
#include <time.h>
using namespace std;
using namespace cv;//测试findContours,drawContours函数用法bool verify(vector<Point> input, int min, int max);int main()
{Mat src = imread("test.bmp", 1);Mat dst = Mat::zeros(src.size(), src.type());//cout<<src.channels();  //画图软件生成3通道图Mat gray;cvtColor(src, gray, CV_BGR2GRAY);Mat thre;threshold(gray, thre, 127, 255, CV_THRESH_BINARY );//thre = gray > 1;  //这种写法可以替代上面那一句imshow("thre", thre);imwrite("thre.bmp", thre);vector< vector<Point> > contours;vector<Vec4i> hierarchy;findContours(thre, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);//计算每个轮廓的面积float temp = 0;for(int i=0; i<contours.size(); ++i) {temp = fabs(contourArea(contours[i]));cout<<"i="<<i<<"  area="<<temp<<endl;}//我要把单独的小轮廓去掉,而保留大轮廓内部的孔洞int idx = 0;for( ; idx >=0; idx = hierarchy[idx][0]){if(verify(contours[idx], 200, 90000)) {Scalar color( rand()&255, rand()&255, rand()&255);drawContours(dst, contours, idx, color, CV_FILLED, 8, hierarchy);        }}imshow("dst",dst);imwrite("dst.bmp", dst);while( 1 ){int key = waitKey(0);    //获取键盘按键   if( (key & 255) == 27 )  //判断ESC是否按下,若按下便退出  {  cout << "程序退出...........\n";  break;  }  }return 0;
}bool verify(vector<Point> input, int min, int max) {float area = fabs(contourArea(input));if((area>min) & (area<max)) return true;else return false;
}

结果图:

Fig 1.2

我计算了每个轮廓的面积, 判断面积在指定范围内就把该轮廓画出来。在查找轮廓的时候,我只查找 hierarchy[i][0],就是不去处理子轮廓。 发现已经达到要求了。

分析:

contours 有6个,hierarchy 也有6个,对应测试图中总共6个内外轮廓。

hierarchy[i] 表示第 i 个轮廓,有四个值 hierarchy[i][0], hierarchy[i][1], hierarchy[i][2], hierarchy[i][3], 分别对应next, previous, first child, paraent.  它们的值是轮廓编号,如果没有响应的轮廓则等于-1. 从局部变量里面查看,这些值是这样子的:

0: 2 -1 1 -1
1: -1 -1 -1 0
2: 4 0 3 -1
3: -1 -1 -1 2
4: 5 2 -1 -1
5: -1 4 -1 -1
计算每个轮廓的面积:

因此推测出轮廓序号是这样子的:

Fig 1.3

这里面关键的一点是:填充序号为0和2的轮廓的时候,CV_FILLED, 并没有把内轮廓也填充掉,而是填充了环形区域!

如果我把 mode 改成 CV_RETR_LIST, 单层轮廓。

代码和测试结果图:

findContours(thre, contours, hierarchy, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
 for(int i=0; i<contours.size(); ++i) {if(verify(contours[i], 200, 90000)) {Scalar color( rand()&255, rand()&255, rand()&255);drawContours(dst, contours, i, color, CV_FILLED, 8, hierarchy);      }       }

Fig 1.4

果真就把内部轮廓也一起填充了。

轮廓重新排列,根据面积和 hierarchy 的值确定轮廓是这样排序的:

先排内部轮廓,再排外部轮廓(可能是随机的)。

Fig 1.5

第二个问题:contours.erase() 函数怎么用

把上面的代码该成:

 int idx = 0;for( ; idx >=0; idx = hierarchy[idx][0]){if(verify(contours[idx], 0, 200)) {contours.erase(contours.begin() + idx);  }}

这样用会报错,当idx = 5 时, 已经不存在 contours[5]; 因为执行 contours.erase() 之后,该 contour 会被清除,后面的 contour 会补位,contours.size()的值变小了。也就是,contours  和 hierarchy 的序号不同步了。

只用contours 序号不会错, 但是我又想要实现最开始给出的那个功能,就要用到hierarchy的信息。怎么办呢?

暂时没有找出办法。

如果有迭代器,contours.erase()是可以用的:

 vector<vector<Point>> contours;vector<Vec4i> hierarchy;findContours(thre, contours, hierarchy, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);vector<vector<Point>>::const_iterator it = contours.begin();while(it != contours.end()){float area = contourArea(*it);float perimeter = fabs(arcLength(*it, true));cout << area << endl << "\t" <<perimeter<<endl;if(area<2000)it = contours.erase(it);elseit++;}

it 指向 vector<vector<Point>>结构,即 contours 的结构(多个contour),它最初指向首地址,每次 it++, 它就向后增加一个 contour 的内存大小,指向了下一个contour。
当执行 contours.erase(it)之后,该contour被删除了,it 的位置在原地没有变,后面所有的轮廓都向前补位,同时contours.end() 的位置也就向前补了一位,处理完所有轮廓后就满足了 it = contours.end(), 然后循环退出。

第三个问题:轮廓面积计算结果和像素点总数的关系

findContours()  提取轮廓, contourArea() 计算面积,得到的结果跟我数像素点个数是不一样的。

测试图:

Fig 3.1

测试代码和结果:

        findContours(thre, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);//计算每个轮廓的面积float temp = 0;for(int i=0; i<contours.size(); ++i) {temp = fabs(contourArea(contours[i]));cout<<"i="<<i<<"  Contour_Area="<<temp<<endl;}//直接计算面积float temp1 = countNonZero(thre);cout<<"NonZero="<<temp1<<endl;

Fig 3.2

只有一个轮廓,通过提取轮廓再求轮廓内部面积得到100, 直接数连通域像素点个数得到121。差别还是很大的。多次试验发现,该算法是取连通域边界像素中心点,连接起来,成为一个轮廓。测试图中的正方形边长为11个像素点,Contour_Area = 10*10=100;   NonZero = 11*11 = 121. 示意图如下:

Fig 3.3

如果提取轮廓再算面积是 3*3=9; 而连通域像素点个数是4*4=16;

因此,countArea() 函数算出来的轮廓面积常常出现 “0“值就可以理解了,比如有的轮廓厚度只有两像素,都是边缘线,那计算出来的厚度就等于0;

第四个问题:findContours()    method 参数的影响

method = CV_CHAIN_APPROX_NONE  存储所有的轮廓点,也就是任意两个相邻的轮廓点8领域相连。存储的轮廓点都是紧挨着的,不会跳过。

method = CV_CHAIN_APPROX_SIMPLE   压缩水平、垂直或斜对角方向的片段,连接两个端点,只存储两个端点像素点。比如矩形,只存储四个点。

测试图片:

Fig 4.1

      

Fig 4.2

两种method的计算结果竟然相同。只是contours[i]  里面存储的点数不一样。边界点存储的少了难道不会丢失信息吗?drawContours 画出来的边界是否完全一样?

Fig 4.3

放大了看边界也是一样的,计算了两者面积也是一样的。

既然这样,CV_CHAIN_APPROX_SIMPLE 还可以减少存储,CV_CHAIN_APPROX_NONE 用在哪里呢?

第五个问题:findContours() 提取轮廓    drawContours() 再画出轮廓,面积和原来的会不会变化?

仍用图4.1做测试,结果:

Fig 5.1
上面三行 contourArea() 算出的三个连通域的面积,sun_area 是三个值的和;(和第一个问题结果一样,该面积小于连通域像素点个数)。
src_area 是原图像连通域像素点个数; dst_area 是经过提取轮廓再画出轮廓后计算出的连通域面积,两者是一样的。

说明连着用这两个函数不会改变原来的形状,相当于复制了一遍。

第六个问题:计算外轮廓面积是环形面积吗?会把内轮廓一块算上吗?

测试图:

Fig 6.1

图中的两个大圆是一样的。

findContours(thre, contours, hierarchy, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);

findContours(thre, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);


虽然排序不同,但是不同参数下的面积是一样的,说明计算外轮廓包围面积的时候是实心的,不会把内轮廓抠掉。

如果我想得到环形的面积,要用外轮廓-内轮廓。

好了,实验到这里。下一步可以去看 contours.cpp 源码。手册上面说了,主要算法叫“ Suzuki85”  (铃木算法?)。

opencv contours的问题相关推荐

  1. openCV Contours详解

    在OpenCV中处理结构分析和形状描述(Structural Analysis and Shape Descriptors),大部分跟contours相关. 轮廓线就是一条连接所有边界点的曲线,其实也 ...

  2. Opencv contours找出最大轮廓

    在处理二值图像时,常用 cv2.findContours 查找轮廓,如下所示: # find all cohntours contours,hierarchy=cv2.findContours(bin ...

  3. OpenCV Contours 使用记录

    labelme 的 polygon 转 contour,并计算面积 # 1. 格式 [[[x1,y1]], [[x2,y2]], ...] # 2. 一定是np.int64 points_list = ...

  4. Contours in OpenCV

    翻译自 Contour in OpenCV Contours Contours 可以简单的理解为一条连通连续点的曲线(沿着边缘),有同样的颜色或者强度.在进行形状分析,目标检测和识别时很有用处. 为了 ...

  5. 利用OpenCV的findContours作轮廓检测

    图像处理开发需求.图像处理接私活挣零花钱,请加微信/QQ 2487872782 图像处理开发资料.图像处理技术交流请加QQ群,群号 271891601 问: 边缘检测与轮廓检测有什么区别? 边缘检测是 ...

  6. openCV 自学笔记

    opencv官方文件 一:Mac下命令方式搭建opencv开发环境 1.安装homebrew 在终端输入homebrew下的命令 2.根据提示配置环境 3.通过以下两个命令安装配置环境 brew in ...

  7. 美美的圣诞树画出来-CoCube

    2022年圣诞节到来啦,很高兴这次我们又能一起度过~ CSDN诚邀各位技术er分享关于圣诞节的各种技术创意,展现你与众不同的精彩!参与本次投稿即可获得[话题达人]勋章+[圣诞快乐]定制勋章(1年1次, ...

  8. 解决:cv2.error: OpenCV(4.6.0) D:\a\opencv-python\opencv-python\opencv\modules\imgproc\src\contours.cpp

    解决:cv2.error: OpenCV(4.6.0) D:\a\opencv-python\opencv-python\opencv\modules\imgproc\src\contours.cpp ...

  9. Opencv 图像处理-Contours函数提取轮廓及感兴趣区域ROI的必用且实用操作技巧-(涵盖Contours的一切使用基础,附代码段)

            需求目的:一般都是做项目时使用opencv的findcontours和drawcontours搭配使用抓取图像内感兴趣区域. 1.Contours函数轮廓点大小排序 当使用findco ...

最新文章

  1. 抽象方法和接口的区别
  2. 2字节十六进制浮点数 qt_Qt中如何实现十六进制“41A4 0000”十六进制转为浮点数20.5呢?...
  3. cobertura-maven-plugin
  4. 【C++】复制构造函数
  5. [你必须知道的.NET]第十三回:从Hello, world开始认识IL
  6. 一文搞定JS事件基础与进阶
  7. 揭秘阿里云 RTS SDK 如何实现直播降低延迟和卡顿
  8. SAP Spartacus B2B 页面信息提示图标的弹出窗口显示实现逻辑
  9. nohttp网络框架
  10. SAP License:初学者使用SAP常见问题
  11. openssl中算法的组织方式
  12. iOS 热敏打印机打印位图
  13. Python的优点和缺点
  14. import clip时Cannot re-initialize CUDA in forked subprocess
  15. thinningopencv
  16. gallery3d源码学习总结(一)——绘制流程drawFocusItems
  17. CF 1139C Edgy Trees
  18. File.separator用法
  19. 苹果平板怎么卸载软件_苹果手机怎么装第三方软件
  20. A Fixed-Point Model for Pancreas Segmentation in Abdominal CT Scans

热门文章

  1. jarsigner: 找不到app的证书链——Android 应用认领【oppo应用商店】
  2. Android 5 Emulator root 模拟器 root
  3. a76比a73强多少_不吹不黑谈谈自己用A7和A73的感受
  4. .properties文件的格式
  5. 百度网盘免费扩容 免费扩容到2048G
  6. 蓝牙耳机比起有线耳机有什么优势?2020新款高续航低延迟蓝牙耳机分享
  7. 对JPA的理解与回顾总结(一)
  8. Oracle的几种客户端
  9. mysql中下列关于创建_在 MySQL 中,下列关于创建数据库表的描述正确的是( )。_学小易找答案...
  10. 用jquery简单的实现京东轮播图效果