梯度简单来说就是求导。OpenCV 提供了三种不同的梯度滤波器,或者说高通滤波器:Sobel
,Scharr
和Laplacian
。
Sobel,Scharr 其实就是求一阶或二阶导数。Scharr 是对Sobel的优化(使用小的卷积核求解求解梯度角度时)。Laplacian 是求二阶导数。
sobelx=cv2.Sobel(gray,cv2.CV_64F,1,0,ksize=5)
参数按顺序为:原图、输出图像的深度、x方向求导的阶数、y方向求导的阶数、卷积核大小。
Sobel 算子是高斯平滑与微分操作的结合体,所以它的抗噪声能力很好。如果ksize=-1,会使用3x3 的Scharr 滤波器,它的的效果要比3x3 的Sobel 滤波器好(而且速度相同,所以在使用3x3 滤波器时应该尽量使用Scharr 滤波器)。3x3 的Scharr 滤波器卷积核如下:
laplacian=cv2.Laplacian(gray,cv2.CV_64F)
拉普拉斯算子可以使用二阶导数的形式定义,可假设其离散实现类似于二阶Sobel 导数,事实上,OpenCV 在计算拉普拉斯算子时直接调用Sobel 算子。
def gradient(self):
gray=cv2.imread(self.infile,0)
#参数1,0为只在x方向求一阶导数,最大可以求2阶导数。y方向同理。
#参数cv2.CV_64F结果图像的深度,可以使用-1,与原图像保持一致(np.uint8)
sobelx=cv2.Sobel(gray,cv2.CV_64F,1,0,ksize=5)
sobely=cv2.Sobel(gray,cv2.CV_64F,0,1,ksize=5)
laplacian=cv2.Laplacian(gray,cv2.CV_64F)
titles = ['raw', 'sobelx','sobely','laplacian']
images = [gray, sobelx,sobely,laplacian]
for i in range(4):
plt.subplot(1,4,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
下面打印了部分gray和sobelx的像素值。有点不太明白为什么出现大片的灰色。超出255和小于0的值是怎么处理的呢?
[[ 98 122 98 … 120 97 98]
[113 206 229 … 246 199 122]
[ 98 234 255 … 255 237 110]
…
[121 248 255 … 254 234 109]
[ 99 198 226 … 232 216 121]
[105 129 103 … 111 120 80]]
[[ 0. 2804. 1940. … -1938. -3142. 0.]
[ 0. 3515. 2286. … -2230. -3667. 0.]
[ 0. 4563. 2669. … -2555. -4429. 0.]
…
[ 0. 4397. 2688. … -2617. -4417. 0.]
[ 0. 3459. 2343. … -2115. -3467. 0.]
[ 0. 2854. 2036. … -1762. -2894. 0.]]
我们应该知道为什么要扩大图像的深度,而不采用原来的深度呢?
想象一下一个
从黑到白
的边界的导数是正数
,而一个从白到黑
的边界点导数却是负数
。如果原图像的深度是np.int8 时,所有的负值都会被截断变成0,换句话说就是把从白到黑边界丢失掉。所以如果这两种边界你都想检测到,最好的的办法就是将输出的数据类型设置的更高,比如cv2.CV_16S,cv2.CV_64F 等。取绝对值然后再把它转回到cv2.CV_8U。
如下图所示:直接用-1为参数,从白色到黑色的边界被丢弃了。而先扩深度,再去绝对值并减小深度,是可以检测出从白色到黑色的边界的。
(1) 此时若把我的代码中的cv2.CV_64F都改为-1,得到结果如下:
sobelx=cv2.Sobel(gray,-1,1,0,ksize=5)
sobely=cv2.Sobel(gray,-1,0,1,ksize=5)
laplacian=cv2.Laplacian(gray,-1)
(2)若我把得到的图像先取绝对值,再把数据深度转为和原来相同的。
sobelx=np.uint8(np.abs(cv2.Sobel(gray,cv2.CV_64F,1,0,ksize=5)))
sobely=np.uint8(np.abs(cv2.Sobel(gray,cv2.CV_64F,0,1,ksize=5)))
laplacian=np.uint8(np.abs(cv2.Laplacian(gray,cv2.CV_64F)))
可以参考卷积实现