摘要:
形態學一般指生物學中研究動物和植物結構的一個分支。用數學形態學(也稱影像代數)表示以形態為基礎對影像進行分析的數學工具。
基本思想是用具有一定形態的結構元素去度量和提取影像中的對應形狀以達到對影像分析和識別的目的。
形態學影像處理的基本運算有:
- 膨脹和腐蝕(膨脹區域填充,腐蝕分割區域)
- 開運算和閉運算(開運算去除噪點,閉運算填充內部孔洞)
- 擊中與擊不中
- 頂帽變換,黑帽變換
形態學的應用:消除噪聲、邊界提取、區域填充、連通分量提取、凸殼、細化、粗化等;分割出獨立的影像元素,或者影像中相鄰的元素;求取影像中明顯的極大值區域和極小值區域;求取影像梯度
?在講各種形態學操作之前,先來看看結構元素:
膨脹和腐蝕操作的核心內容是結構元素。(後面的開閉運算等重要的也是結構元素的設計,一個合適的結構元素的設計可以帶來很好的處理效果)
OpenCV裡面的API介紹:
Mat kernel = getStructuringElement(int shape,Size ksize,Point anchor); shape //結構元素的定義:形狀 (MORPH_RECT \MORPH_CROSS(交叉形) \MORPH_ELLIPSE); ksize //結構元素大小; anchor //錨點 預設是Point(-1, -1)意思就是中心畫素,也可以自己指定
一,腐蝕和膨脹
腐蝕和膨脹是最基本的形態學操作,腐蝕和膨脹都是針對白色部分(高亮部分)而言的。
- 膨脹就是使影像中高亮部分擴張,效果圖擁有比原圖更大的高亮區域(是求區域性最大值的操作)
- 腐蝕是原圖中的高亮區域被蠶食,效果圖擁有比原圖更小的高亮區域(是求區域性最小值的操作)
膨脹與腐蝕能實現多種多樣的功能,主要如下:
1、消除噪聲
2、腐蝕分割(isolate)出獨立的影像元素,膨脹在影像中連線(join)相鄰的元素。
3、尋找影像中的明顯的極大值區域或極小值區域
4、求出影像的梯度
opencv中膨脹/腐蝕API:(兩者相同)
void dilate/erode( const Mat& src, //輸入影像(任意通道的) Mat& dst, //輸出影像 const Mat& element, //結構元素 Point anchor=Point(-1,-1), //中心位置錨點 int iterations=1, //操作次數。省略時為預設值1。 int borderType=BORDER_CONSTANT, //邊緣填充型別 const Scalar& borderValue //填充值(預設即可) )
opencv實現:
Mat src1 = imread("D:/opencv練習圖片/腐蝕膨脹.png"); Mat src_erode, src_dilate; imshow("原圖", src1); Mat kernel = getStructuringElement(MORPH_RECT, Size(60, 60), Point(-1, -1)); erode(src1, src_erode, kernel, Point(-1, -1),2); dilate(src1, src_dilate, kernel, Point(-1, -1),2); imshow("腐蝕", src_erode); imshow("膨脹", src_dilate);
膨脹: 腐蝕:
1️⃣腐蝕操作的原理就是求區域性最小值的操作,並把這個最小值賦值給參考點指定的畫素。這樣就會使影像中的高亮區域逐漸減少。
2️⃣膨脹操作的原理就是求區域性最大值的操作,並把這個最大值賦值給參考點指定的畫素。這樣就會使影像中的高亮區域逐漸增長。
二,高階形態學變換
對於更加高階形態學變換就要用到morphologyEx()函式了,其函式原型如下:
void morphologyEx( InputArray src, //輸入影像 OutputArray dst, //輸出影像 int op, //形態學運算型別 InputArray kernel, //結構元素 Point anchor = Point(-1,-1), //錨點 int iterations = 1, //運算次數 int borderType = BORDER_CONSTANT, )
✨對於輸入引數op(形態學運算型別)有以下幾種引數可以設定:
- MORPH_ERODE(腐蝕)
- MORPH_DILATE(膨脹)
- MORPH_OPEN(開運算)
- MORPH_CLOSE(閉運算)
- MORPH_GRADIENT(形態學梯度,即膨脹圖減腐蝕圖)
- MORPH_TOPHAT(頂帽運算)
- MORPH_BLACKHAT(底帽運算)
- MORPH_HITMISS(擊中與擊不中)
?開運算和頂帽運算
開運算就是先腐蝕膨脹。作用:用來消除影像中細小物件,在纖細點處分離物體和平滑較大物體的邊界而有不明顯改變其面積和形狀。
頂帽運算就是求原圖與原圖的開運算的差值影像。作用:由於開運算可以消除較暗背景下的較亮區域,那麼用原圖減開運算後的結果就可以得到原圖中灰度較亮的區域(即得到開運算消除的區域)
opencv實現:
Mat src1 = imread("D:/opencv練習圖片/開運算.png"); Mat src_open, src_tophat; imshow("原圖", src1); Mat kernel = getStructuringElement(MORPH_RECT, Size(7, 7), Point(-1, -1)); morphologyEx(src1, src_open, MORPH_OPEN, kernel,Point(-1, -1));//開運算 morphologyEx(src1, src_tophat, MORPH_TOPHAT, kernel, Point(-1, -1));//頂帽運算 imshow("開運算", src_open); imshow("頂帽運算", src_tophat);
開運算: 頂帽運算:(可以用來觀察開運算的效果)
?閉運算和底帽運算
閉運算就是先膨脹後腐蝕。作用:用來填充目標內部的細小孔洞(fill hole),將斷開的鄰近目標連線,在不明顯改變物體面積和形狀的情況下平滑其邊界。
底帽運算就是求原圖與原圖的閉運算的差值影像。作用:閉運算是去噪點的過程,所以黑帽操作實質上保留的是噪點的部分。
opencv實現:
Mat src1 = imread("D:/opencv練習圖片/閉運算.png"); Mat src_close, src_blackhat; imshow("原圖", src1); Mat kernel = getStructuringElement(MORPH_RECT, Size(7, 7), Point(-1, -1)); morphologyEx(src1, src_close, MORPH_CLOSE, kernel,Point(-1, -1));//閉運算 morphologyEx(src1, src_blackhat, MORPH_BLACKHAT, kernel, Point(-1, -1));//底帽運算 imshow("閉運算", src_close); imshow("底帽運算", src_blackhat);
閉運算: 底帽運算:
?形態學梯度(求二值圖邊緣)
影像形態學的梯度跟我們前面介紹的影像卷積計算出來的梯度有本質不同,形態學梯度可以幫助我們獲得連通元件的邊緣與輪廓,實現影像輪廓或者邊緣提取。
根據使用的形態學操作不同,形態學梯度又分為:
- 基本梯度(影像膨脹與腐蝕操作之間的差值)
- 內梯度(輸入影像與腐蝕之間的差值)
- 外梯度(膨脹與輸入影像之間的差值)
opencv實現:
Mat src1 = imread("D:/opencv練習圖片/腐蝕膨脹.png"); Mat src_giad1, src_exter,src_inter,src_dilate,src_erode; imshow("原圖", src1); Mat kernel = getStructuringElement(MORPH_RECT, Size(7, 7), Point(-1, -1)); //基本梯度 morphologyEx(src1, src_giad1, MORPH_GRADIENT, kernel,Point(-1, -1)); imshow("基本梯度", src_giad1); //外梯度 morphologyEx(src1, src_dilate, MORPH_DILATE, kernel, Point(-1, -1)); subtract(src_dilate, src1, src_exter);//求差值 imshow("外梯度", src_exter); //內梯度 morphologyEx(src1, src_erode, MORPH_ERODE, kernel, Point(-1, -1)); subtract(src1, src_erode, src_inter); imshow("內梯度", src_inter);
外梯度: 內梯度:
內外梯度區別不是很大。。。
?擊中與擊不中
形態學的擊中擊不中操作, 擊中擊不中也是基礎形態學操作組合,它可以實現物件的細化跟剪枝操作,根據結構元素不同,可以提取二值影像中的一些特殊區域,得到我們想要的結果。並且擊中擊不中操作在二值影像的模式匹配跟發現上也非常有用
Hit-miss演算法步驟(兩次腐蝕,求交集):
擊中擊不中變換是形態學中用來檢測特定形狀所處位置的一個基本工具。它的原理就是使用腐蝕;如果要在一幅影像A上找到B形狀的目標,我們要做的是:
- 首先,建立一個比B大的結構元素B1;使用B1對影像A進行腐蝕,得到影像假設為A_B1;
- 其次,用B減去B1,從而得到結構元素B2(B2-B);使用B2對影像A的補集進行腐蝕,得到影像假設為A_B2;
- 然後,A_B1與A_B2取交集;得到的結果就是B的位置。
opencv實戰(利用擊中與擊不中,提取網繩的結點位置):
Mat src = imread("D:/opencv練習圖片/擊中與擊不中.png"); imshow("原圖", src); // 二值影像 Mat gray, binary, hitImg; cvtColor(src, gray, COLOR_BGR2GRAY); //高斯濾波 Mat gauss; GaussianBlur(gray, gauss, Size(5, 5), 0, 0); //二值化 threshold(gauss, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU); imshow("binary", binary); // 定義結構元素 Mat se = getStructuringElement(MORPH_CROSS, Size(11, 11), Point(-1, -1)); // 擊中擊不中 morphologyEx(binary, hitImg, MORPH_HITMISS, se); imshow("擊中擊不中", hitImg); //膨脹一下 Mat openImg; Mat kern2 = getStructuringElement(MORPH_RECT, Size(3, 3)); morphologyEx(hitImg, openImg, MORPH_OPEN, kern2, Point(-1, -1), 2); imshow("dilate", openImg); //尋找輪廓 vector<vector<Point>>contours; vector<Vec4i>hie; findContours(openImg, contours, hie, RETR_TREE, CHAIN_APPROX_SIMPLE, Point()); for (size_t i = 0; i < contours.size(); i++) { double area = contourArea(contours[i]); if (area < 20.0)continue; Rect rect = boundingRect(contours[i]); rectangle(src, rect, Scalar(0, 0, 255), 1, 8); } // 顯示 imshow("結果", src);