前言
耐心看完一定會有收獲的,大部分內容也會在代碼中體現,結合理論知識和代碼進行理解會更有效。代碼用opencv4.5.1(c++)版實現
一、邊緣檢測算法
邊緣檢測算法是指利用灰度值的不連續性質,以灰度突變為基礎分割出目標區域。對鋁鑄件表面進行成像后會產生一些帶缺陷的區域,這些區域的灰度值比較低,與背景圖像相比在灰度上會有突變,這是由於這些區域對光線產生散射所引起的。因此邊緣檢測算子可以用來對特征的提取。
1、一階算子
一種是基於一階微分的算子,也稱基於搜索的算子,首先通過一階導數計算邊緣強度,然后采用梯度的方向來對邊緣的局部方向進行尋找,同時根據該方向來尋找出局部梯度模的最大值,由此定位邊緣,如Roberts Cross算子,Prewitt算子Sobel算子,Kirsch算子,Canny算子,羅盤算子等;
圖像中的邊緣區域,像素值會發生“跳躍”,對這些像素求導,在其一階導數在邊緣位置為極值,這就是Sobel算子使用的原理——極值處就是邊緣。
2、二階算子
另一種是基於二階微分的算子,也稱基於零交叉的算子,通過尋找由圖像得到的二階導數的過零點來定位檢測邊緣,如Marr-Hildreth算子,Laplacian算子,LOG算子等。如果對像素值求二階導數,會發現邊緣處的導數值為0。
二、一階算子分析
一階微分算子進行邊緣檢測的思路大致就是通過指定大小的核(kernal)(也稱為算子)與圖像進行卷積,將得到的梯度進行平方和或者最大值作為新的梯度賦值給對應的像素點,不同的一階微分算子主要的不同在於其算子即核的元素不同以及核的大小不一樣
以下是連續函數的一階導數求導公式:因為圖像是一個面,就相當於是灰度值關於x,y兩個方向的函數,要求某一點的導數,則是各個方向的偏導數的平方和再進行開方運算。
離散函數的一階導數公式:
y'=[y(x0+h)-y(x0-h)]/(2h);這是一維函數的一階求導,h是步長,在圖像處理中一般為1
首先復習一下什么是卷積?
卷積就是對應的元素相乘再進行累加的過程
實例圖片:
1、Roberts算子
Robert算子是用於求解對角線方向的梯度,因為根據算子GX和GY的元素設置可以看到,只有對角線上的元素非零,其本質就是以對角線作為差分的方向來檢測。
檢測垂直邊緣的效果好於斜向邊緣,定位精度高,對噪聲敏感,無法抑制噪聲的影響。
不同方向的算子模板: 梯度的計算:
代碼示例:
#include<opencv2/opencv.hpp> #include<opencv2/core/core.hpp> #include<opencv2/imgproc/imgproc.hpp> #include<opencv2/highgui/highgui.hpp> #include<iostream> using namespace std; using namespace cv; Mat roberts(Mat srcImage); int main(int argc, char** argv) { Mat src,src_binary,src_gray; src = imread("D:/opencv練習圖片/薛之謙.jpg"); imshow("原圖", src); cvtColor(src, src_gray, COLOR_BGR2GRAY); GaussianBlur(src_gray, src_binary, Size(3, 3),0, 0, BORDER_DEFAULT); Mat dstImage = roberts(src_binary); imshow("dstImage", dstImage); waitKey(0); return 0; } //roberts 邊緣檢測 Mat roberts(Mat srcImage) { Mat dstImage = srcImage.clone(); int nRows = dstImage.rows; int nCols = dstImage.cols; for (int i = 0; i < nRows - 1; i++) { for (int j = 0; j < nCols - 1; j++) { //根據公式計算 int t1 = (srcImage.at<uchar>(i, j) - srcImage.at<uchar>(i + 1, j + 1))* (srcImage.at<uchar>(i, j) - srcImage.at<uchar>(i + 1, j + 1)); int t2 = (srcImage.at<uchar>(i + 1, j) - srcImage.at<uchar>(i, j + 1))* (srcImage.at<uchar>(i + 1, j) - srcImage.at<uchar>(i, j + 1)); //計算g(x,y) dstImage.at<uchar>(i, j) = (uchar)sqrt(t1 + t2); } } return dstImage; }
效果展示:
2、Sobel算子
Sobel算子是主要用於邊緣檢測的離散微分算子,它結合了高斯平滑和微分求導,用於計算圖像灰度函數的近似梯度。
因為Sobel算子結合了高斯平滑和分化,因此結果會具有更多的抗噪性。
不同方向的算子模板: 梯度的計算:
opencv中sobel函數的參數如下:
void cv::Sobel ( InputArray src, // 輸入圖像 OutputArray dst, // 輸出圖像 int ddepth, // 輸出圖像深度,-1 表示等於 src.depth() int dx, // 水平方向的階數 int dy, // 垂直方向的階數 int ksize = 3, // 卷積核的大小,常取 1, 3, 5, 7 等奇數 double scale = 1, // 縮放因子,應用於計算結果 double delta = 0, // 增量數值,應用於計算結果 int borderType = BORDER_DEFAULT // 邊界模式 )
3、Prewitt算子
Prewitt算子利用像素點上下、左右鄰點的灰度差,在邊緣處達到極值檢測邊緣,去掉部分偽邊緣,對噪聲具有平滑作用 。其原理是在圖像空間利用兩個方向模板與圖像進行鄰域卷積來完成的。
這兩個方向模板一個檢測水平邊緣,一個檢測垂直邊緣。
不同方向的算子模板:
Prewitt算子定位精度不如Sobel算子,在真正的使用中,一般不會用到這個算子,效果較差,因此不多做分析。
4、Canny算子
可以說,Canny 邊緣檢測算法是被業界公認的性能最為優良的邊緣檢測算法之一。Canny算法不是像Roberts、Prewitt、Sobel等這樣簡單梯度算子或銳化模板,它是在梯度算子基礎上,引入了一種能獲得抗噪性能好、定位精度高的單像素邊緣的計算策略。
Canny 算子,在一階微分的基礎上,增加了非最大值抑制和雙閾值檢測,是邊緣檢測算子中最常用的一種,常被其它算子作為標准算子來進行優劣比較。
4.1算法步驟
1) 用高斯濾波器對輸入圖像做平滑處理 (大小為 5x5 的高斯核)
2) 計算圖像的梯度強度和角度方向 ( x 和 y 方向上的卷積核)
角度方向近似為四個可能值,即 0, 45, 90, 135
3) 對圖像的梯度強度進行非極大抑制
可看做邊緣細化:只有候選邊緣點被保留,其余的點被移除
4) 利用雙閾值檢測和連接邊緣
若候選邊緣點大於上閾值,則被保留;小於下閾值,則被舍棄;處於二者之間,須視其所連接的像素點,大於上閾值則被保留,反之舍棄
opencv中canny函數的參數如下:
void cv::Canny ( InputArray image, // 輸入圖像 (8位) OutputArray edges, // 輸出圖像 (單通道,8位) double threshold1, // 下閾值 double threshold2, // 上閾值 int apertureSize = 3, bool L2gradient = false )
一般 上閾值 / 下閾值 = 2 ~ 3
L2gradient 默認 flase,表示圖像梯度強度的計算采用近似形式;若為 true,則表示采用更精確的形式
5、Laplace(拉普拉斯算子)
索貝爾算子 (Sobel) 和拉普拉斯算子 (Laplace) 都是用來對圖像進行邊緣檢測的,不同之處在於,前者是求一階導,后者是求二階導。
常用算子模塊:
opencv中Laplace函數的參數如下:
void Laplacian ( InputArray src, OutputArray dst, int ddepth, int ksize = 1, double scale = 1, double delta = 0, int borderType = BORDER_DEFAULT )
簡單對比
在進行 Sobel,Laplacian 和 Canny 邊緣檢測之前,統一調用 GaussianBlur 來降低圖像噪聲
using namespace std; using namespace cv; int main(int argc, char** argv) { Mat src,src_binary,src_gray; Mat grad_x, grad_y; Mat abs_grad_x, abs_grad_y, dst; src = imread("D:/opencv練習圖片/薛之謙.jpg"); imshow("原圖", src); GaussianBlur(src, src, Size(3, 3), 0); cvtColor(src, src_gray, COLOR_BGR2GRAY); Sobel(src_gray, grad_x, CV_16S, 1, 0, 3, 1, 1, BORDER_DEFAULT);//求x方向梯度 convertScaleAbs(grad_x, abs_grad_x);//轉換格式 8u Sobel(src_gray, grad_y, CV_16S, 0, 1, 3, 1, 1, BORDER_DEFAULT);//求y方向梯度 convertScaleAbs(grad_y, abs_grad_y); //合並梯度 addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dst); imshow("solbel算子", dst); Canny(src_gray, dst, 100, 300); imshow("Canny算子", dst); Laplacian(src_gray, dst, -1, 3); imshow("Laplace算子", dst); waitKey(0); return 0; }
效果展示: