目录
- 1、轮廓的定义
- 2、如何在图像中找到轮廓
- opencv自带的查找轮廓函数:findContours()
- 3、轮廓的表达方式
- 1.顶点的序列
- 2.Freeman链码
- 4、轮廓之间的组织方式
- 5、轮廓的特点(这部分可以展开来详细探讨,这里不做过多解释)
- 6、轮廓的匹配
- 7、绘制轮廓drawContours()
- 8、例子
- 动态检测图形的轮廓
- 步骤
- 理解
- 演示效果
1、轮廓的定义
轮廓是构成任何一个形状的边界或外形线。
2、如何在图像中找到轮廓
利用OpenCV提供的方法cvFindContours()可以很方便的查找轮廓。cvFindContours()方法从二值图像中寻找轮廓。
因此此方法处理的图像可以是从cvCanny()函数得到的有边缘像素的图像,或者从cvThreshold()及cvAdaptiveThreshold()得到的图像,这时的边缘是正和负区域之间的边界。
opencv自带的查找轮廓函数:findContours()
参数讲解:
【1】检测轮廓
//定义轮廓和层次结构
vector<vector<Point>> contours; //每个轮廓存储为一个点向量
vector<Vec4i>hierarchy; // Vec4i
findContours(srcImage,contours, //轮廓数组hierarchy,RETR_EXTERNAL, //获取外轮廓CHAIN_APPROX_NONE //获取每个轮廓中的每个像素);
3、轮廓的表达方式
1.顶点的序列
序列是内存存储器中可以存储的一种对象。序列是某种结构的链表。序列在内存中被实现为一个双端队列,因此序列可以实习快速的随机访问,以及快速删除顶端的元素,但是从中间删除元素则稍慢些。
用多个顶点(或各点间的线段)来表达轮廓。假设要表达一个从(0,0)到(2,2)的矩形,
(1)如果用点来表示,那么依次存储的可能是:(0,0),(1,0),(2,0),(2,1),(2,2),(1,2),(0,2),(0,1);
(2)如果用点间的线段来表达轮廓,那么依次存储的可能是:(0,0),(2,0),(2,2),(0,2)。
2.Freeman链码
Freeman链码需要一个起点,以及从起点出发的一系列位移。每个位移有8个方向,从0~7分别指向从正北开始的8个方向。假设要用Freeman链码表达从(0,0)到(2,2)的矩形,可能的表示方法是:起点(0,0),方向链2,2,4,4,6,6,0,0。
4、轮廓之间的组织方式
在查找到轮廓之后,不同轮廓是怎么组织的呢?根据不同的选择,它们可能是:(1)列表;(2)双层结构;(3)树型结构。
从纵向上来看,列表只有一层,双层结构有一或者两层,树型结构可能有一层或者多层。
如果要遍历所有的轮廓,可以使用递归的方式。
5、轮廓的特点(这部分可以展开来详细探讨,这里不做过多解释)
1.轮廓的多边形逼近
轮廓的多边形逼近指的是:使用多边形来近似表示一个轮廓。
多边形逼近的目的是为了减少轮廓的顶点数目。
多边形逼近的结果依然是一个轮廓,只是这个轮廓相对要粗旷一些。 可以使用方法cvApproxPoly()2.轮廓的关键点
轮廓的关键点是:轮廓上包含曲线信息比较多的点。关键点是轮廓顶点的子集。
可以使用cvFindDominantPoints函数来获取轮廓上的关键点,该函数返回的结果一个包含 关键点在轮廓顶点中索引 的序列。再次强调:是索引,不是具体的点。如果要得到关键点的具体坐标,可以用索引到轮廓上去找。
3.轮廓的周长和面积
轮廓的周长可以用cvContourPerimeter或者cvArcLength函数来获取。
轮廓的面积可以用cvContourArea函数来获取。4.轮廓的边界框
有三种常见的边界框:矩形、圆形、椭圆。
(1)矩形:在图像处理系统中提供了一种叫Rectangle的矩形,不过它只能表达边垂直或水平的特例;OpenCv中还有一种叫Box的矩形,它跟数学上的矩形一致,只要4个角是直角即可。
如果要获取轮廓的Rectangle,可以使用cvBoundingRect函数。
如果要获取轮廓的Box,可以使用cvMinAreaRect2函数。
(2)圆形
如果要获取轮廓的圆形边界框,可以使用cvMinEnclosingCircle函数。
(3)椭圆
如果要获取轮廓的椭圆边界框,可以使用cvFitEllipse2函数。
5.轮廓的矩
矩是通过对轮廓上所有点进行积分运算(或者认为是求和运算)而得到的一个粗略特征。
1空间矩
空间矩的实质为面积或者质量。可以通过一阶矩计算质心/重心。
重心:
2中心矩
中心矩体现的是图像强度的最大和最小方向(中心矩可以构建图像的协方差矩阵),其只具有平移不变性,所以用中心矩做匹配效果不会很好。
3归一化的中心矩
归一化后具有尺度不变性。
4Hu矩
6.轮廓的轮廓树
轮廓树用来描述某个特定轮廓的内部特征。注意:轮廓树跟轮廓是一一对应的关系;轮廓树不用于描述多个轮廓之间的层次关系。
轮廓树的创建过程:
从一个轮廓创建一个轮廓树是从底端(叶子节点)到顶端(根节点)的。首先搜索三角形突出或者凹陷的形状的周边(轮廓上的每一个点都不是完全和它的相邻点共线的)每个这样的三角形被一条线段代替,这条线段通过连接非相邻点的两点得到;因此实际上三角形或者被削平或者被填满。每个这样的替换都把轮廓的顶点减少,并且给轮廓树创建一个新节点。如果这样的一个三角形的两侧有原始边,那么她就是得到的轮廓树的叶子;如果一侧已是一个三角形,那么它就是那个三角形的父节点。这个过程的迭代最终把物体的外形简称一个四边形,这个四边形也被剖开;得到的两个三角形是根节点的两个子节点。
结果的二分树最终将原始轮廓的形状性比编码。每个节点被它所对应的三角形的信息所注释。
这样建立的轮廓树并不太鲁棒,因为轮廓上小的改变也可能会彻底改变结果的树,同时最初的三角形是任意选取的。为了得到较好的描述需要首先使用函数cvApproxPoly()之后将轮廓排列(运用循环移动)成最初的三角形不怎么收到旋转影响的状态。
可以用函数cvCreateContourTree来构造轮廓树。
7.轮廓的凸包和凸缺陷
这块内容会在后续详细探讨。
8.轮廓的成对几何直方图
成对几何直方图(pairwise geometrical histogram PGH)是链码编码直方图(chain code histogram CCH)的一个扩展或者延伸。CCH是一种直方图,用来统计一个轮廓的Freeman链码编码每一种走法的数字。这种直方图的一个优良性质为当物体旋转45度,那么新直方图是老直方图的循环平移。这样就可以不受旋转影响。
(1)轮廓保存的是一系列的顶点,轮廓是由一系列线段组成的多边形。对于看起来光滑的轮廓(例如圆),只是线段条数比较多,线段长度比较短而已。实际上,电脑中显示的任何曲线都由线段组成。
(2)每两条线段之间都有一定的关系,包括它们(或者它们的延长线)之间的夹角,两条线段的夹角范围是:(0,180)。
(3)每两条线段上的点之间还有距离关系,包括最短(小)距离、最远(大)距离,以及平均距离。最大距离我用了一个偷懒的计算方法,我把轮廓外界矩形的对角线长度看作了最大距离。
(4)成对几何直方图所用的统计数据包括了夹角和距离。
6、轮廓的匹配
1.Hu矩匹配
轮廓的Hu矩对包括缩放、旋转和镜像映射在内的变化具有不变性。cvMatchShapes函数可以很方便的实现对2个轮廓间的匹配。
2.轮廓树匹配
用树的形式比较两个轮廓。cvMatchContourTrees函数实现了轮廓树的对比。
3.成对几何直方图匹配
在得到轮廓的成对几何直方图之后,可以使用直方图对比的方法来进行匹配。
7、绘制轮廓drawContours()
轮廓的绘制比较简单,用上面提到的方法取得轮廓的所有点,然后把这些点连接成一个多边形即可。
使用函数drawContours(),更加方便。
使用例子:
//【2】绘制轮廓
//遍历所有顶层的轮廓,用不同的颜色绘制出来
int index = 0;
for (; index >= 0; index = hierarchy[index][0])
{Scalar color(rand() & 255,rand() & 255,rand() & 255); //随机数drawContours(dstImage, //outputImagecontours, //轮廓信息index, //当前轮廓的索引值color, //轮廓颜色FILLED, //绘制在轮廓内部8, //8连通线型hierarchy); //轮廓间的层次信息
}
8、例子
动态检测图形的轮廓
步骤
【1】读取原图,转为灰度图并高斯模糊
【2】canny检测图像边缘(滑动条控制阈值)
【3】对canny算子扫描后的图像进行查找轮廓
【4】绘制轮廓
理解
Canny之类的边缘检测算法可以根据像素间的差异检测出轮廓边界的像素,但是它并没有将轮廓作为一个整体。
对canny扫描后的图像查找轮廓是一种较好的选择。
#include <opencv2/opencv.hpp>
#include <iostream>
#include "windows.h"
#include <stdio.h>
#define WINDOW_NAME "【程序窗口】" //为窗口标题定义的宏using namespace cv;
using namespace std;
//===========================动态检测图形的轮廓====================//=================全局变量声明=================
Mat g_srcImage;
Mat g_grayImage;
int g_nThresh = 80;
int g_nThresh_max = 255;
RNG g_rng(12345);
Mat g_cannyMat_output;
vector<vector<Point>> g_vContours;
vector<Vec4i> g_vHierarchy;//=============全局函数声明===============
void on_ThreshChange(int,void*);
int main()
{// Read image 读取图像SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN); //字体为绿色//载入原图g_srcImage = imread("D:\\opencv_picture_test\\lena.jpg",1);//Mat srcImage = imread("D:\\opencv_picture_test\\形态学操作\\孔洞.png", 0); //读取灰度图//转换成灰度并且模糊化降噪cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);blur(g_grayImage, g_grayImage, Size(3, 3));//创建窗口namedWindow("原始图窗口", WINDOW_AUTOSIZE);imshow("原始图窗口", g_srcImage);//创建滑动条并初始化createTrackbar("canny 阈值", "原始图窗口", &g_nThresh,g_nThresh_max, on_ThreshChange);on_ThreshChange(0,0);waitKey(0);return 0;
}
void on_ThreshChange(int, void*)
{//用Canny算子检测边缘Canny(g_grayImage, g_cannyMat_output, g_nThresh, g_nThresh * 2, 3);//寻找轮廓.findContours(g_cannyMat_output, g_vContours, g_vHierarchy,RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));//绘出轮廓Mat drawing = Mat::zeros(g_cannyMat_output.size(), CV_8UC3);for (int i = 0; i < g_vContours.size(); i++){Scalar color = Scalar(g_rng.uniform(0, 255),g_rng.uniform(0, 255), g_rng.uniform(0, 255));//任意值drawContours(drawing, g_vContours, i, color, 2, 8, g_vHierarchy,0, Point());}//显示效果图imshow("轮廓图",drawing);
}
演示效果
参考链接:
主讲轮廓的特性;
轮廓的矩;
轮廓的矩;
以及《《OpenCV3编程入门》毛星云编著_电子工业出版社》。