[Matlab] 影像處理 – Image Segmentation

前言

最近在做研究時需要測量材料的微觀硬度(Microhardness),機器會在設定好的位置上用鑽石壓出一個稜形的印記,較硬的材料抵抗壓印的能力較好,所壓出的印記就越小。因此該印記的大小即是材料硬度的指標,就像下圖所示:

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/indent.png

原本實驗儀器是可以自動量測,但由於它量測的精度實在太差,最後都需要人為校正。一個樣品通常要打600個點,我手上有30個樣品,總共有18000個點要校正。即使一個點校正只要花10秒,這樣總共要浪費我50個小時去校正,想到就覺得頭痛。

由於機器打硬度的形狀都很規整,印記跟基材的對比又非常大,用影像處理寫個程式來自動判斷印記大小並不難。因此就來寫篇文章就來記錄一下我在開發這個程式的過程中學習到的一些影像處理上的技巧。這次的程式是以Matlab來撰寫的,但是所有用到的影像處理方法都是通用的,換成是其他語言也都可行。

本文著重於圖像分割(Image Segmentation)的技巧整理,有關完整的影像處理請參閱[Matlab] 影像處理 – Indent Measurement

本文中所使用到的方法需要先安裝Image Processing Toolbox才能使用喔。

區分印記與基材

我的目的是測量印記的對角線長度,第一步自然是要讓程式辨認出印記與基材的差異。在影像處理中有個專有名詞叫做圖像分割(Image Segmentation),有幾種常見的作法:

二值化 (Image Binerization)

以特定顏色數值為閾值(Threshold),把影像轉為二值圖(Binary Image),超過閾值為白色,不超過為黑色。這是最簡單的一種圖像分割法,缺點是閾值要人為決定。

決定閾值

我們可以沿著某條線將圖像中的像素RGB取出來,觀察RGB值變化最大的地方。以本篇的圖像為例,可以劃一條通過印記與基材的橫線:

>> im = imread("indent.png");
>> imshow(im)
>> row = 150;
>> hold on
>> h = plot([0 size(im,2)],[row, row],'r-');

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled.png

把這條線上的像素RGB值畫成圖表:

>> figure
>> hold on
>> plot(im(row,:,1),'r-')
>> plot(im(row,:,2),'g-')
>> plot(im(row,:,3),'b-')

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%201.png

可以明顯看出印記中的RGB值都小於100,基材的RGB都大於130,數值變化處即為邊緣。

這裡可以把閾值設為130,並把圖像中R數值小於閾值的部分篩選出來:

>> threshold = 130;
>> binary = im(:,:,1) < threshold;
>> imshow(binary)

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%202.png

由於此圖顏色單調,因此RGB數值變化幾乎相同,因此篩選時RGB任選一種效果都差不多。若是顏色分布較複雜的圖像就需要更細緻的篩選機制。

Color Thresholder來找出閾值

除了可以用上面程序的方法來看出閾值之外,Matlab也提供了一個App來讓我們可以像操作軟體一樣簡單方便的決定閾值。

在Apps中找到Color Thresholder打開:

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2012.png

點擊Load Image匯入影像:

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2013.png

匯入影像後,Matlab會計算出影像中像素在不同色彩空間(Color Space)下的3D散布圖。我們可以觀察像素的散布情況來選取適合的色彩空間來進行二值化,這裡我們選RGB來示範。

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2014.png

接下來我們就可以透過拖動右邊的RGB範圍來篩選影像了,篩選好後按下Export就可以匯出影像。畫面中還有許多功能,各位讀者可以慢慢探索。

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2015.png

可適性二值化(Adaptive Thresholding)

可以根據鄰近像素決定區域閾值,優點是不用人為決定閾值,且能適應光線不佳的影像。這個方法要求輸入影像必須是灰度圖(Grayscale image),在Matlab裡可以先用rgb2gray()轉換圖像,再用imBinarize(im,'adaptive')來使用:

>> gray = rgb2gray(im);
>> adaptive = imbinarize(gray,'adaptive');
>> imshow(adaptive)

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%203.png

可惜的是這個方法的閾值適應範圍非常局部,雖然它能夠區分出很細微的特徵,但並不適用於判斷本篇的印記。

適用時機

這個方法真正強大之處是在應對光線不一致的影像,例如下圖中底部影像光線較暗:

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%204.png

使用一般的二值化會得到下面左圖,底部的顆粒沒辦法正確區分。這時使用可適性二值化便可以如下面右圖那樣清晰的區分出米粒。

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%205.png

邊緣偵測 (Edge Detection)

計算影像梯度(Gradient),再選一個適當的閾值針對梯度把圖像二值化,圖像梯度大的地方即為邊緣。此法同樣要求輸入灰度圖。事實上,單邊緣偵測也有非常多種算法,本篇僅介紹較基本的Sobel算法的原理。

Sobel算法

在說明Sobel算法之前要先了解卷積運算,如下圖所示,把輸入影像(Input image)中特定位置與其周圍的像素跟一個濾波器(Filter)進行內積(Dot)運算得到輸出影像對應的像素值。卷積運算在影像處理中非常強大,可以針對局部特徵操作影像像素,實現非常多事情。

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%206.png

Source: https://www.researchgate.net/publication/318849314_Streaming_Architecture_for_Large-Scale_Quantized_Neural_Networks_on_an_FPGA-Based_Dataflow_Platform

Sobel利用了兩種 $3 \times 3$ 的濾波器來分別偵測影像在x方向與y方向的梯度變化,這兩個濾波器又稱為Sobel運算子:

G_x = \begin{bmatrix} 1 & 0 & -1 \\ 2 & 0 & -2 \\ 1 & 0 & -1 \end{bmatrix} G_y = \begin{bmatrix} 1 & 2 & 1 \\ 0 & 0 & 0 \\ -1 & -2 & -1 \end{bmatrix}

想像一下,如果輸入影像像素周圍數值皆相同,與Sobel運算子內積後正負相抵消,結果為零。當輸入像素周圍是垂直邊緣或水平邊緣時,透過Sobel運算結果才不為零。所以整個影像與Sobel算子卷積運算後的結果,就是影像平滑連續處的數值都被消除,只剩下不連續的邊緣處有值。

Sobel算法在Matlab中只要透過edge()函數就可以使用:

>> gray = rgb2gray(im);
>> sobel = edge(gray);
>> imshow(sobel)

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%207.png

可以明顯看到印記的邊緣有被偵測出來,然而雜訊不少,偵測出來的邊緣薄厚不一致,品質不好。

Canny算法

現在邊緣偵測普遍流行使用Canny算法,這個算法可以得出均勻厚度的邊緣,因此比較實用。用法如下:

>> gray = rgb2gray(im);
>> [canny, thres] = edge(gray,'canny');
>> imshow(canny)

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%208.png

結果卻得出雜訊非常多的圖像。

影像平滑(Image Smoothing)

事實上一張影像裡的雜訊(Noise)非常多,雖然肉眼可能看不太出來,但對程式而言,只要顏色有些微差異都會影響到邊緣偵測的品質。就像上面看到的狀況,雖然整張影像看起來印記跟基材界線分明,但其他很多地方也被偵測為邊緣,就是因為該影像的雜訊太多,干擾了程式的判斷。

那麼要如何解決這個問題呢?影像平滑化就可以很有效的消除雜訊。它的原理就是利用如下所示的均值濾波器(Average Filter)

F = {1 \over 9}\begin{bmatrix} 1 & 1 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 1 \\ \end{bmatrix}

讓影像對它進行卷積運算,所有像素點的數值都會跟周圍的像素平均。由於雜訊的形狀通常都像脈衝波(Pulse Signal)一樣,只要跟周圍平均,這樣的雜訊就可以有效被消除了。雖然影像平滑可以消除雜訊,但是代價就是會得到模糊的影像,畢竟所有的像素都跟周圍平均掉了。我們來試著把原本的影像先平滑化之後再做邊緣偵測:

>> f = ones(3,3) / 9

f =

    0.1111    0.1111    0.1111
    0.1111    0.1111    0.1111
    0.1111    0.1111    0.1111

>> smooth = imfilter(gray,f);
>> for i = 1:5
smooth = imfilter(smooth,f);
end
>> imshowpair(gray,smooth,'montage')

在Matlab中使用imfilter()就可以對影像進行卷積運算。

由於只進行一次平滑化效果不明顯,這裡以迴圈執行了6次後再與原先的圖片比較。

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%209.png

左圖是原本的灰度圖,右圖是經過平滑處理的圖像。

可以看出經過平滑處理的圖雜訊減少了,邊緣也比較模糊了。

同時,可以注意到在右圖的邊界多了一圈黑線。這是因為在預設的狀況下,imfilter()做卷積運算時,對影像的邊界採用補零的方式計算。

