###### tags: `OpenCV`,`邊緣檢測`,`Sobel`,`Scharr`,`Canny` # OpenCV 基礎篇-邊緣檢測(edge detection) **邊緣檢測-edge detection** * 邊緣檢測是圖像處理和計算機視覺的基本問題,邊緣檢測的目的是標識圖像中亮度變化明顯的點,圖像屬性中的顯著變化通常反映了屬性的重要事件和變化。 * 在技術上,它是一離散性差分算子,用來運算圖像亮度函數的梯度之近似值。 * 邊緣檢測是圖像處理和電腦視覺中,尤其是特徵提取中的一個研究領域。 * 圖像邊緣檢測大幅度的減少了數據量,並且剔除了可以認為不相關的信息,保留了圖像重要的結構屬性。 * 邊緣計算就是尋找圖像梯度在梯度方向上的極值點。 * 邊緣像素是一個局部概念:它們只是指出相鄰像素之間的顯著差異,只要有這個極值點出現,那就是邊緣。 **方法1-cv2.Sobel** Sobel算子是一種用於邊緣檢測的離散微分算子,它結合了高斯平滑和微分求導。該算子用於計算圖像明暗程度近似值。根據圖像邊緣旁邊明暗程度把該區域內超過某個數的特定點記為邊緣。 該算子包含兩組3x3的矩陣,分別為橫向及縱向,將之與圖像作平面 卷積,即可分別得出橫向及縱向的亮度差分近似值。 ![](https://i.imgur.com/MPMez1P.jpg) ![](https://i.imgur.com/d9C2HfU.jpg) ![](https://i.imgur.com/cwON7ZL.jpg) dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]]) dst 表示輸出的邊緣圖,其大小和通道數與輸入圖像相同 src 表示需要處理的圖像 ddepth 表示你要用多大的數字去記錄計算結果,-1表示採用的是與原圖像相同的深度。目標圖像的深度必須大於等於原圖像的深度圖像深度是指存儲每個像素值所用的位數,例如cv2.CV_8U,指的是8位無符號數,取值範圍為0~255,超出範圍則會被截斷(截斷指的是,當數值大於255保留為255,當數值小於0保留為0,其餘不變)。 深度選擇還有:CV_16S(16位無符號數),CV_16U(16位有符號數),CV_32F(32位浮點數),CV_64F(64位浮點數),一般選用cv2.CV_16S dx和dy表示的是求導的階數, dx 表示x方向上的差分階數,取值為1或者0, dy 表示y方向上的差分階數,取值為1或者0, 0表示這個方向上沒有求導,1就是這個方向上要求導 ksize是Sobel算子的大小,其值必須是正數和奇數,通常為1、3、5、7。 scale是縮放導數的比例常數,默認情況下沒有伸縮係數; delta是一個可選的增量,將會加到最終的dst中,同樣,默認情況下沒有額外的值加到dst中; borderType是判斷圖像邊界的模式。這個參數默認值cv2.BORDER_DEFAULT。 ```python= import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread("sobel4.bmp") x = cv2.Sobel(img, cv2.CV_16S, 1, 0) #表示dx方向上的差分階數,取值為1 y = cv2.Sobel(img, cv2.CV_16S, 0, 1) #表示dy方向上的差分階數,取值為1 absX = cv2.convertScaleAbs(x)# 轉回uint8 absY = cv2.convertScaleAbs(y) dst = cv2.addWeighted(absX, 0.6, absY, 0.4, 0) #混和設定不同比例 titles = ['Origin Image', 'absX', 'absY', 'Result'] #對應的圖 images = [img, absX, absY, dst] plt.rcParams['figure.figsize'] = [200, 100] plt.rcParams['font.size'] = 150 for i in range(4): # 畫4次 plt.subplot(1, 4, i + 1) plt.imshow(images[i], cmap='gray') plt.title(titles[i]) plt.xticks([]), plt.yticks([]) plt.show() ``` ![](https://i.imgur.com/DC4JwKV.jpg) **方法2-cv2.Scharr** OpenCV提供Scharr運算元,該運算元和Sobel有同樣的速度,且精度更高 cv2.Scharr()語法格式: dst=cv2.Scharr(src,ddepth,dx,dy[,scale[,delta[,borderType]]]) 注意:ddepth設定為”cv2.CV_64F”且dx跟dy不能同時為1 ```python= import cv2 o = cv2.imread('scharr.bmp',cv2.IMREAD_GRAYSCALE) scharrx = cv2.Scharr(o,cv2.CV_64F,1,0) scharrx = cv2.convertScaleAbs(scharrx) # 轉回uint8 scharrx = cv2.cvtColor(scharrx , cv2.COLOR_BGR2RGB) scharry = cv2.Scharr(o,cv2.CV_64F,0,1) scharry = cv2.convertScaleAbs(scharry) # 轉回uint8 scharry = cv2.cvtColor(scharry , cv2.COLOR_BGR2RGB) scharrxy = cv2.addWeighted(scharrx,0.5,scharry,0.5,0) scharrxy = cv2.cvtColor(scharrxy , cv2.COLOR_BGR2RGB) plt.rcParams['figure.figsize'] = [20,10] plt.rcParams['font.size'] = 20 titles = ['Origin Image', 'scharrx', 'scharry', 'scharrxy'] #對應的圖 images = [o, scharrx, scharry, scharrxy] plt.rcParams['figure.figsize'] = [200, 100] plt.rcParams['font.size'] = 150 for i in range(4): # 畫4次 plt.subplot(1, 4, i + 1) plt.imshow(images[i], cmap='gray') plt.title(titles[i]) plt.xticks([]), plt.yticks([]) plt.show() ``` ![](https://i.imgur.com/FUdWJSJ.jpg) **方法3-cv2.Canny** * 好的信噪比,即將非邊緣點判定為邊緣點的概率要低,將邊緣點判為非邊緣點的概率要低。 * 高的定位效能,即檢測出的邊緣點要儘可能在實際邊緣的中心。 步驟如下: 1. 用高斯濾波器平滑影像,去除影像雜訊 2. 用一階偏導有限差分計算梯度幅值和方向 3. 對梯度幅值進行非極大值抑制 4. 用雙閾值演算法檢測和連線邊緣 ![](https://i.imgur.com/mBArxCN.jpg) ![](https://i.imgur.com/a7y9vxG.jpg) ![](https://i.imgur.com/Wbvmifu.jpg) edges = cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]]) edges:表示輸出的邊緣圖,其大小和類型與輸入圖像相同 image:表示輸入圖像(必須是灰階圖像) threshold1:表示第一個滯後性閾值 threshold2:表示第二個滯後性閾值用來區分 weak edge(threshold1) 和 strong edge(threshold2),範圍都是 0 ~ 255,其中較大的閾值2用於檢測影象中明顯的邊緣,但一般情況下檢測的效果不會那麼完美,邊緣檢測出來是斷斷續續的。所以這時候用較小的閾值1將這些間斷的邊緣連線起來。通常選擇 threshold1 / threshold2 = 1/2 ~ 1/3,例如 (70, 140), (70, 210) apertureSize:用來計算梯度的 kernel size,也就是 Sobel 的 ksize,其默認值為3 L2gradient:選擇要用 L1 norm(絕對值平均)還是 L2 norm(平方根)當作梯度的大小。默認值為FALSE,預設是用 L1 norm True:表示使用更精確的L2範數進行計算(即兩個方向的倒數的平方和再開方) False:表示使用L1範數(直接將兩個方嚮導數的絕對值相加) ```python= import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread("sobel4.bmp") blurred = cv2.GaussianBlur(img, (3, 3), 0) #去除雜質 gray = cv2.cvtColor(blurred, cv2.COLOR_RGB2GRAY) edge_output = cv2.Canny(gray, 60, 110,apertureSize=3) canny = cv2.cvtColor(edge_output , cv2.COLOR_BGR2RGB) plt.rcParams['figure.figsize'] = [20,10] plt.rcParams['font.size'] = 20 plt.subplot(1, 2, 1) plt.imshow(img) plt.title("original") plt.xticks([]), plt.yticks([]) plt.show() plt.subplot(1, 2, 2) plt.imshow(canny) plt.title("Canny") plt.xticks([]), plt.yticks([]) plt.show() ``` ![](https://i.imgur.com/PzC79hR.jpg) **拿實際照片測試一下** ```python= import cv2 import numpy as np img = cv2.imread("noodle.jpg") imgnew = cv2.resize(img, (0,0), fx=0.2, fy=0.2) imgnew = cv2.cvtColor(imgnew , cv2.COLOR_BGR2RGB) blurred = cv2.GaussianBlur(imgnew, (5,5), 0) #去除雜質 gray = cv2.cvtColor(blurred, cv2.COLOR_RGB2GRAY) canny = cv2.Canny(gray, 70, 210,apertureSize=3) canny = cv2.cvtColor(canny , cv2.COLOR_BGR2RGB) plt.rcParams['figure.figsize'] = [80, 40] plt.rcParams['font.size'] = 150 plt.subplot(1, 2, 1) plt.imshow(imgnew) plt.title("original") plt.xticks([]), plt.yticks([]) plt.show() plt.subplot(1, 2, 2) plt.imshow(canny) plt.title("canny") plt.xticks([]), plt.yticks([]) plt.show() ``` ![](https://i.imgur.com/mnkzIj3.jpg) ![](https://i.imgur.com/TbPZHMV.jpg)