iT邦幫忙

2023 iThome 鐵人賽

DAY 25
0

一、介紹

上個章節我們介紹了如何求出輪廓的Hu矩Hu矩可以描述為形狀的特徵,並且對影像的平移、旋轉和縮放具有不變性。那我們求出Hu矩的用意是什麼,其實就是要把它拿來做形狀比對的衡量標準。由於Hu矩的組成是由含有7個小數點的陣列組成的,在形狀比對上只要取出兩個形狀的Hu矩並算出每個陣列元素相差多少最後相加總,就可以衡量兩個形狀的差異性。

二、原理

1. 形狀比對

形狀比對的原理是利用Hu矩比較兩個形狀,並計算它們之間的數值差異。然而,要如何比對兩個帶有七個小數點的數據呢?形狀比對的流程如下:

  1. 計算兩形狀的Hu矩。
  2. 選定的距離度量方式,計算兩個Hu矩之間的差異。
  3. 如果差異值夠小,則可以認形狀為相似,反之形狀為不相似。

2. 距離度量方式

距離度量方式是用來衡量兩個物體、數據集或點之間的相似性或差異性的方法。通常用於數據分析、機器學習等領域,評估兩個實體之間的距離或相似程度。

在我們進行距離度量之前,我們需要先將兩個矩數據內個別的元素做計算求出ηiAηiB,才有辦法做進一步的距離度量,ηiAηiB的公式如下:
https://ithelp.ithome.com.tw/upload/images/20230927/20161732908plqwYM2.png

1) 歐氏距離(Euclidean Distance)

歐氏距離用於衡量兩個向量或數據集之間的相似度或差異性,其中 Δ1是兩個數據集之間的歐氏距離,ηiA和 ηiB分別代表兩個形狀的七個Hu矩中的相應值。

但實際在OpenCV的運算上,不會進行平方或開根號運算,推測可能是因為進行平方和根號運算會增加運算量。
https://ithelp.ithome.com.tw/upload/images/20230927/20161732SkTvnNrUGW.png

2) 曼哈頓距離(Manhattan Distance)

絕對差異總和可以用來評估兩個數據集之間的差異程度。其中Δ2 表示兩個數據集之間的絕對差異總和。
https://ithelp.ithome.com.tw/upload/images/20230927/20161732g4yk5AdeDf.png

3) 相對差異總和(Sum of Relative Differences)

相對差異總和可以用來評估兩個數據集之間的相對差異程度,Δ3 表示兩個數據集之間的相對差異總和。這在數據分析和模式考慮了數值的比例關係,在兩數據的差異上除以ηiA。
https://ithelp.ithome.com.tw/upload/images/20230927/20161732zvPyTuFued.png

三、程式碼

這支程式的主要功能是讀取灰階影像,並允許使用者進行互動式形狀比較和標記。使用者可以透過滑鼠點擊影像上的輪廓,選擇特定輪廓以進行比較。程式會計算所選輪廓與其他輪廓的形狀相似度,並將這些相似度與用戶設定的閾值進行比較。如果相似度低於閾值,程式將在相應的輪廓周圍繪製標記,以突出差異。最終,程式會顯示原始影像以及帶有輪廓和形狀相似度標記的影像,使使用者能夠直觀地了解形狀的比較結果。

1. 逐行解釋

1) 比較形狀的差異性

計算兩個輪廓之間的形狀差異度,並分別計算三種不同的差異度的指標,即delta_1delta_2delta_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_I1cv::CONTOURS_MATCH_I2cv::CONTOURS_MATCH_I3:分別指定要使用的距離度量方式,分別為歐氏距離曼哈頓距離相對差異總和
  • 0:是一個保留的參數,目前沒有任何用途。

2) 尋找點選的輪廓

findSelectedContourIndex函式接受兩個整數參數x和y,代表滑鼠點擊的位置座標。

  • 通過一個for迴圈遍歷所有的輪廓,這些輪廓存儲在contours這個向量中。
  • 在每次迴圈中,使用cv::boundingRect函式計算第i個輪廓的外接矩形,並將結果儲存在名為rectcv::Rect型別變數中。
  • 檢查滑鼠點擊的位置座標是否位於rect所代表的外接矩形範圍內。
  • 如果for迴圈遍歷完所有輪廓仍然找不到位於滑鼠點擊位置附近的輪廓,則函式返回-1,表示未找到選定的輪廓。
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;
}

2. 完整程式碼

  1. 讀取一張灰階影像。
  2. 建立滑動條,允許用戶調整差異度閾值。
  3. 通過應用OTSU二值化方法,將灰階影像轉換為二值影像。
  4. 尋找影像中的輪廓並對每個輪廓進行以下操作:
    • 獲取輪廓的邊界框(Bounding Box)。
    • 在輪廓上繪製綠色的輪廓線,用文字標出輪廓的索引。
  5. 使用滑鼠點擊事件,當點擊輪廓時執行以下操作:
    • 在影像上繪製黃色的矩形框以標註選定的輪廓。
    • 在矩形框的左上角添加選定輪廓的索引。
    • 計算選定輪廓與其他輪廓的形狀差異度。
    • 如果差異度低於閾值,則在相似的輪廓上繪製紅色的矩形框。
#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);
}

3. 輸出結果

1) 測試圖

https://ithelp.ithome.com.tw/upload/images/20231009/20161732TXEP5iP2Fj.jpg

當你點擊某個輪廓時,輪廓會被黃色的框框起來,代表以目前的形狀比對每個輪廓的相似度。你可以透過調整閾值來調整輪廓的差異度的容忍範圍,當差異度小於閾值時就會用紅色框框起來。

https://ithelp.ithome.com.tw/upload/images/20230927/20161732DoZDlKwiDQ.png


上一篇
【Day24】使用OpenCV求出輪廓矩(Moments)
下一篇
【Day26】使用OpenCV進行霍夫線轉換(Hough Line Transform)
系列文
圖解C++影像處理與OpenCV應用:從基礎到高階,深入學習超硬核技術!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言