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

效果展示:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/657212.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【图文详解】阿里云服务器放行高防IP加入安全组

打开阿里云的云服务器配置面板&#xff0c;在要操作实例的操作列找到更多 > 网络和安全组 > 安全组配置。 对已有安全组配置规则&#xff0c;或者直接添加安全组规则。 根据需要放通高防IP在内的IP段相应协议类型的端口访问。

QEMU - e1000全虚拟化前端与TAP/TUN后端流程简析

目录 1. Host -> Guest 2.Guest ->Host 3. 如何修改以支持TUN设备的后端&#xff1f; 4. 相关 QEMU 源码 5. 实验 1. Host -> Guest 2.Guest ->Host 3. 如何修改以支持TUN设备的后端&#xff1f; 1. 简单通过后端网卡名字来判断是TUN还是TAP。 2. 需要前端全…

Adobe Camera Raw forMac/win:掌控原始之美的秘密武器

Adobe Camera Raw&#xff0c;这款由Adobe开发的插件&#xff0c;已经成为摄影师和设计师们的必备工具。对于那些追求完美、渴望探索更多创意可能性的专业人士来说&#xff0c;它不仅仅是一个插件&#xff0c;更是一个能够释放无尽创造力的平台。 在数字摄影时代&#xff0c;R…

使用Win32API实现贪吃蛇小游戏

目录 C语言贪吃蛇项目 基本功能 需要的基础内容 Win32API 介绍 控制台程序部分指令 设置控制台窗口的长宽 设置控制台的名字 控制台在屏幕上的坐标位置结构体COORD 检索指定标准设备的句柄&#xff08;标准输入、标准输出或标准错误&#xff09; 光标信息结构体类型CONSOLE_CUR…

洛谷P8599 [蓝桥杯 2013 省 B] 带分数

[蓝桥杯 2013 省 B] 带分数 题目描述 100 100 100 可以表示为带分数的形式&#xff1a; 100 3 69258 714 100 3 \frac{69258}{714} 100371469258​。 还可以表示为&#xff1a; 100 82 3546 197 100 82 \frac{3546}{197} 100821973546​。 注意特征&#xff1a;带分…

条款32:确定你的public继承塑模出 is-a 关系

如果你编写类D(“派生类”)public继承类B(“基类”)&#xff0c;就是在告诉C编译器(以及代码的读者)每个类型D的对象都是类型B的对象&#xff0c;但反之则不然。 class Person {...}; class Student: public Person {...}; void eat(const Person& p); // 素有的Person都…

云计算HCIE备考经验分享

大家好&#xff0c;我是来自深圳信息职业技术学院22级鲲鹏3-1班的刘同学&#xff0c;在2023年9月19日成功通过了华为云计算HCIE认证&#xff0c;并且取得了A的成绩。下面把我的考证经验分享给大家。 转专业进鲲鹏班考HCIE 大一上学期的时候&#xff0c;在上Linux课程的时候&…

ES Serverless让日志检索更加便捷

前言 在项目中,或者开发过程中,出现bug或者其他线上问题,开发人员可以通过查看日志记录来定位问题。通过日志定位 bug 是一种常见的软件开发和运维技巧,只有观察日志才能追踪到具体代码。在软件开发过程中,开发人员会在代码中添加日志记录,以记录程序的运行情况和异常信…

发现了一款宝藏学习项目,包含了Web全栈的知识体系,JS、Vue、React知识就靠它了!

前言 在当今互联网时代&#xff0c;一切以页面、UI为主要呈现方式&#xff0c;web全栈开发工程师的需求越来越大。 然而&#xff0c;市场上大多数工程师只会使用api而不了解其原理&#xff0c;这种情况使得他们变得可替代。 因此&#xff0c;成为一个高级开发工程师需要具备…

用React给XXL-JOB开发一个新皮肤(四):实现用户管理模块

目录 一. 简述二. 模块规划 2.1. 页面规划2.2. 模型实体定义 三. 模块实现 3.1. 用户分页搜索3.2. Modal 配置3.3. 创建用户表单3.4. 修改用户表单3.5. 删除 四. 结束语 一. 简述 上一篇文章我们实现登录页面和管理页面的 Layout 骨架&#xff0c;并对接登录和登出接口。这篇…

