圖像的梯度計算的是圖像變化的速度,對於邊緣部分呢灰度值變換大,梯度值也大,相反則灰度值變化小,梯度值小
圖像梯度值嚴格說應該需要求導數,但是圖像梯度一般通過計算像素值的差,來得到梯度的近似值
以下介紹三種算子的使用Sobel算子、Scharr算子和Laplacian算子
Sobel算子是一種離散的微分算子,該算子結合了高斯平滑處理和微分求導運算。 該算子利用局部差尋找邊緣
Sobel算子如下所示
-1 | 0 | 1 |
-2 | 0 | 2 |
-1 | 0 | 1 |
圖一
-1 | -2 | -1 |
0 | 0 | 0 |
1 | 2 | 1 |
圖二
將Sobel算子圖一和原始圖像卷積可以得到水平方向的像素值變化,與圖二卷積的到垂直方向的像素值變化
P1 | P2 | P3 |
P4 | P5 | P6 |
P7 | P8 | P9 |
如果要計算P5的水平方向的偏導數,則需要Sobel算子及P5鄰域點
公式為:P5x = (P3 - P1 ) + 2 * ( P6 - P4 ) + ( P9 - P7 )
用P5右側的像素點減左側的像素點,因為P4和P6離P5較近,所以權值為2,其他為1
垂直方向類似,垂直是下減上
函數形式
dst = cv2.Sobel( src , ddepth, dx , dy [, ksize [, scale [, delta [, borderType]]]])
dst目標圖像
src原始圖像
ddepth 輸出圖像的深度,
dx x方向上的求導階數
dy y方向上的求導階數
ksize 代表Sobel核的大小,該值為-1時,會使用Scharr算子進行計算
scale計算導數值時采用的縮放因子,默認為1,沒有縮放
delta加在目標圖像dst上的值,默認為0
borderType 邊界樣式 (上篇博客提到過不再重復)
可以將ddepth設置為-1,在計算時可能得到的結果時錯誤的,
如果處理的圖像是8位圖類型,且ddepth為-1 意味着指定運算結果也是8位圖類型,所有負數都自動處理為0
為了避免信息丟失在計算是要先使用更高的數據類型嗯 cv2.CV_64F,在通過取絕對值將其映射位8位圖類型
所以通常要將ddepth設置位cv2.CV_64F
如下代碼所示
1 import cv2 2 o = cv2.imread("example.bmp" , cv2.IMREAD_GRAYSCALE) 3 Sobelx = cv2.Sobel(o , -1 , 1 , 0) 4 cv2.imshow("original" , o) 5 cv2.imshow("x" , Sobelx) 6 cv2.waitKey() 7 cv2.destroyAllWindows()
原圖
效果圖
如上圖所示在8位灰度圖中因為黑色的像素值位0,而白色為255,當使用Sobel算子后,對其水平方向計算近似偏導數
右側減左側,右側邊緣得到的是正數所以可以正常顯示,而左側的邊緣得到的是負數被處理為0后便顯示黑色不能得到准確的結果
將ddepth設置為cv2.CV_64F后 還要取絕對值才能獲得所需的圖像,則需要絕對值函數 cv2.convertScaleAbs()
dx = 1 , dy = 0
1 import cv2 2 o = cv2.imread("example.bmp" , cv2.IMREAD_GRAYSCALE) 3 Sobelx = cv2.Sobel(o , cv2.CV_64F , 1 ,0) 4 Sobelx = cv2.convertScaleAbs(Sobelx) 5 cv2.imshow("original" , o) 6 cv2.imshow("x" , Sobelx) 7 cv2.waitKey() 8 cv2.destroyAllWindows()
如圖 可以獲得完整邊緣信息
垂直方向的邊緣信息 dx = 0 . dy = 1
1 import cv2 2 o = cv2.imread("example.bmp" , cv2.IMREAD_UNCHANGED) 3 Sobely = cv2.Sobel(o , cv2.CV_64F , 0 , 1) 4 Sobely = cv2.convertScaleAbs(Sobely) 5 cv2.imshow("original" , o) 6 cv2.imshow("y" , Sobely) 7 cv2.waitKey() 8 cv2.destroyAllWindows()
如圖
當dx =1 , dy = 1
1 import cv2 2 o = cv2.imread("example.bmp" , cv2.IMREAD_GRAYSCALE) 3 Sobelxy = cv2.Sobel(o , cv2.CV_64F , 1 , 1) 4 sobelxy = cv2.convertScaleAbs(Sobelxy) 5 cv2.imshow("original" , o) 6 cv2.imshow("xy" , Sobelxy) 7 cv2.waitKey() 8 cv2.destroyAllWindows()
沒有達到我們想要的結果,而是只留下幾個點
若想要x,y方向都顯示邊緣需要兩個方向分別處理,然后向疊加
import cv2 o = cv2.imread("example.bmp" , cv2.IMREAD_GRAYSCALE ) Sobelx = cv2.Sobel(o , cv2.CV_64F , 1 , 0) Sobely = cv2.Sobel(o , cv2.CV_64F , 0 , 1) Sobelx = cv2.convertScaleAbs(Sobelx) Sobely = cv2.convertScaleAbs(Sobely) Sobelxy = cv2.addWeighted(Sobelx , 0.5 , Sobely , 0.5 , 0) cv2.imshow("original" , o) cv2.imshow("xy" , Sobelxy) cv2.waitKey() cv2.destroyAllWindows()
Scharr算子
在使用3x3的Sobel算子是精度可能不高,Scharr速度與Sobel算子一樣 ,但精度更高
Scharr算子通常為
-3 | 0 | 3 |
-10 | 0 | 10 |
-3 | 0 | 3 |
-3 | -10 | -3 |
0 | 0 | 0 |
3 | 10 | 3 |
函數形式為
dst = cv2.Scharr( src , ddepth , dx , dy [ , scale [ ,delta [, borderType]]])
與Sobel相似少了Ksize參數, 即當Sobel中ksize = -1 時會使用Scharr算子計算
函數cv2.Scharr()與cv2.Sobel()相似
但有一些約束條件
dx >= 0 && dy >=0 && dx + dy == 1
x方向和y方向的邊緣疊加
1 import cv2 2 o = cv2.imread("example.bmp" , cv2.IMREAD_GRAYSCALE) 3 Scharrx = cv2.Scharr(o , cv2.CV_64F , 1 , 0) 4 Scharry = cv2.Scharr(o , cv2.CV_64F , 0 , 1) 5 Scharrx = cv2.convertScaleAbs(Scharrx) 6 Scharry = cv2.convertScaleAbs(Scharry) 7 Scharrxy = cv2.addWeighted(Scharrx , 0.5 , Scharry , 0.5 , 0) 8 cv2.imshow("original" , o) 9 cv2.imshow("xy" , Scharrxy) 10 cv2.waitKey() 11 cv2.destroyAllWindows()
Sobel和Scharr的比較
原圖
Sobel
Scharr
Laplacian算子
該算子是一個二階導數算子,具有旋轉不變性,可以滿足不同方向的圖像邊緣銳化(邊緣檢測)的要求,
通常情況下算子的系數和要為0
Laplacian算子
0 | 1 | 0 |
1 | -4 | 1 |
0 | 1 | 0 |
p1 | p2 | p3 |
p4 | p5 | p6 |
p7 | p8 | p9 |
P5點的近似導數值
P5lap =( P2 + P4 + P6 + P8 ) - 4* P5
該結果可能是正數也可能是負數,需要對計算結果取絕對值
函數形式
dst = cv2.Laplacian(src , ddepth , [,ksize [ ,scale [, delta [.borderType]]]])
dst 目標圖像
src原始圖像
ddepth目標時圖像的深度
ksize二階導數的核尺寸大小
scale 計算Laplacian值的縮放比例因子
delta 夾道目標圖像上的可選值
borderType 邊界樣式
1 import cv2 2 o = cv2.imread("example.bmp" , cv2.IMREAD_GRAYSCALE) 3 Laplacian = cv2.Laplacian(o , cv2.CV_64F) 4 Laplacian = cv2.convertScaleAbs(Laplacian) 5 cv2.imshow("original" , o) 6 cv2.imshow("Laplacina" , Laplacian) 7 cv2.waitKey() 8 cv2.destroyAllWindows()