OpenCV成長之路:數學形態學基本操作及其應用

數學形態學實際上可以理解爲一種濾波行爲,所以很多地方稱它爲形態學濾波。有了個這概念,我們就能更好的理解它。我們濾波中用的濾波器(kernel)在這裏被稱爲結構元素,結構元素往往是由一個特殊的形狀構成,如:線條、矩形、圓、菱形等。我們把結構元素的中心(Anchor Point)與圖像上像素點對齊,然後結構元素覆蓋的領域像素就是我們要分析的像素,我們定義一種操作就形成了一種形態學運算。

我們在這裏不解釋形態學操作的算法原理及它們的意義,有興趣的可以參見相關數字圖像處理方面的教材,或關注本博客,博主打算在OpenCV系列寫完後,開始寫圖像處理方面算法系列的文章。

一、形態學的基本操作

腐蝕運算:erode

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

膨脹跟腐蝕的參數完全一致,就不過多的說明了。這兩個形態學操作是最基本的兩個操作。

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;
}

image

下面要介紹的兩個形態學操作,在實際應用中要比上面兩個更加廣泛,但實際上它們是上面兩種操作的一個組合式的操作。

開運算與閉運算

這兩個運算都是使用函數morphologyEx來實現的,這個函數的接口如下:

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:對圖像進行閉運算。

下面我們還是以小貓圖像爲例顯示一下對二值圖像進行開運算和閉運算後得到的結果。

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;
}

image

從圖片中我們可以得出結論:

閉運算可以填充圖像中的孔洞,連接一些缺口;開運算可以去除圖像中一些較小的結構。前提是這些孔洞或碎片要與進行運算的結構元素尺度相當。

二、用形態學操作來檢測邊緣和角點

其實用形態學來檢測邊緣的原理非常簡單,我們打開源碼看它是怎麼操作的:

case CV_MOP_GRADIENT:
        erode( src, temp, kernel, anchor, iterations, borderType, borderValue );
        dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
        dst -= temp;
        break;

可以看出來,它是對圖像先做了一個腐蝕,再做了一次膨脹,然後將兩次的結果相減即可。

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;
}

image

下面我們來實現用形態學操作來檢測角點。

首先我們需要定義幾個特殊的結構元素,我們這裏都用Mat來定義,並像素式的賦值,你可以選擇OpenCV裏的getStructElement來更快的實現。

// 定義結構元素
    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”

型。

然後我們按下面的順序對一幅圖像進行操作,並對最後的結果進行閾值化。

Mat result;
dilate(image,result,cross);
erode(result,result,diamond);
Mat result2;
dilate(image,result2,x);
erode(result2,result2,square);
absdiff(result2,result,result);

經過上面步驟,我們得到了一張二值圖像,顯示了圖像的一些角點的位置。

image

爲了更形象的說明,我們將上面的這些點在原彩色圖像上標出來:

// 標記角點
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));// 畫圈
        }
    }
}

image

三、用數學形態學進行車牌定位

智能交通中車牌的檢測與識別一直是核心問題,而車牌識別發展了20餘年,已經存在了很多的解決方案,我們這裏採用數學形態學來進行車牌檢測。本部分並不是一個完整的車牌定位程序,後面還需要進行連通區域的標記和篩選以及僞車牌去除等,有興趣的讀者可以繼續現實或和我交流。

第一步:應用形態學做豎直方向的邊緣檢測。

文章第二部分中已經介紹了用形態學做邊緣檢測,我們這裏只需要修改結構元素就可以實現只檢測豎直方向上的邊緣。

image

第二步:定義水平方向的閉運算和豎直方向的閉運算,將豎直的線條連成一塊。

image

第三步:遍歷連通區域,按照車牌的一些限制條件進行篩選即可。

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;
}

注:上面的例程只是簡單的用來介紹數學形態學的用法,想應用在實際工程中,還需要進行一步改進。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章