本文为课程《OpenCV计算机视觉实战》的课程笔记。
本章介绍如何计算图像梯度。图像的梯度表示每个像素与周围像素之间的差异程度。
1 Sobel算子
Sobel算子关注像素点和上下左右四个像素点的差异程度,即分别从x方向和y方向计算锚点梯度。
先用得到锚点
方向的梯度,再用
得到
方向的梯度。最后,将两个方向的梯度加权相加,即可得到锚点的梯度值。 ※这里的“×”是相同位置的元素相乘
根据上图模型,可知Sobel算子是用锚点右边的像素值减去左边像素值,用下边像素值减去上边像素值(注意不是形状外部减去内部)。
CV2中Sobel算子的函数原型如下:
cv2.Sobel(src, ddepth, dx, dy, ksize)
- ddepth:输出图像的深度(所占位数),-取1表示与原图像一致
- dx、dy:待计算的梯度方向。例如,当
、
时,表示求
方向的一阶导数。
※不建议dx、dy同时取1,这样效果不佳。 - ksize:算子的大小,必须为不大于31的奇数,默认取3(当
时,应该取的是默认值)
可选参数还有scale、delta、borderType,具体含义参考这篇博文。
返回值是图像梯度图。
首先看一下用Sobel算子计算圆形图像的结果:
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.Sobel(img, cv2.CV_64F,0, 1, ksize=3)
sobely = cv2.convertScaleAbs(sobely)
# 求sobelx和sobely的加权和
sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
cv_show('sobelxy',np.hstack((sobelx, sobely, sobelxy)))
计算深度选取了cv2.CV_64F表示64位浮点数(可以存储负数,防止溢出后cv2截断),这篇博文表示imshow()在显示图像时像素点的取值范围为[0, 1]:
- 如果像素值为负:取绝对值
- 若像素值大于1:映射为1,否则保留原值
当cv2.imshow()处理不同深度的图像时的映射方式如下:
- CV_8U,范围[0, 255]:显示原图
- CV_16U,范围[0, 65535]:除以256映射到[0, 255]
- CV_32F和CV_64F,范围[0, 1]:乘以255映射到[0, 255]
课程讲说,由于是右侧像素点减去左侧像素点,故cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)左半圆的计算结果为正,右半圆的计算结果为负,而cv2.imshow()语句将负数显示为0(说法跟上述博文冲突),因此最后cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)的显示结果为半个圆的轮廓。
同理。
sobelx = cv2.convertScaleAbs(sobelx)
cv2.convertScaleAbs()方法先对传入数组取绝对值,然后转换为8位无符号数,从而能够显示完整的轮廓。
2 Scharr算子
靠近锚点的像素权重更大,因此效果更明显。
cv2中的写法与Sobel算子相近,详见4。
3 Laplacian算子
是二阶导方法,表征的是锚点和周围像素点的差异程度,即变化率。该算子要比Scharr算子和Sobel算子要更敏感,因此更容易受噪音影响。
通常与其他方法结合使用。
cv2中的写法详见4。
4 三种算子的比较
# 不同算子之间的差异
img = cv2.resize(cv2.imread('./img/cat.jpg',cv2.IMREAD_GRAYSCALE),(300, 300))
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.convertScaleAbs(sobely)
sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
scharrx = cv2.Scharr(img, cv2.CV_64F, 1, 0)
scharry = cv2.Scharr(img, cv2.CV_64F, 0, 1)
scharrx = cv2.convertScaleAbs(scharrx)
scharry = cv2.convertScaleAbs(scharry)
scharrxy = cv2.addWeighted(scharrx, 0.5, scharry, 0.5, 0)
laplacian = cv2.Laplacian(img, cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)
res = np.hstack((img, sobelxy,scharrxy, laplacian))
cv_show('res', res)
从上图可以看出,通过Sobel算子得到的轮廓较为干净,但细节有限;通过Scharr算子得到的梯度对比强烈,且蕴含信息更丰富;Laplacian算子的计算结果中包含的信息较少。
版权声明:本文为博主mustuo原创文章,版权归属原作者,如果侵权,请联系我们删除!