当前位置: 首页 > news >正文

16- OpenCV:轮廓的发现和轮廓绘制、凸包

目录

一、轮廓发现

1、轮廓发现(find contour in your image) 的含义

2、相关的API 以及代码演示

二、凸包

1、凸包(Convex Hull)的含义

2、Graham扫描算法- 概念介绍

3、cv::convexHull 以及代码演示

三、轮廓周围绘制矩形和圆形框


一、轮廓发现

1、轮廓发现(find contour in your image) 的含义

轮廓发现是基于图像边缘提取的基础寻找对象轮廓的方法。 所以边缘提取的阈值选定会影响最终轮廓发现结果。

找出并画出图中的轮廓。

2、相关的API 以及代码演示

(1)轮廓发现(find contour)

cv::findContours(

InputOutputArray  binImg, // 输入图像,非0的像素被看成1,0的像素值保持不变,8-bit  

OutputArrayOfArrays  contours,//  全部发现的轮廓对象

OutputArray,  hierachy// 图该的拓扑结构,可选,该轮廓发现算法正是基于图像拓扑结构实现。

int mode, //  轮廓返回的模式

int method,// 发现方法

Point offset=Point()//  轮廓像素的位移,默认(0, 0)没有位移

)

(2)轮廓绘制(draw contour):cv::findContours之后对发现的轮廓数据进行绘制显示

drawContours(

InputOutputArray  binImg, // 输出图像  

OutputArrayOfArrays  contours,//  全部发现的轮廓对象

Int contourIdx// 轮廓索引号

const Scalar & color,// 绘制时候颜色

int  thickness,// 绘制线宽

int  lineType ,// 线的类型LINE_8

InputArray hierarchy,// 拓扑结构图

int maxlevel,// 最大层数, 0只绘制当前的,1表示绘制绘制当前及其内嵌的轮廓

Point offset=Point()// 轮廓位移,可选

(3)代码流程主要步骤:

- 输入图像转为灰度图像cvtColor

- 使用Canny进行边缘提取,得到二值图像

- 使用findContours寻找轮廓

- 使用drawContours绘制轮廓

(4)代码例子:

#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>using namespace std;
using namespace cv;Mat src, dst;
const char* output_win = "findcontours-demo";
int threshold_value = 100;
int threshold_max = 255;
RNG rng;
void Demo_Contours(int, void*);
int main(int argc, char** argv) {src = imread("fish.png");if (src.empty()) {printf("could not load image...\n");return -1;}namedWindow("input-image", CV_WINDOW_AUTOSIZE);namedWindow(output_win, CV_WINDOW_AUTOSIZE);imshow("input-image", src);cvtColor(src, src, CV_BGR2GRAY);const char* trackbar_title = "Threshold Value:";createTrackbar(trackbar_title, output_win, &threshold_value, threshold_max, Demo_Contours);Demo_Contours(0, 0);waitKey(0);return 0;
}void Demo_Contours(int, void*) {Mat canny_output;vector<vector<Point>> contours;vector<Vec4i> hierachy;Canny(src, canny_output, threshold_value, threshold_value * 2, 3, false);findContours(canny_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));dst = Mat::zeros(src.size(), CV_8UC3);RNG rng(12345);for (size_t 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, 2, 8, hierachy, 0, Point(0, 0));}imshow(output_win, dst);
}

效果展示:

二、凸包

1、凸包(Convex Hull)的含义

在一个多变形边缘或者内部任意两个点的连线都包含在多边形边界或者内部。

正式定义: 包含点集合S中所有点的最小凸多边形称为凸包。

左边的图才是正确的凸包,右边的不算正确的

              

 凸包的检测算法是:Graham扫描法

2、Graham扫描算法- 概念介绍

(1)首先选择Y方向最低的点作为起始点p0;

(2)从p0开始极坐标扫描,依次添加p1….pn(排序顺序是根据极坐标的角度大小,逆时针方向);

(3)对每个点pi来说,如果添加pi点到凸包中导致一个左转向(逆时针方法)则添加该点到凸包, 反之如果导致一个右转向(顺时针方向)删除该点从凸包中;

3、cv::convexHull 以及代码演示

