opencv——轮廓发现与轮廓(二值图像)分析
引言
二值图像分析最常见的一个主要方式就是轮廓发现与轮廓分析,其中轮廓发现的目的是为轮廓分析做准备,经过轮廓分析我们可以得到轮廓各种有用的属性信息。
这里顺带提下边缘检测,和轮廓提取的区别:
边缘检测主要是通过一些手段检测数字图像中明暗变化剧烈(即梯度变化比较大)像素点,偏向于图像中像素点的变化。如canny边缘检测,结果通常保存在和源图片一样尺寸和类型的边缘图中。
轮廓检测指检测图像中的对象边界,更偏向于关注上层语义对象。如OpenCV中的findContours()函数, 它会得到每一个轮廓并以点向量方式存储,除此也得到一个图像的拓扑信息,即一个轮廓的后一个轮廓、前一个轮廓、父轮廓和内嵌轮廓的索引编号。
一,轮廓的发现与绘制
在OpenCV里面利用findContours()函数和drawContours()函数实现这一功能。
- findContours()函数
void findContours(
InputArray image,
OutputArrayOfArrays contours,
OutputArray hierarchy,
int mode,
int method,
Point offset = Point()
)
参数一: image,输入图像、八位单通道的,背景为黑色的二值图像。(一般是经过Canny、拉普拉斯等边缘检测算子处理过的二值图像)
参数二:contours,输出轮廓图像。是一个向量,向量的每个元素都是一个轮廓。因此,这个向量的每个元素仍是一个向量。即:
vector<vector<Point> > contours;
参数三:hierarchy,输出各个轮廓的继承关系。hierarchy也是一个向量,长度和contours相等,每个元素和contours的元素对应。hierarchy的每个元素是一个包含四个整型数的向量。即:
vector<Vec4i> hierarchy;
参数四:mode,检测轮廓的方法。有四种方法:
- RETR_EXTERNAL:只检测外轮廓。忽略轮廓内部的洞。
- RETR_LIST:检测所有轮廓,但不建立继承(包含)关系。
- RETR_TREE:检测所有轮廓,并且建立所有的继承(包含)关系。
- RETR_CCOMP:检测所有轮廓,但是仅仅建立两层包含关系。
参数五:method,每个轮廓的编码信息。也有四种(常用前两种)
- CHAIN_APPROX_NONE:把轮廓上所有的点存储。
- CHAIN_APPROX_SIMPLE:只存储轮廓上的拐点。
- CHAIN_APPROX_TC89_L1,CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法
参数六: Point,偏移量。默认为0
注意:该函数将白色区域当作前景物体。所以findContours()函数是黑色背景下找白色轮廓。(重要!!!)
- drawContours()函数
drawContours(
InputOutputArray binImg, // 输出图像
OutputArrayOfArrays contours,// 全部发现的轮廓对象
Int contourIdx// 轮廓索引号,-1表示绘制所有轮廓
const Scalar & color,// 绘制时候颜色
int thickness,// 绘制线宽,-1表示填充轮廓内部
int lineType,// 线的类型LINE_8
InputArray hierarchy,// 拓扑结构图
int maxlevel,// 最大层数, 0只绘制当前的,1表示绘制绘制当前及其内嵌的轮廓
Point offset = Point()// 轮廓位移,可选
)
二,轮廓分析(二值图像分析)
在得到图像的轮廓以后,我们就可以进行轮廓分析。经过轮廓分析我们可以得到轮廓各种有用的属性信息、常见的如下:
计算轮廓面积 :
contourArea(contour, oriented = False) //计算轮廓的面积 参数说明:contour为输入的单个轮廓值;oriented:轮廓方向,默认值false。
如果为true,该函数返回一个带符号的面积,其正负取决于轮廓的方向(顺时针还是逆时针)。
如果是默认值false,则面积以绝对值的形式返回.
根据这个特性可以根据面积的符号来确定轮廓的位置。
- 计算轮廓周长:
arcLength(contour, closed) // 计算轮廓的周长 参数说明:contour为输入的单个轮廓值,closed表示轮廓是否封闭(true为封闭,false为不封闭)
- 计算几何矩与中心距: moments()
Moments m = moments(contours[t]); //获取轮廓的距
//计算轮廓质心
double cx = m.m10 / m.m00;
double cy = m.m01 / m.m00;
- 轮廓的外接矩形:
轮廓的外接矩形有两种,如下图,绿色的叫外接矩形boundingRect(),表示不考虑旋转并且能包含整个轮廓的矩形。蓝色的叫最小外接矩形minAreaRect(),考虑了旋转
1️⃣外接矩形Rect boundingRect(InputArray points)
输入参数points可以一系列点的集合,对轮廓来说就是该轮廓的点集 返回结果是一个正矩形,包含以下信息:
- 矩形左上角的坐标(rect.x,rect.y)
- 矩形的宽和高(rect.width,rect.height)
Rect rect = boundingRect(Mat(contours[i]));//获取轮廓外接正矩形
rectangle(src, rect, (0, 0, 255), 2, 8, 0);
2️⃣最小外接矩形minAreaRect()
输入参数points可以一系列点的集合,对轮廓来说就是该轮廓的点集 返回结果是一个旋转矩形,包含下面的信息:
- 旋转矩形的中心坐标(rect.center)
- 旋转矩形的宽和高(rect.size.width,rect.size.height)
- 旋转矩形的角度(rect.angle)
RotatedRect rect = minAreaRect(contours[i]);//获取轮廓最小外接矩形
Point2f P[4];
rect.points(P);//获取四顶点坐标
for (int j = 0; j <= 3; j++)
{
line(src, P[j], P[(j + 1) % 4], Scalar(0,0,255), 1);//依次连线
}
- 最小外接圆/拟合圆:minEnclosingCircle()
void minEnclosingCircle(InputArray points, Point2f& center, float& radius);
points,输入的二维点集,可以是 vector 或 Mat 类型。
center,圆的输出圆心。
radius,圆的输出半径。 例如:
findContours(bin_img, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
//寻找包裹轮廓的最小圆
vector<Point2f>centers(contours.size());//圆心个数
vector<float>radius(contours.size());//半径个数
for (int i = 0; i < contours.size(); i++)
{
//寻找并绘制最小圆
minEnclosingCircle(contours[i], centers[i], radius[i]);
circle(src, centers[i], radius[i], scalar(0,0,255), 2);
}
- 拟合椭圆:fitEllipse()
RotatedRect fitEllipse(InputArray points);
//唯一一个参数是输入的二维点集,可以是 vector 或 Mat 类型。
例如:
// 轮廓发现与绘制
vector<vector<Point>> contours;
findContours(binary, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point());
for (size_t t = 0; t < contours.size(); t++)
{
// 拟合椭圆
RotatedRect rrt = fitEllipse(contours[t]);
ellipse(src, rrt, Scalar(0, 0, 255), 2, 8);
}
imshow("contours", src);
- 拟合直线:fitLine()
OpenCV中直线拟合正是基于最小二乘法实现的。其函数将计算出的直线信息存放在 line 中,(为Vec4f 类型)。line[0]、line[1] 存放的是直线的方向向量,float cosθ = oneline[0]; float sinθ = oneline[1]。line[2]、line[3] 存放的是直线上一个点的坐标。
实现直线拟合的API如下:
void fitLine(
InputArray points, //输入待拟合的二维点的数组或vector
OutputArray line, //输出直线,Vec4f (2d)或Vec6f (3d)的vector
int distType, //距离类型
double param, //距离参数(一般设为0)
double reps, //径向的精度参数(一般设为0.01)
double aeps //角度精度参数(一般设为0.01)
)
distType(距离类型)有六种参数:(DIST_L2就是最小二乘法)
opencv实现:
Mat src = imread("D:/opencv练习图片/直线拟合.png");
imshow("原图片", src);
// 去噪声与二值化
Mat dst, gray, binary;
Canny(src, binary, 80, 160, 3, false);
imshow("canny二值化", binary);
Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
dilate(binary, binary, k);
// 轮廓发现与绘制
vector<vector<Point>> contours;
findContours(binary, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point());
for (size_t t = 0; t < contours.size(); t++) {
// 最大外接轮廓
Rect rect = boundingRect(contours[t]);
int m = max(rect.width, rect.height);
if (m < 30) continue;
// 直线拟合
Vec4f oneline;
fitLine(contours[t], oneline, DIST_L1, 0, 0.01, 0.01);
float cosθ = oneline[0];
float sinθ = oneline[1];
float x0 = oneline[2];
float y0 = oneline[3]; // 直线参数斜率k与截矩b
float k = sinθ / cosθ; //求tanθ,也就是斜率
float b = y0 - k * x0; float x = 0;
float y = k * x + b;
line(src, Point(x0, y0), Point(x, y), Scalar(0, 0, 255), 2, 8, 0);
}
imshow("结果", src);
- 轮廓的凸包:convexHull()
凸包(Convex Hull)是一个计算几何(图形学)中常见的概念。简单来说,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它是能包含点集中所有点的。
理解物体形状或轮廓的一种比较有用的方法便是计算一个物体的凸包,然后计算其凸缺陷(convexity defects)。
convexHull ( InputArray points, /输入的二维点集,Mat类型数据即可
OutputArray hull, //输出参数,用于输出找到的凸包
bool clockwise = false, //操作方向,为True时,输出的凸包为顺时针方向,否则为逆时针方向
bool returnPoints = true //凸包的返回形式,默认值为true,此时返回点坐标的形式,否则返回对应点的索引值
)
凸包检测原理:
opencv实现:
Mat src = imread("D:/opencv练习图片/凸包检测.jpg");
imshow("原图片", src);
// 二值化
Mat dst, gray, binary;
cvtColor(src, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
// 形态学去除干扰
Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(binary, binary, MORPH_OPEN, k);
imshow("binary", binary);
// 轮廓发现与绘制
vector<vector<Point>> contours;
findContours(binary, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point());
for (size_t t = 0; t < contours.size(); t++) {
vector<Point> hull;
convexHull(contours[t], hull);//凸包检测
bool isHull = isContourConvex(contours[t]);//判断轮廓是否为凸包
printf("test convex of the contours %s", isHull ? "Y" : "N");
int len = hull.size();
//绘制凸包
for (int i = 0; i < hull.size(); i++) {
circle(src, hull[i], 4, Scalar(255, 0, 0), 2, 8, 0);//点
line(src, hull[i%len], hull[(i + 1) % len], Scalar(0, 0, 255), 2, 8, 0);//线
}
}
imshow("凸包检测", src);
多边形逼近-逼近真实形状:approxPolyDP()
轮廓的多边形逼近指的是:使用多边形来近似表示一个轮廓。 多边形逼近的目的是为了减少轮廓的顶点数目。 多边形逼近的结果依然是一个轮廓,只是这个轮廓相对要粗旷一些。
void approxPolyDP( InputArray curve, //输入曲线,一般是由图像的轮廓点组成的点集
OutputArray approxCurve, //表示输出的逼近后多边形的点集(类型与输入曲线的类型相同)
double epsilon, //轮廓逼近的顶点距离真实轮廓曲线的最大距离,该值越小表示越逼近真实轮廓
bool closed //表示输出的多边形是否封闭
)
opencv实现:
Mat src = imread("D:/opencv练习图片/多边形逼近.png");
Mat dstImage_3(src.size(), CV_8UC3, Scalar(0));
Mat dstImage_6(src.size(), CV_8UC3, Scalar(0));
Mat dstImage_10(src.size(), CV_8UC3, Scalar(0));
imshow("原图片", src);
// 二值化
Mat dst, gray, binary;
cvtColor(src, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
// 形态学去除干扰
Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(binary, binary, MORPH_OPEN, k);
imshow("binary", binary);
// 轮廓发现与绘制
vector<vector<Point>> contours;
findContours(binary, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point());
vector<vector<Point>> contours_poly(contours.size());
for (int i = 0; i < contours.size(); i++)
{
//epsilon==3
approxPolyDP(Mat(contours[i]), contours_poly[i], 3, true);
drawContours(dstImage_3, contours_poly, i, Scalar(230,130,255), 1, LINE_AA);
//epsilon==6
approxPolyDP(Mat(contours[i]), contours_poly[i], 6, true);
drawContours(dstImage_6, contours_poly, i, Scalar(255,255,160), 1, LINE_AA);
//epsilon==10
approxPolyDP(Mat(contours[i]), contours_poly[i], 10, true);
drawContours(dstImage_10, contours_poly, i, Scalar(175, 255, 255), 1, LINE_AA); }
imshow("epsilon=3", dstImage_3);
imshow("epsilon=6", dstImage_6);
imshow("epsilon=10", dstImage_10);
从以上结果可以看出,设置的精度epsilon越小,多边形越拟合。
- 检测点是否在轮廓内pointPolygonTest()
OpenCV中实现这个功能的API叫做点多边形测试,它可以准确的得到一个点距离多边形的距离,如果点是轮廓点或者属于轮廓多边形上的点,距离是零,如果是多边形内部的点是是正数,如果是负数返回表示点是外部。
利用这个特性,我们可以巧妙的获取轮廓最大内接圆的半径:
当这个点在轮廓内部(与轮廓距离为正数),其返回的距离是最大值的时候,这个距离就是轮廓的最大内接圆的半径,该点就是最大内接圆的圆心。这样我们就巧妙的获得了圆心的位置与半径,剩下的工作就很容易了完成,绘制一个圆而已,一行代码就可以搞定。
double pointPolygonTest(
InputArray contour, //输入轮廓点集合
Point2f pt, //输入图像上任一点
bool measureDist MeasureDist//如果是True,则返回每个点到轮廓的距离,如果是False则返回+1,0,-1三个值,其中+1表示点在轮廓内部,0表示点在轮廓上,-1表示点在轮廓外
)
opencv实现:(绘制轮廓的最大内接圆和最小外接圆)
Mat src = imread("D:/opencv练习图片/图像最大内接圆.png");
imshow("原图片", src);
// 二值化
Mat dst, gray, binary;
cvtColor(src, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
imshow("binary", binary);
// 轮廓发现与绘制
vector<vector<Point>> contours;
findContours(binary, contours, RETR_LIST, CHAIN_APPROX_NONE);
Mat dist =Mat::zeros(src.size(), CV_32F);//定义一个Mat对象,存放原图中每个点到该轮廓的距离,为浮点型数据
int dist1 = 0;
int maxdist = 0;
Point center;
//寻找最大内接圆参数
//遍历每个点,计算该点到轮廓距离
for (int row = 0; row < dist.rows; row++)
{
for (int col = 0; col < dist.cols; col++)
{
//通过点多边形检测计算获得点到轮廓距离,并存放至dist中
dist1 = pointPolygonTest(contours[0], Point(col, row), true); if (dist1 > maxdist)
{
maxdist = dist1;//找出dist1中的最大值
center = Point(col, row);//获取最大值的坐标
}
}
} //寻找最小外接圆参数
vector<Point2f>centers(contours.size());//圆心个数
vector<float>radius(contours.size());//半径个数
minEnclosingCircle(contours[1], centers[1], radius[1]);
//绘制最小外接圆
circle(src, centers[1], radius[1], Scalar(0, 0, 255), 2);
//绘制最大的内接圆
circle(src, center, maxdist, Scalar(0, 255, 0), 1, LINE_8, 0);
imshow("src", src);
需要注意的是:
1️⃣寻找轮廓的findContours的RETR_LIST参数是从里向外找轮廓,因此内部轮廓的索引为contours[0],外轮廓索引为contours[1]。而RETR_TREE正好与之相反,从外向内找轮廓
2️⃣实战发现,在找寻dist1对象的最大值时,用minMaxLoc函数找到好多值。(目前未理解其中缘由)
opencv——轮廓发现与轮廓(二值图像)分析的更多相关文章
- Python+OpenCV图像处理(十六)—— 轮廓发现
简介:轮廓发现是基于图像边缘提取的基础寻找对象轮廓的方法,所以边缘提取的阈值选定会影响最终轮廓发现结果. 代码如下: import cv2 as cv import numpy as np def c ...
- 【python+opencv】轮廓发现
python+opencv---轮廓发现 轮廓发现---是基于图像边缘提取的基础寻找对象轮廓的方法, 所有边缘提取的阈值选定会影响最终轮廓发现的结果. 介绍两种API使用: -cv.findConto ...
- opencv python:轮廓发现
example import cv2 as cv import numpy as np def edge_demo(image): blurred = cv.GaussianBlur(image, ( ...
- opencv::轮廓发现(find contour in your image)
轮廓发现(find contour) 轮廓发现是基于图像边缘提取的基础寻找对象轮廓的方法. 所以边缘提取的阈值选定会影响最终轮廓发现结果 //发现轮廓 cv::findContours( InputO ...
- Opencv笔记(十八)——轮廓的更多函数及其层次结构
凸缺陷 前面我们已经学习了轮廓的凸包,对象上的任何凹陷都被成为凸缺陷.OpenCV 中有一个函数 cv.convexityDefect() 可以帮助我们找到凸缺陷.函数调用如下: hull = cv2 ...
- OpenCV计算机视觉学习(8)——图像轮廓处理(轮廓绘制,轮廓检索,轮廓填充,轮廓近似)
如果需要处理的原图及代码,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice 1, ...
- python实现轮廓发现
目录: (一)轮廓发现的介绍 (二)代码实现 (1)使用直接使用阈值方法threshold方法获取二值化图像来选择轮廓 (2)使用canny边缘检测获取二值化图像 (一)轮廓发现的介绍与API的介绍 ...
- OpenCV学习代码记录——轮廓(contour)检测
很久之前学习过一段时间的OpenCV,当时没有做什么笔记,但是代码都还在,这里把它贴出来做个记录. 代码放在码云上,地址在这里https://gitee.com/solym/OpenCVTest/tr ...
- OpenCV函数:提取轮廓相关函数使用方法
opencv中提供findContours()函数来寻找图像中物体的轮廓,并结合drawContours()函数将找到的轮廓绘制出.首先看一下findContours(),opencv中提供了两种定义 ...
随机推荐
- Jmeter(四十) - 从入门到精通进阶篇 - Jmeter配置文件的刨根问底 - 中篇(详解教程)
1.简介 为什么宏哥要对Jmeter的配置文件进行一下讲解了,因为有的童鞋或者小伙伴在测试中遇到一些需要修改配置文件的问题不是很清楚也不是很懂,就算修改了也是模模糊糊的.更有甚者觉得那是禁地神圣不可轻 ...
- 我的xshell配色方案,绿色/护眼/留存/备份
[mycolor] text(bold)=e9e9e9 magenta(bold)=ff00ff text=00ff80 white(bold)=fdf6e3 green=80ff00 red(bol ...
- SqlServer游标的创建与使用
前言 大家都对SqlServer视图.存储过程.触发器的创建与使用有一定的了解了,我们来看下什么是游标,怎么使用,什么时候用. SqlServer视图的创建与使用 SqlServer存储过程的创建与使 ...
- C. 【例题3】畜栏预定
C . [ 例 题 3 ] 畜 栏 预 定 C. [例题3]畜栏预定 C.[例题3]畜栏预定 题解 考虑贪心 Code #include <bits/stdc++.h> using nam ...
- [树形DP]加分二叉树
加 分 二 叉 树 加分二叉树 加分二叉树 题目描述 设一个n个节点的二叉树tree的中序遍历为(l,2,3,-,n),其中数字1,2,3,-,n为节点编号.每个节点都有一个分数(均为正整数),记第j ...
- nacos下载慢吗?来这里
https://gitee.com/soul_PreCoder/springcloudalibab/repository/archive/master.zip
- (八)Struts2中的参数封装
一.静态参数封装 什么是静态参数? 静态参数就是硬编码的,不可随意改变. 例子: (1)我们首先创建一个Action类,里面有两个参数,用来封装请求参数 public class User exten ...
- MySQL提升笔记(2):存储引擎盘点
在前面我们了解了server层调用存储引擎层接口来完成sql的执行,使用存储引擎的好处是:每个存储引擎都有各自的特点,能够根据具体的应用建立不同存储引擎表. 需要注意的是,存储引擎是基于表的,而不是数 ...
- leetcode 刷题(数组篇)152题 乘积最大子数组 (动态规划)
题目描述 给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积. 示例 1: 输入: [2,3,-2,4] 输出: 6 解释: 子 ...
- 设计原则:里式替换原则(LSP)
系列文章 设计原则:单一职责(SRP) 设计原则:开闭原则(OCP) 设计原则:里式替换原则(LSP) 设计原则:接口隔离原则(ISP) 设计原则:依赖倒置原则(DIP) 何谓高质量代码? 理解RES ...