很多人在學習影象處理的時候,都會接觸到邊緣檢測演算法。但是,大部分人可能都只是會呼叫演算法,而不知道演算法的原理,也不知道邊界檢測之後應該怎麼辦。不知道怎麼應用邊緣檢測的結果,感覺不知所措,只是肉眼可見檢測的結果,而不知道下一步應該怎麼處理。邊緣檢測只是影象處理的中間步驟,其實我們進行影象處理的目的是要提取出想要的特徵,然後將特徵表達出來。比如,提取影象的輪廓特徵,矩特徵等基本特徵,還有一些高階特徵也可以透過邊緣檢測實現。又如,可以將邊緣檢測的結果作為特徵點,然後利用這些特徵點實現匹配、定位、識別、分類等操作。
影象邊緣檢測的目的是目標物件與背景,為下一步的處理做準備,比如,可以利用邊緣檢測的結果對影象進行分割;可以利用邊緣檢測的結果提取影象的特徵;可以用邊緣檢測的結果對特定區域進行定位等等。常用的邊緣檢測方法有很多,如sobel運算元、Roberts運算元、Prewitt運算元、Laplacian運算元、Canny運算元、Scharr運算元、高斯拉普拉斯運算元(LOG,Laplacian of Gaussian)、高斯差分運算元(DOG)等。不管哪種運算元,其中最終的原理都差不多,就是利用影象的梯度資訊。
影象的梯度定義的是影象的變化幅值和方向。這個概念在影象處理中非常重要,大部分的影象處理都離不開影象梯度的概念。要說清楚影象的梯度,先要了解影象的微分和差分計算方法。按照高等數學上學的求導與微分的計算方法,影象是二維離散函式,計算方法也是一樣的。設影象為f(x,y),
一階微分的計算公式非常簡單:
離散化之後就是影象的差分計算方程:
其中,梯度方向就是 :
梯度幅值就是 :
上面的公式看起來好像有點複雜,其實對於影象而言,就是對應的位置畫素值的加減運算。如果在說簡單一點,這些也可以弄成一個模板,然後與影象進行卷積運算。比如,Roberts運算元計算如下:
其實Roberts運算元也就是計算了2x2區域的對角線畫素值之差。
sobel運算元計算如下:
sobel運算元的計算模板如下:
其他的邊界檢測運算元類似,Laplacian運算元是計算的二階差分,LOG運算元是先對影象進行高斯濾波,然後在利用拉普拉斯運算元根據二階導數的過零點來檢測影象的邊界。DOG運算元就是高斯差分,其邊界檢測結果和LOG差不多,只不過計算比LOG快一些。Canny運算元是比較有名的邊緣檢測運算元,這個運算元先用高斯濾波器對影象進行濾波,去除影象噪聲;然後得到影象的梯度強度和方向;再對梯度進行“非極大抑制”,並且對抑制結果取兩次閾值,Th1和Th2,一般Th1=0。4Th2 ;最後連結邊緣得到檢測結果。
在opencv裡面實現了部分邊緣檢測函式,下面以具體的程式碼看一下這些邊界檢測函式怎麼實現的,具體的效果在這裡不討論,因為針對不同的影象需要調整不同的引數,才能夠達到理想的效果,這裡只是學會如何使用這些函式。
#include “iostream”
#include “opencv2\opencv。hpp”
using namespace std;
using namespace cv;
int main(int argc, char ** argv)
{
Mat src = imread(“e:\\img1。ppm”, 1);
namedWindow(“原圖”, 0);
imshow(“原圖”, src);
Mat srcGray;
cvtColor(src, srcGray, COLOR_BGR2GRAY);
namedWindow(“原圖轉成灰度圖”, 0);
imshow(“原圖轉成灰度圖”, srcGray);
Mat cannyResult;
Canny(srcGray, cannyResult, 50, 120);
namedWindow(“canny邊界”, 0);
imshow(“canny邊界”, cannyResult);
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y, sobelDst;
//求 X方向梯度
Sobel(srcGray, grad_x, CV_16S, 1, 0, 3, 1, 1, BORDER_DEFAULT);
convertScaleAbs(grad_x, abs_grad_x);
namedWindow(“X方向Sobel”, 0);
imshow(“X方向Sobel”, abs_grad_x);
//求Y方向梯度
Sobel(srcGray, grad_y, CV_16S, 0, 1, 3, 1, 1, BORDER_DEFAULT);
convertScaleAbs(grad_y, abs_grad_y);
namedWindow(“Y方向Sobel”, 0);
imshow(“Y方向Sobel”, abs_grad_y);
//合併梯度(近似)
addWeighted(abs_grad_x, 0。5, abs_grad_y, 0。5, 0, sobelDst);
namedWindow(“整體方向Sobel”, 0);
imshow(“整體方向Sobel”, sobelDst);
Mat gaussBlur;
GaussianBlur(src, gaussBlur, cv::Size(3, 3),0, 0, cv::BORDER_DEFAULT);
Mat LOGDst;
// 拉普拉斯變換
Laplacian(src, LOGDst, CV_16S, 3);
convertScaleAbs(LOGDst, LOGDst);
namedWindow(“LOG”, 0);
cv::imshow(“LOG”, LOGDst);
Mat dst;
GaussianBlur(src, dst, Size(3, 3), 0);
Mat img_DoG = src - dst;
normalize(img_DoG, img_DoG, 255, 0, NORM_MINMAX);
namedWindow(“DoG”, 0);
cv::imshow(“DoG”, img_DoG);
waitKey(0);
return 0;
}
圖1 邊緣檢測效果
邊緣檢測有很多用途,這裡只是展示了怎麼使用這些函式。學會呼叫這些函式,知道每個函式的檢測原理對以後的特徵檢測是非常有用的。這裡實現了部分函式,其他的檢測方式有興趣的可以自己去試一試。