- 本篇记录学习图像梯度的计算。
- 查找图像渐变,边缘等
- 将学习以下函数:cv2.Sobel(),cv2.Scharr(),cv2.Laplacian()等
原理:
梯度简单来说就是求导。OpenCV 提供了三种不同的梯度滤波器,或者说高通滤波器: Sobel,Scharr 和 Laplacian。
Sobel, Scharr 其实就是求一阶或二阶导数。 Scharr 是对 Sobel(使用小的卷积核求解求解梯度角度时)的优化。 Laplacian 是求二阶导数。
Sobel 算子和 Scharr 算子:
Sobel 算子是高斯平滑与微分操作的结合体,所以它的抗噪声能力很好。我们可以设定求导的方向( xorder 或 yorder)。
水平梯度=右-左;垂直梯度=下-上。我们以下图为例进行展示:
在opencv3中,看老师视频会讲到要取绝对值运算,即要用到cv.convertScaleAbs()函数,因为水平梯度=右-左,如图
因此输出应该是只有左半部分一个半圆弧,右边会被截断。但是我在使用opencv4进行计算的时候,发现左右都有一个圆弧,(猜测原因是opencv4进行了改进)代码及结果如下:
垂直梯度运算同样opencv3和人 opencv4运算结果不一样。
但是,如果图像深度和原图像保持一致,即cv.Sobel(src, -1,1, 0, ksize=3)函数的第二个参数填入 -1,结果就会出现截断情况,如图所示:
在进行梯度运算是水平和垂直要分别进行计算,最后在进行合成,用公式:,不建议进行一块运算,原因如下图:
整体运算:
分开运算-->再合成:
# -*- coding:utf-8 -*-
# Author : MMagicLoren
# @Email : [email protected]
# @Time : 2019/10/13 15:59
# @File : 图像梯度计算.py
# @Project : Workspace
import cv2 as cv
def sobel_demo(image):
grad_x = cv.Sobel(src, cv.CV_64F, 1, 0, ksize=3)
gradx = cv.convertScaleAbs(grad_x)
grad_y = cv.Sobel(src, cv.CV_64F, 0, 1, ksize=3)
grady = cv.convertScaleAbs(grad_y)
gradxy = cv.addWeighted(gradx, 0.5, grady, 0.5, 0)
cv.imshow("grad_x", grad_x) # 将src图片放入该创建的窗口中
cv.imshow("grad_y", grad_y) # 将src图片放入该创建的窗口中
cv.imshow("gradxy", gradxy) # 将src图片放入该创建的窗口中
if __name__ == '__main__':
src = cv.imread("F:/Pycharm/opencv_exercises-master/images/circular.png") # 读入图片放进src中
cv.namedWindow("input image", cv.WINDOW_AUTOSIZE) # 创建窗口, 窗口尺寸自动调整
cv.imshow("input image", src)
sobel_demo(src)
cv.waitKey(0) # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
cv.destroyAllWindows()
还可以设定使用的卷积核的大小( ksize)。如果 ksize=-1,会使用 3x3 的 Scharr 滤波器,它的的效果要比 3x3 的 Sobel 滤波器好(而且速度相同,所以在使用 3x3 滤波器时应该尽量使用 Scharr 滤波器)。 3x3 的 Scharr 滤波器卷积核如下:
# -*- coding:utf-8 -*-
# Author : MMagicLoren
# @Email : [email protected]
# @Time : 2019/10/13 15:59
# @File : 图像梯度计算.py
# @Project : Workspace
import cv2 as cv
def scharr_demo(image):
scharr_x = cv.Scharr(src, cv.CV_64F, 1, 0)
scharrx = cv.convertScaleAbs(scharr_x)
scharr_y = cv.Scharr(src, cv.CV_64F, 0, 1)
scharry = cv.convertScaleAbs(scharr_y)
scharrxy = cv.addWeighted(scharrx, 0.5, scharry, 0.5, 0)
# cv.imshow("grad_x", grad_x) # 将src图片放入该创建的窗口中
# cv.imshow("grad_y", grad_y) # 将src图片放入该创建的窗口中
cv.imshow("gradxy", scharrxy) # 将src图片放入该创建的窗口中
if __name__ == '__main__':
src = cv.imread("F:/Pycharm/opencv_exercises-master/images/CrystalLiu1.jpg") # 读入图片放进src中
cv.namedWindow("input image", cv.WINDOW_AUTOSIZE) # 创建窗口, 窗口尺寸自动调整
cv.imshow("input image", src)
# sobel_demo(src)
scharr_demo(src)
cv.waitKey(0) # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
cv.destroyAllWindows()
可见scharr算子对线条更加敏感,对图像描述的更细致。
laplacian算子:
laplacian算子在实际应用中很少单独用到,都是结合一些其他的函数共同使用。
# -*- coding:utf-8 -*-
# Author : MMagicLoren
# @Email : [email protected]
# @Time : 2019/10/13 15:59
# @File : 图像梯度计算.py
# @Project : Workspace
import cv2 as cv
def laplacian_demo(image):
laplacian = cv.Laplacian(src, cv.CV_64F)
laplacian = cv.convertScaleAbs(laplacian)
cv.imshow("laplacian", laplacian) # 将src图片放入该创建的窗口中
if __name__ == '__main__':
src = cv.imread("F:/Pycharm/opencv_exercises-master/images/CrystalLiu1.jpg") # 读入图片放进src中
cv.namedWindow("input image", cv.WINDOW_AUTOSIZE) # 创建窗口, 窗口尺寸自动调整
cv.imshow("input image", src)
# sobel_demo(src)
# scharr_demo(src)
laplacian_demo(src)
cv.waitKey(0) # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
cv.destroyAllWindows()
我们也可以自己定义一个拉普拉斯算子进行计算,看看效果如何。定义算子为:
# -*- coding:utf-8 -*-
# Author : MMagicLoren
# @Email : [email protected]
# @Time : 2019/10/13 15:59
# @File : 图像梯度计算.py
# @Project : Workspace
import cv2 as cv
import numpy as np
def laplacian_demo(image):
# laplacian = cv.Laplacian(src, cv.CV_64F)
kernel = np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1, ]])
dst = cv.filter2D(src, cv.CV_64F, kernel=kernel)
laplacian = cv.convertScaleAbs(dst)
cv.imshow("laplacian", laplacian) # 将src图片放入该创建的窗口中
if __name__ == '__main__':
src = cv.imread("F:/Pycharm/opencv_exercises-master/images/CrystalLiu1.jpg") # 读入图片放进src中
cv.namedWindow("input image", cv.WINDOW_AUTOSIZE) # 创建窗口, 窗口尺寸自动调整
cv.imshow("input image", src)
# sobel_demo(src)
# scharr_demo(src)
laplacian_demo(src)
cv.waitKey(0) # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
cv.destroyAllWindows()
可见自定义的算子也是对原始算子的一个增强。