轮廓分析拟合方面我现在只考虑矩形拟合和圆形拟合

细分的话,椭圆拟合,矩形拟合,最小外接矩形,最小外接圆。

对于一张图像可能有不同的图形,不同的圆,不同的矩形,我需要对其进行筛选,也需要对检测的目标对针对性计算,例如面积,周长,圆心点坐标。

也需要对筛选的模块进行进一步检索,例如都是圆,两个大一个小,我就需要通过筛选他们的面积来确定我具体要哪一个圆。

那最后就是显示功能了,因为轮廓分析往往是基于图像处理后显示的,这样不够直观也不够美观,我们需要让他显示在原图上,我们可以选择它显示的方式,最小圆,最小矩形,十字线,中心点这样会好看很多。

我们整体的页面思路已经设计好了下面看一下实现

一.界面方面

从头再过一下加强Qt的控件记忆

设计轮廓分析的主界面设计,采用垂直排序的方式,

setContentsMargins(10, 10, 10, 10):设置布局的内边距为 10 像素,即布局内容与面板边缘之间保持 10 像素的空白距离

setSpacing(15):设置布局中各个子控件之间的垂直间距为 15 像素

建立一个名字为计算分析的QGroupBox控件,它内部的控制方式QGridLayout,也就是网格布局。之后再建立一个QLabe将他方式到 计算分析的组里面这样可以通过QGridLayout的方式来控制它的布局。

perimeterCheckBox->setChecked(true);  // 选中"周长"复选框
areaCheckBox->setChecked(true);       // 选中"面积"复选框

默认选中状态

之后就是对控件进行排序看一下效果

还是比较美观的之后进行下一步拟合分析组的设计和上面的设计是一样的可以看一下

后面的过滤设置有一些不一样我么看一下,首先是一个范围值,要有最大和最小来将其圈起来

还是建立一个QGroupBox将将我需要的组件放进去

建立Qlabel来放置周长的最大值最小值筛选,显示最小值

QDoubleSpinBox 是 Qt 提供的一个数值输入控件,允许用户通过上下箭头或直接输入来选择一个带小数的数值(即双精度浮点数)

以此建立我们需要的控制数值的变量

我们看一下效果

最后就是我们的显示和前面也是一样的,直接看效果

最后我们还需要一个轮廓信息帮我们显性的显示每一个找到的轮廓的面积信息

要显示

之后算法方面

二.算法方面

cv::findContours(InputArray image,              // 输入图像(通常为二值图)OutputArrayOfArrays contours,  // 检测到的轮廓点集OutputArray hierarchy,         // 轮廓的层级关系int mode,                      // 轮廓检索模式int method,                    // 轮廓点近似方法Point offset = Point()         // 可选的偏移量
);

比较基础的APi主要检测图像的外轮廓,后面会更新增加内部轮廓,如孔洞

下面是遍历整个关键点存储容器以此取出点位等价

for (size_t i = 0; i < contours.size(); ++i) {const std::vector<cv::Point>& contour = contours[i];// ...
}

这里有一个知识点就是contour到底是什么,里面包含了什么?

在 OpenCV 中,contour 是一个存储轮廓点集的数据结构,本质是 std::vector<cv::Point>(或 std::vector<cv::Point2f>),表示由一系列连续点构成的曲线。

// 示例:contour 的类型定义
using Contour = std::vector<cv::Point>;  // 二维点集
struct Point {int x;  // x坐标int y;  // y坐标
};
  • 存储形式
    contour 是有序的点序列,相邻点之间用直线连接,形成闭合或开放的曲线。
    例如,一个矩形轮廓可能存储为四个顶点的坐标:
    [(x1,y1), (x2,y2), (x3,y3), (x4,y4)]

根据这个特性我们就可以求一些关键信息,例如周长面积矩,重心等信息

double perimeter = cv::arcLength(contour, true);    // 周长
double area = cv::contourArea(contour);             // 面积
cv::Moments moments = cv::moments(contour);         // 矩(用于计算重心等)
cv::Point2f centroid(moments.m10/moments.m00, moments.m01/moments.m00);  // 重心

