Qt轮廓分析设计+算法+避坑

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

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

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

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

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

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

一.界面方面

从头再过一下加强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);   // ID
    m_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);
}

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

相关推荐
恣艺1 小时前
LeetCode 132:分割回文串 II
算法·leetcode·代理模式
LZQqqqqo3 小时前
C# 中生成随机数的常用方法
java·算法·c#
葵续浅笑4 小时前
LeetCode - 合并两个有序链表 / 删除链表的倒数第 N 个结点
java·算法·leetcode
哪 吒8 小时前
【2025C卷】华为OD机试九日集训第3期 - 按算法分类,由易到难,提升编程能力和解题技巧
python·算法·华为od·华为od机试·2025c卷
机器学习之心HML8 小时前
PSO-TCN-BiLSTM-MATT粒子群优化算法优化时间卷积神经网络-双向长短期记忆神经网络融合多头注意力机制多特征分类预测/故障诊断Matlab实现
神经网络·算法·cnn
数据与人工智能律师8 小时前
智能合约漏洞导致的损失,法律责任应如何分配
大数据·网络·人工智能·算法·区块链
天天开心(∩_∩)9 小时前
代码随想录算法训练营第三十九天
算法
weisian1519 小时前
力扣经典算法篇-41-旋转图像(辅助数组法,原地旋转法)
算法·leetcode·职场和发展
朝朝又沐沐10 小时前
算法竞赛阶段二-数据结构(40)数据结构栈的STL
开发语言·数据结构·c++·算法
2501_9277730710 小时前
数据结构——单向链表
数据结构·算法