本篇文章中,我們將一起學習OpenCV中邊緣檢測的各種算子和濾波器——Canny算子,Sobel算子,Laplace算子以及Scharr濾波器。文章中包含了五個淺墨為大家准備的詳細注釋的博文配套源代碼。在介紹四塊知識點的時候分別一個,以及最后的綜合示例中的一個。文章末尾提供配套源代碼的下載。
**** 給大家分享一個OpenCv中寫代碼是節約時間的小常識。其實OpenCv中,不用nameWindow,直接imshow就可以顯示出窗口。大家看下文的示例代碼就可以發現,淺墨在寫代碼的時候並沒有用namedWindow,遇到想顯示出來的Mat變量直接imshow。我們一般是為了規范,才先用namedWindow創建窗口,再imshow出它來,因為我們還有需要用到指定窗口名稱的地方,比如用到trackbar的時候。而一般情況想顯示一個Mat變量的圖片的話,直接imshow就可以啦。
一、關於邊緣檢測
在具體介紹之前,先來一起看看邊緣檢測的一般步驟吧。。
(1)濾波:邊緣檢測的 算法 主要是基於圖像強度的一階和二階導數,但導數通常對噪聲很敏感,因此必須采用濾波器來改善與噪聲有關的邊緣檢測的性能。常見的濾波方法主要有 高斯濾波,即采用離散化的高斯函數產生一組歸一化的高斯核(具體見“高斯濾波原理及其編程離散化實現方法”一文),然后基於高斯核函數對圖像灰度矩陣的每一點進行加權和(具體程序見下文)
(2)增強 :增強邊緣 的基礎是確定圖像各點鄰域強度的變化值。增強算法可以將圖像灰度點鄰域強度值有顯著變化的點凸顯出來。在具體編程實現時,可通過計算梯度幅值來確定。
(3)檢測: 經過增強的圖像,往往鄰域中有很多點的梯度值比較大,而在特定的應用中,這些點並不是我們想要的找的邊緣點,所以應該采用某種方法來對這些點進行取舍。在實際工程中,常用的方法是 通過閾值化方法來檢測。
另外,需要注意,下文中講到的Laplace算子,sobel算子和Scharr算子都是帶方向的,所以,示例中我們分別寫了X方向,Y方向和最終合成的的效果圖。
二、canny算子篇
2.1 canny算子相關理論與概念講解
2.1.1 canny算子簡介
Canny 邊緣檢測算子是John F.Canny於 1986 年開發出來的一個多級邊緣檢測算法。更為重要的是 Canny 創立了邊緣檢測計算理論(Computational theory ofedge detection),解釋了這項技術是如何工作的。Canny邊緣檢測算法以Canny的名字命名,被很多人推崇為當今最優的邊緣檢測的算法。
其中,Canny 的目標是找到一個最優的邊緣檢測算法,讓我們看一下最優邊緣檢測的三個主要評價標准:
1 低錯誤率:標識出盡可能多的實際邊緣,同時盡可能的減少噪聲產生的誤報
2 高定位性: 標識出的邊緣要與圖像中的實際邊緣盡可能接近
3 最小響應: 圖像中的邊緣只能標識一次,並且可能存在的圖像噪聲不應標識為邊緣
為了滿足這些要求,Canny使用了 變分法 ,這是一種尋找滿足特定功能的函數的方法。最優檢測使用4個指數函數項的和來表示,但是它們非常近似於高斯函數的一階導數。
2.1.2 Canny 邊緣檢測的步驟
1 消除噪聲。一般情況下,使用高斯平滑濾波器卷積降噪。 如下顯示了一個 size = 5 的高斯內核示例:
不能理解還。。。。。。。。。。。。。。
2.計算梯度幅值和方向。(此處按照Sobel濾波器的步驟)
(1)運用一對卷積陣列(分別作用於x和y方向)
(2)使用下列公式計算梯度幅值和方向
梯度方向近似到四個可能角度之一(一般為0, 45, 90, 135)
(3)非極大值抑制。這一步排除非邊緣像素,僅僅保留了一些細線條(候選邊緣)
(4)滯后閾值。最后一步,Canny使用了滯后閾值,滯后閾值需要兩個閾值(高閾值和低閾值):
(1)如果某一像素位置的幅值超過高閾值,該像素被保留為邊緣像素
(2)如果某一像素位置的幅值小於低閾值,該像素被排除
(3)如果某一像素位置的幅值在倆個閾值之間,該像素僅僅在連接到一個高於高閾值的像素時被保留
tips:對於Canny函數的使用,推薦的高低閾值比在2:1到3:1之間。
更多的細節,可以參考canny算子的wikipedia:http://en.wikipedia.org/wiki/Canny_edge_detector
發現淺墨大神推薦的倆個網址失效了,改天我會專門 寫一節關於Canny的博客。。。。這里暫時先不做過多的理解
2.2 OpenCV中Canny函數詳解
Canny函數利用Canny算法來進行圖像的邊緣檢測。
1 void Canny(InputArray image,OutputArray edges,double threshold1, 2 threshold2,int apertureSize = 3,bool L2gradient=false);
- 第一個參數,InputArray類型的image,輸入圖像,即源圖像,填Mat類的對象即可,且需為單通道8位圖像。
- 第二個參數,OutputArray類型的edges,輸出的邊緣圖,需要和源圖片有一樣的尺寸和類型。
- 第三個參數,double類型的threshold1,第一個滯后性閾值。
- 第四個參數,double類型的threshold2,第二個滯后性閾值。
- 第五個參數,int類型的apertureSize,表示應用Sobel算子的孔徑大小,其有默認值3。
- 第六個參數,bool類型的L2gradient,一個計算圖像梯度幅值的標識,有默認值false。
需要注意的是,這個函數閾值1和閾值2兩者的小者用於邊緣連接,而大者用來控制強邊緣的初始段,推薦的高低閾值比在2:1到3:1之間。
調用示例:
1 //載入原始圖
2 Mat src = imread("1.jpg"); 3 Canny(src,src,3,9,3); 4 imshow("【效果圖】Canny邊緣檢測", src);
如上三句,就有結果出來,非常好用。
2.3 調用Canny函數的實例代碼
1 #include <opencv2/core/core.hpp>
2 #include <opencv2/opencv.hpp>
3 #include <opencv2/highgui/highgui.hpp>
4 #include <opencv2/imgproc/imgproc.hpp>
5 #include <iostream>
6
7 using namespace std; 8 using namespace cv; 9
10 /*------------------------------------------- 11 【1】Canny算子 12 --------------------------------------------*/
13 int main() 14 { 15 //載入原始圖
16 Mat src = imread("1.jpg"); 17 Mat src1 = src.clone(); 18 //Mat src2 = src.clone(); 19
20 //顯示原始圖
21 imshow("【原始圖】Canny邊緣檢測", src); 22
23 /*------------------------------------------- 24 (1) 最簡單的canny用法,拿到原圖后直接用 25 ---------------------------------------------*/
26 Canny(src,src,150,100,3); 27 imshow("【效果圖】Canny邊緣檢測", src); 28
29 /*------------------------------------------------------------------------------------------------------------ 30 (2)高階的canny用法,轉成灰度圖,降噪,用Canny,最后得到的邊緣作為掩碼,拷貝原圖到效果圖上,得到彩色的邊緣圖 31 --------------------------------------------------------------------------------------------------------------*/
32 Mat dst, edge, gray; 33
34 //(1)創建於src 同類型和大小的矩陣(dst)
35 dst.create(src1.size(),src1.type()); 36
37 //(2)將原圖轉換為灰度圖像
38 cvtColor(src1,gray,CV_BGR2GRAY); 39
40 //(3)使用3*3內核來降噪
41 blur(gray,edge,Size(3,3)); 42
43 //(4)運行Canny算子
44 Canny(edge,edge,3,9,3); 45
46 //(5)將g_dstImage內的所有元素設置為0
47 dst = Scalar::all(0); 48
49 //(6)使用Canny算子輸出的邊緣圖g_cannyDetectEdges作為掩碼,來將原圖g_srcImage拷到目標圖g_dstImage中
50 src1.copyTo(dst,edge); 51
52 //(7)顯示效果圖
53 imshow("【效果圖】Canny邊緣檢測2", dst); 54
55 waitKey(); 56
57 return 0; 58 }
我這里第一個不知道為什么不成功,顯示出的是一片黑。。。。。。。。。。(我猜是不是在3版本中不能這樣直接用,或者格式不是這樣的)
三、sobel算子篇
3.1 sobel算子相關理論與概念講解
3.1.1 基本概念
sobel 算子是一個主要用做邊緣檢測的離散微分算子(discrete differentiation operator).Sobel算子結合了高斯平滑和微分求導,用來計算圖像灰度函數的近似梯度。在圖像的任何一點使用此算子,將會產生對應的梯度矢量或是其法矢量。
sobel算子相關概念,還可以參看這篇博文:http://www.cnblogs.com/lancidie/archive/2011/07/17/2108885.html
3.1.2 sobel算子的計算過程
我們假設被作用圖像為 I.然后進行如下的操作:
1 分別在x 和 y 倆個方向求導
(1)水平變化:將I 與一個奇數大小的內核進行卷積,比如,當內核大小為3時,
的計算結果為:
(2)垂直變化:將: I 與一個奇數大小的內核進行卷積。比如,當內核大小為3時,
的計算結果為:
2 在圖像的每一點,結合以上倆個結果求出近似梯度:
另外有時,也可以用下面更簡單的公式代替
3.2 OpenCV中Sobel函數詳解
Sobel 函數使用擴展的Sobel 算子,來計算一階,二階,三階或混合圖像差分
1 void Sobel(InputArray src,//輸入圖
2 OutputArray dst,//輸出圖
3 int ddepth,//輸出圖像的深度
4 int dx, 5 int dy, 6 int ksize=3, 7 double scale=1, 8 double delta=0, 9 int borderType=BORDER_DEFAULT);
- 第一個參數,InputArray 類型的src,為輸入圖像,填Mat類型即可。
- 第二個參數,OutputArray類型的dst,即目標圖像,函數的輸出參數,需要和源圖片有一樣的尺寸和類型。
- 第三個參數,int類型的ddepth,輸出圖像的深度,支持如下src.depth()和ddepth的組合:
若src.depth() = CV_8U, 取ddepth =-1/CV_16S/CV_32F/CV_64F
若src.depth() = CV_16U/CV_16S, 取ddepth =-1/CV_32F/CV_64F
若src.depth() = CV_32F, 取ddepth =-1/CV_32F/CV_64F
若src.depth() = CV_64F, 取ddepth = -1/CV_64F
- 第四個參數,int類型dx,x 方向上的差分階數。
- 第五個參數,int類型dy,y方向上的差分階數。
- 第六個參數,int類型ksize,有默認值3,表示Sobel核的大小;必須取1,3,5或7。
- 第七個參數,double類型的scale,計算導數值時可選的縮放因子,默認值是1,表示默認情況下是沒有應用縮放的。我們可以在文檔中查閱getDerivKernels的相關介紹,來得到這個參數的更多信息。
- 第八個參數,double類型的delta,表示在結果存入目標圖(第二個參數dst)之前可選的delta值,有默認值0。
- 第九個參數, int類型的borderType,我們的老朋友了(萬年是最后一個參數),邊界模式,默認值為BORDER_DEFAULT。這個參數可以在官方文檔中borderInterpolate處得到更詳細的信息。
一般情況下,都是用ksize x ksize內核來計算導數的。然而,有一種特殊情況——當ksize為1時,往往會使用3 x 1或者1 x 3的內核。且這種情況下,並沒有進行高斯平滑操作。
一些補充說明:
1 當內核大小為3時,我們的Sobel內核可能產生比較明顯的誤差(畢竟,Sobel算子只是求取了導數的近似值而已)。為解決這一問題,Opencv提供了Scharr函數,但函數僅作用於大小為3的內核。該函數的運算與Sobel函數一樣快,但結果卻更加精確,其內核是這樣的:
2 因為Sobel算子結合了高斯平滑和分化(differentiation),因此結果會具有更多的抗噪性。大多數情況下,我們使用sobel函數時,取【xorder = 1,yorder = 0,ksize = 3】來計算圖像X方向的導數,【xorder = 0,yorder = 1,ksize = 3】來計算圖像y方向的導數。
計算圖像X方向的導數,取【xorder= 1,yorder = 0,ksize = 3】情況對應的內核:
而計算圖像Y方向的導數,取【xorder= 0,yorder = 1,ksize = 3】對應的內核:
3.3 調用Sobel函數的實例代碼
調用Sobel 函數的實例代碼如下,這里只是教大家如何使用Sobel函數,就沒有先用幾句cvtColor 將原圖轉化為灰度圖,而是直接用彩色圖操作
1 int main() 2 { 3 //【0】創建 grad_x,grad_y 矩陣
4 Mat grad_x, grad_y; 5 Mat abs_grad_x, abs_grad_y; 6
7 //【1】載入原始圖
8 Mat src = imread("1.jpg"); 9 Mat dst; 10
11 //【2】顯示原始圖
12 imshow("【原始圖】sobel邊緣檢測",src); 13
14 //【3】求 x 方向梯度
15 Sobel(src,grad_x,CV_16S,1,0,3,1,1,BORDER_DEFAULT); 16 convertScaleAbs(grad_x,abs_grad_x); 17 imshow("【效果圖】 X方向Sobel",abs_grad_x); 18
19 //【4】求 y 方向梯度
20 Sobel(src, grad_y, CV_16S, 0, 1, 3, 1, 1, BORDER_DEFAULT); 21 convertScaleAbs(grad_y, abs_grad_y); 22 imshow("【效果圖】 Y方向Sobel", abs_grad_y); 23
24 //【5】合並梯度
25 addWeighted(abs_grad_x,0.5,abs_grad_y,0.5,0,dst); 26 imshow("【效果圖】整體方向Sobel", dst); 27
28 waitKey(); 29
30 return 0; 31 }
四、Laplace算子篇
4.1 Laplace算子相關理論與概念講解
Laplace 算子是n維歐幾里德空間中的一個二階微分算子,定義為梯度grad()的散度div().因此如果f 是二階可微的實函數,則f的拉普拉斯算子定義為:
(1)f 的拉普拉斯算子也算是笛卡爾坐標系中的所有非混合二階偏導數求和
(2)作為一個二階微分算子,拉普拉斯算子把C函數映射到C函數,對於k ≥ 2。表達式(1)(或(2))定義了一個算子Δ :C(R) → C(R),或更一般地,定義了一個算子Δ : C(Ω) → C(Ω),對於任何開集Ω。
根據圖像處理的原理我們知道,二階導數可以用來進行邊緣檢測。因為圖像是“二維”,我們需要在倆個方向進行求導。使用Laplace算子將會使求導過程變得簡單
Laplacian 算子的定義:
需要點破的是,由於 Laplacian使用了圖像梯度,它內部的代碼其實是調用了 Sobel 算子的。
另附一個小tips:讓一幅圖像減去它的Laplacian可以增強對比度。
關於Laplace算子的相關概念闡述,可以參看這篇博文:http://www.cnblogs.com/xfzhang/archive/2011/01/19/1939020.html
4.2 OpenCV中Laplacian函數詳解
Laplacian 函數可以計算出圖像經過拉普拉斯變換后的結果
1 void Laplacian(InputArray src,OutputArray dst, int ddepth, int ksize=1, double scale=1, double delta=0, intborderType=BORDER_DEFAULT);
- 第一個參數,InputArray類型的image,輸入圖像,即源圖像,填Mat類的對象即可,且需為單通道8位圖像。
- 第二個參數,OutputArray類型的edges,輸出的邊緣圖,需要和源圖片有一樣的尺寸和通道數。
- 第三個參數,int類型的ddept,目標圖像的深度。
- 第四個參數,int類型的ksize,用於計算二階導數的濾波器的孔徑尺寸,大小必須為正奇數,且有默認值1。
- 第五個參數,double類型的scale,計算拉普拉斯值的時候可選的比例因子,有默認值1。
- 第六個參數,double類型的delta,表示在結果存入目標圖(第二個參數dst)之前可選的delta值,有默認值0。
- 第七個參數, int類型的borderType,邊界模式,默認值為BORDER_DEFAULT。這個參數可以在官方文檔中borderInterpolate()處得到更詳細的信息。
Laplacian( )函數其實主要是利用sobel算子的運算。它通過加上sobel算子運算出的圖像x方向和y方向上的導數,來得到我們載入圖像的拉普拉斯變換結果。
其中,sobel算子(ksize>1)如下:
而當ksize=1時,Laplacian()函數采用以下3x3的孔徑:
4.3 調用Laplacian函數的實例代碼
1 /*------------------------------------------- 2 【3】Laplace算子 3 --------------------------------------------*/
4
5 int main() 6 { 7 //【0】變量的定義
8 Mat src,dst, src_gray, abs_dst; 9
10 //【1】載入原始圖
11 src = imread("1.jpg"); 12
13 //【2】顯示原始圖
14 imshow("【原始圖】圖像Laplace變換",src); 15
16 //【3】使用高斯濾波消除噪聲
17 GaussianBlur(src,src,Size(3,3),0,0,BORDER_DEFAULT); 18
19 //【4】轉換為灰度圖
20 cvtColor(src,src_gray,CV_RGB2GRAY); 21
22 //【5】使用Laplace函數
23 Laplacian(src_gray,dst,CV_16S,3,1,0,BORDER_DEFAULT); 24
25 //【6】計算絕對值,並將結果轉換成8位
26 convertScaleAbs(dst,abs_dst); 27
28 //顯示效果圖
29 imshow("【效果圖】圖像Laplace變換", abs_dst); 30
31 waitKey(); 32
33 return 0; 34
35 }
五、scharr濾波器篇
scharr一般我就直接稱它為濾波器,而不是算子。上文我們已經講到,它在OpenCV中主要是配合Sobel算子的運算而存在的,一個萬年備胎。讓我們直接來看看函數講解吧。
5.1 OpenCV中Scharr函數詳解
使用Scharr濾波器運算符計算x或y方向的圖像差分,其實它的參數變量和Sobel基本上是一樣的,除了沒有ksize核的大小
1 void Scharr(InputArray src, //源圖
2 OutputArray dst, //目標圖
3 int ddepth,//圖像深度
4 int dx,// x方向上的差分階數
5 int dy,//y方向上的差分階數
6 double scale=1,//縮放因子
7 double delta=0,// delta值
8 intborderType=BORDER_DEFAULT );// 邊界模式
- 第一個參數,InputArray 類型的src,為輸入圖像,填Mat類型即可。
- 第二個參數,OutputArray類型的dst,即目標圖像,函數的輸出參數,需要和源圖片有一樣的尺寸和類型。
- 第三個參數,int類型的ddepth,輸出圖像的深度,支持如下src.depth()和ddepth的組合:
若src.depth() = CV_8U, 取ddepth =-1/CV_16S/CV_32F/CV_64F
若src.depth() = CV_16U/CV_16S, 取ddepth =-1/CV_32F/CV_64F
若src.depth() = CV_32F, 取ddepth =-1/CV_32F/CV_64F
若src.depth() = CV_64F, 取ddepth = -1/CV_64F
- 第四個參數,int類型dx,x方向上的差分階數。
- 第五個參數,int類型dy,y方向上的差分階數。
- 第六個參數,double類型的scale,計算導數值時可選的縮放因子,默認值是1,表示默認情況下是沒有應用縮放的。我們可以在文檔中查閱getDerivKernels的相關介紹,來得到這個參數的更多信息。
- 第七個參數,double類型的delta,表示在結果存入目標圖(第二個參數dst)之前可選的delta值,有默認值0。
- 第八個參數, int類型的borderType,我們的老朋友了(萬年是最后一個參數),邊界模式,默認值為BORDER_DEFAULT。這個參數可以在官方文檔中borderInterpolate處得到更詳細的信息。
不難理解,如下兩者是等價的:
1 Scharr(src, dst, ddepth, dx, dy, scale,delta, borderType);
1 Sobel(src, dst, ddepth, dx, dy, CV_SCHARR,scale, delta, borderType);
5.2 調用Scharr函數的實例代碼
1 /*------------------------------------------- 2 【4】Scharr濾波器 3 --------------------------------------------*/
4 int main() 5 { 6 //【0】創建grad_x和grad_y 矩陣
7 Mat grad_x, grad_y; 8 Mat dst,abs_grad_x, abs_grad_y; 9
10 //【1】載入原始圖
11 Mat src = imread("1.jpg"); 12
13 //【2】顯示原始圖
14 imshow("【原始圖】Scharr濾波器", src); 15
16 //【3】求x 方向梯度
17 Scharr(src,grad_x,CV_16S,1,0,1,0,BORDER_DEFAULT); 18 convertScaleAbs(grad_x,abs_grad_x); 19 imshow("【效果圖】 X方向Scharr",abs_grad_x); 20
21 //【4】求y 方向梯度
22 Scharr(src, grad_y, CV_16S, 0, 1, 1, 0, BORDER_DEFAULT); 23 convertScaleAbs(grad_y, abs_grad_y); 24 imshow("【效果圖】 Y方向Scharr", abs_grad_y); 25
26 //【5】合並梯度(近似)
27 addWeighted(abs_grad_x,0.5,abs_grad_y,0.5,0,dst); 28
29 //【6】顯示效果圖
30 imshow("【效果圖】合並梯度后Scharr", dst); 31
32 waitKey(); 33 return 0; 34 }
六、綜合示例篇——在實戰中熟稔
1 /*------------------------------------------------------------------------ 2 OpenCV邊緣檢測:Canny算子,Sobel算子,Laplace算子,Scharr濾波器合輯 3 ----------------------------------------------------------------------------*/
4
5 // 全局變量聲明
6 Mat g_srcImage, g_srcGrayImage, g_dstImage; //原圖,原圖的灰度版,目標圖 7
8 //Canny邊緣檢測相關變量
9 Mat g_cannyDetectedEdges; 10 int g_cannyLowThreshold = 1; //trackbar 位置參數 11
12 //Sobel邊緣檢測相關變量
13 Mat g_sobelGradient_X, g_sobelGradient_Y; 14 Mat g_sobelAbsGradient_X, g_sobelAbsGradient_Y; 15 int g_sobelKernelSize = 1; //trackbar位置參數 16
17 //scharr濾波器相關變量
18 Mat g_scharrGradient_X, g_scharrGradient_Y; 19 Mat g_scharrAbsGradient_X, g_scharrAbsGradient_Y; 20
21
22
23 //全局函數聲明
24 static void ShowHelpText(); 25 static void on_Canny(int,void *); //Canny邊緣檢測窗口滾動條的回調函數
26 static void on_Sobel(int,void *); //Sobel邊緣檢測窗口滾動條的回調函數
27 void Scharr(); //封裝了Scharr邊緣檢測相關代碼的函數
28
29
30 int main(int argc,char **argv) 31 { 32 //改變console字體顏色
33 system("color 2F"); 34
35 //顯示歡迎語
36 ShowHelpText(); 37
38 //載入原圖
39 g_srcImage = imread("1.jpg"); 40 if (!g_srcImage.data) 41 { 42 printf("Oh,no,讀取srcImage錯誤~! \n"); 43 return false; 44 } 45
46 //顯示原始圖
47 namedWindow("【原始圖】"); 48 imshow("【原始圖】", g_srcImage); 49
50 //創建於src同類型和大小的矩陣(dst)
51 g_dstImage.create(g_srcImage.size(), g_srcImage.type()); 52
53 //將原始圖轉換為灰度圖像
54 cvtColor(g_srcImage, g_srcGrayImage, CV_BGR2GRAY); 55
56 //創建顯示窗口
57 namedWindow("【效果圖】Canny邊緣檢測", CV_WINDOW_AUTOSIZE); 58 namedWindow("【效果圖】Sobel邊緣檢測", CV_WINDOW_AUTOSIZE); 59
60 //創建trackbar
61 createTrackbar("參數值:", "【效果圖】Canny邊緣檢測", &g_cannyLowThreshold, 120, on_Canny); 62 createTrackbar("參數值:", "【效果圖】Sobel邊緣檢測", &g_sobelKernelSize, 3, on_Sobel); 63
64 //調用回調函數
65 on_Canny(0, 0); 66 on_Sobel(0, 0); 67
68 //調用封裝了Scharr邊緣檢測代碼的函數
69 Scharr(); 70
71 //輪詢獲取按鍵信息,若按下Q,程序退出
72 while ((char(waitKey(1)) != 'q')) {} 73
74 return 0; 75 } 76
77
78
79
80 static void ShowHelpText() 81 { 82 // 輸出一些幫助信息
83 printf("\n\n\t嗯。運行成功,請調整滾動條觀察圖像效果~\n\n"
84 "\t按下“q”鍵時,程序退出~!\n"
85 "\n\n\t\t\t\t by hehhehhe"); 86 } 87
88
89 //Canny邊緣檢測窗口滾動條的回調函數
90 void on_Canny(int, void *) 91 { 92 //先使用3*3內核來降噪
93 blur(g_srcGrayImage,g_cannyDetectedEdges,Size(3,3)); 94
95 //運行我們的Canny算子
96 Canny(g_cannyDetectedEdges,g_cannyDetectedEdges,g_cannyLowThreshold,g_cannyLowThreshold*3,3); 97
98 //先將g_dstImage內的所有元素設置為0
99 g_dstImage = Scalar::all(0); 100
101 //使用Canny算子輸出的邊緣圖g_cannyDetectedEdges作為掩碼,來將原圖g_srcImage拷到目標圖g_dstImage中
102 g_srcImage.copyTo(g_dstImage,g_cannyDetectedEdges); 103
104 //顯示效果圖
105 imshow("【效果圖】Canny邊緣檢測", g_dstImage); 106 } 107
108
109 //Sobel邊緣檢測窗口滾動條的回調函數
110
111 void on_Sobel(int, void *) 112 { 113 //求X方向梯度
114 Sobel(g_srcImage,g_sobelGradient_X,CV_16S,1,0,(2*g_sobelKernelSize+1),1,1,BORDER_DEFAULT); 115 convertScaleAbs(g_sobelGradient_X,g_sobelAbsGradient_X); //計算絕對值,並將結果轉換成8位 116
117 //求Y方向梯度
118 Sobel(g_srcImage, g_sobelGradient_Y, CV_16S, 0, 1, (2 * g_sobelKernelSize + 1), 1, 1, BORDER_DEFAULT); 119 convertScaleAbs(g_sobelGradient_Y, g_sobelAbsGradient_Y); //計算絕對值,並將結果轉換成8位 120
121 //合並梯度
122 addWeighted(g_sobelAbsGradient_X,0.5,g_sobelAbsGradient_Y, 0.5, 0, g_dstImage); 123 //addWeighted(g_sobelAbsGradient_X, 0.5, g_sobelAbsGradient_Y, 0.5, 0, g_dstImage); 124
125 //顯示效果圖
126 imshow("【效果圖】Sobel邊緣檢測", g_dstImage); 127 } 128
129
130
131
132 //封裝了Scharr邊緣檢測相關代碼的函數
133
134 void Scharr() 135 { 136 //求X方向梯度
137 Scharr(g_srcImage,g_scharrGradient_X,CV_16S,1,0,1,0,BORDER_DEFAULT); 138 convertScaleAbs(g_scharrGradient_X,g_scharrAbsGradient_X); //計算絕對值,並將結果轉換成8位 139
140 //求Y方向梯度
141 Scharr(g_srcImage, g_scharrGradient_Y, CV_16S, 0, 1, 1, 0, BORDER_DEFAULT); 142 convertScaleAbs(g_scharrGradient_Y, g_scharrAbsGradient_Y); //計算絕對值,並將結果轉換成8位 143
144 //合並梯度
145 addWeighted(g_scharrAbsGradient_X, 0.5, g_scharrAbsGradient_Y, 0.5, 0, g_dstImage); 146
147 //顯示效果圖
148 imshow("【效果圖】Scharr濾波器", g_dstImage); 149 }