現在對平滑後的影像做邊緣偵測:

>> canny_smooth = edge(smooth,'canny');
>> imshow(canny_smooth)

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2010.png

可以看出雜訊明顯減少了。

邊緣偵測的閾值

然而即使是經過6次平滑處理的影像,偵測出來的邊界還是有雜訊,這是因為Matlab預設選取的閾值太低了。edge()方法會回傳一個thres代表篩選邊緣的閾值,我們可以更改這個數值,再把它作為參數輸入到edge()裡面來得到理想的邊緣:

>> [canny_smooth,thres] = edge(smooth,'canny');
>> thres

thres =

    0.0250    0.0625

>> thres = thres + 0.2

thres =

    0.2250    0.2625

>> [canny_smooth,thres] = edge(smooth,'canny',thres);
>> imshow(canny_smooth)

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2011.png

如此得到的邊緣便清晰多了。

檢驗邊緣精度

我們可以把偵測出來的邊緣重疊到原圖上面看看偵測的精確程度,只要把黑色部分設為透明即可。具體代碼如下:

>> canny_smooth = uint8(canny_smooth) * 255; %把遮罩轉換成影像
>> imshow(gray)
>> hold on
>> h = imshow(canny_smooth);
>> h.AlphaData = canny_smooth;

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2016.png

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2017.png

如此一來便可以清楚的看見偵測出的邊緣位置。在局部放大圖中可以看到偵測的邊緣在實際邊緣的內部,這是因為之前為了去除雜訊,使用了平滑處理把邊緣的特徵模糊掉了。我們可以把邊緣重疊到平滑處理後的圖看看邊緣的狀況:

>> imshow(smooth)
>> hold on
>> h = imshow(canny_smooth);
>> h.AlphaData = canny_smooth;

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2018.png

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2019.png

從放大圖可以看出邊緣基本上整個糊掉了,難怪偵測出的邊緣不精確。可以看出,用平滑處理去除雜訊是會影響邊緣偵測的,若想要得到高質量的邊緣同時去除多餘的雜訊,就需要在兩者之間取得一個好的平衡點。

保留邊緣的影像平滑 (Edge-Preserving Smoothing)

現在也發展出可以在一定程度上保留邊緣的平滑處理,但使用上較為複雜,且效果不穩定,仍需人為調控。

小結 – 選用二值化

上面介紹了三種影像分割的常見做法,以及分別展示了每個方法的優缺點以及實際應用的效果。在影像處理時,針對不同的場景有各自適合的最佳用法,在使用之前需要了解每個方法才能發揮它們最大的效用。

根據我的需求,我需要精確量測壓印在垂直與水平方向的對角線長度,因此圖像分割時必須要完整保留壓印邊角的特徵,並能明確與基材分割開。經過測試,我選用二值化(Image Binarization)來擷取壓印圖像,因為我的輸入影像光線條線一致,只需要人工找出閾值一次,就可以套用到所有其他的圖;並且它能最穩定的區分壓印邊緣,這樣量測長度的結果最準確。

以下面左圖為例,經過二值化後得到的結果如下右圖。

>> im = imread("indents.jpg");
>> gray = rgb2gray(im);
>> threshold = 130;
>> bw = gray < threshold;
>> imshow(bw)

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2020.png

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2021.png


影像增強 (Image Enhancement)

得到二值圖後,由於我要保留壓印邊緣而沒有先做平滑處理(Image Smoothing),所以途中的雜訊很多。並且輸入圖像中邊界不平整,邊界在二值化中被篩選進來。此外壓印中也有空洞,這是因為壓因中間顏色較亮,沒有通過二值化篩選。這些都是不必要的資訊,會對之後的影像處理造成困擾,所以必須進一步移除。因此通常在影像分割(Image Segmentation)完,真正開始進行影像處理之前,還會進一步做影像增強,目的不外乎就是為了加強影像特徵,讓後續處理更順利。

消除邊界

首先我們先來消除不平整的邊界,Matlab中提供了imclearborder()這個方便的方法讓我們直接使用:

>> bw = imclearborder(bw);
>> imshow(bw);

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2021.png

消除邊界前

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2022.png

消除邊界後

填補空洞

至於要填補壓印中的空洞也很簡單,只要用imfill()即可:

>> bw = imfill(bw,'holes');
>> imshow(bw);

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2022.png

填補空洞前

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2023.png

