上個章節我們介紹了如何求出輪廓的Hu矩,Hu矩可以描述為形狀的特徵,並且對影像的平移、旋轉和縮放具有不變性。那我們求出Hu矩的用意是什麼,其實就是要把它拿來做形狀比對的衡量標準。由於Hu矩的組成是由含有7個小數點的陣列組成的,在形狀比對上只要取出兩個形狀的Hu矩並算出每個陣列元素相差多少最後相加總,就可以衡量兩個形狀的差異性。
形狀比對的原理是利用Hu矩比較兩個形狀,並計算它們之間的數值差異。然而,要如何比對兩個帶有七個小數點的數據呢?形狀比對的流程如下:
距離度量方式是用來衡量兩個物體、數據集或點之間的相似性或差異性的方法。通常用於數據分析、機器學習等領域,評估兩個實體之間的距離或相似程度。
在我們進行距離度量之前,我們需要先將兩個矩數據內個別的元素做計算求出ηiA和ηiB,才有辦法做進一步的距離度量,ηiA和ηiB的公式如下:
歐氏距離用於衡量兩個向量或數據集之間的相似度或差異性,其中 Δ1是兩個數據集之間的歐氏距離,ηiA和 ηiB分別代表兩個形狀的七個Hu矩中的相應值。
但實際在OpenCV的運算上,不會進行平方或開根號運算,推測可能是因為進行平方和根號運算會增加運算量。
絕對差異總和可以用來評估兩個數據集之間的差異程度。其中Δ2 表示兩個數據集之間的絕對差異總和。
相對差異總和可以用來評估兩個數據集之間的相對差異程度,Δ3 表示兩個數據集之間的相對差異總和。這在數據分析和模式考慮了數值的比例關係,在兩數據的差異上除以ηiA。
這支程式的主要功能是讀取灰階影像,並允許使用者進行互動式形狀比較和標記。使用者可以透過滑鼠點擊影像上的輪廓,選擇特定輪廓以進行比較。程式會計算所選輪廓與其他輪廓的形狀相似度,並將這些相似度與用戶設定的閾值進行比較。如果相似度低於閾值,程式將在相應的輪廓周圍繪製標記,以突出差異。最終,程式會顯示原始影像以及帶有輪廓和形狀相似度標記的影像,使使用者能夠直觀地了解形狀的比較結果。
計算兩個輪廓之間的形狀差異度,並分別計算三種不同的差異度的指標,即delta_1
、delta_2
和delta_3
。使用這個函數,你不需要計算Hu矩,這個函式的內部本身就會先計算輸入的Hu矩,並進行比對。
double delta_1 = cv::matchShapes(contours[selected_index], contours[i], cv::CONTOURS_MATCH_I1, 0);
double delta_2 = cv::matchShapes(contours[selected_index], contours[i], cv::CONTOURS_MATCH_I2, 0);
double delta_3 = cv::matchShapes(contours[selected_index], contours[i], cv::CONTOURS_MATCH_I3, 0);
contours[selected_index]
:表示使用者選擇的輪廓。contours[i]
:表示要與選定輪廓進行比較的其他輪廓。cv::CONTOURS_MATCH_I1
、cv::CONTOURS_MATCH_I2
和cv::CONTOURS_MATCH_I3
:分別指定要使用的距離度量方式,分別為歐氏距離、曼哈頓距離、相對差異總和。0
:是一個保留的參數,目前沒有任何用途。findSelectedContourIndex
函式接受兩個整數參數x和y,代表滑鼠點擊的位置座標。
contours
這個向量中。cv::boundingRect
函式計算第i個輪廓的外接矩形,並將結果儲存在名為rect
的cv::Rect
型別變數中。rect
所代表的外接矩形範圍內。int findSelectedContourIndex(int x, int y) {
for (int i = 0; i < contours.size(); i++) {
cv::Rect rect = cv::boundingRect(contours[i]);
if (rect.contains(cv::Point(x, y))) {
return i;
}
}
return -1;
}
#include <iostream>
#include "opencv2/opencv.hpp"
#include "opencv2/core/utils/logger.hpp"
using namespace std;
cv::Mat image; // 存儲讀取的影像
vector<vector<cv::Point>> contours; // 存儲影像輪廓
int selected_index = -1; // 選定的輪廓索引
int threshold; // 閾值,用於形狀相似度比較
// 函式聲明
int findSelectedContourIndex(int x, int y); // 在滑鼠點擊位置尋找選定的輪廓索引
void onClick(int event, int x, int y, int flags, void* param); // 滑鼠點擊事件處理函式
void onTrackbarSlide(int position, void*); // 滑動條值變化事件處理函式
int main()
{
cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_ERROR);
// 讀取灰階影像
image = cv::imread("C:\\Users\\vince\\Downloads\\test_image5.jpg", cv::IMREAD_GRAYSCALE);
// 建立一個視窗用於顯示影像和輪廓
cv::namedWindow("Output", cv::WindowFlags::WINDOW_NORMAL);
cv::resizeWindow("Output", cv::Size(512.0 * ((float)image.cols / image.rows), 512));
// 設定滑鼠點擊事件處理函式
cv::setMouseCallback("Output", onClick);
// 建立一個用於設定形狀相似度閾值的滑動條
cv::createTrackbar("threshold", "Output", &threshold, 1000, onTrackbarSlide);
// 將影像二值化
cv::Mat binary;
cv::threshold(image, binary, 0, 255, cv::THRESH_OTSU);
// 建立一個用於顯示輪廓的影像
cv::Mat output = cv::Mat::zeros(cv::Size(binary.cols, binary.rows), CV_8UC3);
// 查找影像中的輪廓
cv::findContours(binary, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_TC89_KCOS);
// 對每個輪廓進行處理
for (int i = 0; i < contours.size(); i++) {
cv::Rect bounding_rect = cv::boundingRect(contours[i]);
cv::putText(output, to_string(i), cv::Point(bounding_rect.x, bounding_rect.y - 5), cv::FONT_HERSHEY_COMPLEX, 0.5, cv::Scalar(0, 255, 0));
cv::drawContours(output, contours, i, cv::Scalar(0, 255, 0));
}
// 顯示帶有輪廓的影像
cv::imshow("Output", output);
// 等待使用者操作
cv::waitKey(0);
return 0;
}
// 函式定義
// 在滑鼠點擊位置尋找選定的輪廓索引
int findSelectedContourIndex(int x, int y) {
for (int i = 0; i < contours.size(); i++) {
cv::Rect rect = cv::boundingRect(contours[i]);
if (rect.contains(cv::Point(x, y))) {
return i;
}
}
return -1;
}
// 滑鼠點擊事件處理函式
void onClick(int event, int x, int y, int flags, void* param)
{
if (event & cv::EVENT_LBUTTONDOWN)
{
if (x != -1 && y != -1) {
selected_index = findSelectedContourIndex(x, y);
}
if (selected_index == -1)
return;
// 建立一個影像,用於顯示選定的輪廓
cv::Mat output = cv::Mat::zeros(cv::Size(image.cols, image.rows), CV_8UC3);
cv::Rect target = cv::boundingRect(contours[selected_index]);
cv::rectangle(output, target, cv::Scalar(0, 255, 255));
cv::putText(output, to_string(selected_index), cv::Point(target.x, target.y - 5), cv::FONT_HERSHEY_COMPLEX, 0.5, cv::Scalar(0, 255, 255));
// 在影像上繪製所有輪廓
cv::drawContours(output, contours, -1, cv::Scalar(0, 255, 0));
// 比較選定的輪廓與其他輪廓的形狀相似度
for (int i = 0; i < contours.size(); i++) {
if (selected_index == i)
continue;
cv::Rect rect = cv::boundingRect(contours[i]);
cv::putText(output, to_string(i), cv::Point(rect.x, rect.y - 5), cv::FONT_HERSHEY_COMPLEX, 0.5, cv::Scalar(0, 255, 0));
double delta_1 = cv::matchShapes(contours[selected_index], contours[i], cv::CONTOURS_MATCH_I1, 0);
double delta_2 = cv::matchShapes(contours[selected_index], contours[i], cv::CONTOURS_MATCH_I2, 0);
double delta_3 = cv::matchShapes(contours[selected_index], contours[i], cv::CONTOURS_MATCH_I3, 0);
if (delta_3 < threshold / 10.0) {
cv::rectangle(output, rect, cv::Scalar(0, 0, 255));
}
}
// 顯示更新後的影像
cv::imshow("Output", output);
return;
}
}
// 滑動條值變化事件處理函式
void onTrackbarSlide(int position, void*) {
onClick(cv::EVENT_LBUTTONDOWN, -1, -1, -1, NULL);
}
當你點擊某個輪廓時,輪廓會被黃色的框框起來,代表以目前的形狀比對每個輪廓的相似度。你可以透過調整閾值來調整輪廓的差異度的容忍範圍,當差異度小於閾值時就會用紅色框框起來。