OpenCV笔记10——寻找轮廓和凸包以及绘制轮廓的外接矩形和圆

    科技2025-12-28  12

    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.凸包问题——概述

    3.数学:凸包算法详解

    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); }

    3.4 运行结果


    参考文章: 1.https://blog.csdn.net/weixin_41695564/article/details/80085012 2.https://blog.csdn.net/qq_26907755/article/details/81740206 3.https://www.cnblogs.com/little-monkey/p/7429381.html

    Processed: 0.014, SQL: 9