在centos 7 中 安装 配置 并 远程连接 MySQL5.7

目录 安装MySQL 1.卸载CentOS7系统自带的mariadb 2.安装依赖库 3.上传MySQL并解压 4.安装MySQL 配置MySQL 1.修改登录密码 2.修改字符集 3.配置远程连接 前言&#xff1a; 安装MySQL版本&#xff1a;mysql-5.7.30-1.el7.x86_64.rpm-bundle 文件需求后台私信 以下7条为…

STM32学习笔记(四) —— 位段别名区的使用

STM32F103RCT6有两个位段区 (SRAM 最低1M空间和片内外设存储区最低1M空间)&#xff0c; 这两个区域都有各自的别名区&#xff0c;在别名区中每个字会映射到位段区的一个位&#xff0c;所以在别名区修改一个字相当于修改位段区中对应的一个位 映射公式( 别名区中的字与位段区中的…

实现vue3响应式系统核心-watch

简介 今天我们来看看 watch 的实现。 watch本质就是观测一个响应式数据&#xff0c;当数据发生变化时通知并执行相应的回调函数。实际上&#xff0c;watch的实现本质上就是利用了 effect 以及 options.scheduler选项。 代码地址&#xff1a; https://github.com/SuYxh/share-…

flask基于python的个人理财备忘录记账提醒系统vue

在当今高度发达的信息中&#xff0c;信息管理改革已成为一种更加广泛和全面的趋势。 “备忘记账系统”是基于Mysql数据库&#xff0c;在python程序设计的基础上实现的。为确保中国经济的持续发展&#xff0c;信息时代日益更新&#xff0c;蓬勃发展。同时&#xff0c;随着信息社…

【智能家居入门2】(MQTT协议、微信小程序、STM32、ONENET云平台)

此篇智能家居入门与前两篇类似&#xff0c;但是是使用MQTT协议接入ONENET云平台&#xff0c;实现微信小程序与下位机的通信&#xff0c;这里相较于使用http协议的那两篇博客&#xff0c;在主程序中添加了独立看门狗防止程序卡死和服务器掉线问题。后续还有使用MQTT协议连接MQTT…

输入和输出

按字符输入输出 按字符输出putchar&#xff08;&#xff09; 格式 #include <stdio.h> int putchar(int c); 功能&#xff1a;向终端输出一个字符 参数&#xff1a;要输出的字符的ASCII码值 返回值&#xff1a; 成功&#xff0c;返回输出字符的ASCII码值 失败&#xff…

基于springboot汽车租赁系统源码和论文

首先,论文一开始便是清楚的论述了系统的研究内容。其次,剖析系统需求分析,弄明白“做什么”,分析包括业务分析和业务流程的分析以及用例分析,更进一步明确系统的需求。然后在明白了系统的需求基础上需要进一步地设计系统,主要包括软件架构模式、整体功能模块、数据库设计。本项…

Missing or invalid credentials.(Git push报错解决方案)

前言 本文主要讲解git push后报错Missing or invalid credentials的解决方案。这里针对的是windows的。 编程环境&#xff1a;VsCode 问题原因 问题翻译起来就是 凭据缺失或无效。这里我们解决方案是取消vscode里面默认的控制终端git凭据来解决,具体方案如下. 解决方案 1…

3D效果图加树进去太卡,渲染太慢怎么办?

周末的时候&#xff0c;有个朋友私信来问&#xff1a;3dmax模型加树进去打开时特别的卡&#xff0c;是怎么回事。 不知道有没有朋友遇上这么个情况。 3dmax加树建议就用代理&#xff0c;这样相比于直接加而言&#xff0c;会流畅许多。 在3D效果图中&#xff0c;“树代理”是…

萝卜视频源码前后端带视频演示

萝卜影视源码前端是用JAVA开发的全原生APP源码&#xff0c;后端用的是二次开发的苹果CMS&#xff0c;支持局域网投屏&#xff0c;视频软解硬解&#xff0c;播放器自带弹幕功能。支持解析官方视频&#xff0c;支持M3U8&#xff0c;MP4。 开屏广告&#xff0c;全局广告&#xff0…