Python OpenCV _5邊緣檢測(Sobel運算元,Laplacian運算元,Canny運算元)
Python OpenCV這個初級影象處理系列是參考他人的文章寫的,有些地方做了一些改動,沒有太多理論,側重程式碼實現,主要目的是將這些基本操作程式碼系統地梳理一遍,也是為了以後能快速查詢。
此係列原始碼在我的GitHub裡:https://github.com/yeyujujishou19/Python-OpenCV
一,Sobel運算元
Sobel運算元依然是一種過濾器,只是其是帶有方向的。在Python-OpenCV中,使用Sobel的運算元的函式原型如下:
dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])
前四個是必須的引數:
第一個引數是需要處理的影象;
第二個引數是影象的深度,-1表示採用的是與原影象相同的深度。目標影象的深度必須大於等於原影象的深度;
dx和dy表示的是求導的階數,0表示這個方向上沒有求導,一般為0、1、2。
其後是可選的引數:
dst不用解釋了;
ksize是Sobel運算元的大小,必須為1、3、5、7。
scale是縮放導數的比例常數,預設情況下沒有伸縮係數;
delta是一個可選的增量,將會加到最終的dst中,同樣,預設情況下沒有額外的值加到dst中;
borderType是判斷影象邊界的模式。這個引數預設值為cv2.BORDER_DEFAULT。
程式碼:
# coding=utf-8 import cv2 import numpy as np img = cv2.imread("D:/test/26.png", 0) ''' 在Sobel函式的第二個引數這裡使用了cv2.CV_16S。 因為OpenCV文件中對Sobel運算元的介紹中有這麼一句: “in the case of 8-bit input images it will result in truncated derivatives”。 即Sobel函式求完導數後會有負值,還有會大於255的值。 而原影象是uint8,即8位無符號數,所以Sobel建立的影象位數不夠,會有截斷。 因此要使用16位有符號的資料型別,即cv2.CV_16S。 在經過處理後,別忘了用convertScaleAbs()函式將其轉回原來的uint8形式。 否則將無法顯示影象,而只是一副灰色的視窗。convertScaleAbs()的原型為: dst = cv2.convertScaleAbs(src[, dst[, alpha[, beta]]]) 其中可選引數alpha是伸縮係數,beta是加到結果上的一個值。結果返回uint8型別的圖片。 由於Sobel運算元是在兩個方向計算的,最後還需要用cv2.addWeighted(...)函式將其組合起來。 其函式原型為: dst = cv2.addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]]) 其中alpha是第一幅圖片中元素的權重,beta是第二個的權重,gamma是加到最後結果上的一個值。 ''' x = cv2.Sobel(img, cv2.CV_16S, 1, 0) y = cv2.Sobel(img, cv2.CV_16S, 0, 1) absX = cv2.convertScaleAbs(x)# 轉回uint8 absY = cv2.convertScaleAbs(y) dst = cv2.addWeighted(absX, 0.5, absY, 0.5, 0) cv2.imshow("orign", img) cv2.imshow("absX", absX) cv2.imshow("absY", absY) cv2.imshow("Result", dst) cv2.waitKey(0) cv2.destroyAllWindows()
程式碼結果:
二,Laplacian運算元
原理:
影象中的邊緣區域,畫素值會發生“跳躍”,對這些畫素求導,在其一階導數在邊緣位置為極值,這就是Sobel運算元使用的原理——極值處就是邊緣。如下圖(下圖來自OpenCV官方文件):
如果對畫素值求二階導數,會發現邊緣處的導數值為0。如下(下圖來自OpenCV官方文件):
Laplace函式實現的方法是先用Sobel 運算元計算二階x和y導數,再求和:
在OpenCV-Python中,Laplace運算元的函式原型如下:
dst = cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])
前兩個是必須的引數:
第一個引數是需要處理的影象;
第二個引數是影象的深度,-1表示採用的是與原影象相同的深度。目標影象的深度必須大於等於原影象的深度;
其後是可選的引數:
dst不用解釋了;
ksize是運算元的大小,必須為1、3、5、7。預設為1。
scale是縮放導數的比例常數,預設情況下沒有伸縮係數;
delta是一個可選的增量,將會加到最終的dst中,同樣,預設情況下沒有額外的值加到dst中;
borderType是判斷影象邊界的模式。這個引數預設值為cv2.BORDER_DEFAULT。
程式碼:
# coding=utf-8
import cv2
import numpy as np
img = cv2.imread("D:/test/26.png", 0)
gray_lap = cv2.Laplacian(img, cv2.CV_16S, ksize=3)
dst = cv2.convertScaleAbs(gray_lap) # 轉回uint8
cv2.imshow("orign", img)
cv2.imshow('laplacian', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
程式碼結果:
三,Canny運算元
函式:
OpenCV-Python中Canny函式的原型為:
edge = cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient ]]])
必要引數:
第一個引數是需要處理的原影象,該影象必須為單通道的灰度圖;
第二個引數是閾值1;
第三個引數是閾值2。
其中較大的閾值2用於檢測影象中明顯的邊緣,但一般情況下檢測的效果不會那麼完美,邊緣檢測出來是斷斷續續的。所以這時候用較小的第一個閾值用於將這些間斷的邊緣連線起來。
可選引數中apertureSize就是Sobel運算元的大小。而L2gradient引數是一個布林值,如果為真,則使用更精確的L2範數進行計算(即兩個方向的倒數的平方和再開放),否則使用L1範數(直接將兩個方向導數的絕對值相加)。
函式返回一副二值圖,其中包含檢測出的邊緣。
程式碼一:
# coding=utf-8
import cv2
import numpy as np
img = cv2.imread("D:/test/26.png", 0)
'''
由於Canny只能處理灰度圖,所以將讀取的影象轉成灰度圖。
用高斯平滑處理原影象降噪。
呼叫Canny函式,指定最大和最小閾值,其中apertureSize預設為3。
'''
img = cv2.GaussianBlur(img, (3, 3), 0)
canny = cv2.Canny(img, 50, 150)
cv2.imshow("orign", img)
cv2.imshow('Canny', canny)
cv2.waitKey(0)
cv2.destroyAllWindows()
程式碼一結果:
程式碼二:
在執行時調整閾值大小的程式。其程式碼如下:
import cv2
import numpy as np
def CannyThreshold(lowThreshold):
detected_edges = cv2.GaussianBlur(gray, (3, 3), 0)
detected_edges = cv2.Canny(detected_edges, lowThreshold, lowThreshold * ratio, apertureSize=kernel_size)
dst = cv2.bitwise_and(img, img, mask=detected_edges) # just add some colours to edges from original image.
cv2.imshow('canny demo', dst)
lowThreshold = 0
max_lowThreshold = 100
ratio = 3
kernel_size = 3
img = cv2.imread("D:/test/26.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.namedWindow('canny demo')
cv2.createTrackbar('Min threshold', 'canny demo', lowThreshold, max_lowThreshold, CannyThreshold)
CannyThreshold(0) # initialization
if cv2.waitKey(0) == 27:
cv2.destroyAllWindows()
程式碼二結果:
歡迎掃碼關注微信公眾號