入门详解Python OpenCV图像处理(第五部分):图像金字塔、图像梯度与Canny边缘检测算法
一、图像金字塔
图像金字塔是图像多尺度表达的一种,是一种以多分辨率来解释图像的有效且概念简单的结构。一幅的图像金是一系列以金字塔形状排列的分辨率逐步降低,且来源于同一张原始图的图像集合。其通过梯次向下采样获得,直到达到某个终止条件才停止采样。我们将一层一层的图像比喻成金字塔,层级越高,则图像越小,分辨率越低字塔
高斯金字塔:用于下采样。高斯金字塔是最基本的图像塔。原理:首先将原图像作为最底层图像G0(高斯金字塔的第0层),利用高斯核(5*5)对其进行卷积,然后对卷积后的图像进行下采样(去除偶数行和列)得到上一层图像G1,将此图像作为输入,重复卷积和下采样操作得到更上一层图像,反复迭代多次,形成一个金字塔形的图像数据结构,即高斯金字塔。
拉普拉斯金字塔:用于重建图像,也就是预测残差,对图像进行最大程度的还原。比如一幅小图像重建为一幅大图,原理:用高斯金字塔的每一层图像减去其上一层图像上采样并高斯卷积之后的预测图像,得到一系列的差值图像即为 LP 分解图像。
- reduce = 高斯模糊 + 降采样
- expand = 扩大 + 卷积
- PyrDown:降采样
- PyrUp:还原
- 高斯金字塔和拉普拉斯金字塔
代码如下:
import cv2 as cv # 高斯金字塔 def pyramid_demo(image): level = 3 # 金字塔层数 temp = image.copy() pyramid_images = [] for i in range(level): dst = cv.pyrDown(temp) # 降采样 pyramid_images.append(dst) # 降采样的结果添加进列表 cv.imshow('pyramid_down' + str(i), dst) # 金字塔第几层 imshow temp = dst.copy() # 采样的图像又赋给temp 接着降采样 return pyramid_images # 拉普拉斯金字塔 # 由高斯金字塔可以构建拉普拉斯金字塔 def lapalian_demo(image): pyramid_images = pyramid_demo(image) level = len(pyramid_images) # 求层数 for i in range(level - 1, -1, -1): # 每次递减 if (i - 1) < 0: expand = cv.pyrUp(pyramid_images[i], dstsize=image.shape[:2]) # 升采样 lpls = cv.subtract(image, expand) cv.imshow("lapalian_down" + str(i), lpls) else: expand = cv.pyrUp(pyramid_images[i], dstsize=pyramid_images[i - 1].shape[:2]) lpls = cv.subtract(pyramid_images[i - 1], expand) cv.imshow("lapalian_down_" + str(i), lpls) if __name__ == '__main__': img = cv.imread(r'./test/004.jpg') # 图片须是2^n 格式大小 例如(512*512)的 cv.imshow("input image", img) lapalian_demo(img) cv.waitKey(0) cv.destroyAllWindows()
运行效果如下:
二、图像梯度
图像梯度可以把图像看成二维离散函数,图像梯度其实就是这个二维离散函数的求导。
1. Sobel算子
- Sobel算子用来计算图像灰度函数的近似梯度。Sobel算子根据像素点上下、左右邻点灰度加权差,在边缘处达到极值这一现象检测边缘。对噪声具有平滑作用,提供较为精确的边缘方向信息,边缘定位精度不够高。当对精度要求不是很高时,是一种较为常用的边缘检测方法。
- Sobel具有平滑和微分的功效。即:Sobel算子先将图像横向或纵向平滑,然后再纵向或横向差分,得到的结果是平滑后的差分结果。
cv2.Sobel(src, ddepth, dx, dy, dst=None, ksize=None, scale=None, delta=None, borderType=None)
- src:输入需要处理的图像
- ddepth:输出图像深度
- dx:x方向上的差分阶数,1或0
- dy:y 方向上的差分阶数,1或0
- ksize:Sobel算子的大小,必须为1、3、5、7。
- scale:缩放导数的比例常数,默认情况下没有伸缩系数。
- delta:一个可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到 dst 中。
- borderType:判断图像边界的模式,这个参数默认值为cv2.BORDER_DEFAULT。
import cv2 def sobel_demo(image): grad_x = cv2.Sobel(image, cv2.CV_32F, 1, 0) # x方向一阶导数 grad_y = cv2.Sobel(image, cv2.CV_32F, 0, 1) # y方向一阶导数 # 一阶导数算出来可能有正负 最后全部转到8位的图像上去 gradx = cv2.convertScaleAbs(grad_x) grady = cv2.convertScaleAbs(grad_y) cv2.imshow("gradient_x", gradx) cv2.imshow("gradient_y", grady) # 计算两个数组的加权和 gradxy = cv2.addWeighted(gradx, 0.5, grady, 0.5, 0) cv2.imshow("gradient_xy", gradxy) if __name__ == "__main__": src = cv2.imread(r"./test/018.jpg") src = cv2.resize(src, None, fx=0.5, fy=0.5) cv2.imshow("image", src) sobel_demo(src) cv2.waitKey(0) cv2.destroyAllWindows()
运行效果如下:
2. Scharr算子
原理跟 Sobel 算子类似,是 Sobel 算子的增强版本,当你用 Sobel 算子得不到很好的边缘效果时,用 Scharr 算子吧!
import cv2 def scharr_demo(image): grad_x = cv2.Scharr(image, cv2.CV_32F, 1, 0) # x方向一阶导数 grad_y = cv2.Scharr(image, cv2.CV_32F, 0, 1) # y方向一阶导数 # 一阶导数算出来可能有正负 最后全部转到8位的图像上去 gradx = cv2.convertScaleAbs(grad_x) grady = cv2.convertScaleAbs(grad_y) cv2.imshow("gradient_x", gradx) cv2.imshow("gradient_y", grady) # 计算两个数组的加权和 gradxy = cv2.addWeighted(gradx, 0.5, grady, 0.5, 0) cv2.imshow("gradient_xy", gradxy) if __name__ == "__main__": src = cv2.imread(r"./test/018.jpg") src = cv2.resize(src, None, fx=0.5, fy=0.5) cv2.imshow("image", src) scharr_demo(src) cv2.waitKey(0) cv2.destroyAllWindows()
运行效果如下:
3. 拉普拉斯算子
拉普拉斯算子(Laplace Operator)是 n 维欧几里德空间中的一个二阶微分算子,定义为梯度(▽f)的散度(▽·f)。
cv2.Laplacian(src, ddepth, dst=None, ksize=None, scale=None, delta=None, borderType=None)
- src:输入需要处理的图像
- ddepth:输出图像深度
- dst:参数表示输出与src相同大小和相同通道数的图像
- ksize:用于计算二阶导数滤波器的孔径大小,大小必须是正数和奇数。
- scale:计算拉普拉斯算子值的比例因子,默认情况下没有伸缩系数。
- delta:一个可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到 dst 中。
- borderType:判断图像边界的模式,这个参数默认值为cv2.BORDER_DEFAULT。
import cv2 as cv import numpy as np # 拉普拉斯算子 def Laplace_demo(image): dst = cv.Laplacian(image, cv.CV_32F) lpls_1 = cv.convertScaleAbs(dst) cv.imshow("Laplace_1", lpls_1) # 自定义拉普拉斯算子 kernel = np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]]) dst = cv.filter2D(image, cv.CV_32F, kernel) lpls_2 = cv.convertScaleAbs(dst) cv.imshow("Laplace_2", lpls_2) if __name__ == "__main__": src = cv.imread(r"./test/018.jpg") src = cv.resize(src, None, fx=0.5, fy=0.5) cv.imshow("image", src) Laplace_demo(src) cv.waitKey(0) cv.destroyAllWindows()
运行效果如下: