數學形態學實際上可以理解為一種濾波行為,所以很多地方稱它為形態學濾波。有了個這概念,我們就能更好的理解它。我們濾波中用的濾波器(kernel)在這裡被稱為結構元素,結構元素往往是由一個特殊的形狀構成,如:線條、矩形、圓、菱形等。我們把結構元素的中心(Anchor Point)與圖像上像素點對齊,然後結構元素覆蓋的領域像素就是我們要分析的像素,我們定義一種操作就形成了一種形態學運算。
我們在這裡不解釋形態學操作的算法原理及它們的意義,有興趣的可以參見相關數字圖像處理方面的教材,或關注本博客,博主打算在OpenCV系列寫完後,開始寫圖像處理方面算法系列的文章。
2 3void
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 16int
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 2void
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 15int
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 5case
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 13int
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// 定義結構元素
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 7Mat result;
dilate(image,result,cross);
erode(result,result,diamond);
Mat result2;
dilate(image,result2,x);
erode(result2,result2,square);
absdiff(result2,result,result);
經過上面步驟,我們得到了一張二值圖像,顯示了圖像的一些角點的位置。
為了更形象的說明,我們將上面的這些點在原彩色圖像上標出來:
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));
// 畫圈
}
}
}