Python OpenCV高斯金字塔与拉普拉斯金字塔的实现
本文详解Python OpenCV中高斯金字塔与拉普拉斯金字塔的实现原理。通过cv2.pyrDown和cv2.pyrUp函数演示图像的下采样与上采样过程,分析尺寸需为2的整次幂的限制。重点介绍利用拉普拉斯金字塔进行图像细节还原的方法,解决常规上下采样的有损模糊问题,并附带完整代码示例。
基础知识铺垫
图像金字塔相关的学习资料在网上较为丰富,检索过程相对便捷。
图像金字塔本质上是图像的多尺度表达,即同一张图像在不同分辨率下的多种展示形式。
金字塔结构中,底层图像像素最高;随着层级向上,像素数量逐步减少,分辨率也随之降低。
高斯金字塔
本文侧重于应用实践,暂不对概念做过多理论解释。考虑到学习周期较长(如365天),先掌握应用,后续再补充理论知识。
高斯金字塔主要用于向下采样,它是构建图像金字塔的基础。
关于其原理,互联网上的简化说明如下:
以高斯金字塔的第0层(最底层)为例,使用高斯核(如5x5)对图像进行卷积操作。该卷积主要处理偶数行与列,从而生成金字塔的第1层图像。对第1层重复此卷积操作得到第2层,依此类推,即可构建完整的高斯金字塔。
每次操作后,M×N尺寸的图像将变为M/2 × N/2尺寸,即宽和高各减少一半。
实测表明,输入图像的宽和高应保持一致,且宽高数值最好为2的次幂(如8、16、32像素等),建议读者实际测试验证。
图像金字塔操作涉及 cv2.pyrDown() 和 cv2.pyrUp() 两个函数。
cv2.pyrDown 与 cv2.pyrUp 函数原型
通过 help 函数查看到的函数原型如下:
pyrDown(src[, dst[, dstsize[, borderType]]]) -> dst
pyrUp(src[, dst[, dstsize[, borderType]]]) -> dst
这两个函数的参数一致,具体说明如下:
- src:输入图像;
- dst:输出图像;
- dstsize:输出图像尺寸,默认值按照
((src.cols+1)/2, (src.rows+1)/2)计算。
关于这两个函数的补充说明:
- cv2.pyrDown 用于从高分辨率的大尺寸图像构建金字塔,运行结果使图像变小、分辨率降低(即下采样);
- cv2.pyrUp 是上采样过程,虽然图像相对尺寸变大,但分辨率不会增加,图像会变得更模糊。
测试代码如下:
import cv2 as cv
src = cv.imread("./testimg.jpeg")
print(src.shape[:2])
cv.imshow("src", src)
# 向下采样
dst = cv.pyrDown(src)
print(dst.shape[:2])
cv.imshow("dst", dst)
# 再次向下采样
dst1 = cv.pyrDown(dst)
print(dst1.shape[:2])
cv.imshow("dst1", dst1)
cv.waitKey()
运行代码后,得到三张图片,大小依次减小,分辨率逐步降低。

通过上述运行得到的最小图,再执行向上采样后,图片会变模糊。这说明上采样和下采样是非线性处理,属于不可逆的有损处理,因此下采样后的图像无法完全还原,即使放大图片也会变模糊(后续学习拉普拉斯金字塔可解决此问题)。
# 向上采样
dst2 = cv.pyrUp(dst1)
print(dst2.shape[:2])
cv.imshow("dst2", dst2)

总结上采样和下采样的步骤:
- 上采样:使用 cv2.pyrUp 函数,先将图像在每个方向放大为原来的两倍,新增的行和列用 0 填充,再使用先前同样的内核与放大后的图像卷积,获得新增像素的近似值;
- 下采样:使用 cv2.pyrDown 函数,先对图像进行高斯内核卷积,再将所有偶数行和列去除。
拉普拉斯金字塔(Laplacian Pyramid, LP)
拉普拉斯金字塔主要用于图像重建。前文已知,使用高斯金字塔时,上采样和下采样会导致图像细节丢失。
拉普拉斯金字塔的作用是在放大图像时预测残差。所谓残差,即小图像放大时需要插入像素值。前文直接插入的是 0,而拉普拉斯金字塔算法可根据周围像素进行预测,从而实现对图像最大程度的还原。
其原理如下:用高斯金字塔的每一层图像,减去其上一层图像上采样并高斯卷积之后的预测图像,得到一系列的差值图像,即为 LP 分解图像(LP 即拉普拉斯金字塔图像)。
关于拉普拉斯还有一个公式(本系列课程首次引入公式),其中 L 为拉普拉斯金字塔图像,G 为高斯金字塔图像。

使用下面的代码进行测试。
import cv2 as cv
src = cv.imread("./testimg.jpeg")
print(src.shape[:2])
cv.imshow("src", src)
# 向下采样一次
dst = cv.pyrDown(src)
print(dst.shape[:2])
cv.imshow("dst", dst)
# 向上采样一次
dst1 = cv.pyrUp(dst)
print(dst1.shape[:2])
cv.imshow("dst1", dst1)
# 计算拉普拉斯金字塔图像
# 原图 - 向上采样一次的图
laplace = cv.subtract(src, dst1)
cv.imshow("laplace", laplace)
cv.waitKey()
运行结果如下,相关图像已呈现,请重点注意最右侧的图片。

