OpenCV之圖形學的腐蝕與膨脹
一、理論與概念講解——從現象到本質
1.1 形態學概述
形態學(morphology)一詞通常表示生物學的一個分支,該分支主要研究動植物的形態和結構。而我們影象處理中指的形態學,往往表示的是數學形態學。下面一起來了解數學形態學的概念。
數學形態學(Mathematical morphology) 是一門建立在格論和拓撲學基礎之上的影象分析學科,是數學形態學影象處理的基本理論。其基本的運算包括:二值腐蝕和膨脹、二值開閉運算、骨架抽取、極限腐蝕、擊中擊不中變換、形態學梯度、Top-hat變換、顆粒分析、流域變換、灰值腐蝕和膨脹、灰值開閉運算、灰值形態學梯度等。
簡單來講,形態學操作就是基於形狀的一系列影象處理操作。OpenCV為進行影象的形態學變換提供了快捷、方便的函式。最基本的形態學操作有二種,他們是:膨脹與腐蝕(Dilation與Erosion)。
膨脹與腐蝕能實現多種多樣的功能,主要如下:
- 消除噪聲
- 分割(isolate)出獨立的影象元素,在影象中連線(join)相鄰的元素。
- 尋找影象中的明顯的極大值區域或極小值區域
- 求出影象的梯度
在進行腐蝕和膨脹的講解之前,首先需要注意,腐蝕和膨脹是對白色部分(高亮部分)而言的,不是黑色部分。膨脹就是影象中的高亮部分進行膨脹,“領域擴張”,效果圖擁有比原圖更大的高亮區域。腐蝕就是原圖中的高亮部分被腐蝕,“領域被蠶食”,效果圖擁有比原圖更小的高亮區域。
個人筆記:影象的腐蝕與膨脹,其實就是一個核結構(矩形、圓形或十字形)從頭到尾進行影象矩陣遍歷,並將錨點所在畫素賦予核區域內畫素最大值或最小值的過程;腐蝕操作即以核結構的錨點(預設為中心點)為畫素點依次遍歷影象所有畫素點,取核結構區域內的最小值賦給錨點所在的畫素點,這樣就達到了腐蝕效果(將影象高畫素值(高亮)區域縮小),具體腐蝕範圍(縮小範圍)以核矩陣區域大小及錨點位置為準(若3*3矩形,錨點在中心,則影象縮小了1個畫素,以此類推核區域越大腐蝕效果越明顯);膨脹操作與腐蝕操作相反,將最大值賦給錨點,膨脹範圍原理與腐蝕相同。
1.2膨脹
其實,膨脹就是求區域性最大值的操作。
按數學方面來說,膨脹或者腐蝕操作就是將影象(或影象的一部分割槽域,我們稱之為A)與核(我們稱之為B)進行卷積。
核可以是任何的形狀和大小,它擁有一個單獨定義出來的參考點,我們稱其為錨點(anchorpoint)。多數情況下,核是一個小的中間帶有參考點和實心正方形或者圓盤,其實,我們可以把核視為模板或者掩碼。
而膨脹就是求區域性最大值的操作,核B與圖形卷積,即計算核B覆蓋的區域的畫素點的最大值,並把這個最大值賦值給參考點指定的畫素。這樣就會使影象中的高亮區域逐漸增長。如下圖所示,這就是膨脹操作的初衷。
膨脹的數學表示式:
照片膨脹效果圖:
1.3 腐蝕
再來看一下腐蝕,大家應該知道,膨脹和腐蝕是一對好基友,是相反的一對操作,所以腐蝕就是求區域性最小值的操作。
我們一般都會把腐蝕和膨脹對應起來理解和學習。下文就可以看到,兩者的函式原型也是基本上一樣的。
原理圖:
腐蝕的數學表示式:
照片腐蝕效果圖:
二、深入——OpenCV原始碼分析溯源
直接上原始碼吧,在…\opencv\sources\modules\imgproc\src\ morph.cpp路徑中 的第1353行開始就為erode(腐蝕)函式的原始碼,1361行為dilate(膨脹)函式的原始碼。
- //-----------------------------------【erode()函式中文註釋版原始碼】----------------------------
- // 說明:以下程式碼為來自於計算機開源視覺庫OpenCV的官方原始碼
- // OpenCV原始碼版本:2.4.8
- // 原始碼路徑:…\opencv\sources\modules\imgproc\src\ morph.cpp
- // 原始檔中如下程式碼的起始行數:1353行
- // 中文註釋by淺墨
- //--------------------------------------------------------------------------------------------------------
- void cv::erode( InputArray src, OutputArraydst, InputArray kernel,
- Point anchor, int iterations,
- int borderType, constScalar& borderValue )
- {
- //呼叫morphOp函式,並設定識別符號為MORPH_ERODE
- morphOp( MORPH_ERODE, src, dst, kernel, anchor, iterations, borderType,borderValue );
- }
- //-----------------------------------【dilate()函式中文註釋版原始碼】----------------------------
- // 說明:以下程式碼為來自於計算機開源視覺庫OpenCV的官方原始碼
- // OpenCV原始碼版本:2.4.8
- // 原始碼路徑:…\opencv\sources\modules\imgproc\src\ morph.cpp
- // 原始檔中如下程式碼的起始行數:1361行
- // 中文註釋by淺墨
- //--------------------------------------------------------------------------------------------------------
- void cv::dilate( InputArray src,OutputArray dst, InputArray kernel,
- Point anchor, int iterations,
- int borderType, constScalar& borderValue )
- {
- //呼叫morphOp函式,並設定識別符號為MORPH_DILATE
- morphOp( MORPH_DILATE, src, dst, kernel, anchor, iterations, borderType,borderValue );
- }
可以發現erode和dilate這兩個函式內部就是呼叫了一下morphOp,只是他們呼叫morphOp時,第一個引數識別符號不同,一個為MORPH_ERODE(腐蝕),一個為MORPH_DILATE(膨脹)。
morphOp函式的原始碼在…\opencv\sources\modules\imgproc\src\morph.cpp中的第1286行。
三、淺出——API函式快速上手
3.1 形態學膨脹——dilate函式
erode函式,使用畫素鄰域內的區域性極大運算子來膨脹一張圖片,從src輸入,由dst輸出。支援就地(in-place)操作。
函式原型:
- C++: 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型別的src,輸入影象,即源影象,填Mat類的物件即可。影象通道的數量可以是任意的,但影象深度應為CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
- 第二個引數,OutputArray型別的dst,即目標影象,需要和源圖片有一樣的尺寸和型別。
- 第三個引數,InputArray型別的kernel,膨脹操作的核。若為NULL時,表示的是使用參考點位於中心3x3的核。
我們一般使用函式 getStructuringElement配合這個引數的使用。getStructuringElement函式會返回指定形狀和尺寸的結構元素(核心矩陣)。
其中,getStructuringElement函式的第一個引數表示核心的形狀,我們可以選擇如下三種形狀之一:
- 矩形: MORPH_RECT
- 交叉形: MORPH_CROSS
- 橢圓形: MORPH_ELLIPSE
而getStructuringElement函式的第二和第三個引數分別是核心的尺寸以及錨點的位置。
我們一般在呼叫erode以及dilate函式之前,先定義一個Mat型別的變數來獲得getStructuringElement函式的返回值。對於錨點的位置,有預設值Point(-1,-1),表示錨點位於中心。且需要注意,十字形的element形狀唯一依賴於錨點的位置。而在其他情況下,錨點只是影響了形態學運算結果的偏移。
getStructuringElement函式相關的呼叫示例程式碼如下:
- int g_nStructElementSize = 3; //結構元素(核心矩陣)的尺寸
- //獲取自定義核
- Mat element = getStructuringElement(MORPH_RECT,
- Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),
- Point( g_nStructElementSize, g_nStructElementSize ));
呼叫這樣之後,我們便可以在接下來呼叫erode或dilate函式時,第三個引數填儲存了getStructuringElement返回值的Mat型別變數。對應於我們上面的示例,就是填element變數。
- 第四個引數,Point型別的anchor,錨的位置,其有預設值(-1,-1),表示錨位於中心。
- 第五個引數,int型別的iterations,迭代使用erode()函式的次數,預設值為1。
- 第六個引數,int型別的borderType,用於推斷影象外部畫素的某種邊界模式。注意它有預設值BORDER_DEFAULT。
- 第七個引數,const Scalar&型別的borderValue,當邊界為常數時的邊界值,有預設值morphologyDefaultBorderValue(),一般我們不用去管他。需要用到它時,可以看官方文件中的createMorphologyFilter()函式得到更詳細的解釋。
使用erode函式,一般我們只需要填前面的三個引數,後面的四個引數都有預設值。而且往往結合getStructuringElement一起使用。
呼叫範例:
- //載入原圖
- Mat image = imread("1.jpg");
- //獲取自定義核
- Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
- Mat out;
- //進行膨脹操作
- dilate(image, out, element);
3.2 形態學腐蝕——erode函式
erode函式,使用畫素鄰域內的區域性極小運算子來腐蝕一張圖片,從src輸入,由dst輸出。支援就地(in-place)操作。
看一下函式原型:
- C++: void erode(
- InputArray src,
- OutputArray dst,
- InputArray kernel,
- Point anchor=Point(-1,-1),
- int iterations=1,
- int borderType=BORDER_CONSTANT,
- const Scalar& borderValue=morphologyDefaultBorderValue()
- );
引數詳解:
- 第一個引數,InputArray型別的src,輸入影象,即源影象,填Mat類的物件即可。影象通道的數量可以是任意的,但影象深度應為CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
- 第二個引數,OutputArray型別的dst,即目標影象,需要和源圖片有一樣的尺寸和型別。
- 第三個引數,InputArray型別的kernel,腐蝕操作的核心。若為NULL時,表示的是使用參考點位於中心3x3的核。我們一般使用函式 getStructuringElement配合這個引數的使用。getStructuringElement函式會返回指定形狀和尺寸的結構元素(核心矩陣)。(具體看上文中淺出部分dilate函式的第三個引數講解部分)
- 第四個引數,Point型別的anchor,錨的位置,其有預設值(-1,-1),表示錨位於單位(element)的中心,我們一般不用管它。
- 第五個引數,int型別的iterations,迭代使用erode()函式的次數,預設值為1。
- 第六個引數,int型別的borderType,用於推斷影象外部畫素的某種邊界模式。注意它有預設值BORDER_DEFAULT。
- 第七個引數,const Scalar&型別的borderValue,當邊界為常數時的邊界值,有預設值morphologyDefaultBorderValue(),一般我們不用去管他。需要用到它時,可以看官方文件中的createMorphologyFilter()函式得到更詳細的解釋。
同樣的,使用erode函式,一般我們只需要填前面的三個引數,後面的四個引數都有預設值。而且往往結合getStructuringElement一起使用。
呼叫範例:
- //載入原圖
- Mat image = imread("1.jpg");
- //獲取自定義核
- Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
- Mat out;
- //進行腐蝕操作
- erode(image,out, element);
四、綜合示例——在實戰中熟稔
// erodeANDdilate.cpp : 定義控制檯應用程式的入口點。 // #include "stdafx.h" #include <iostream> #include<opencv2/opencv.hpp> using namespace cv; using namespace std; int _tmain(int argc, _TCHAR* argv[]) { int i; cout<<"請輸入數字(0、1或2):"<<endl; cout<<"0表示進行腐蝕和膨脹操作"<<endl; cout<<"1表示進行腐蝕操作"<<endl; cout<<"2表示進行膨脹操作"<<endl; cin>>i; Mat src=imread("./1.png",1); Mat dst_erode,dst_dilate; if (i!=0 && i!=1 && i!=2) { cout<<"數值輸入錯誤,請輸入0、1、2!"<<endl; return -1; } if (i==1) { Mat element_erode=getStructuringElement(MORPH_RECT,Size(50,50)); erode(src,dst_erode,element_erode); imshow("src",src); imshow("dst_erode",dst_erode); waitKey(0); } if (i==2) { Mat element_dilate=getStructuringElement(MORPH_RECT,Size(50,50)); dilate(src,dst_dilate,element_dilate); imshow("src",src); imshow("dst_dilate",dst_dilate); waitKey(0); } if (i==0) { Mat element_erode=getStructuringElement(MORPH_RECT,Size(50,50)); Mat element_dilate=getStructuringElement(MORPH_RECT,Size(50,50)); erode(src,dst_erode,element_erode); dilate(src,dst_dilate,element_dilate); imshow("src",src); imshow("dst_erode",dst_erode); imshow("dst_dilate",dst_dilate); waitKey(0); return 0; } }
本文摘自:《OpenCV3 程式設計入門》一書,詳情訪問作者部落格http://blog.csdn.net/poem_qianmo