第一个问题:

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

Fig 1.1

函数原型:

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

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

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

  1. #include <opencv2\opencv.hpp>
  2. #include <time.h>
  3. using namespace std;
  4. using namespace cv;
  5. //测试findContours,drawContours函数用法
  6. bool verify(vector<Point> input, int min, int max);
  7. int main()
  8. {
  9. Mat src = imread("test.bmp", 1);
  10. Mat dst = Mat::zeros(src.size(), src.type());
  11. //cout<<src.channels(); //画图软件生成3通道图
  12. Mat gray;
  13. cvtColor(src, gray, CV_BGR2GRAY);
  14. Mat thre;
  15. threshold(gray, thre, 127, 255, CV_THRESH_BINARY );
  16. //thre = gray > 1; //这种写法可以替代上面那一句
  17. imshow("thre", thre);
  18. imwrite("thre.bmp", thre);
  19. vector< vector<Point> > contours;
  20. vector<Vec4i> hierarchy;
  21. findContours(thre, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
  22. //计算每个轮廓的面积
  23. float temp = 0;
  24. for(int i=0; i<contours.size(); ++i) {
  25. temp = fabs(contourArea(contours[i]));
  26. cout<<"i="<<i<<" area="<<temp<<endl;
  27. }
  28. //我要把单独的小轮廓去掉,而保留大轮廓内部的孔洞
  29. int idx = 0;
  30. for( ; idx >=0; idx = hierarchy[idx][0])
  31. {
  32. if(verify(contours[idx], 200, 90000)) {
  33. Scalar color( rand()&255, rand()&255, rand()&255);
  34. drawContours(dst, contours, idx, color, CV_FILLED, 8, hierarchy);
  35. }
  36. }
  37. imshow("dst",dst);
  38. imwrite("dst.bmp", dst);
  39. while( 1 )
  40. {
  41. int key = waitKey(0); //获取键盘按键
  42. if( (key & 255) == 27 ) //判断ESC是否按下,若按下便退出
  43. {
  44. cout << "程序退出...........\n";
  45. break;
  46. }
  47. }
  48. return 0;
  49. }
  50. bool verify(vector<Point> input, int min, int max) {
  51. float area = fabs(contourArea(input));
  52. if((area>min) & (area<max))
  53. return true;
  54. else
  55. return false;
  56. }

结果图:

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);
  1. for(int i=0; i<contours.size(); ++i) {
  2. if(verify(contours[i], 200, 90000)) {
  3. Scalar color( rand()&255, rand()&255, rand()&255);
  4. drawContours(dst, contours, i, color, CV_FILLED, 8, hierarchy);
  5. }
  6. }

Fig 1.4

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

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

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

Fig 1.5

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

把上面的代码该成:

  1. int idx = 0;
  2. for( ; idx >=0; idx = hierarchy[idx][0])
  3. {
  4. if(verify(contours[idx], 0, 200)) {
  5. contours.erase(contours.begin() + idx);
  6. }
  7. }

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

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

暂时没有找出办法。

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

  1. vector<vector<Point>> contours;
  2. vector<Vec4i> hierarchy;
  3. findContours(thre, contours, hierarchy, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
  4. vector<vector<Point>>::const_iterator it = contours.begin();
  5. while(it != contours.end())
  6. {
  7. float area = contourArea(*it);
  8. float perimeter = fabs(arcLength(*it, true));
  9. cout << area << endl << "\t" <<perimeter<<endl;
  10. if(area<2000)
  11. it = contours.erase(it);
  12. else
  13. it++;
  14. }

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

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

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

测试图:

Fig 3.1

测试代码和结果:

  1. findContours(thre, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
  2. //计算每个轮廓的面积
  3. float temp = 0;
  4. for(int i=0; i<contours.size(); ++i) {
  5. temp = fabs(contourArea(contours[i]));
  6. cout<<"i="<<i<<" Contour_Area="<<temp<<endl;
  7. }
  8. //直接计算面积
  9. float temp1 = countNonZero(thre);
  10. 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”  (铃木算法?)。

Contours OpenCv问题相关推荐

  1. OpenCV 学习笔记03 boundingRect、minAreaRect、minEnclosingCircle、boxPoints、int0、circle、rectangle函数的用法...

    函数中的代码是部分代码,详细代码在最后 1 cv2.boundingRect 作用:矩形边框(boundingRect),用于计算图像一系列点的外部矩形边界. cv2.boundingRect(arr ...

  2. 基于代数距离的椭圆拟合

    问题 给定离散点集Xi=(xi,yi),i=1,2,...NX_i=(x_i,y_i) ,i=1,2,...NXi​=(xi​,yi​),i=1,2,...N,我们希望找到误差最小的椭圆去拟合这些离散 ...

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

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

  4. 解决: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 ...

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

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

  6. opencv contours的问题

    第一个问题: 问题:假如我有如下一张图,我要把边上两个小点去除,又要保留大轮廓内部的空洞,怎么办? Fig 1.1 函数原型: C++: void findContours(InputOutputAr ...

  7. openCV Contours详解

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

  8. opencv中关于轮廓检测识别Contours及相关函数的介绍

    最近在用vs和opencv库在做图像处理的项目,关于轮廓识别部分,我查阅了一些资料, 现结合自己的理解整理出来,希望能对你有用. 1.contours概述 在利用openCV对图像进行处理时,我们可能 ...

  9. Contours in OpenCV

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

最新文章

  1. 区域经济、地理信息、互联网三者交叉之行业背景分析
  2. 高效使用Google
  3. python匿名函数lambda_python的匿名函数lambda解释及用法
  4. 如果BigDecimal是答案,那肯定是一个奇怪的问题
  5. C++ 输出单个字符
  6. 淮海工学院期末考试Oracle,淮海工学院大一物理期末试卷
  7. python go rpc_Go实现简易RPC框架的方法步骤
  8. Android 系统(231)--OTA对要发布的编译版本进行签名
  9. SysUtils.UpperCase、SysUtils.LowerCase - 大小写转换
  10. spring cloud构建java版 b2b2c o2o电子商务云商平台
  11. 梦笔记2021-03-05
  12. java对象的序列化和反序列化_Java对对象的序列化和反序列化详解
  13. java 网络编程发展过程以及nio的特点
  14. python任务栏显示网速_win10状态栏显示网速小工具_超好用
  15. 项目实施中的风险控制与管理
  16. 函数式编程-Either函子篇
  17. 设计模式笔记-----七大原则
  18. 国产CAD软件对于AutoCAD,更适合哪种?
  19. BinaryWriter
  20. Java中的IO技术使用总结

热门文章

  1. 封装navbar组件
  2. Suterusu对话Waves | 隐私, DeFi繁荣的关键
  3. maven install 本地jar包
  4. 用python画小猪佩奇动画片全集_教你用Python画小猪佩奇
  5. java集合类(collection)
  6. 【活动预告】Cocos2d-x即将首次赴台办沙龙 让你满载而归
  7. mybatis中@Results,@ResultMap注解使用
  8. 2022年轻薄笔记本推荐:对屏幕有高需求的学生党,看过来
  9. win11如何安装msi文件
  10. php微信公众号首次关注自动回复,PHP_PHP微信开发之文本自动回复,首先,先去微信公众平台注册 - phpStudy...