【OpenCV入門教程之十】 形態學圖像處理(一):膨脹與腐蝕
一、理論與概念講解——從現象到本質
形態學(morphology)一詞通常表示生物學的一個分支,該分支主要研究動植物的形態和結構。而我們圖像處理中指的形態學,往往表示的是數學形態學。
數學形態學(Mathematical morphology) 是一門建立在格論和拓撲學基礎之上的圖像分析學科,是數學形態學圖像處理的基本理論。
其基本的運算包括:二值腐蝕和膨脹、二值開閉運算、骨架抽取、極限腐蝕、擊中擊不中變換、形態學梯度、Top-hat變換、顆粒分析、流域變換、灰值腐蝕和膨脹、灰值開閉運算、灰值形態學梯度等。
最基本的形態學操作有二種:膨脹與腐蝕(Dilation與Erosion)。膨脹與腐蝕能實現多種多樣的功能,主要如下:
- 消除噪聲
- 分割(isolate)出獨立的圖像元素,在圖像中連接(join)相鄰的元素。
- 尋找圖像中的明顯的極大值區域或極小值區域
- 求出圖像的梯度
在進行腐蝕和膨脹的講解之前,首先需要注意,腐蝕和膨脹是對白色部分(高亮部分)而言的,不是黑色部分。膨脹就是圖像中的高亮部分進行膨脹,“領域擴張”,效果圖擁有比原圖更大的高亮區域。腐蝕就是原圖中的高亮部分被腐蝕,“領域被蠶食”,效果圖擁有比原圖更小的高亮區域。
1.1 結構元和腐蝕
1.2 膨脹
按數學方面來說,膨脹或者腐蝕操作就是將圖像(或圖像的一部分區域,我們稱之為A)與核(我們稱之為B)進行卷積。核可以是任何的形狀和大小,它擁有一個單獨定義出來的參考點,我們稱其為錨點(anchorpoint)。多數情況下,核是一個小的中間帶有參考點和實心正方形或者圓盤,其實,我們可以把核視為模板或者掩碼。
而膨脹就是求局部最大值的操作,核B與圖形卷積,即計算核B覆蓋的區域(體現局部)的像素點的最大值,并把這個最大值賦值給參考點指定的像素。這樣就會使圖像中的高亮區域逐漸增長。
注意:其實右圖要比左圖大了一圈
膨脹可以理解為B的中心(錨點)沿著A的外邊界走了一圈。膨脹是對高亮部分而言,A區域之外的部分 < A的高亮像素,所里外面被里面取代。
效果圖,高亮部分膨脹
膨脹的數學表達式:
用(x, y)
周邊區域(x+x', y+y')
內的最大值代替(x, y)
的值。
1.3 腐蝕
腐蝕與膨脹是相反的操作,腐蝕是求局部最小值。
可與膨脹對比理解。
注意:其實右圖要比左圖小了一圈
腐蝕可以理解為B的中心(錨點)沿著A的內邊界走了一圈。腐蝕也是對高亮部分而言,A區域之外的部分 < A的高亮像素,所里里面被外面取代。
A中能完全包含B的像素被留下來了。
效果圖,高亮部分被腐蝕
二、函數和實例
2.1 函數源碼
opencv\sources\modules\imgproc\src\morph.cpp
// 腐蝕
void cv::erode( InputArray src, OutputArray dst, InputArray kernel,
Point anchor, int iterations,
int borderType, const Scalar& borderValue )
{
CV_INSTRUMENT_REGION()
morphOp( MORPH_ERODE, src, dst, kernel, anchor, iterations, borderType, borderValue );
}
// 膨脹
void cv::dilate( InputArray src, OutputArray dst, InputArray kernel,
Point anchor, int iterations,
int borderType, const Scalar& borderValue )
{
CV_INSTRUMENT_REGION()
morphOp( MORPH_DILATE, src, dst, kernel, anchor, iterations, borderType, borderValue );
}
erode和dilate這兩個函數內部就是調用了一下morphOp,只是調用morphOp時,第一個參數標識符不同,一個為MORPH_ERODE(腐蝕),一個為MORPH_DILATE(膨脹)。
這些函數在imgproc.hpp
中后面的參數是設置了默認值。
void dilate( InputArray src, OutputArray dst, InputArray kernel,
Point anchor = Point(-1,-1), int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar& borderValue = morphologyDefaultBorderValue() );
- 第三個參數,InputArray類型的kernel,膨脹操作的核。若為NULL時,表示的是使用參考點位于中心3x3的核。我們一般使用函數
getStructuringElement
返回指定形狀和尺寸的 結構元(SE)。
Mat getStructuringElement(int shape, Size ksize, Point anchor = Point(-1,-1));
# 第一個參數表示內核的形狀,我們可以選擇如下三種形狀之一
# 矩形: MORPH_RECT
# 交叉形: MORPH_CROSS
# 橢圓形: MORPH_ELLIPSE
#
# 第二和第三個參數分別是內核的尺寸以及錨點的位置。
# 我們一般在調用erode以及dilate函數之前,先定義一個Mat類型的變量來獲得getStructuringElement函數的返回值。
# 對于錨點的位置,有默認值Point(-1,-1),表示錨點位于中心。
# 且需要注意,交叉形的element形狀唯一依賴于錨點的位置。
# 而在其他情況下,錨點只是影響了形態學運算結果的偏移。
- 第四個參數,Point類型的anchor,錨的位置,默認值(-1,-1),表示錨位于中心。
- 第五個參數,int類型的iterations,迭代使用erode()函數的次數,默認值為1。
- 第六個參數,int類型的borderType,用于推斷圖像外部像素的某種邊界模式。注意它有默認值BORDER_DEFAULT。
- 第七個參數,const Scalar&類型的borderValue,當邊界為常數時的邊界值,有默認值morphologyDefaultBorderValue(),一般我們不用去管他。需要用到它時,可以看官方文檔中的createMorphologyFilter()函數得到更詳細的解釋。
2.2 實例
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main() {
Mat src = imread("../pics/pig.jpg");
namedWindow("src");
imshow("src", src);
// 獲得結構元
Mat se = getStructuringElement(MORPH_RECT, Size(10, 10));
Mat dst;
dilate(src, dst, se); // 后4個參數使用默認值
namedWindow("膨脹");
imshow("膨脹", dst);
waitKey(0);
}
Size(10, 10)
將膨脹代碼給為腐蝕
erode(src, dst, se);
三、總結
雖然膨脹和腐蝕是相反的操作,但是如果用同樣的SE連續執行2個操作,也不一定能恢復原圖。其實就是開閉操作。
先腐蝕再膨脹 其實就是開操作??
開操作一般會平滑物體的輪廓,斷開較窄的狹頸并消除細的突出物。
erode(src, dst, se);
namedWindow("先腐蝕");
imshow("先腐蝕", dst);
dilate(dst, dst, se);
namedWindow("再膨脹");
imshow("再膨脹", dst);
先膨脹再腐蝕 其實就是閉操作??
閉操作也會平滑輪廓的一部分,但與開操作相反,通常會彌合較窄的間斷和細長的溝壑,消除小的孔洞,填補輪廓線中的斷裂。
dilate(src, dst, se);
namedWindow("先膨脹");
imshow("先膨脹", dst);
erode(dst, dst, se);
namedWindow("再腐蝕");
imshow("再腐蝕", dst);