1.形状分析

bool isConvex = cv::isContourConvex(contour);      // 是否为凸多边形
std::vector<cv::Point> approx;
cv::approxPolyDP(contour, approx, epsilon, true);  // 多边形逼近
double circularity = 4 * CV_PI * area / (perimeter * perimeter);  // 圆形度

2.边界框计算

cv::Rect boundingRect = cv::boundingRect(contour);  // 直立外接矩形
cv::RotatedRect minRect = cv::minAreaRect(contour); // 最小外接矩形(带旋转)
cv::Point2f center; float radius;
cv::minEnclosingCircle(contour, center, radius);    // 最小外接圆

3.绘制与可视化

// 绘制轮廓
cv::drawContours(image, std::vector<std::vector<cv::Point>>{contour}, -1, color, thickness);// 绘制关键点
for (const auto& point : contour) {cv::circle(image, point, 2, cv::Scalar(0, 255, 0), -1);
}

我们先通过这个contour来获得我们需要的信息

之后通过筛选来选择我们要的图像轮廓

这里有一个坑要>=不能是== == 要求参数精确等于某个值,在现实中几乎不可能满足;而 >= 和 <= 允许参数在合理范围内波动,更符合实际情况。

之后实时绘制轮廓,上面的筛选轮廓用于后续需要点位信息的时候调用

轮廓信息有了我们开始记录信息便于轮廓可视化处理,我们需要建立一个结构体用来存储信息

2.1几个关键代码解释:

1. 计算并存储轮廓质心
cv::Moments m = cv::moments(contour);
if (m.m00 != 0) {info.center = cv::Point2f(m.m10 / m.m00, m.m01 / m.m00);
}

  • cv::moments(contour):计算轮廓的几何矩,包括面积矩m00(即轮廓面积)、一阶矩m10m01
  • 质心公式
    • 质心横坐标 = m10 / m00
    • 质心纵坐标 = m01 / m00
  • 条件检查:当轮廓面积m.m00为 0(如空轮廓或单点)时,跳过赋值以避免除零错误。此时info.center的值保持未初始化(潜在风险!)。

2. 计算并存储轮廓凸度

info.convexity = cv::isContourConvex(contour) ? 1.0 : 0.0;

  • cv::isContourConvex(contour):判断轮廓是否为凸多边形(所有内角 ≤ 180°)。
  • 凸度值
    • 1.0:轮廓是凸的。
    • 0.0:轮廓是非凸的(有凹陷)。

3. 计算并存储最小外接圆

cv::minEnclosingCircle(contour, info.enclosingCircleCenter, info.enclosingCircleRadius);

  • cv::minEnclosingCircle():计算完全包含轮廓的最小圆。
  • 输出参数
    • info.enclosingCircleCenter:圆心坐标(cv::Point2f)。
    • info.enclosingCircleRadius:圆半径(float)。

4. 计算并存储最小外接矩形的角度

cv::RotatedRect minRect = cv::minAreaRect(contour);
info.angle = minRect.angle;

  • cv::minAreaRect(contour):计算完全包含轮廓的最小旋转矩形(可能倾斜)。
  • minRect.angle:返回矩形的旋转角度(单位:度),范围为[-90, 0)。角度定义为水平轴与矩形短边的夹角。

5. 将结构体添加到列表

contourInfoList.push_back(info);

  • 将当前轮廓的所有特征信息存入容器contourInfoList

轮廓分析的核心代码可以用与下面的两种方式。1:当我点击轮廓信息的时候将信息以弹窗的方式体现出来。2:将这些信息以绘图的方式画出来。

三.弹窗方面

构建ContourInfoDialog这个构造函数

  • 这个类借助构造函数接收一个轮廓信息列表和一个可选的父窗口部件。
  • 父窗口部件的初始化工作由 Qt 框架处理,通常是通过调用基类的构造函数来实现。
  • 析构函数会确保在对象被销毁时,所有资源都能被正确释放。

类的内部实现

1.构建窗口的格式

2.窗口内表格构建

void ContourInfoDialog::initUI(const std::vector<ContourInfo>& contourInfoList) {QVBoxLayout* mainLayout = new QVBoxLayout(this);// 创建表格m_infoTable = new QTableWidget(static_cast<int>(contourInfoList.size()), 10, this);m_infoTable->setHorizontalHeaderLabels({"ID", "位置(X,Y)", "尺寸(W,H)", "周长", "面积","圆度", "凸度", "横纵比", "外接圆(半径)", "角度"});// 设置表头样式m_infoTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);m_infoTable->setColumnWidth(0, 50);   // IDm_infoTable->setColumnWidth(1, 120);  // 位置m_infoTable->setColumnWidth(2, 120);  // 尺寸m_infoTable->setColumnWidth(3, 80);   // 周长m_infoTable->setColumnWidth(4, 80);   // 面积m_infoTable->setColumnWidth(5, 60);   // 圆度m_infoTable->setColumnWidth(6, 60);   // 凸度m_infoTable->setColumnWidth(7, 60);   // 横纵比m_infoTable->setColumnWidth(8, 100);  // 外接圆m_infoTable->setColumnWidth(9, 60);   // 角度// 填充数据for (size_t i = 0; i < contourInfoList.size(); ++i) {const auto& info = contourInfoList[i];m_infoTable->setItem(static_cast<int>(i), 0, new QTableWidgetItem(QString::number(i + 1)));m_infoTable->setItem(static_cast<int>(i), 1, new QTableWidgetItem(QString("(%1, %2)").arg(info.center.x).arg(info.center.y)));m_infoTable->setItem(static_cast<int>(i), 2, new QTableWidgetItem(QString("%1 x %2").arg(info.boundingRect.width).arg(info.boundingRect.height)));m_infoTable->setItem(static_cast<int>(i), 3, new QTableWidgetItem(QString::number(info.perimeter, 'f', 1)));m_infoTable->setItem(static_cast<int>(i), 4, new QTableWidgetItem(QString::number(info.area, 'f', 1)));// 计算圆度 = 4π*面积/周长²double circularity = 4 * CV_PI * info.area / (info.perimeter * info.perimeter);m_infoTable->setItem(static_cast<int>(i), 5, new QTableWidgetItem(QString::number(circularity, 'f', 3)));m_infoTable->setItem(static_cast<int>(i), 6, new QTableWidgetItem(info.convexity > 0.5 ? "是" : "否"));m_infoTable->setItem(static_cast<int>(i), 7, new QTableWidgetItem(QString::number(info.aspectRatio, 'f', 2)));m_infoTable->setItem(static_cast<int>(i), 8, new QTableWidgetItem(QString("R:%1").arg(info.enclosingCircleRadius, 0, 'f', 1)));m_infoTable->setItem(static_cast<int>(i), 9, new QTableWidgetItem(QString::number(info.angle, 'f', 1) + "°"));}mainLayout->addWidget(m_infoTable);// 添加关闭按钮QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this);connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);mainLayout->addWidget(buttonBox);
}

这样我们就能完成想要的寻找轮廓的操作

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

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

相关文章

C++中STL六大组件List的简单介绍

一、前言C非常重视效率&#xff0c;对效率有损失的代码常常是能省则省。使用list要包含的头文件是<list>&#xff0c;要包含头文件就是#iinclude <list>&#xff0c;List肯定是一种链表&#xff0c;我们不妨回忆一下那种链表插入删除效率最快也就是最简单&#xff…

第十五节:Vben Admin 最新 v5.0 (vben5) + Python Flask 快速入门 - vue前端 生产部署

Vben Admin vben5 系列文章目录 💻 基础篇 ✅ 第一节:Vben Admin 最新 v5.0 (vben5) + Python Flask 快速入门 ✅ 第二节:Vben Admin 最新 v5.0 (vben5) + Python Flask 快速入门 - Python Flask 后端开发详解(附源码) ✅ 第三节:Vben Admin 最新 v5.0 (vben5) + Python …

背包初步(0-1背包、完全背包)

当月光洒在我的脸上 我想我就快变了模样 有一种叫做撕心裂肺的汤 喝了它有神奇的力量 动态规划初步&#xff08;完全背包&#xff09; 目录动态规划初步&#xff08;完全背包&#xff09;0-1背包简介完全背包检查数组是否存在有效划分&#xff08;前缀划分DP&#xff09;单词拆…

Linux驱动06 --- UDP

目录 一、UDP 1.1 介绍 1.2 UDP 的通信方式 1.3 单播 发送函数 接收函数 1.4 广播 1.5 组播/多播 一、UDP 1.1 介绍 传输层的另外一个协议 面向无连接&#xff0c;不稳定&#xff0c;速度快&#xff0c;可以一对多 UDP&#xff08;User Datagram Protocol&…

AJAX 投票:技术解析与应用场景

AJAX 投票:技术解析与应用场景 引言 随着互联网技术的不断发展,Web应用的用户体验越来越受到重视。AJAX(Asynchronous JavaScript and XML)作为一种重要的技术,在实现异步数据交互方面发挥着关键作用。本文将深入探讨AJAX投票系统的技术原理、应用场景以及优化策略。 A…

【字节跳动】数据挖掘面试题0017:推荐算法:双塔模型,怎么把内容精准地推送给用户

文章大纲 双塔模型:推荐算法中的“高效匹配引擎一、双塔模型的核心思想:“分而治之” 的匹配逻辑二、双塔模型的结构:从特征输入到相似度输出1. 输入层:特征的 “原材料处理”2. 塔网络层:用户与物品的“个性化编码”3. 交互层:向量相似度的“偏好打分”三、双塔模型的优…

7月14日日记

数学类今天考完最后一科英语放假回家了。有点羡慕他们。今天英语成绩出来了&#xff0c;我是89分&#xff0c;一开始有点失望&#xff0c;感觉没有上90&#xff0c;这是一个很好的冲击4.0 的机会。但是后来一想好像也没什么可惜的&#xff0c;这个分数还是很高的。舍友小林是90…

js的局部变量和全局变量

全局变量常常定义在函数外&#xff0c;具有全局定义域&#xff0c;在整个js代码的任何地方都可以使用&#xff0c;这个就叫全局变量局部变量定义在函数内部&#xff0c;只在当前函数的定义域可以被使用&#xff0c;而且不同的函数可以定义相同的局部变量&#xff0c;他们之间相…

C++ 多态详解:从概念到实现原理----《Hello C++ Wrold!》(14)--(C/C++)

文章目录前言多态的概念多态的定义和实现虚函数虚函数的重写(覆盖)多态的构成条件override 和 final&#xff08;C11提出&#xff09;finaloverride重载、覆盖(重写)、隐藏(重定义)的对比抽象类接口继承和实现继承多态的原理虚函数表(也叫做虚表)引申:虚表的打印多态的原理静态…

Node.js + Express的数据库AB View切换方案设计

方案总览数据导入过程&#xff1a; - 根据控制表判断当前活跃组&#xff08;假设当前活跃的是a&#xff0c;那么接下来要导入到b&#xff09;。 - 清空非活跃表&#xff08;即b表&#xff09;的数据&#xff0c;然后将新数据导入到b表。 - 切换控制表&#xff0c;将活…

C++_编程提升_temaplate模板_案例

类模板案例案例描述: 实现一个通用的数组类&#xff0c;要求如下&#xff1a;可以对内置数据类型以及自定义数据类型的数据进行存储将数组中的数据存储到堆区构造函数中可以传入数组的容量提供对应的拷贝构造函数以及operator防止浅拷贝问题提供尾插法和尾删法对数组中的数据进…

Win11系统安装Anaconda环境极简教程

