圖像梯度 邊緣檢測
圖像梯度,圖像邊界
使用到的函數(shù)有: cv2.Sobel(), cv2.Schar(), cv2.Laplacian()
梯度簡單來說就是求導(dǎo),OpenCV 提供了三種不同的梯度濾波器,或者說高通濾波器: Sobel,Scharr 和 Laplacian。
- Sobel, Scharr 其實(shí)就是求一階或二階導(dǎo)數(shù)。
- Scharr 是對 Sobel(使用小的卷積核求解求解梯度角度時(shí))的優(yōu)化。
- Laplacian 是求二階導(dǎo)數(shù)。
Sobel算子 cv2.Sobel()
dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])
前四個(gè)是必須的參數(shù):
- src參數(shù)是需要處理的圖像;
- ddepth參數(shù)是圖像的深度,-1表示采用的是與原圖像相同的深度。目標(biāo)圖像的深度必須大于等于原圖像的深度;
- dx和dy表示的是求導(dǎo)的階數(shù),0表示這個(gè)方向上沒有求導(dǎo),一般為0、1、2。
其后是可選的參數(shù):
- ksize是Sobel算子的大小,必須為1、3、5、7。
- scale是縮放導(dǎo)數(shù)的比例常數(shù),默認(rèn)情況下沒有伸縮系數(shù);
- delta是一個(gè)可選的增量,將會加到最終的dst中,同樣,默認(rèn)情況下沒有額外的值加到dst中;
- borderType是判斷圖像邊界的模式。這個(gè)參數(shù)默認(rèn)值為cv2.BORDER_DEFAULT。
Sobel算子是高斯平滑與微分操作的結(jié)合體,所以它的抗噪聲能力很好,可以設(shè)定求導(dǎo)的方向(xorder 或 yorder),還可以設(shè)定使用的卷積核的大?。╧size),如果 ksize=-1,會使用 3x3 的 Scharr 濾波器,它的的效果要比 3x3 的 Sobel 濾波器好(而且速度相同,所以在使用 3x3 濾波器時(shí)應(yīng)該盡量使用 Scharr 濾波器)。
%matplotlib inline
from matplotlib import pyplot as plt
import cv2
import numpy as np
img = cv2.imread('edage.jpg', 0)
"""
在Sobel函數(shù)的第二個(gè)參數(shù)這里使用了cv2.CV_16S。
因?yàn)镺penCV文檔中對Sobel算子的介紹中有這么一句:
“in the case of 8-bit input images it will result in truncated derivatives”。
即Sobel函數(shù)求完導(dǎo)數(shù)后會有負(fù)值,還有會大于255的值。而原圖像是uint8,即8位無符號數(shù),
所以Sobel建立的圖像位數(shù)不夠,會有截?cái)唷R虼艘褂?6位有符號的數(shù)據(jù)類型,即cv2.CV_16S
"""
x = cv2.Sobel(img,cv2.CV_16S,1,0)
y = cv2.Sobel(img,cv2.CV_16S,0,1)
"""
在經(jīng)過處理后,別忘了用convertScaleAbs()函數(shù)將其轉(zhuǎn)回原來的uint8形式。否則將無法顯示圖像,而只是一副灰色的窗口。
dst = cv2.convertScaleAbs(src[, dst[, alpha[, beta]]])
可選參數(shù)alpha是伸縮系數(shù),beta是加到結(jié)果上的一個(gè)值。結(jié)果返回uint8類型的圖片
"""
absX = cv2.convertScaleAbs(x) # 轉(zhuǎn)回uint8
absY = cv2.convertScaleAbs(y)
"""
由于Sobel算子是在兩個(gè)方向計(jì)算的,最后還需要用cv2.addWeighted(...)函數(shù)將其組合起來
dst = cv2.addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]])
其中alpha是第一幅圖片中元素的權(quán)重,beta是第二個(gè)的權(quán)重,gamma是加到最后結(jié)果上的一個(gè)值。
"""
dst = cv2.addWeighted(absX,0.5,absY,0.5,0)
img_h1 = np.hstack([img, absX])
img_h2 = np.hstack([absY, dst])
img_all = np.vstack([img_h1, img_h2])
plt.figure(figsize=(20,10))
plt.imshow(img_all, cmap=plt.cm.gray)
plt.show()

Laplacian 算子
拉普拉斯算子可以使用二階導(dǎo)數(shù)的形式定義,可假設(shè)其離散實(shí)現(xiàn)類似于二階Sobel導(dǎo)數(shù),事實(shí)上,OpenCV在計(jì)算拉普拉斯算子時(shí)直接調(diào)用Sobel 算子。
Laplacian算子:圖像中的邊緣區(qū)域,像素值會發(fā)生“跳躍”,對這些像素求導(dǎo),在其一階導(dǎo)數(shù)在邊緣位置為極值,這就是Sobel算子使用的原理——極值處就是邊緣。
dst = cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])
前兩個(gè)是必須的參數(shù):
- src參數(shù)是需要處理的圖像;
- ddepth參數(shù)是圖像的深度,-1表示采用的是與原圖像相同的深度。目標(biāo)圖像的深度必須大于等于原圖像的深度;
其后是可選的參數(shù):
- ksize是算子的大小,必須為1、3、5、7。默認(rèn)為1。
- scale是縮放導(dǎo)數(shù)的比例常數(shù),默認(rèn)情況下沒有伸縮系數(shù);
- delta是一個(gè)可選的增量,將會加到最終的dst中,同樣,默認(rèn)情況下沒有額外的值加到dst中;
- borderType是判斷圖像邊界的模式。這個(gè)參數(shù)默認(rèn)值為cv2.BORDER_DEFAULT。
拉普拉斯對噪聲敏感,會產(chǎn)生雙邊效果。不能檢測出邊的方向。通常不直接用于邊的檢測,只起輔助的角色,檢測一個(gè)像素是在邊的亮的一邊還是暗的一邊利用零跨越,確定邊的位置。
%matplotlib inline
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("edage.jpg", 0)
gray_lap = cv2.Laplacian(img, cv2.CV_16S, ksize=3)
dst = cv2.convertScaleAbs(gray_lap)
plt.imshow(dst, cmap=plt.cm.gray)
plt.show()

邊緣檢測
邊緣檢測的一般步驟:
- 濾波——消除噪聲
- 增強(qiáng)——使邊界輪廓更加明顯
- 檢測——選出邊緣點(diǎn)
Canny邊緣檢測
圖像的邊緣檢測的原理是檢測出圖像中所有灰度值變化較大的點(diǎn),而且這些點(diǎn)連接起來就構(gòu)成了若干線條,這些線條就可以稱為圖像的邊緣。
Canny邊緣檢測算子是John F. Canny于 1986 年開發(fā)出來的一個(gè)多級邊緣檢測算法。
Canny算子檢測原理是通過圖像信號函數(shù)的極大值來判定圖像的邊緣像素點(diǎn)。邊緣檢測的算法主演是基于圖像強(qiáng)度的一階和二階微分操作,但導(dǎo)數(shù)通常對噪聲很敏感,邊緣檢測算法常常需要根據(jù)圖像源的數(shù)據(jù)進(jìn)行預(yù)處理操作,因此必須采用濾波器來改善與噪聲有關(guān)的邊緣檢測的性能。在進(jìn)行Canny算子邊緣檢測前,應(yīng)當(dāng)先對原始數(shù)據(jù)與高斯模板進(jìn)行卷積操作,得到的圖像與原圖像相比有些模糊。通常使用高斯平滑濾波器卷積降噪。
edge = cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient ]]])
必要參數(shù):
- 第一個(gè)參數(shù)是需要處理的原圖像,該圖像必須為單通道的灰度圖;
- 第二個(gè)參數(shù)是閾值1;
- 第三個(gè)參數(shù)是閾值2。 其中較大的閾值2用于檢測圖像中明顯的邊緣,但一般情況下檢測的效果不會那么完美,邊緣檢測出來是斷斷續(xù)續(xù)的。所以這時(shí)候用較小的第一個(gè)閾值用于將這些間斷的邊緣連接起來.
可選參數(shù)中apertureSize就是Sobel算子的大小。
而L2gradient參數(shù)是一個(gè)布爾值,如果為真,則使用更精確的L2范數(shù)進(jìn)行計(jì)算(即兩個(gè)方向的倒數(shù)的平方和再開放),否則使用L1范數(shù)(直接將兩個(gè)方向?qū)?shù)的絕對值相加)
Canny邊緣檢測基本原理:
- 圖象邊緣檢測必須滿足兩個(gè)條件:一能有效地抑制噪聲;二必須盡量精確確定邊緣的位置。
- 根據(jù)對信噪比與定位乘積進(jìn)行測度,得到最優(yōu)化逼近算子。這就是Canny邊緣檢測算子。
- 類似與Marr(LoG)邊緣檢測方法,也屬于先平滑后求導(dǎo)數(shù)的方法。
Canny 的目標(biāo)是找到一個(gè)最優(yōu)的邊緣檢測算法,最優(yōu)邊緣檢測的含義是:
- 好的檢測 - 算法能夠盡可能多地標(biāo)識出圖像中的實(shí)際邊緣。
- 好的定位 - 標(biāo)識出的邊緣要盡可能與實(shí)際圖像中的實(shí)際邊緣盡可能接近。
- 最小響應(yīng) - 圖像中的邊緣只能標(biāo)識一次,并且可能存在的圖像雜訊不應(yīng)標(biāo)識為邊緣。
canny 算法五步驟
- 高斯模糊
- 灰度轉(zhuǎn)換
- 計(jì)算梯度
- 非最大信號抑制
- 高低閾值輸出二值圖像
%matplotlib inline
import cv2
import numpy as np
import matplotlib.pyplot as plt
def edge_detect(img):
#高斯模糊,降低噪聲
blurred = cv2.GaussianBlur(img,(3,3),0)
#灰度圖像
gray = cv2.cvtColor(blurred,cv2.COLOR_RGB2GRAY)
#圖像梯度
xgrad = cv2.Sobel(gray,cv2.CV_16SC1,1,0)
ygrad = cv2.Sobel(gray,cv2.CV_16SC1,0,1)
#計(jì)算邊緣
#50和150參數(shù)必須符合1:3或者1:2
edge_output = cv2.Canny(xgrad,ygrad,50,150)
dst = cv2.bitwise_and(img,img,mask=edge_output)
return edge_output, dst
img = cv2.imread('edage.jpg')
edge_output, canny_edge = edge_detect(img.copy())
plt.figure(figsize=(20, 8))
plt.subplot(131)
plt.imshow(img[:,:,::-1])
plt.subplot(132)
plt.imshow(canny_edge[:,:,::-1])
plt.subplot(133)
plt.imshow(edge_output, cmap=plt.cm.gray)
plt.tight_layout()
plt.show()
