详细分析图像形态学操作
????????原文鏈接:http://blog.csdn.net/poem_qianmo/article/details/23710721
??????? 還是比教科書上的圖文并茂的多。以防刪除,如有疑問或者版權問題,請移步原博客或者告知本人。
 
本篇文章中,我們一起探究了圖像處理中,最基本的形態學運算——膨脹與腐蝕。淺墨在文章開頭友情提醒,用人物照片做腐蝕和膨脹的素材圖片得到的效果會比較驚悚,毀三觀的,不建議嘗試。。。。。。。。。。
 
OK,開始吧,依然是先放一張截圖:
 
 
 
一、理論與概念講解——從現象到本質
 
 
1.1?形態學概述
?
形態學(morphology)一詞通常表示生物學的一個分支,該分支主要研究動植物的形態和結構。而我們圖像處理中指的形態學,往往表示的是數學形態學。下面一起來了解數學形態學的概念。
數學形態學(Mathematical morphology) 是一門建立在格論和拓撲學基礎之上的圖像分析學科,是數學形態學圖像處理的基本理論。其基本的運算包括:二值腐蝕和膨脹、二值開閉運算、骨架抽取、極限腐蝕、擊中擊不中變換、形態學梯度、Top-hat變換、顆粒分析、流域變換、灰值腐蝕和膨脹、灰值開閉運算、灰值形態學梯度等。
?
簡單來講,形態學操作就是基于形狀的一系列圖像處理操作。OpenCV為進行圖像的形態學變換提供了快捷、方便的函數。最基本的形態學操作有二種,他們是:膨脹與腐蝕(Dilation與Erosion)。
膨脹與腐蝕能實現多種多樣的功能,主要如下:
- 消除噪聲
- 分割(isolate)出獨立的圖像元素,在圖像中連接(join)相鄰的元素。
- 尋找圖像中的明顯的極大值區域或極小值區域
- 求出圖像的梯度
?
 
我們在這里給出下文會用到的,用于對比膨脹與腐蝕運算的“淺墨”字樣毛筆字原圖:
?
在進行腐蝕和膨脹的講解之前,首先需要注意,腐蝕和膨脹是對白色部分(高亮部分)而言的,不是黑色部分。膨脹就是圖像中的高亮部分進行膨脹,“領域擴張”,效果圖擁有比原圖更大的高亮區域。腐蝕就是原圖中的高亮部分被腐蝕,“領域被蠶食”,效果圖擁有比原圖更小的高亮區域。
?
 
 
 
 
1.2 膨脹
?
其實,膨脹就是求局部最大值的操作。
按數學方面來說,膨脹或者腐蝕操作就是將圖像(或圖像的一部分區域,我們稱之為A)與核(我們稱之為B)進行卷積。
核可以是任何的形狀和大小,它擁有一個單獨定義出來的參考點,我們稱其為錨點(anchorpoint)。多數情況下,核是一個小的中間帶有參考點和實心正方形或者圓盤,其實,我們可以把核視為模板或者掩碼。
?
而膨脹就是求局部最大值的操作,核B與圖形卷積,即計算核B覆蓋的區域的像素點的最大值,并把這個最大值賦值給參考點指定的像素。這樣就會使圖像中的高亮區域逐漸增長。如下圖所示,這就是膨脹操作的初衷。
 
 
膨脹的數學表達式:
 
膨脹效果圖(毛筆字):
?
照片膨脹效果圖:
 
?
 
 
1.3 腐蝕
 
再來看一下腐蝕,大家應該知道,膨脹和腐蝕是一對好基友,是相反的一對操作,所以腐蝕就是求局部最小值的操作。
我們一般都會把腐蝕和膨脹對應起來理解和學習。下文就可以看到,兩者的函數原型也是基本上一樣的。
?
原理圖:
?
腐蝕的數學表達式:
?
腐蝕效果圖(毛筆字):
 
照片腐蝕效果圖:
?
?淺墨表示這張狗狗超可愛:D
?
?
 
 
二、深入——OpenCV源碼分析溯源
?
 