填補空洞後

消除二值圖的雜訊

上文中提到可以用平滑處理來消除雜訊,但那樣做會模糊掉邊緣特徵。經過二值化後,我們就不需要用平滑處理來消除雜訊了,因為所有小的雜訊在二值圖中都有一個明顯的特徵:面積很小。因此我們只要依據面積大小把雜訊篩選掉就可以了,在Matlab中可以使用bwareaopen(),做法如下:

>> areaMin = 4500;
>> bw = bwareaopen(bw, areaMin);
>> imshow(bw)

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2023.png

消除雜訊前

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2024.png

消除雜訊後

可以調整areaMin來改變要篩選掉的雜訊大小,直到能夠完全去除雜訊,又不會影像到壓印。

使用Image Region Analyzer來做影像增強

Matlab也提供了一個App來讓我們方便的使用上面提到的方法。

在Apps中找到Image Region Analyzer打開:

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2025.png

由於這個App只能接收二值圖,因此要先在Workspace把圖像二值化,再透過Load Image From Workspace匯入:

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2026.png

匯入後Matlab就會自動計算影像中所有區域的各種屬性,諸如面積(Area)、周長(Perimeter)等等,我們可以在右方欄位中看到。勾選上方Fill Holes與Exclude Border就可以填補空洞與去除邊界了,另外可以透過Sort Table裡選取Area來對所有區域進行排序:

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2028.png

可以明顯看出雜訊的面積皆小於100,壓印的面積在6000至7000之間。我們可以在左上方Filter裡設置篩選的條件,以面積大小把雜訊消除掉。

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2029.png

這樣就能得到與上文相同的結果了。


形態處理 (Morphological Operations)

讓我們觀察一下增強後的圖像,可以發現這時壓印上還會有縫隙與毛邊。我們希望這些不必要的特徵也能被清除填補,得到一個完美的稜形,形態處理可以幫我們達成這個。

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2024.png

增強後的圖像

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2030.png

增強後的壓印放大圖

形態學的基本操作

原理與卷積運算中的濾波器(Filter)、卷積核(Kernel)類似,形態學透過一個結構元素(Structuring Element)來操作影像像素。差別在於結構元素是1跟0組成的矩陣,並透過邏輯運算(AND, OR之類的)來處理影像。

舉體的運算細節可以參考Wiki,但那只是一些邏輯符號運算,看著吃力,這裡就不再贅述。為方便理解,我拿一個形態學課本上著名的圖片簡單說明:

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2031.png

上圖中,A是要處理的影像,B是圓形的結構元素,可以想成是NxN的矩陣中內切圓內的元素皆為1,圓外的元素皆為0。四種基本操作的說明如下:

  1. 侵蝕(Erosion)
    • B沿著A的邊緣走一圈,B走過的地方都要消除掉。
    • 會讓A的外圍少一圈,好像被侵蝕過了一樣。
    • 邏輯上類似於 A – B(滑過處)。
  2. 膨脹(Dilation)
    • B沿著A的邊緣走一圈,B走過的地方都要填進A。
    • 會讓A的外圍多一圈,好像膨脹了一樣。
    • 邏輯上類似於 A + B(滑過處)。
  3. 開放(Opening)
    • 先做侵蝕再做膨脹,相當於B在A內部任意移動,走不到的地方就消除掉。
    • 會把A中原先連結的地方斷開,其他地方不變。
  4. 封閉(Closing)
    • 先做膨脹再做侵蝕,相當於B在A外面任意移動,走不到的地方就填進A。
    • 會把A中原先的隙縫填滿,其他地方不變。

應用形態學操作

了解形態學操作的效果後,回頭看一下我們上面的需求:填補縫隙與去除毛邊,不難想像可以分別使用開放與封閉操作來達成。至於結構元素的設計,應該使用最符合壓印形狀的菱形,這樣才不會影響到其他邊角處的特徵。

Matlab中提供strel(type, size)可以取得幾種基本形狀的結構元素,type表示結構元素的種類,size表示結構元素的大小。例如:strel('diamond',5)會得到

>> se = strel('diamond',5)

se = 

strel is a diamond shaped structuring element with properties:

      Neighborhood: [11×11 logical]
    Dimensionality: 2

>> se.Neighborhood

