1. 程式人生 > >Python OpenCV _5邊緣檢測(Sobel運算元,Laplacian運算元,Canny運算元)

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()

程式碼二結果:

                    

 

歡迎掃碼關注微信公眾號