iT邦幫忙

2023 iThome 鐵人賽

DAY 19
0

一、介紹

擴張(Dilation)和侵蝕(Erosion)、開運算(Opening)、閉運算(Closing)是影像處理中的形態學運算,用於處理影像和物體分割。這些運算可以去除雜訊、修復斷裂、填充空洞,以及幫助識別物體特徵。在影像處理中有廣泛的應用。

二、原理

1. 擴張(Dilation)

擴張的原理基於一個稱為結構元素的核,在影像上做摺積。以下是擴張運算的原理:

  1. 結構元素(kernel):擴張運算使用一個事先定義的結構元素,通常是一個小的矩形、圓形或其他形狀的核w(x,y)
  2. 摺積操作:尋找結構元素中的最大值,並將這個最大值放在結構元素的錨(anchor)上,意思是核的中心點,下圖例右圖紅色的數字即為。可以看到下圖例,只要結構元素內有一個元素為1,摺積出來的結果就為1
  3. 結果生成:產生一個新的影像,可以看到下圖1的區域變得更大、更廣泛。

https://ithelp.ithome.com.tw/upload/images/20230925/20161732FvduKymuWK.png

2. 侵蝕(Erosion)

侵蝕(Erosion)原理一樣是基於一個結構元素的核,在影像上做摺積。以下是侵蝕運算的原理:

  1. 結構元素(kernel):侵蝕運算使用一個事先定義的結構元素,通常是一個小的矩形、圓形或其他形狀的核w(x,y)
  2. 卷積操作:尋找結構元素中的最小值,並將這個最大值放在結構元素的錨(anchor)上,可以看到下圖例,只要結構元素內有一個元素不為1,摺積出來的結果就為0
  3. 結果生成:產生一個新的影像,可以看到下圖1的區域變得更小、更緊湊。

https://ithelp.ithome.com.tw/upload/images/20230925/20161732Q890dIucUA.png

3. 開運算(Opening)

開運算是由侵蝕(Erosion)後接著擴張(Dilation)所組成的運算。運算的步驟如下:

  1. 對影像應用侵蝕運算。這個步驟會縮小或消除影像中小的白色區域(或物體),去除小的噪聲或連接物體的細小連接部分。
  2. 對侵蝕後的影像應用擴張運算。這個步驟可以填充或擴展物體,但被侵蝕的細小部分無法恢復。

4. 閉運算(Closing)

閉運算是由擴張(Dilation)後接著侵蝕(Erosion)所組成的運算。運算的步驟如下:

  1. 對影像座擴張運算。這個步驟會擴大或填充影像中的白色區域(或物體),有助於閉合物體的小間隙。
  2. 對擴張後的影像進行侵蝕運算。這個步驟會縮小物體,但無法消除雜訊。

三、程式碼

1. 逐行講解

1) 二值化

假設圖片是白底黑字,需要先將圖片轉成負片,將圖片的每一個元素對255相減。透過大津二值化取得一個二進位的圖片,顯示在"Original"視窗上。

cv::threshold(255- image, binary_image, 0, 255, cv::THRESH_OTSU);

2) 使用OpenCV實現擴張、侵蝕

onErodeonDilate 函式:當滑塊"Kernel Size"滑動時。進行侵蝕或擴張運算,首先檢查核的大小是否為偶數,如果是,將大小增加1,以確保使用的結構元素的大小是奇數,才會有中心點的產生。使用 cv::getStructuringElement 建立一個矩形結構元素,並使用 cv::erodecv::dilate函數對二值影像進行侵蝕、擴張運算,最後將結果顯示在"Erode"、"Dilate"視窗上。

void onErode(int kernel_size, void*) {
	if (kernel_size % 2 == 0)
		kernel_size++;

	cv::Mat dst;
	cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT,cv::Size(kernel_size,kernel_size));
	cv::erode(binary_image,dst,kernel);
	cv::imshow("Erode", dst);
}

2. 完整程式碼

#include <iostream>
#include <opencv2/opencv.hpp>
#include "opencv2/core/utils/logger.hpp"

using namespace std;
void onErode(int kernel_size, void*);
void onDilate(int kernel_size, void*);
void onOpening(int kernel_size, void*);
void onClosing(int kernel_size, void*);

cv::Mat binary_image;