ans =

  11×11 logical array

   0   0   0   0   0   1   0   0   0   0   0
   0   0   0   0   1   1   1   0   0   0   0
   0   0   0   1   1   1   1   1   0   0   0
   0   0   1   1   1   1   1   1   1   0   0
   0   1   1   1   1   1   1   1   1   1   0
   1   1   1   1   1   1   1   1   1   1   1
   0   1   1   1   1   1   1   1   1   1   0
   0   0   1   1   1   1   1   1   1   0   0
   0   0   0   1   1   1   1   1   0   0   0
   0   0   0   0   1   1   1   0   0   0   0
   0   0   0   0   0   1   0   0   0   0   0

另外imclose()imopen()則可以執行開放和封閉的操作。應用在增強過的壓印圖效果如下:

>> bw = imclose(bw,strel('diamond',5));
>> imshow(bw)
>> bw = imopen(bw,strel('diamond',15));
>> imshow(bw)

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2030.png

增強後的壓印放大圖

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2032.png

封閉運算後的壓印

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2033.png

開放運算後的壓印

結構元素的大小要依據需要消除的雜訊或填補的空隙大小而定,通常需要看影像狀況人為調整。

使用Image Segmenter執行圖像分割

上面說到結構元素的大小需要人為調整,因此Matlab就提供了Image Segmenter來幫我們方便的執行形態學操作。事實上,這個App不只能做形態學操作,所有本文上面提及的內容以及 [Matlab] 影像處理 – Indent Measurement 裡的技巧它都能做。下面我們就用原始的壓印陣列圖像來用Image Segmenter一步步完成圖像分割,最後擷取出完整的壓印特徵。

在Apps中打開Image Segmenter

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2034.png

Load Image匯入圖像,可以從檔案或Workspace。這裡要匯入灰階圖,才能使用裡面的二值化處理。

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2035.png

在Create Mask裡面打開Threshold,選擇Manual Threshold並調整Threshold,按下Create Mask完成。

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2036.png

在Refine Mask中選擇"Invert Mask", "Clear Border", "Fill Holes",做影像增強。

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2037.png

在Refine Mask中選擇Morphology,在Operation選擇close,在Structure Element中選擇Diamond,並調整Radius直到可以填補所有壓印中的縫隙,點選Apply。

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2038.png

把Operation改成Close,再調整Radius直到可以消除所有雜訊,點選Apply後關閉。

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2039.png

最後就可以按Export匯出擷取的壓印圖啦!

https://raw.githubusercontent.com/alex1392/blog-images/master/%5BMatlab%5D%20%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%20-%20Image%20Segmentation/Untitled%2040.png


計算壓印之硬度

取得所有壓印的二值圖後,就可以來測量壓印的對角線長度,並轉換成硬度了。Matlab提供了regionprops(bw)函式用來計算二值圖中各個範圍(Region)的屬性,預設的狀況下會計算面積(Area)、形心(Centroid)、和邊框(Bounding Box)。而對於本文的需求來說,這些屬性就足以計算硬度了。透過形心我們可以得知壓印的所在位置,而邊框的大小即為壓印的對角線長度了。因此,下列代碼可以取得所有壓印的位置與大小:

>> indents = regionprops(bw);
>> locations = vertcat(indents.Centroid); 
>> boxes = vertcat(indents.BoundingBox);
>> diagonals = boxes(:,3:4);

這裡所得的位置與大小的單位都是像素(Pixel),若要轉換成實際長度則要再根據圖中的比例尺乘上像素與實際長度的比例。

有了所有壓印的對角線長度後,再根據Vicker’s的硬度公式:

\text{Hv} = {2\cdot F\cdot\sin\left({\theta\over2}\right) \over d_{\text{avg}}^2}

其中 $d_{avg}$ 是壓印的平均對角線長度,$F$ 為施力,$\theta$ 為indenter的稜形角度。這樣就能把所有的硬度算出來了,代碼如下:

>> force = 0.5; %kg
>> angle = 136; %degree
>> HV = 2 * force * sind(angle/2) ./ ((mean(diagonals,2)).^2);

結語

本文記錄了我在開發自動辨識壓印程式的過程中,所學到的所有影像處理的技巧。過程中因為不夠了解各種方法的適用時機與效果,因此只能每種方法胡亂嘗試,沒有系統的去處理影像。經過這篇文章的撰寫,也讓我重新思考並理解了影像處理大致上的架構,且對各方法都有了更深刻的認識。希望我在過程中所學習到的東西也能讓你有所收穫。

參考資料

發表留言

在 WordPress.com 建立網站或網誌

向上 ↑