convexHull(

InputArray points,// 输入候选点,来自findContours

OutputArray hull,// 凸包

bool clockwise,// default true, 顺时针方向

bool returnPoints // true 表示返回点个数,如果第二个参数是vector<Point>则自动忽略

代码的操作主要步骤:

- 首先把图像从RGB转为灰度

- 然后再转为二值图像

- 在通过发现轮廓得到候选点

- 凸包API调用

- 绘制显示。

具体代码:

#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>using namespace std;
using namespace cv;
Mat src, src_gray, dst;
int threshold_value = 100;
int threshold_max = 255;
const char* output_win = "convex hull demo";
void Threshold_Callback(int, void*);
RNG rng(12345);
int main(int argc, char** argv) {src = imread("fish.png");if (!src.data) {printf("could not load image...\n");return -1;}const char* input_win = "input image";namedWindow(input_win, CV_WINDOW_AUTOSIZE);namedWindow(output_win, CV_WINDOW_NORMAL);const char* trackbar_label = "Threshold : ";cvtColor(src, src_gray, CV_BGR2GRAY);blur(src_gray, src_gray, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);imshow(input_win, src_gray);createTrackbar(trackbar_label, output_win, &threshold_value, threshold_max, Threshold_Callback);Threshold_Callback(0, 0);waitKey(0);return 0;
}void Threshold_Callback(int, void*) {Mat bin_output;vector<vector<Point>> contours;vector<Vec4i> hierachy;threshold(src_gray, bin_output, threshold_value, threshold_max, THRESH_BINARY);findContours(bin_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));vector<vector<Point>> convexs(contours.size());for (size_t i = 0; i < contours.size(); i++) {convexHull(contours[i], convexs[i], false, true);}// 绘制dst = Mat::zeros(src.size(), CV_8UC3);vector<Vec4i> empty(0);for (size_t k = 0; k < contours.size(); k++) {Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));drawContours(dst, contours, k, color, 2, LINE_8, hierachy, 0, Point(0, 0));drawContours(dst, convexs, k, color, 2, LINE_8, empty, 0, Point(0, 0));}imshow(output_win, dst);return;
}

效果展示:

三、轮廓周围绘制矩形和圆形框

1、含义

        可以使用轮廓检测函数findContours找到图像中的轮廓,并通过绘制矩形和圆形框来突出显示这些轮廓。(如下图所示)

2、相关的API

(1)cv::approxPolyDP:用于对曲线进行多边形逼近的函数之一。它可以将曲线逼近为一个更简单的多边形。适用于对任意曲线进行逼近,不仅限于轮廓曲线。

基于RDP算法实现,目的是减少多边形轮廓点数。精简化得图像。


void cv::approxPolyDP(
    InputArray curve, // 输入的曲线,可以是一个包含点坐标的数组。
    OutputArray approxCurve, // 输出的逼近多边形曲线,保存了逼近后的点坐标。
    double epsilon, // 逼近精度,即逼近后的多边形与原曲线之间的最大距离。

                                两点之间最小的距离
    bool closed // 是否将曲线视为闭合曲线,如果为true,则认为曲线是闭合的,否则认为曲线是开放的。

);

通过调用approxPolyDP函数,可以将输入的曲线逼近为一个更简单的多边形,并将逼近后的多边形曲线保存在输出数组approxCurve中。逼近的精度由epsilon参数控制,较小的epsilon值会得到更接近原曲线的逼近结果。

(2)cv::boundingRect(InputArray points):用于计算一组点的最小外接矩形的函数。该矩形是以水平和垂直方向为边界的最小矩形,能够完全包围所有输入点。

得到轮廓周围最小矩形左上交点坐标和右下角点坐标,绘制一个矩形。

cv::Rect cv::boundingRect(

InputArray points // 输入的点集,可以是一个包含点坐标的数组。

)

返回值:

  • cv::Rect:表示最小外接矩形的矩形对象,包含了最小外接矩形的位置和大小信息。

(3)cv::minAreaRect(InputArray  points):得到一个旋转的矩形,返回旋转矩形

(4)cv::minEnclosingCircle:用于计算一组点的最小外接圆的函数。该圆是能够完全包围所有输入点的最小圆。