int main()
{
    cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_ERROR);
	cv::Mat image = cv::imread("C:\\Users\\vince\\Downloads\\test_image.jpg",cv::IMREAD_GRAYSCALE);
	cv::threshold(255- image, binary_image, 0, 255, cv::THRESH_OTSU);


	cv::namedWindow("Original", cv::WindowFlags::WINDOW_NORMAL);
	cv::resizeWindow("Original",cv::Size(512*(float)image.cols/image.rows,512));
	cv::imshow("Original",binary_image);

	cv::namedWindow("Dilate", cv::WindowFlags::WINDOW_NORMAL);
	cv::resizeWindow("Dilate",cv::Size(512*(float)image.cols/image.rows,512));

	cv::imshow("Dilate",binary_image);
	cv::createTrackbar("Kernel Size", "Dilate", NULL, 100,onDilate);

	cv::namedWindow("Erode", cv::WindowFlags::WINDOW_NORMAL);
	cv::resizeWindow("Erode",cv::Size(512*(float)image.cols/image.rows,512));
	cv::imshow("Erode",binary_image);
	cv::createTrackbar("Kernel Size", "Erode", NULL, 100,onErode);

	cv::namedWindow("Opening", cv::WindowFlags::WINDOW_NORMAL);
	cv::resizeWindow("Opening",cv::Size(512*(float)image.cols/image.rows,512));
	cv::imshow("Opening",binary_image);
	cv::createTrackbar("Kernel Size", "Opening", NULL, 100,onOpening);


	cv::namedWindow("Closing", cv::WindowFlags::WINDOW_NORMAL);
	cv::resizeWindow("Closing",cv::Size(512*(float)image.cols/image.rows,512));
	cv::imshow("Closing",binary_image);
	cv::createTrackbar("Kernel Size", "Closing", NULL, 100,onClosing);


	cv::waitKey(0);
	return 0;
}

void onErode(int kernel_size, void*) {
	if (kernel_size % 2 == 0)
		kernel_size++;

	cv::Mat dst;
	cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT,cv::Size(kernel_size,kernel_size));
	cv::erode(binary_image,dst,kernel);
	cv::imshow("Erode", dst);
}

void onDilate(int kernel_size, void*) {
	if (kernel_size % 2 == 0)
		kernel_size++;

	cv::Mat dst;
	cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT,cv::Size(kernel_size,kernel_size));
	cv::dilate(binary_image,dst,kernel);
	cv::imshow("Dilate", dst);
	
}

void onOpening(int kernel_size, void*) {
	if (kernel_size % 2 == 0)
		kernel_size++;

	cv::Mat dst;
	cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT,cv::Size(kernel_size,kernel_size));

	cv::erode(binary_image,dst,kernel);
	cv::dilate(dst,dst,kernel);
	cv::imshow("Opening", dst);
	
}
void onClosing(int kernel_size, void*) {
	if (kernel_size % 2 == 0)
		kernel_size++;

	cv::Mat dst;
	cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT,cv::Size(kernel_size,kernel_size));

	cv::dilate(binary_image,dst,kernel);
	cv::erode(dst,dst,kernel);
	cv::imshow("Closing", dst);

}

3. 測試結果

1) 測試圖

這是一張帶有椒鹽雜訊的圖片,雜訊有黑色也有白色,可以看到文字被白色的雜訊侵蝕,在W的字元上甚至出現斷裂。

https://ithelp.ithome.com.tw/upload/images/20230925/20161732QLj1XvuJDP.jpg

2) 二進位圖

這張就是經過負運算以後的影像,白色是我們要處理的目標。

https://ithelp.ithome.com.tw/upload/images/20230925/20161732C8UBzUoDoo.png

3) 擴張結果

經過擴張運算以後,可以看到文字上的空缺被填滿了,但是噪點也被放大了。

https://ithelp.ithome.com.tw/upload/images/20230925/20161732xYPUnN5f3U.png

4) 侵蝕結果

經過侵蝕運算以後,可以白色的噪點被侵蝕掉了,但是文字內的空缺也更大了。

https://ithelp.ithome.com.tw/upload/images/20230925/20161732xr2TBJSCtR.png

5) 開運算結果

進行開運算以後,噪點被消除,但是文字的輪廓發生斷裂的現象。

https://ithelp.ithome.com.tw/upload/images/20230925/20161732eQEEyFmOHN.png

6) 閉運算結果

進行開運算以後,原先文字斷裂或空缺的部分被消除,但是噪點仍存在,有些文字甚至因為過度擴張出現相連的情況。

https://ithelp.ithome.com.tw/upload/images/20230925/20161732LcV088jlw6.png


上一篇
【Day18】​OpenCV HSV色彩空間轉換:手掌前景提取
下一篇
【Day20】影像梯度(Gradient)和邊緣(Edge)性質:深入探討邊緣檢測
系列文
圖解C++影像處理與OpenCV應用:從基礎到高階,深入學習超硬核技術!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言