图像金字塔
图像金字塔是由一幅图像的多个不同分辨率的子图所构成的图像集合
该组图像是由单个图像通过不断地降采样所产生的,最小的图像可能仅仅有一个像素点。
图像金字塔是一系列以金字塔形状排列的、自底向上分辨率逐渐降低的图像集合。
通常情况下,图像金字塔的底部是待处理的高分辨率图像(原始图像),而顶部则为其低分辨率的近似图像。
向金字塔的顶部移动时,图像的尺寸和分辨率都不断地降低。通常情况下,每向上移动一级,图像的宽和高都降低为原来的二分之一。
理论基础
下采样
图像金字塔是同一图像不同分辨率的子图集合,是通过对原图像不断地向下采样而产生的,即由高分辨率的图像(大尺寸)产生低分辨率的近似图像(小尺寸)。
最简单的图像金字塔可以通过不断地删除图像的偶数行和偶数列得到。
也可以先对原始图像滤波,得到原始图像的近似图像,然后将近似图像的偶数行和偶数列删除以获取向下采样的结果。有多种滤波器可以选择。例如:
- 邻域滤波器:采用邻域平均技术求原始图像的近似图像。该滤波器能够产生平均金字塔。
- 高斯滤波器:采用高斯滤波器对原始图像进行滤波,得到高斯金字塔。这是OpenCV函数cv2.pyrDown()所采用的方式。
高斯金字塔是通过不断地使用高斯滤波器滤波、采样所产生的。
原始图像与各次向下采样所得到的结果图像共同构成了高斯金字塔。
例如,可以将原始图像称为第0层,第1次向下采样的结果图像称为第1层,第2次向下采样的结果图像称为第3层,以此类推。
上采样
在向上采样的过程中,通常将图像的宽度和高度都变为原来的2倍。
这意味着,向上采样的结果图像的大小是原始图像的4倍。因此,要在结果图像中补充大量的像素点。
对新生成的像素点进行赋值,称为插值处理,该过程可以通过多种方式实现,例如最临近插值就是用最邻近的像素点给当前还没有值的像素点赋值。
有一种常见的向上采样,对像素点以补零的方式完成插值。通常是在每列像素点的右侧插入值为零的列,在每行像素点的下方插入值为零的行。
接下来,使用向下采样时所用的高斯滤波器(高斯核)对补零后的图像进行滤波处理,以获取向上采样的结果图像。
但是需要注意,此时图像中四分之三像素点的值都是零。所以,要将高斯滤波器系数乘以4,以保证得到的像素值范围在其原有像素值范围内。(补了很多0,导致滤波后结果偏小)
向上采样和向下采样是相反的两种操作。但是,由于向下采样会丢失像素值,所以这两种操作并不是可逆的。
也就是说,对一幅图像先向上采样、再向下采样,是无法恢复其原始状态的;同样,对一幅图像先向下采样、再向上采样也无法恢复到原始状态。
pyrDown函数
函数cv2.pyrDown(),用于实现图像高斯金字塔操作中的向下采样,其语法形式为:
dst = cv2.pyrDown( src[, dstsize[, borderType]] )
- dst为目标图像
- src为原始图像
- dstsize为目标图像的大小
- borderType为边界类型,默认值为BORDER_DEFAULT,且这里仅支持BORDER_DEFAULT
默认情况下,输出图像的大小为Size((src.cols+1)/2, (src.rows+1)/2)。
在任何情况下,图像尺寸必须满足如下条件:
- |dst. width∗2-src. cols|≤2
- |dst. height∗2-src. rows|≤2
cv2.pyrDown()函数首先对原始图像进行高斯滤波变换,以获取原始图像的近似图像。在获取近似图像后,该函数通过抛弃偶数行和偶数列来实现向下采样。
**例子:**使用函数cv2.pyrDown()对一幅图像进行向下采样
import cv2 o=cv2.imread("./img/hand1.png", 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()
经过向下采样后,图像的分辨率会变低。
pyrUp函数
在OpenCV中,使用函数cv2.pyrUp()实现图像金字塔操作中的向上采样,其语法形式如下:
dst = cv2.pyrUp( src[, dstsize[, borderType]] )
- dst为目标图像
- src为原始图像
- dstsize为目标图像的大小
- borderType为边界类型,默认值为BORDER_DEFAULT,且这里仅支持BORDER_DEFAULT。
目标图像的大小为Size(src.cols*2, src.rows*2)。
在任何情况下,图像尺寸需要满足下列条件:
- |dst. width-src. cols∗2|≤mod(dst. widh,2)
- |dst. height-src. rows∗2|≤mod(dst. height,2)
对图像向上采样时,在每个像素的右侧、下方分别插入零值列和零值行,得到一个偶数行、偶数列(即新增的行、列)都是零值的新图像New。接下来,用向下采样时所使用的高斯滤波器对新图像New进行滤波,得到向上采样的结果图像。
为了确保像素值区间在向上采样后与原始图像保持一致,需要将高斯滤波器的系数乘以4。
**例子:**使用函数cv2.pyrUp()对一幅图像进行向上采样
import cv2 o=cv2.imread("./img/hand2.png") 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()
采样可逆性的研究
拉普拉斯金字塔
为了在向上采样时能够恢复具有较高分辨率的原始图像,就要获取在采样过程中所丢失的信息,这些丢失的信息就构成了拉普拉斯金字塔。
拉普拉斯金字塔的定义形式为:
Li=Gi - pyrUp(Gi+ 1)
- Li表示拉普拉斯金字塔中的第i层
- Gi表示高斯金字塔中的第i层
拉普拉斯金字塔中的第i层,等于“高斯金字塔中的第i层”与“高斯金字塔中的第i+1层的向上采样结果”之差。
例子: 使用函数cv2.pyrDown()和cv2.pyrUp()构造拉普拉斯金字塔。
import cv2 O=cv2.imread("./img/hand2.png") G0=O G1=cv2.pyrDown(G0) G2=cv2.pyrDown(G1) G3=cv2.pyrDown(G2) ### 注意: 可能两个图像的大小不一样 L0=G0-cv2.resize(cv2.pyrUp(G1),G0.shape[0:2][::-1]) L1=G1-cv2.resize(cv2.pyrUp(G2),G1.shape[0:2][::-1]) L2=G2-cv2.resize(cv2.pyrUp(G3),G2.shape[0:2][::-1]) print("L0.shape=", L0.shape) print("L1.shape=", L1.shape) print("L2.shape=", L2.shape) cv2.imshow("L0", L0) cv2.imshow("L1", L1) cv2.imshow("L2", L2) cv2.waitKey() cv2.destroyAllWindows()
拉普拉斯金字塔的作用在于,能够恢复高分辨率的图像。
向上采样恢复高分辨率图像:
G0=L0+cv2.pyrUp(G1) G1=L1+cv2.pyrUp(G2) G2=L2+cv2.pyrUp(G3)
构造拉普拉斯金字塔的目的就是为了恢复高分辨率的图像。
例子: 使用拉普拉斯金字塔及高斯金字塔恢复原始图像。
import cv2 import numpy as np O=cv2.imread("./img/hand1.png") G0=O G1=cv2.pyrDown(G0) L0=O-cv2.resize(cv2.pyrUp(G1),G0.shape[0:2][::-1]) RO=L0+cv2.resize(cv2.pyrUp(G1),G0.shape[0:2][::-1]) # 通过拉普拉斯图像复原的原始图像 print("O.shape=", O.shape) print("RO.shape=", RO.shape) result=RO-O # 将O和RO做减法运算 # 计算result的绝对值 result=abs(result) # 计算result所有元素的和 print("原始图像O与恢复图像RO之差的绝对值和:", np.sum(result)) cv2.imshow("origin",O) cv2.imshow("ReO",RO) cv2.waitKey() cv2.destroyAllWindows()