數學形態學實際上可以理解為一種濾波行為,所以很多地方稱它為形態學濾波。有了個這概念,我們就能更好的理解它。我們濾波中用的濾波器(kernel)在這裡被稱為結構元素,結構元素往往是由一個特殊的形狀構成,如:線條、矩形、圓、菱形等。我們把結構元素的中心(Anchor Point)與影像上畫素點對齊,然後結構元素覆蓋的領域畫素就是我們要分析的畫素,我們定義一種操作就形成了一種形態學運算。
我們在這裡不解釋形態學操作的演算法原理及它們的意義,有興趣的可以參見相關數字影像處理方面的教材,或關注本部落格,博主打算在OpenCV系列寫完後,開始寫影像處理方面演算法系列的文章。
一、形態學的基本操作
腐蝕運算:erode
1 2 3 |
void erode(InputArray src, OutputArray dst, InputArray kernel, Point anchor=Point(-1,-1), int iterations=1, int borderType=BORDER_CONSTANT, const Scalar& borderValue=morphologyDefaultBorderValue()); |
src:輸入影像,很多場合下我們使用的是二值影像,當然灰度影像也可以。
dst:輸出影像,格式和輸入影像一致。
kernel:定義的結構元素。
anchor:結構元素的中心,如果是預設引數(-1,-1),程式會自動將其設定為結構元素的中心。
iterations:迭代次數,我們可以選擇對影像進行多次形態學運算。
後面兩個引數是邊界型別,由於要處理領域問題,所以影像需要擴充邊界。一般情況下使用預設即可。
膨脹運算:dilate
膨脹跟腐蝕的引數完全一致,就不過多的說明了。這兩個形態學操作是最基本的兩個操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
int main() { Mat image=imread("../cat.png"); // 彩色轉灰度 cvtColor(image,image,CV_BGR2GRAY); // 閾值化 threshold(image,image,255*(0.5),255,THRESH_BINARY); // 形態學操作 // 如果把結構元素設定為Mat(),則將用預設的3*3的矩形結構元素 Mat eroded; erode(image,eroded,Mat()); Mat dilated; dilate(image,dilated,Mat()); return 0; } |
下面要介紹的兩個形態學操作,在實際應用中要比上面兩個更加廣泛,但實際上它們是上面兩種操作的一個組合式的操作。
開運算與閉運算
這兩個運算都是使用函式morphologyEx來實現的,這個函式的介面如下:
1 2 |
void morphologyEx(InputArray src, OutputArray dst, int op, InputArray kernel, Point anchor=Point(-1,-1), int iterations=1, int borderType=BORDER_CONSTANT, const Scalar& borderValue=morphologyDefaultBorderValue()); |
函式的大部分引數都與上面介紹的erode函式引數是一樣的,這裡面的op是我們要進行的形態學的型別:
MORPH_OPEN:對影像進行開運算。
MORPH_CLOSE:對影像進行閉運算。
下面我們還是以小貓影像為例顯示一下對二值影像進行開運算和閉運算後得到的結果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
int main() { Mat image=imread("../cat.png"); // 彩色轉灰度 cvtColor(image,image,CV_BGR2GRAY); // 閾值化 threshold(image,image,255*(0.5),255,THRESH_BINARY); // 定義結構元素 Mat se(5,5,CV_8U,Scalar(1)); Mat closed; morphologyEx(image,closed,MORPH_CLOSE,se); Mat opened; morphologyEx(image,opened,MORPH_OPEN,se); return 0; } |
從圖片中我們可以得出結論:
閉運算可以填充影像中的孔洞,連線一些缺口;開運算可以去除影像中一些較小的結構。前提是這些孔洞或碎片要與進行運算的結構元素尺度相當。
二、用形態學操作來檢測邊緣和角點
其實用形態學來檢測邊緣的原理非常簡單,我們開啟原始碼看它是怎麼操作的:
1 2 3 4 5 |
case CV_MOP_GRADIENT: erode( src, temp, kernel, anchor, iterations, borderType, borderValue ); dilate( src, dst, kernel, anchor, iterations, borderType, borderValue ); dst -= temp; break; |
可以看出來,它是對影像先做了一個腐蝕,再做了一次膨脹,然後將兩次的結果相減即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
int main() { Mat image=imread("../cat.png"); // 彩色轉灰度 cvtColor(image,image,CV_BGR2GRAY); Mat catEdge; morphologyEx(image,catEdge,MORPH_GRADIENT,Mat()); // 閾值化 threshold(catEdge,catEdge,40,255,THRESH_BINARY); namedWindow("catEdge");imshow("catEdge",catEdge); waitKey(); return 0; } |
下面我們來實現用形態學操作來檢測角點。
首先我們需要定義幾個特殊的結構元素,我們這裡都用Mat來定義,並畫素式的賦值,你可以選擇OpenCV裡的getStructElement來更快的實現。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// 定義結構元素 Mat cross(5,5,CV_8U,Scalar(0)); Mat diamond(5,5,CV_8U,Scalar(1)); Mat square(5,5,CV_8U,Scalar(1)); Mat x(5,5,CV_8U,Scalar(0)); for(int i=0;i<5;i++) { cross.at<uchar>(2,i)=1; cross.at<uchar>(i,2)=1; } diamond.at<uchar>(0,0)=0; diamond.at<uchar>(0,1)=0; diamond.at<uchar>(1,0)=0; diamond.at<uchar>(4,4)=0; diamond.at<uchar>(3,4)=0; diamond.at<uchar>(4,3)=0; diamond.at<uchar>(4,0)=0; diamond.at<uchar>(4,1)=0; diamond.at<uchar>(3,0)=0; diamond.at<uchar>(0,4)=0; diamond.at<uchar>(0,3)=0; diamond.at<uchar>(1,4)=0; for(int i=0;i<5;i++){ x.at<uchar>(i,i)=1; x.at<uchar>(4-i,i)=1; } |
第一個為一個十字型的結構元素,第二個為菱形,第三個是矩形,第四個是一個“X”
型。
然後我們按下面的順序對一幅影像進行操作,並對最後的結果進行閾值化。
1 2 3 4 5 6 7 8 9 10 |
Mat result; dilate(image,result,cross); erode(result,result,diamond); Mat result2; dilate(image,result2,x); erode(result2,result2,square); absdiff(result2,result,result); threshold(result,result,40,255,THRESH_BINARY); |
經過上面步驟,我們得到了一張二值影像,顯示了影像的一些角點的位置。
為了更形象的說明,我們將上面的這些點在原彩色影像上標出來:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 標記角點 void drawOnImage(const Mat& binary,Mat& image) { for(int i=0;i<binary.rows;i++) { // 獲取行指標 const uchar* data=binary.ptr<uchar>(i); for(int j=0;j<binary.cols;j++) { if(data[j]) //角點影像上的白點 circle(image,Point(j,i),8,Scalar(0,255,0));// 畫圈 } } } |
三、用數學形態學進行車牌定位
智慧交通中車牌的檢測與識別一直是核心問題,而車牌識別發展了20餘年,已經存在了很多的解決方案,我們這裡採用數學形態學來進行車牌檢測。本部分並不是一個完整的車牌定位程式,後面還需要進行連通區域的標記和篩選以及偽車牌去除等,有興趣的讀者可以繼續現實或和我交流。
第一步:應用形態學做豎直方向的邊緣檢測。
文章第二部分中已經介紹了用形態學做邊緣檢測,我們這裡只需要修改結構元素就可以實現只檢測豎直方向上的邊緣。
第二步:定義水平方向的閉運算和豎直方向的閉運算,將豎直的線條連成一塊。
第三步:遍歷連通區域,按照車牌的一些限制條件進行篩選即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
int main() { Mat cimage=imread("../car.png"); Mat image; cvtColor(cimage,image,CV_BGR2GRAY); Mat result; //檢測豎直邊緣 morphologyEx(image,result,MORPH_GRADIENT,Mat(1,2,CV_8U,Scalar(1))); //閾值化 threshold(result,result,255*(0.2),255,THRESH_BINARY); //水平方向閉運算 morphologyEx(result,result,MORPH_CLOSE,Mat(1,20,CV_8U,Scalar(1))); //豎起方向閉運算 morphologyEx(result,result,MORPH_CLOSE,Mat(10,1,CV_8U,Scalar(1))); return 0; } |
注:上面的例程只是簡單的用來介紹數學形態學的用法,想應用在實際工程中,還需要進行一步改進。