輪廓分析擬合方面我現在只考慮矩形擬合和圓形擬合
細分的話,橢圓擬合,矩形擬合,最小外接矩形,最小外接圓。
對于一張圖像可能有不同的圖形,不同的圓,不同的矩形,我需要對其進行篩選,也需要對檢測的目標對針對性計算,例如面積,周長,圓心點坐標。
也需要對篩選的模塊進行進一步檢索,例如都是圓,兩個大一個小,我就需要通過篩選他們的面積來確定我具體要哪一個圓。
那最后就是顯示功能了,因為輪廓分析往往是基于圖像處理后顯示的,這樣不夠直觀也不夠美觀,我們需要讓他顯示在原圖上,我們可以選擇它顯示的方式,最小圓,最小矩形,十字線,中心點這樣會好看很多。
我們整體的頁面思路已經設計好了下面看一下實現
一.界面方面
從頭再過一下加強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
(即輪廓面積)、一階矩m10
和m01
。- 質心公式:
- 質心橫坐標 =?
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π*面積/周長2double 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);
}
這樣我們就能完成想要的尋找輪廓的操作