cv::minEnclosingCircle(

InputArray points, //得到最小区域圆形

Point2f& center, // 输出参数,表示最小外接圆的圆心坐标。

float& radius // 输出参数,表示最小外接圆的半径。

(5)cv::fitEllipse(InputArray  points):得到最小椭圆

3、代码演示

(1)主要的处理步骤:

- 首先将图像变为灰度图像cvtColor

- 模糊(椒盐噪声多选择高斯模糊GaussianBlur,不然选择中值模糊),减低噪声

- 获得二值图像(threshold);

- 发现轮廓,找到图像轮廓;

- 通过相关API在轮廓点上找到最小包含矩形和圆,旋转矩形与椭圆;

- 绘制它们。

(2)具体代码:

#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>using namespace std;
using namespace cv;
Mat src, gray_src, drawImg;
int threshold_v = 170;
int threshold_max = 255;
const char* output_win = "rectangle-demo";
RNG rng(12345);
void Contours_Callback(int, void*);
int main(int argc, char** argv) {src = imread("fish.png");if (!src.data) {printf("could not load image...\n");return -1;}cvtColor(src, gray_src, CV_BGR2GRAY);blur(gray_src, gray_src, Size(3, 3), Point(-1, -1));const char* source_win = "input image";namedWindow(source_win, CV_WINDOW_AUTOSIZE);namedWindow(output_win, CV_WINDOW_AUTOSIZE);imshow(source_win, src);createTrackbar("Threshold Value:", output_win, &threshold_v, threshold_max, Contours_Callback);Contours_Callback(0, 0);waitKey(0);return 0;
}void Contours_Callback(int, void*) {Mat binary_output;vector<vector<Point>> contours;vector<Vec4i> hierachy;threshold(gray_src, binary_output, threshold_v, threshold_max, THRESH_BINARY);//imshow("binary image", binary_output);findContours(binary_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(-1, -1));vector<vector<Point>> contours_ploy(contours.size());vector<Rect> ploy_rects(contours.size());vector<Point2f> ccs(contours.size());vector<float> radius(contours.size());vector<RotatedRect> minRects(contours.size());vector<RotatedRect> myellipse(contours.size());for (size_t i = 0; i < contours.size(); i++) {approxPolyDP(Mat(contours[i]), contours_ploy[i], 3, true);ploy_rects[i] = boundingRect(contours_ploy[i]);minEnclosingCircle(contours_ploy[i], ccs[i], radius[i]);if (contours_ploy[i].size() > 5) {myellipse[i] = fitEllipse(contours_ploy[i]);minRects[i] = minAreaRect(contours_ploy[i]);}}// draw itdrawImg = Mat::zeros(src.size(), src.type());Point2f pts[4];for (size_t t = 0; t < contours.size(); t++) {Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));//rectangle(drawImg, ploy_rects[t], color, 2, 8);//circle(drawImg, ccs[t], radius[t], color, 2, 8);if (contours_ploy[t].size() > 5) {ellipse(drawImg, myellipse[t], color, 1, 8);minRects[t].points(pts);for (int r = 0; r < 4; r++) {line(drawImg, pts[r], pts[(r + 1) % 4], color, 1, 8);}}}imshow(output_win, drawImg);return;
}

效果展示:

相关文章:

  • 学习MySQL的CSV存储引擎
  • 量化交易学习4(投资组合基本认识)
  • ubuntu下命令行安装指定版本的jdk,并在多jdk时指定默认版本
  • IDEA 配置以及一些技巧
  • flutter抓包绕过
  • 如何结合ChatGPT生成个人魔法咒语词库
  • RabbitMQ之交换机
  • 工厂模式与抽象工厂模式
  • [Android] 240204批量生成联系人,短信,通话记录的APK
  • 数据结构之红黑树
  • C语言第十八弹---指针(二)
  • 1.0 Hadoop 教程
  • 聊聊java中的Eureka和Nacos
  • python-自动化篇-运维-语音识别
  • ctfshow misc入门 misc24-31
  • php的引用
  • 【翻译】babel对TC39装饰器草案的实现
  • 【许晓笛】 EOS 智能合约案例解析(3)
  • Angularjs之国际化
  • Flannel解读
  • Javascripit类型转换比较那点事儿,双等号(==)
  • Java知识点总结(JDBC-连接步骤及CRUD)
  • magento 货币换算
  • storm drpc实例
  • Vue.js-Day01
  • 从tcpdump抓包看TCP/IP协议
  • 给第三方使用接口的 URL 签名实现
  • 关于springcloud Gateway中的限流
  • 技术发展面试
  • 如何使用 JavaScript 解析 URL
  • 如何抓住下一波零售风口?看RPA玩转零售自动化
  • 使用 @font-face
  • 微信支付JSAPI,实测!终极方案
  • 物联网链路协议
  • 项目实战-Api的解决方案
  • 正则与JS中的正则
  • 阿里云API、SDK和CLI应用实践方案
  • ​LeetCode解法汇总307. 区域和检索 - 数组可修改
  • (C语言版)链表(三)——实现双向链表创建、删除、插入、释放内存等简单操作...
  • (webRTC、RecordRTC):navigator.mediaDevices undefined
  • (二)WCF的Binding模型
  • (附源码)springboot 基于HTML5的个人网页的网站设计与实现 毕业设计 031623
  • (附源码)ssm跨平台教学系统 毕业设计 280843
  • (离散数学)逻辑连接词
  • (提供数据集下载)基于大语言模型LangChain与ChatGLM3-6B本地知识库调优:数据集优化、参数调整、Prompt提示词优化实战
  • .Net 6.0 处理跨域的方式
  • .NET Framework杂记
  • .NET委托:一个关于C#的睡前故事
  • @Resource和@Autowired的区别
  • [ MSF使用实例 ] 利用永恒之蓝(MS17-010)漏洞导致windows靶机蓝屏并获取靶机权限
  • [2019/05/17]解决springboot测试List接口时JSON传参异常
  • [AIGC] MySQL存储引擎详解
  • [Android View] 可绘制形状 (Shape Xml)
  • [C#]C#学习笔记-CIL和动态程序集
  • [C++] cout、wcout无法正常输出中文字符问题的深入调查(1):各种编译器测试