此处需注意,若使用 cv.subtract(src, dst1) 函数,会得到前述效果,但在还原时会发现问题。建议直接使用 - 完成计算以匹配公式,修改代码如下:
# cv.subtract(src, dst1)
laplace = src - dst1
代码运行效果如下。

学习过程中发现一段说明:图像尺寸最好是 2 的整次幂(如 256、512 等),否则在金字塔向上过程中图像尺寸会不等,这会导致在拉普拉斯金字塔处理时因不同尺寸矩阵相减而出错。
实测证实确实如此。例如案例中使用的图像,在向下采样 2 次时,图像尺寸会发生变化,测试代码如下:
import cv2 as cv
src = cv.imread("./testimg.jpeg")
print(src.shape[:2])
cv.imshow("src", src)
# 向下采样1次
dst1 = cv.pyrDown(src)
print(dst1.shape[:2])
cv.imshow("dst", dst1)
# 向下采样2次
dst2 = cv.pyrDown(dst1)
print(dst1.shape[:2])
cv.imshow("dst2", dst2)
# 向上采样1次
up_dst1 = cv.pyrUp(dst2)
print(up_dst1.shape[:2])
cv.imshow("up_dst1", up_dst1)
# 计算拉普拉斯金字塔图像
# 采样1次 - 向上采样1次的图
laplace = dst1 - up_dst1
cv.imshow("laplace", laplace)
cv.waitKey()
注意 print(up_dst1.shape[:2]) 部分的输出如下:
(710, 400)
(355, 200)
(355, 200)
(356, 200)
如果在此基础上使用拉普拉斯图像金字塔,就会出现如下错误。
Sizes of input arguments do not match
总结拉普拉斯图像金字塔的执行过程:
- 向下采样:用高斯金字塔的第 i 层减去 i+1 层做上采样的图像,得到拉普拉斯第 i 层的图像;
- 向上采样:用高斯金字塔的 i+1 层向上采样加上拉普拉斯的第 i 层,得到第 i 层的原始图像。
向下采样上面的代码已实现,但拉普拉斯向上采样尚未实现。完善代码如下,为清晰起见,修改了变量命名。
import cv2 as cv
src = cv.imread("./testimg_rect.jpeg")
print(src.shape[:2])
cv.imshow("src", src)
# 高斯金字塔第 0 层
gus0 = src # 原图
# 高斯金字塔第 1 层
gus1 = cv.pyrDown(gus0)
# 高斯第 2 层
gus2 = cv.pyrDown(gus1)
# 拉普拉斯金字塔第 0 层
lap0 = gus0 - cv.pyrUp(gus1)
# 拉普拉斯金字塔第 1 层
lap1 = gus1 - cv.pyrUp(gus2)
# 显示拉普拉斯第一层代码
cv.imshow("laplace", lap1)
cv.waitKey()
下面用修改好的代码完成还原图片的操作。
import cv2 as cv
src = cv.imread("./testimg_rect.jpeg")
print(src.shape[:2])
cv.imshow("src", src)
# 高斯金字塔第 0 层
gus0 = src # 原图
# 高斯金字塔第 1 层
gus1 = cv.pyrDown(gus0)
# 高斯第 2 层
gus2 = cv.pyrDown(gus1)
# 拉普拉斯金字塔第 0 层
lap0 = gus0 - cv.pyrUp(gus1)
# 拉普拉斯金字塔第 1 层
lap1 = gus1 - cv.pyrUp(gus2)
rep = lap0 + cv.pyrUp(lap1 + cv.pyrUp(gus2))
gus_rep = cv.pyrUp(cv.pyrUp(gus2))
cv.imshow("rep", rep)
cv.imshow("gus_rep", gus_rep)
cv.waitKey()
以上代码最重要的部分为下面两句:
rep = lap0 + cv.pyrUp(lap1 + cv.pyrUp(gus2))
gus_rep = cv.pyrUp(cv.pyrUp(gus2))
第一行代码中 lap1 + cv.pyrUp(gus2) 即文字公式【用高斯金字塔的 i+1 层向上采样加上拉普拉斯的第 i 层,得到第 i 层的原始图像】的代码翻译。
第二行代码是使用直接向上采样,最终得到的是损失细节的图像。
上述代码运行结果如下,通过拉普拉斯可以完美还原图像。
学习本案例后,你可以复盘本文开始部分的代码,并对其进行修改。

最后学习一种技巧,可以直接将两幅图片并排呈现,代码如下:
import cv2 as cv
import numpy as np
src = cv.imread("./testimg_rect.jpeg")
print(src.shape[:2])
cv.imshow("src", src)
# 向下采样1次
down_dst1 = cv.pyrDown(src)
print(down_dst1.shape[:2])
cv.imshow("dst", down_dst1)
# 向上采样1次
up_dst1 = cv.pyrUp(down_dst1)
print(up_dst1.shape[:2])
cv.imshow("up_dst1", up_dst1)
res = np.hstack((up_dst1, src))
cv.imshow('res', res)
cv.waitKey()
运行之后,通过 np.hstack((up_dst1, src)) 函数将两个图像矩阵合并,实现效果如下:

到此这篇关于Python OpenCV高斯金字塔与拉普拉斯金字塔的实现的文章就介绍到这了,更多相关Python OpenCV金字塔内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













