OpenCV學習筆記(14):形態學濾波對影象進行邊緣及角點檢測
</pre><pre name="code" class="cpp">#include "stdafx.h" #include<opencv2/opencv.hpp> using namespace cv; class MorphoFeatures{ private: //用於生成二值影象的閾值 int threshold; //角點檢測用到的結構元素 Mat cross; Mat diamond; Mat square; Mat x; public: //檢測角點 //opencv沒有直接實現它,需要定義四種不同的結構元素, //包括:菱形,方形,十字形,x形。並在建構函式中完成 MorphoFeatures() :threshold(-1), cross(5, 5, CV_8U, Scalar(0)), diamond(5, 5, CV_8U, Scalar(1)), square(5, 5, CV_8U, Scalar(1)), 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; //建立x形 for (int i = 0; i < 5; i++) { x.at<char>(i, i) = 1; x.at<char>(4 - i, i) = 1; } } Mat getEdges(const Mat &image) { //得到梯度圖 Mat result; //morphology函式配上合適的濾波器就可以實現直線檢測 morphologyEx(image, result, MORPH_GRADIENT, Mat()); applyThreshold(result); return result; } void setThreshold(int t) { threshold = t; } void applyThreshold(Mat & result) { if (threshold > 0) cv::threshold(result, result, threshold, 255, THRESH_BINARY); } //需要連線使用這些結構元素,得到最終的角點對映圖 Mat getCorners(const Mat&image) { Mat result; //十字形膨脹 dilate(image, result, cross); //菱形腐蝕 erode(result, result, diamond); Mat result2; //x形膨脹 dilate(image, result2, x); //方形腐蝕 erode(result2, result2, square); //通過對兩張影象做差值,得到角點影象 absdiff(result2, result, result); //閾值化得到二值影象 applyThreshold(result); return result; } //為了視覺化,在在二值影象中每個監測點繪製一個圓 void drawOnImage(const Mat & binary, Mat & image) { Mat_<uchar>::const_iterator it = binary.begin<uchar>(); Mat_<uchar>::const_iterator itend = binary.end<uchar>(); //遍歷每個畫素 for (int i = 0; it != itend; ++i, ++it) { if (!*it) circle(image, Point(i%image.step, i / image.step), 5, Scalar(0, 255, 0)); } } }; int _tmain(int argc, _TCHAR* argv[]) { //直線檢測 Mat image = imread("building.jpg", 0); //imshow("d", image); if (!image.data) return -1; MorphoFeatures morpho; morpho.setThreshold(40); Mat edges; edges = morpho.getEdges(image); imshow("d", edges); //角點檢測 morpho.setThreshold(-1); Mat corners; corners = morpho.getCorners(image); morphologyEx(corners, corners, MORPH_TOPHAT, Mat()); threshold(corners, corners, 40, 255, THRESH_BINARY_INV); //imshow("角點",corners); //展示圖片上的角點 morpho.drawOnImage(corners, image); imshow("jj", image); waitKey(0); return 0; }
首先如何理解對灰度影象進行形態學操作?
一種比較形象的方法是將灰度影象看做是“等高線”:亮的區域代表山峰,而暗的區域代表山谷,影象的邊沿就對應於峭壁。如果腐蝕一幅影象,會導致山谷被擴充套件,而峭壁減少了。相反的,如果膨脹一幅影象,峭壁則會增加。但是這兩種情況下,中間的部分(大片的谷底和高原)基本保持不變。
在上述理解的基礎上,如果我們對影象的腐蝕和膨脹的結果做差,就能提取影象的邊界:因為邊界區域,二者完全不同。(實際上,我們也可以用腐蝕或者膨脹的結果與源影象做差得出類似結果,但提取的邊界會比較細)。可以看出,結構元越大,邊界越粗。在OpenCV中,將形態學操作函式morphologyEx 的第4個引數設為MORPH_GRADIENT,就能完成上述工作。
開運算可以 用來消除小物體、在纖細點處分離物體、平滑較大物體的邊界的同時並不明顯改變其面積。
閉運算能夠排除小型黑洞(黑色區域)
形態學梯度(Morphological
Gradient)為膨脹圖與腐蝕圖之差,對二值影象進行這一操作可以將團塊(blob)的邊緣突出出來。我們可以用形態學梯度來保留物體的邊緣輪廓
頂帽運算(Top Hat)為原影象與“開運算“的結果圖之差,因為開運算帶來的結果是放大了裂縫或者區域性低亮度的區域,因此,從原圖中減去開運算後的圖,得到的效果圖突出了比原圖輪廓周圍的區域更明亮的區域,且這一操作和選擇的核的大小相關。頂帽運算往往用來分離比鄰近點亮一些的斑塊。當一幅影象具有大幅的背景的時候,而微小物品比較有規律的情況下,可以使用頂帽運算進行背景提取。
void cv::morphologyEx( InputArray _src, OutputArray _dst, int op,
InputArray kernel, Point anchor, int iterations,
int borderType, const Scalar& borderValue )
{
Mat src = _src.getMat(), temp;
_dst.create(src.size(), src.type());
Mat dst = _dst.getMat();
switch( op )
{
case MORPH_ERODE:
erode( src, dst, kernel, anchor, iterations, borderType, borderValue );
break;
case MORPH_DILATE:
dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
break;
case MORPH_OPEN:
erode( src, dst, kernel, anchor, iterations, borderType, borderValue );
dilate( dst, dst, kernel, anchor, iterations, borderType, borderValue );
break;
case CV_MOP_CLOSE:
dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
erode( dst, dst, kernel, anchor, iterations, borderType, borderValue );
break;
case CV_MOP_GRADIENT:
erode( src, temp, kernel, anchor, iterations, borderType, borderValue );
dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );
dst -= temp;
break;
case CV_MOP_TOPHAT:
if( src.data != dst.data )
temp = dst;
erode( src, temp, kernel, anchor, iterations, borderType, borderValue );
dilate( temp, temp, kernel, anchor, iterations, borderType, borderValue );
dst = src - temp;
break;
case CV_MOP_BLACKHAT:
if( src.data != dst.data )
temp = dst;
dilate( src, temp, kernel, anchor, iterations, borderType, borderValue );
erode( temp, temp, kernel, anchor, iterations, borderType, borderValue );
dst = temp - src;
break;
default:
CV_Error( CV_StsBadArg, "unknown morphological operation" );
}
}
- 第一個引數,InputArray型別的src,輸入影象,即源影象,填Mat類的物件即可。影象位深應該為以下五種之一:CV_8U, CV_16U,CV_16S, CV_32F 或CV_64F。
- 第二個引數,OutputArray型別的dst,即目標影象,函式的輸出引數,需要和源圖片有一樣的尺寸和型別。
- 第三個引數,int型別的op,表示形態學運算的型別,可以是如下之一的識別符號:
- MORPH_OPEN – 開運算(Opening operation)
- MORPH_CLOSE – 閉運算(Closing operation)
- MORPH_GRADIENT -形態學梯度(Morphological gradient)
- MORPH_TOPHAT - “頂帽”(“Top hat”)
- MORPH_BLACKHAT - “黑帽”(“Black hat“)
- 第四個引數,InputArray型別的kernel,形態學運算的核心。若為NULL時,表示的是使用參考點位於中心3x3的核。我們一般使用函式 getStructuringElement配合這個引數的使用。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或morphologyEx 函式時,kernel引數填儲存getStructuringElement返回值的Mat型別變數。對應於我們上面的示例,就是填element變數。
- 第五個引數,Point型別的anchor,錨的位置,其有預設值(-1,-1),表示錨位於中心。
- 第六個引數,int型別的iterations,迭代使用函式的次數,預設值為1。
- 第七個引數,int型別的borderType,用於推斷影象外部畫素的某種邊界模式。注意它有預設值BORDER_ CONSTANT。
- 第八個引數,const Scalar&型別的borderValue,當邊界為常數時的邊界值,有預設值morphologyDefaultBorderValue(),一般我們不用去管他。需要用到它時,可以看官方文件中的createMorphologyFilter()函式得到更詳細的解釋。