1. 寻找轮廓
1.1 相关API
说明:
- 第一个参数:输入的图像是经过边缘提取处理后的二值化图像;
- conturs向量是用来存储轮廓点的,可以这样理解:一个轮廓的所有点用一个小容器
vector
,所有小容器再用一个大容器vector
装起来,所以像下面这样定义第二个参数:vector<vector<Point>> contours;
,相当于是一个二维向量吧,如下: - 第三个参数是轮廓的索引值;
- 第四个参数:轮廓检索模式,有四种,如下:
第一种:cv::RETR_EXTERNAL(仅检索最外层的轮廓)
第二种:cv::RETR_LIST(检索所有轮廓并将它们放入列表中)
第三种:cv::RETR_CCOMP(检索所有轮廓,将它们组织为两级层次结构,其中顶层边界是组件的外部边界,第二级边界是孔的边界)
第四种:cv::RETR_CCOMP(检索所有轮廓并建立树形的嵌套层次结构)
5.第六个参数是轮廓逼近算法,有以下四种:
CHAIN_APPROX_NONE
CHAIN_APPROX_SIMPLE
CHAIN_APPROX_TC89_L1
CHAIN_APPROX_TC89_KCOS
1.2 步骤
1.图像灰度化处理
2.滤波去噪
3.二值化
4.形态学操作
5.寻找轮廓
6.绘制轮廓
1.3 实验代码
主函数:
int main()
{
img = imread("E:/coin.jpg");
cvtColor(img, img_gray, CV_BGR2GRAY);//图像灰度化
medianBlur(img_gray, img_gray, 3);//进行中值滤波,去除一些噪声点
createTrackbar("Threshold", str_OutputWindowTitle, &threshold_value, threshold_max, Find_Contours);
Find_Contours(0, 0);
waitKey(0);
return 0;
}
寻找轮廓函数:
void Find_Contours(int, void*)
{
//二值化
threshold(img_gray, img_bin, threshold_value, threshold_max, THRESH_BINARY);
//根据实际二值化的效果进行相应的形态学操作
Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
morphologyEx(img_bin, img_bin, MORPH_CLOSE, element);//闭操作
imshow("bin+morphologyEx", img_bin);
//轮廓发现,轮廓绘制
vector<vector<Point>> contours;//双层,意思是有很多个轮廓,每个轮廓有很多个点
vector<Vec4i> hierachy;//4个元素分别存储该轮廓的【后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓】的索引编号
findContours(img_bin, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
dst = Mat::zeros(img.size(), CV_8UC3);//新建一个图像用来绘制寻找到的轮廓
//绘制所有轮廓像素点
for (auto i = 0; i < contours.size(); ++i)
{
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));//随机颜色
drawContours(dst, contours, i, color, 1, 8, hierachy, 0, Point(0, 0));//重新绘制轮廓
}
imshow(str_OutputWindowTitle, dst);
}
1.4 运行结果
适当调整阈值使其达到最好的效果:
根据提取到的轮廓点绘制找到的轮廓:
2. 凸包
2.1 凸包的原理
是一个计算几何(图形学)中的概念,凸包是一个过某些点作一个多边形,使这个多边形能把所有点都“包”起来。当这个多边形是凸多边形的时候,我们就叫它“凸包”。
举个形象的栗子:木板上定了十几个钉子,把一根橡皮筋撑大,套住所有钉子,然后让橡皮筋自然收缩,并繃紧,则橡皮筋形成的闭曲线就是凸包。
更多关于“凸包”的理论知识参考下面的几篇文章:
1.什么是“凸包”
2.凸包问题——概述
2.2 相关API
2.3 步骤
1.寻找轮廓
2.发现凸包
3.绘制凸包
2.4 实验代码
void Get_ConvexHull(int, void*)
{
//二值化
threshold(img_gray, img_bin, threshold_value, threshold_max, THRESH_BINARY);
Mat element = getStructuringElement(MORPH_RECT, Size(9, 9));
//根据实际二值化的效果进行相应的形态学操作
morphologyEx(img_bin, img_bin, MORPH_CLOSE, element);
imshow("bin+morphologyEx", img_bin);
//轮廓发现
vector<vector<Point>> contours;
findContours(img_bin, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);//发现轮廓
//寻找凸包
vector<vector<Point>> convex(contours.size());
for (size_t i = 0; i < contours.size(); i++)
{
convexHull(contours[i], convex[i]);
}
//绘制凸包
dst = Mat::zeros(img.size(), CV_8UC3);
for (size_t j = 0; j < contours.size(); j++)
{
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
//drawContours(dst, contours, j, color);//绘制轮廓,可以对比凸包
drawContours(dst, convex, j, color);//绘制凸包
}
imshow(str_OutputWindowTitle, dst);
}
2.5 运行结果
第一组:
对照组1:
对照组2:
3. 在轮廓外绘制矩形和圆
3.1 相关API
API功能:使用多边形简化轮廓;
注:epsilon参数的意义是,这是原始多边形和最终近似多边形之间允许的最大偏差,取值越大得到的多边形越简单。
为了验证一下,取下图其中的一个轮廓做测试,直接执行findContours之后,该轮廓的总点数为85,如下图:
当执行approxPolyDP()之后,epsilon值取3的时候,简化后的轮廓有9个点,绘制出来的多边形是9个边,如下图:
epsilon值取7的时候,简化后的轮廓有6个点,绘制出来的多边形是6个边,如下图:
API功能:获取简化后多边形轮廓的外接矩形;
API功能:获取简化后多边形轮廓外接圆的“圆心坐标”和“半径”,注意数据类型;
API功能:获取简化后多边形轮廓的“最适合”椭圆;
API功能:获取简化后多边形轮廓的“最适合”(最小的)的矩形;
3.2 步骤
1.灰度化、滤波
2.二值化、相关形态学操作
3.寻找轮廓
4.轮廓简化
5.依次获取轮廓外接矩形、外接圆、最小矩形、最小椭圆;
6.绘制需要的形状
3.3 实验代码
void Get_PolyRrc_Cir(int, void*)
{
//二值化
threshold(img_gray, img_bin, threshold_value, threshold_max, THRESH_BINARY_INV);
//根据实际二值化的效果进行相应的形态学操作
Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
morphologyEx(img_bin, img_bin, MORPH_CLOSE, element);
imshow("bin+morphologyEx", img_bin);
//寻找轮廓
vector<vector<Point>>controus;
findContours(img_bin, controus, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
vector<vector<Point>> controus_ploy(controus.size());//简化后的多边形轮廓集合
vector<Rect> poly_rects(controus.size());//轮廓外接矩形
vector<Point2f>circles(controus.size());//轮廓外接圆圆心坐标
vector<float> radius(controus.size());//轮廓外接圆半径
vector<RotatedRect>min_ellipse(controus.size());//轮廓最终形成的最小椭圆
vector<RotatedRect>min_Rects(controus.size());//轮廓最终形成的最小的旋转矩形
Point2f All_Rec_Point[4];//存储旋转矩形的四个顶点
for (size_t i = 0; i < controus.size(); i++)
{
//轮廓简化
approxPolyDP(Mat(controus[i]), controus_ploy[i], 7, true);
poly_rects[i] = boundingRect(controus_ploy[i]);//获取轮廓周围最小矩形
minEnclosingCircle(controus_ploy[i], circles[i], radius[i]);//获取轮廓周围最小圆
if (controus_ploy[i].size() > 5)
{
min_ellipse[i] = fitEllipse(controus_ploy[i]);//得到一个最小椭圆,若contours_ploy[i]的点数size小于5会报错
min_Rects[i] = minAreaRect(controus_ploy[i]);//得到一个旋转的矩形
}
}
//绘制外接矩形、圆、椭圆和旋转矩形
img.copyTo(dst);
for (size_t j = 0; j < controus.size(); j++)
{
if (controus_ploy[j].size() > 4)
{
//contours_ploy[i]的点数size小于5的,不处理
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
rectangle(dst, poly_rects[j], color, 2);//画矩形
circle(dst, circles[j], radius[j], color, 2);//画圆
ellipse(dst, min_ellipse[j], color, 2);//画椭圆
min_Rects[j].points(All_Rec_Point);
//Rect min_Rec(All_Rec_Point[0], All_Rec_Point[2]);//只通过两点画出来的圆不是旋转的,哈哈
//rectangle(dst, min_Rec, color, 2);
for (int r = 0; r < 4; r++)
{
line(dst, All_Rec_Point[r], All_Rec_Point[(r + 1) % 4], color, 2);//通过获得旋转矩形的四个顶点,画旋转矩形
}
}
}
imshow(str_OutputWindowTitle, dst);
}