Python OpenCV 之图像金字塔,高斯金字塔与拉普拉斯金字塔

Python OpenCV 365 天学习计划,与橡皮擦一起进入图像领域吧。

基础知识铺垫

学习图像金字塔,发现网上的资料比较多,检索起来比较轻松。

图像金字塔是一张图像多尺度的表达,或者可以理解成一张图像不同分辨率展示。

金字塔越底层的图片,像素越高,越向上,像素逐步降低,分辨率逐步降低。

高斯金字塔

我们依旧不对概念做过多解释,第一遍学习应用,应用,毕竟 365 天的周期,时间长,后面补充理论知识。

高斯金字塔用于向下采样,同时它也是最基本的图像塔。

在互联网检索原理,得到最简单的说明如下:

将图像的最底层(高斯金字塔的第 0 层),例如高斯核(5x5)对其进行卷积操作,这里的卷积主要处理掉的是偶数行与列,然后得到金字塔上一层图像(即高斯金字塔第 1 层),在针对该图像重复卷积操作,得到第 2 层,反复执行下去,即可得到高斯金字塔。

每次操作之后,都会将 M×N 图像变成 M/2 × N/2 图像,即减少一半。

还有实测中发现,需要用图像的宽和高一致的图片,并且宽高要是 2 的次幂数,例如,8 像素,16 像素,32 像素等等,一会你也可以实际测试一下。

图像金字塔应用到的函数有 <kbd>cv2.pyrDown()</kbd> 和 <kbd>cv2.pyrUp() </kbd>。

cv2.pyrDown 与 cv2.pyrUp 函数原型

通过 help 函数得到函数原型如下:

pyrDown(src[, dst[, dstsize[, borderType]]]) -> dst
pyrUp(src[, dst[, dstsize[, borderType]]]) -> dst

两个函数原型参数一致,参数说明如下:

  • <kbd>src</kbd>:输入图像;
  • <kbd>dst</kbd>: 输出图像;
  • <kbd>dstsize</kbd>: 输出图像尺寸,默认值按照 ((src.cols+1)/2, (src.rows+1)/2) 计算。

关于两个函数的补充说明:

  • <kbd>cv2.pyrDown</kbd> 从一个相对高分辨率的大尺寸的图像上构建一个金字塔,运行之后的结果是,图像变小,分辨率降低(下采样);
  • <kbd>cv2.pyrUp</kbd> 是一个上采样的过程,尽管相对尺寸变大,但是分辨率不会增加,图像会变得更模糊。

测试代码如下:

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()

运行代码之后,得到三张图片,大小依次减小,分辨率降低。

20210131213917372[1].png

通过上面运行得到的最小图,在执行向上采样之后,图片会变的模糊,这也说明上采样和下采样是非线性处理,它们是不可逆的有损处理,因此下采样后的图像是无法还原的,即使放大图片也会变模糊(后面学习到拉普拉斯金字塔可以解决该问题)。

# 向上采样
dst2 = cv.pyrUp(dst1)
print(dst2.shape[:2])
cv.imshow("dst2", dst2)
20210131214148685[1].png

在总结一下上采样和下采样的步骤:

  1. 上采样:使用 <kbd>cv2.pyrUp</kbd> 函数, 先将图像在每个方向放大为原来的两倍,新增的行和列用 0 填充,再使用先前同样的内核与放大后的图像卷积,获得新增像素的近似值;
  2. 下采样:使用 <kbd>cv2.pyrDown</kbd> 函数,先对图像进行高斯内核卷积 ,再将所有偶数行和列去除。

拉普拉斯金字塔(Laplacian Pyramid, LP)

拉普拉斯金字塔主要用于重建图像,由上文我们已经知道在使用高斯金字塔的的时候,上采样和下采样会导致图像细节丢失。

拉普拉斯就是为了在放大图像的时候,可以预测残差,何为残差,即小图像放大的时候,需要插入一些像素值,在上文直接插入的是 0,拉普拉斯金字塔算法可以根据周围像素进行预测,从而实现对图像最大程度的还原。

学习到原理如下:用高斯金字塔的每一层图像,减去其上一层图像上采样并高斯卷积之后的预测图像,得到一系列的差值图像即为 LP 分解图像(其中 LP 即为拉普拉斯金字塔图像)。

关于拉普拉斯还存在一个公式(这是本系列课程第一次书写公式),其中 L 为拉普拉斯金字塔图像,G 为高斯金字塔图像

L_n = G_n-PyrUp(PyrDown(G_n))

使用下面的代码进行测试。

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()

运行结果如下,相关的图像已经呈现出来,重点注意最右侧的图片。

20210201071432214[1].png

这个地方需要注意下,如果你使用 <kbd>cv.subtract(src, dst1)</kbd> 函数,得到的是上图效果,但是在使用还原的时候会发现问题,建议直接使用 <kbd>-</kbd>完成,匹配公式,修改代码如下:

# cv.subtract(src, dst1)
laplace = src - dst1

代码运行效果如下。


20210201082447927[1].png

学习过程中发现这样一段话:图像尺寸最好是 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 层的原始图像】的翻译。

第二行代码是使用直接向上采样,最终得到的是损失细节的图像。

上述代码运行的结果如下,通过拉普拉斯可以完美还原图像。

学习本案例之后,你可以在复盘本文开始部分的代码,将其进行修改。


20210201081348164[1].png

最后在学习一种技巧,可以直接将两幅图片呈现,代码如下:

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()

运行之后,通过 <kbd>np.hstack((up_dst1, src))</kbd>函数,将两个图像矩阵合并,实现效果如下:


20210201074426473[1].png

橡皮擦的小节

希望今天的 1 个小时,你有所收获,我们下篇博客见~

相关阅读


技术专栏

  1. Python 爬虫 100 例教程,超棒的爬虫教程,立即订阅吧
  2. Python 爬虫小课,精彩 9 讲

逗趣程序员

  1. 曝光,程序员的 10 个摸鱼神器
  2. 5 个无聊 Python 程序,用 Python 整蛊你的朋友们吧
  3. 10 年老程序员教你甩锅必杀技,论【如何优雅的甩锅】
  4. 你想被开除吗?来看看程序员【离职小技巧】吧

今天是持续写作的第 <font color="red">71</font> / 100 天。
如果你有想要交流的想法、技术,欢迎在评论区留言。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容