图像金字塔是由一幅图像的多个不同分辨率的子图所构成的图像集合。该组图像是由单个图像通过不断地降采样所产生的,最小的图像可能仅仅有一个像素点。
《OpenCV轻松入门:面向Python》学习笔记(八)
1. 理论基础
图像金字塔是同一图像不同分辨率的子图集合,是通过对原图像不断地向下采样而产生的,即由高分辨率的图像(大尺寸)产生低分辨率的近似图像(小尺寸)。
1.1 向下采样
最简单的图像金字塔可以通过不断地删除图像的偶数行和偶数列得到。例如,有一幅图像,其大小是NN,删除其偶数行和偶数列后得到一幅(N/2)(N/2)大小的图像。经过上述处理后,图像大小变为原来的四分之一,不断地重复该过程,就可以得到该图像的图像金字塔。
也可以先对原始图像滤波,得到原始图像的近似图像,然后将近似图像的偶数行和偶数列,删除以获取向下采样的结果。有多种滤波器可以选择。例如:
- 邻域滤波器:采用邻域平均技术求原始图像的近似图像。该滤波器能够产生平均金字塔。
- 高斯滤波器:采用高斯滤波器对原始图像进行滤波,得到高斯金字塔。这是OpenCV函数cv2.pyrDown()所采用的方式。
1.2 向上采样
在向上采样的过程中,通常将图像的宽度和高度都变为原来的2 倍。这意味着,向上采样的结果图像的大小是原始图像的4倍。因此,要在结果图像中补充大量的像素点。对新生成的像素点进行赋值,称为插值处理,该过程可以通过多种方式实现,例如最临近插值就是用最邻近的像素点给当前还没有值的像素点赋值。
有一种常见的向上采样,对像素点以补零的方式完成插值。通常是在每列像素点的右侧插入值为零的列,在每行像素点的下方插入值为零的行。在下图中,左侧是要进行向上采样的4个像素点,右侧是向上采样时进行补零后的处理结果。
接下来,使用向下采样时所用的高斯滤波器(高斯核)对补零后的图像进行滤波处理,以获取向上采样的结果图像。但是需要注意,此时图像中四分之三像素点的值都是零。所以,要将高斯滤波器系数乘以4,以保证得到的像素值范围在其原有像素值范围内。
通过以上分析可知,向上采样和向下采样是相反的两种操作。但是,由于向下采样会丢失像素值,所以这两种操作并不是可逆的。也就是说,对一幅图像先向上采样、再向下采样,是无法恢复其原始状态的;同样,对一幅图像先向下采样、再向上采样也无法恢复到原始状态。
2. pyrDown 函数及使用
OpenCV 提供了函数cv2.pyrDown(),用于实现图像高斯金字塔操作中的向下采样:
dst = cv2.pyrDown( src[, dstsize[, borderType]] )
- dst 为目标图像。
- src 为原始图像。
- dstsize 为目标图像的大小。
- borderType 为边界类型, 默认值为BORDER_DEFAULT ,且这里仅支持BORDER_DEFAULT。
采样步骤为:
-
对原始图像进行高斯滤波变换,将图像与高斯内核卷积。
-
删除所有偶数行和列。
import cv2
o=cv2.imread("lena.bmp",cv2.IMREAD_GRAYSCALE)
r1=cv2.pyrDown(o)
r2=cv2.pyrDown(r1)
r3=cv2.pyrDown(r2)
print("o.shape=",o.shape)
print("r1.shape=",r1.shape)
print("r2.shape=",r2.shape)
print("r3.shape=",r3.shape)
cv2.imshow("original",o)
cv2.imshow("r1",r1)
cv2.imshow("r2",r2)
cv2.imshow("r3",r3)
cv2.waitKey()
cv2.destroyAllWindows()
程序运行后,会显示如下结果:
o.shape= (512, 512)
r1.shape= (256, 256)
r2.shape= (128, 128)
r3.shape= (64, 64)
3. pyrUp 函数及使用
在 OpenCV 中,使用函数cv2.pyrUp()实现图像金字塔操作中的向上采样:
dst = cv2.pyrUp( src[, dstsize[, borderType]] )
- 参数说明与函数pyrDown中的相同。
采样步骤为:
- 将图像在每个方向扩大为原来的两倍,新增的行和列以0填充。
- 使用先前同样的内核(乘以4)与放大后的图像卷积,获得近似值。
import cv2
o=cv2.imread("lenas.bmp")
r1=cv2.pyrUp(o)
r2=cv2.pyrUp(r1)
r3=cv2.pyrUp(r2)
print("o.shape=",o.shape)
print("r1.shape=",r1.shape)
print("r2.shape=",r2.shape)
print("r3.shape=",r3.shape)
cv2.imshow("original",o)
cv2.imshow("r1",r1)
cv2.imshow("r2",r2)
cv2.imshow("r3",r3)
cv2.waitKey()
cv2.destroyAllWindows()
程序运行后,会输出如下结果:
o.shape= (64, 64, 3)
r1.shape= (128, 128, 3)
r2.shape= (256, 256, 3)
r3.shape= (512, 512, 3)
4. 采样可逆性研究
一幅图像在先后经过向下采样、向上采样后,会恢复为原始大小,但是向上采样和向下采样不是互逆的。也就是说,虽然在经历两次采样操作后,得到的结果图像与原始图像的大小一致,肉眼看起来也相似,但是二者的像素值并不是一致的。
import cv2
o=cv2.imread("lena.bmp")
down=cv2.pyrDown(o)
up=cv2.pyrUp(down)
diff=up-o #构造diff 图像,查看up 与o 的区别
print("o.shape=",o.shape)
print("up.shape=",up.shape)
cv2.imshow("original",o)
cv2.imshow("up",up)
cv2.imshow("difference",diff)
cv2.waitKey()
cv2.destroyAllWindows()
运行程序,会显示如下输出:
o.shape= (512, 512, 3)
up.shape= (512, 512, 3)
5. 拉普拉斯金字塔
前面我们介绍了高斯金字塔,高斯金字塔是通过对一幅图像一系列的向下采样所产生的。有时,我们希望通过对金字塔中的小图像进行向上采样以获取完整的大尺寸高分辨率图像,这时就需要用到拉普拉斯金字塔。
5.1 定义
前面我们已经介绍过,一幅图像在经过向下采样后,再对其进行向上采样,是无法恢复为原始状态的。对此,我们也用程序进行了验证。向上采样并不是向下采样的逆运算。这是很明显的,因为向下采样时在使用高斯滤波器处理后还要抛弃偶数行和偶数列,不可避免地要丢失一些信息。
为了在向上采样时能够恢复具有较高分辨率的原始图像,就要获取在采样过程中所丢失的信息,这些丢失的信息就构成了拉普拉斯金字塔。
拉普拉斯金字塔的定义形式为:
- Li 表示拉普拉斯金字塔中的第i层。
- Gi 表示高斯金字塔中的第i层。
拉普拉斯金字塔中的第i 层,等于“高斯金字塔中的第i 层”与“高斯金字塔中的第i+1层的向上采样结果”之差。
5.2 应用
拉普拉斯金字塔的作用在于,能够恢复高分辨率的图像。下图演示了如何通过拉普拉斯金字塔恢复高分辨率图像。其中,右图是对左图的简化。
import cv2
import numpy as np
O=cv2.imread("lena.bmp")
#==============生成高斯金字塔===================
G0=O
G1=cv2.pyrDown(G0)
G2=cv2.pyrDown(G1)
G3=cv2.pyrDown(G2)
#============生成拉普拉斯金字塔===================
L0=G0-cv2.pyrUp(G1) #拉普拉斯金字塔第0 层
L1=G1-cv2.pyrUp(G2) #拉普拉斯金字塔第1 层
L2=G2-cv2.pyrUp(G3) #拉普拉斯金字塔第2 层
#=================复原G0======================
RG0=L0+cv2.pyrUp(G1) #通过拉普拉斯图像复原的原始图像G0
print("G0.shape=",G0.shape)
print("RG0.shape=",RG0.shape)
result=RG0-G0 #将RG0 和G0 相减
#计算result 的绝对值,避免求和时负负为正,3+(-3)=0
result=abs(result)
#计算result 所有元素的和
print("原始图像G0 与恢复图像RG0 差值的绝对值和:",np.sum(result))
#=================复原G1======================
RG1=L1+cv2.pyrUp(G2) #通过拉普拉斯图像复原G1
print("G1.shape=",G1.shape)
print("RG1.shape=",RG1.shape)
result=RG1-G1 #将RG1 和G1 相减
print("原始图像G1 与恢复图像RG1 之差的绝对值和:",np.sum(abs(result)))
#=================复原G2======================
RG2=L2+cv2.pyrUp(G3) #通过拉普拉斯图像复原G2
print("G2.shape=",G2.shape)
print("RG2.shape=",RG2.shape)
result=RG2-G2 #将RG2 和G2 相减
print("原始图像G2 与恢复图像RG2 之差的绝对值和:",np.sum(abs(result)))
程序运行后,会输出如下运行结果:
G0.shape= (512, 512, 3)
RG0.shape= (512, 512, 3)
原始图像 G0 与恢复图像RG0 之差的绝对值和:0
G1.shape= (256, 256, 3)
RG1.shape= (256, 256, 3)
原始图像 G1 与恢复图像RG1 之差的绝对值和:0
G2.shape= (128, 128, 3)
RG2.shape= (128, 128, 3)
原始图像 G2 与恢复图像RG2 之差的绝对值和:0
从程序运行结果可以看到,在每一层中,原始图像与恢复图像差值的绝对值和都为“0”。这说明使用拉普拉斯金字塔恢复的图像与原始图像完全一致。