Win11系统安装Anaconda环境极简教程 &#x1f4e5; 第一步&#xff1a;下载 Anaconda 安装包 打开浏览器&#xff0c;访问 Anaconda 官网&#xff0c;选择View All Installers 选择所需版本&#xff08;此文以2024.02-1为例&#xff09;&#xff0c;点击进行下载&#xff08;…

Datawhale AI夏令营-基于带货视频评论的用户洞察挑战赛

一.赛事目标基于星火大模型Spark 4.0 Ultra&#xff0c;对视频和评论的数据进行商品识别&#xff0c;情感分析&#xff0c;归类分析&#xff0c;最终为带货效果进行评价。并通过优化模型来提高评价准确度二.赛事环境1.基础平台&#xff1a;星火大模型Spark 4.0 Ultra2.赛事数据…

如何基于FFMPEG 实现视频推拉流

文章目录 前言环境准备为什么选择 FFmpeg什么是nginx 1.7.11.3 GryphonNginx的conf配置启动nginx推流命令接收视频流Untiy播放视频流最后前言 我们经常会有在电脑上实现推拉流的需求,Unity 和Unreal 都提供了基于WebRTC 的视频流方案,效果还不错,但是当我们需要推拉整个电脑…

飞算JavaAI:从情绪价值到代码革命,智能合并项目与定制化开发新范式

目录一、飞算 JavaAI 是什么&#xff1f;二、飞算JavaAI&#xff1a;安装登录2.1 IDEA插件市场安装&#xff08;推荐&#xff09;2.2 离线安装包三、飞算JavaAI核心功能&#xff1a;一键生成完整工程代码功能背景3.1 理解需求3.2 设计接口3.3 表结构自动设计3.4 处理逻辑&#…

Python 基础语法与数据类型(十一) - 类 (class) 与对象 (实例)

文章目录1. 什么是类 (Class)&#xff1f;1.1 定义一个类2. 什么是对象 (Object) 或实例 (Instance)&#xff1f;2.1 创建对象&#xff08;实例化&#xff09;3. 访问属性和调用方法4. 类属性 vs 实例属性5. self 的重要性总结练习题练习题答案前几篇文章我们学习了变量、数据类…

精准数据检索+数据飞轮自驱优化,彩讯AI知识库助力企业知识赋能和效率创新

近两年&#xff0c;人工智能技术的精细化发展&#xff0c;让知识库概念重新成为“热门词汇”&#xff0c;腾讯ima等智能工作台产品为个人用户打造专属知识库&#xff0c;而面向B端市场&#xff0c;企业AI知识库也逐步成为企业集中存储与管理核心文档、数据、经验和流程的知识中…

打破空间边界!Nas-Cab用模块化设计重构个人存储逻辑

文章目录前言1. Windows安装Nas-Cab2. 本地局域网连接Nas-Cab3. 安装Cpolar内网穿透4. 固定Nas-Cab 公网地址"数据管理不该受制于硬件形态或地理边界。这个开源方案证明&#xff1a;当功能模块化且可扩展时&#xff0c;私有云可以像水一样渗透进所有设备——现在就去Git仓…

Sigma-Aldrich细胞培养基础知识:细胞培养的安全注意事项

细胞培养实验室风险评估风险评估的主要目的是防止人员受伤&#xff0c;保护财产&#xff0c;并避免对个人和环境的伤害。在许多国家&#xff0c;法律要求进行风险评估。例如&#xff0c;英国的《英国职业健康与安全法案&#xff08;1974年&#xff09;》就是一个例子。欧洲共同…

Imx6ull用网线与电脑连接

理解工作方式没有路由器时&#xff0c;可以使用&#xff0c;只要保持虚拟机的两个网卡一个与电脑在同一网,一个与板子在同一网段(保持通信)就可以从虚拟机往板子下载第一步&#xff1a;查看电脑连接的网络这一步是在找到主机ip地址这两步在其他同类教程里一样的第二步:设置以太…