直接上源碼吧,在…\opencv\sources\modules\imgproc\src\ morph.cpp路徑中 的第1353行開始就為erode(腐蝕)函數的源碼,1361行為dilate(膨脹)函數的源碼。
[cpp] view plaincopyprint?
 
可以發現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)操作。
函數原型:
[cpp] view plaincopyprint?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一起使用。
調用范例:
[cpp] view plaincopyprint?用上面核心代碼架起來的完整程序代碼:
?
//載入原圖 Mat image = imread("1.jpg"); //獲取自定義核 Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); Mat out; //進行膨脹操作 dilate(image, out, element);
 
// 描述:包含程序所依賴的頭文件 //---------------------------------------------------------------------------------------------- #include <opencv2/core/core.hpp> #include<opencv2/highgui/highgui.hpp> #include<opencv2/imgproc/imgproc.hpp> #include <iostream> //-----------------------------------【命名空間聲明部分】--------------------------------------- // 描述:包含程序所使用的命名空間 //----------------------------------------------------------------------------------------------- using namespace std; using namespace cv; //-----------------------------------【main( )函數】-------------------------------------------- // 描述:控制臺應用程序的入口函數,我們的程序從這里開始 //----------------------------------------------------------------------------------------------- int main( ) { //載入原圖 Mat image = imread("1.jpg"); //創建窗口 namedWindow("【原圖】膨脹操作"); namedWindow("【效果圖】膨脹操作"); //顯示原圖 imshow("【原圖】膨脹操作", image); <span style="white-space:pre"> </span>//獲取自定義核 Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); Mat out; <span style="white-space:pre"> </span>//進行膨脹操作 dilate(image,out, element); //顯示效果圖 imshow("【效果圖】膨脹操作", out); waitKey(0); return 0; }
?運行截圖:
 
 
?
?
?
3.2?形態學腐蝕——erode函數
 
 
erode函數,使用像素鄰域內的局部極小運算符來腐蝕一張圖片,從src輸入,由dst輸出。支持就地(in-place)操作。
?
看一下函數原型:
[cpp] view plaincopyprint? 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一起使用。
調用范例:
[cpp] view plaincopyprint?//載入原圖 Mat image = imread("1.jpg"); //獲取自定義核 Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); Mat out; //進行腐蝕操作 erode(image,out, element);
用上面核心代碼架起來的完整程序代碼:
?
[cpp] view plaincopyprint?
 
