前文传送门:
「Python 图像处理 OpenCV (2):像素处理与 Numpy 操作以及 Matplotlib 显示图像」
「Python 图像处理 OpenCV (3):图像属性、图像感兴趣 ROI 区域及通道处理」
「Python 图像处理 OpenCV (4):图像算数运算以及修改颜色空间」
「Python 图像处理 OpenCV (5):图像的几何变换」
「Python 图像处理 OpenCV (6):图像的阈值处理」
「Python 图像处理 OpenCV (7):图像平滑(滤波)处理」
「Python 图像处理 OpenCV (8):图像腐蚀与图像膨胀」
「Python 图像处理 OpenCV (9):图像处理形态学开运算、闭运算以及梯度运算」
「Python 图像处理 OpenCV (10):图像处理形态学之顶帽运算与黑帽运算」
「Python 图像处理 OpenCV (11):Canny 算子边缘检测技术」
「Python 图像处理 OpenCV (12): Roberts 算子、 Prewitt 算子、 Sobel 算子和 Laplacian 算子边缘检测技术」
「Python 图像处理 OpenCV (13): Scharr 算子和 LOG 算子边缘检测技术」
引言
前面的文章中,我们有用过图像方法或者缩小的函数 resize()
,这个函数既可以放大图像,也可以缩小图像,其中:
- 缩小图像:一版使用
CV_INETR_AREA
(区域插值)来插值。 - 放大图像,一般使用
CV_INTER_LINEAR
(线性插值)来插值。
图像缩放除了可以使用函数 resize()
,还有另外的一种方式 —— 「图像金字塔」。
图像金字塔是什么?
在说清楚什么事图像金字塔之前,要先介绍另一个概念:「尺度」。
尺度:先从字面意思来看说的就是尺寸和分辨率。
我们在进行图像处理的时候,会经常对源图像的尺寸进行放大或者缩小的变换,进而转换为我们需要的尺寸的目标图像。
对图像进行放大和缩小的变换的这个过程,称为尺度调整。
而图像金字塔则是图像多尺度调整表达的一种重要的方式。
图像金字塔是图像多尺度表达的一种,是一种以多分辨率来解释图像的有效但概念简单的结构。一幅图像的金字塔是一系列以金字塔形状排列的分辨率逐步降低,且来源于同一张原始图的图像集合。其通过梯次向下采样获得,直到达到某个终止条件才停止采样。我们将一层一层的图像比喻成金字塔,层级越高,则图像越小,分辨率越低。
图像金字塔方法的总体思想主要是是:将参加融合的的每幅图像分解为多尺度的金字塔图像序列,将低分辨率的图像在上层,高分辨率的图像在下层,上层图像的大小为前一层图像大小的 1/4 。层数为 0 , 1 , 2 …… N 。将所有图像的金字塔在相应层上以一定的规则融合,就可得到合成金字塔,再将该合成金字塔按照金字塔生成的逆过程进行重构,得到融合金字塔。
实现方式
通常而言,我们一般讨论两种图像金字塔:「高斯金字塔( Gaussian pyramid )」 和 「拉普拉斯金字塔( Laplacian pyramid )」 。
高斯金字塔( Gaussian pyramid )
高斯金字塔是由底部的最大分辨率图像逐次向下采样得到的一系列图像。最下面的图像分辨率最高,越往上图像分辨率越低。
高斯金字塔向下采样:
这个过程实际上就是一个重复高斯平滑并重新对图像采样的过程。
- 对于原始图像先进行一次高斯平滑处理,使用高斯核(
5 * 5
)进行一次卷积处理。下面是5 * 5
的高斯核。
\left[
\begin{matrix}
1 & 4 & 6 & 4 & 1\\
4 & 16 & 24 & 16 & 4\\
6 & 24 & 36 & 24 & 6\\
4 & 16 & 24 & 16 & 4\\
1 & 4 & 6 & 4 & 1\\
\end{matrix}
\right]
\]
- 接下来是对图像进行采样,这一步会去除图像中的偶数行和奇数列,从而得到一张图像。
- 再然后是重复上面两步,直到得到最终的目标图像为止。
从上面的步骤可以看出,再每次循环中,得到的结果图像只有原图像的 1/4 大小(横纵向均做隔行采样)。
注意:向下采样会逐渐丢失图像信息,属于非线性的处理,此过程不可逆,属于有损处理。
高斯金字塔向上采样:
- 将图像在每个方向扩大为原来的两倍,新增的行和列以 0 填充。
- 使用高斯核(
5 * 5
)对得到的图像进行一次高斯平滑处理,获得 「新增像素」的近似值。
注意:此过程与向下采样的过程一样,属于非线性处理,无法逆转,属于有损处理。
此过程得到的图像为放大后的图像,与原图相比会比较模糊,因为在缩放的过程中丢失了一些图像信息,如果想在缩小和放大整个过程中减少信息的丢失。
如果在缩放过程中想要减少图像信息的丢失,这就引出了第二个图像金字塔 —— 「拉普拉斯金字塔」 。
拉普拉斯金字塔( Laplacian pyramid )
拉普拉斯金字塔可以认为是残差金字塔,用来存储下采样后图片与原始图片的差异。
上面我们介绍了基于高斯金字塔,一个原始图像 Gi
,先进行向下采样得到 G(i-1)
,再对 G(i-1)
进行向上采样得到 Up(Down(Gi))
,最终得到的 Up(Down(Gi))
与原始的 Gi
是存在差异的。
这是因为向下采样丢失的信息并不能由向上采样来进行恢复,高斯金字塔是一种有损的采样方式。
如果我们想要完全恢复原始图像,那么我们在进行采样的时候就需要保留差异信息。
这就是拉普拉斯金字塔的核心思想,每次向下采样后,将再次向上采样,得到向上采样的 Up(Down(Gi))
后,记录 Up(Down(Gi))
与 Gi
的差异信息。
下面这个公式是差异的记录过程:
\]
OpenCV 函数
OpenCV 为向上采样和向下采样提供了两个函数: pyrDown()
和 pyrUp()
。
pyrDown()
的原函数如下:
def pyrDown(src, dst=None, dstsize=None, borderType=None)
- src: 表示输入图像。
- dst: 表示输出图像,它与src类型、大小相同。
- dstsize: 表示降采样之后的目标图像的大小。
- borderType: 表示表示图像边界的处理方式。
注意:dstsize 参数是有默认值的,调用函数的时候不指定第三个参数,那么这个值是按照 Size((src.cols+1)/2, (src.rows+1)/2) 计算的。而且不管如何指定这个参数,一定必须保证满足以下关系式:|dstsize.width * 2 - src.cols| ≤ 2; |dstsize.height * 2 - src.rows| ≤ 2。也就是说降采样的意思其实是把图像的尺寸缩减一半,行和列同时缩减一半。
pyrUp()
的原函数如下:
def pyrUp(src, dst=None, dstsize=None, borderType=None)
- src: 表示输入图像。
- dst: 表示输出图像,它与src类型、大小相同。
- dstsize: 表示降采样之后的目标图像的大小。
- borderType: 表示表示图像边界的处理方式。
参数释义和上面的 pyrDown()
保持一致。
下面是高斯金字塔和拉普拉斯金字塔的代码示例:
import cv2 as cv
#高斯金字塔
def gaussian_pyramid(image):
level = 3 #设置金字塔的层数为3
temp = image.copy() #拷贝图像
gaussian_images = [] #建立一个空列表
for i in range(level):
dst = cv.pyrDown(temp) #先对图像进行高斯平滑,然后再进行降采样(将图像尺寸行和列方向缩减一半)
gaussian_images.append(dst) #在列表末尾添加新的对象
cv.imshow("gaussian"+str(i), dst)
temp = dst.copy()
return gaussian_images
#拉普拉斯金字塔
def laplacian_pyramid(image):
gaussian_images = gaussian_pyramid(image) #做拉普拉斯金字塔必须用到高斯金字塔的结果
level = len(gaussian_images)
for i in range(level-1, -1, -1):
if (i-1) < 0:
expand = cv.pyrUp(gaussian_images[i], dstsize = image.shape[:2])
laplacian = cv.subtract(image, expand)
# 展示差值图像
cv.imshow("laplacian_down_"+str(i), laplacian)
else:
expand = cv.pyrUp(gaussian_images[i], dstsize = gaussian_images[i-1].shape[:2])
laplacian = cv.subtract(gaussian_images[i-1], expand)
# 展示差值图像
cv.imshow("laplacian_down_"+str(i), laplacian)
src = cv.imread('maliao.jpg')
print(src.shape)
# 先将图像转化成正方形,否则会报错
input_image = cv.resize(src, (560, 560))
# 设置为 WINDOW_NORMAL 可以任意缩放
cv.namedWindow('input_image', cv.WINDOW_AUTOSIZE)
cv.imshow('input_image', src)
laplacian_pyramid(src)
cv.waitKey(0)
cv.destroyAllWindows()
上面这段程序有一点需要注意,我当前使用 opencv-python
的版本是 4.3.0.36
,理论上在向上采样的过程中,目标大小只需要满足关系 |dstsize.width - src.cols * 2| ≤ (dstsize.width mod 2)
即可。
实际上经过测试,输入图像是必须使用正方形,长方形的图像会直接爆出如下错误:
error: (-215:Assertion failed) std::abs(dsize.width - ssize.width*2) == dsize.width % 2 && std::abs(dsize.height - ssize.height*2) == dsize.height % 2 in function 'cv::pyrUp_'
具体原因并没有想通,希望哪位知道的大佬可以解释下。