Python OpenCV高斯金字塔与拉普拉斯金字塔的实现

小岛来信
小岛来信
发布时间:2026-10-14| 6人浏览过
原创

本文详解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金字塔内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。