運行結果:
?
?
?
?
四、綜合示例——在實戰中熟稔
?
?
依然是每篇文章都會配給大家的一個詳細注釋的博文配套示例程序,把這篇文章中介紹的知識點以代碼為載體,展現給大家。
這個示例程序中的效果圖窗口有兩個滾動條,顧名思義,第一個滾動條“腐蝕/膨脹”用于在腐蝕/膨脹之間進行切換;第二個滾動條”內核尺寸”用于調節形態學操作時的內核尺寸,以得到效果不同的圖像,有一定的可玩性。廢話不多說,上代碼吧:
[cpp] view plaincopyprint??
//-----------------------------------【程序說明】---------------------------------------------- // 程序名稱::《【OpenCV入門教程之十】形態學圖像處理(一):膨脹與腐蝕 》 博文配套源碼 // 開發所用IDE版本:Visual Studio 2010 // 開發所用OpenCV版本: 2.4.8 // 2014年4月14日 Create by 淺墨 // 淺墨的微博:@淺墨_毛星云 //------------------------------------------------------------------------------------------------ //-----------------------------------【頭文件包含部分】--------------------------------------- // 描述:包含程序所依賴的頭文件 //---------------------------------------------------------------------------------------------- #include <opencv2/opencv.hpp> #include <opencv2/highgui/highgui.hpp> #include<opencv2/imgproc/imgproc.hpp> #include <iostream> //-----------------------------------【命名空間聲明部分】--------------------------------------- // 描述:包含程序所使用的命名空間 //----------------------------------------------------------------------------------------------- using namespace std; using namespace cv; //-----------------------------------【全局變量聲明部分】-------------------------------------- // 描述:全局變量聲明 //----------------------------------------------------------------------------------------------- Mat g_srcImage, g_dstImage;//原始圖和效果圖 int g_nTrackbarNumer = 0;//0表示腐蝕erode, 1表示膨脹dilate int g_nStructElementSize = 3; //結構元素(內核矩陣)的尺寸 //-----------------------------------【全局函數聲明部分】-------------------------------------- // 描述:全局函數聲明 //----------------------------------------------------------------------------------------------- void Process();//膨脹和腐蝕的處理函數 void on_TrackbarNumChange(int, void *);//回調函數 void on_ElementSizeChange(int, void *);//回調函數 //-----------------------------------【main( )函數】-------------------------------------------- // 描述:控制臺應用程序的入口函數,我們的程序從這里開始 //----------------------------------------------------------------------------------------------- int main( ) { //改變console字體顏色 system("color5E"); //載入原圖 g_srcImage= imread("1.jpg"); if(!g_srcImage.data ) { printf("Oh,no,讀取srcImage錯誤~!\n"); return false; } //顯示原始圖 namedWindow("【原始圖】"); imshow("【原始圖】", g_srcImage); //進行初次腐蝕操作并顯示效果圖 namedWindow("【效果圖】"); //獲取自定義核 Mat element = getStructuringElement(MORPH_RECT, Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),Point( g_nStructElementSize, g_nStructElementSize )); erode(g_srcImage,g_dstImage, element); imshow("【效果圖】", g_dstImage); //創建軌跡條 createTrackbar("腐蝕/膨脹", "【效果圖】", &g_nTrackbarNumer, 1, on_TrackbarNumChange); createTrackbar("內核尺寸", "【效果圖】",&g_nStructElementSize, 21, on_ElementSizeChange); //輸出一些幫助信息 cout<<endl<<"\t嗯。運行成功,請調整滾動條觀察圖像效果~\n\n" <<"\t按下“q”鍵時,程序退出~!\n" <<"\n\n\t\t\t\tby淺墨"; //輪詢獲取按鍵信息,若下q鍵,程序退出 while(char(waitKey(1))!= 'q') {} return 0; } //-----------------------------【Process( )函數】------------------------------------ // 描述:進行自定義的腐蝕和膨脹操作 //----------------------------------------------------------------------------------------- void Process() { //獲取自定義核 Mat element = getStructuringElement(MORPH_RECT, Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),Point( g_nStructElementSize, g_nStructElementSize )); //進行腐蝕或膨脹操作 if(g_nTrackbarNumer== 0) { erode(g_srcImage,g_dstImage, element); } else{ dilate(g_srcImage,g_dstImage, element); } //顯示效果圖 imshow("【效果圖】", g_dstImage); } //-----------------------------【on_TrackbarNumChange( )函數】------------------------------------ // 描述:腐蝕和膨脹之間切換開關的回調函數 //----------------------------------------------------------------------------------------------------- void on_TrackbarNumChange(int, void *) { //腐蝕和膨脹之間效果已經切換,回調函數體內需調用一次Process函數,使改變后的效果立即生效并顯示出來 Process(); } //-----------------------------【on_ElementSizeChange( )函數】------------------------------------- // 描述:腐蝕和膨脹操作內核改變時的回調函數 //----------------------------------------------------------------------------------------------------- void on_ElementSizeChange(int, void *) { //內核尺寸已改變,回調函數體內需調用一次Process函數,使改變后的效果立即生效并顯示出來 Process(); }
 
放出一些效果圖吧。原始圖:
?
 
膨脹效果圖:
?
 
 
 
 
 
腐蝕效果圖:
 
 
 
 
 
 
腐蝕和膨脹得到的圖,都特有喜感,但千變萬變,還是原圖好看:
 
 
OK,就放出這些吧,具體更多的運行效果大家就自己下載示例程序回去玩吧。
 
 
本篇文章到這里就基本結束了,最后放出文章配套示例程序的打包下載地址。
?
本篇文章的配套源代碼請點擊這里下載:
 
 
【淺墨OpenCV入門教程之十】配套源代碼下載
?
OK,今天的內容大概就是這些,我們下篇文章見:)
 
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎
總結
以上是生活随笔為你收集整理的详细分析图像形态学操作的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 怎么制作SolidWorks宏按钮? s
- 下一篇: solidworks怎么画圆桌? sw建
