Fork me on GitHub

OpenCV计算机视觉学习(7)——图像金字塔(高斯金字塔,拉普拉斯金字塔,图像缩放resize函数)

如果需要处理的原图及代码,请移步小编的GitHub地址

  传送门:请点击我

  如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice

  本节学习图像金字塔,图像金字塔包括高斯金字塔,高斯差分金字塔和拉普拉斯金字塔。它是图像中多尺度表达的一种,最主要用于图像的分割,是一种以多分辨率来解释图像的有效但概念简单的结构。简单来说,图像金字塔就是用来进行图像缩放的

1,图像金字塔

  图像金字塔是指一组图像且不同分辨率的子图集合,它是图像多尺度表达的一种,以多分辨率来解释图像的结构,主要用于图像的分割或压缩。一幅图像的金字塔是一系列以金字塔性质排列的分辨率逐步降低,且来源于同一张原始图的图像集合,如下图所示,它包括了五层图像,将这一层一层的图像比喻成金字塔。图像金字塔可以通过梯次向下采样获得,直到达到某个终止条件才停止采样,在向下采样中,层次越高,分辨率越低。

   生成图像金字塔主要包括两种方式——向下取样,向上取样,在上图中:

  • 将level0级别的图像转换为 level1,level2,level3,level4,图像分辨率不断降低的过程称为向下取样
  • 将level4级别的图像转换为 level3,level2,level1,leve0,图像分辨率不断增大的过程称为向上取样

  获得图像金子塔一般包括两个步骤:

  • 1,利用低通滤波器平滑图像
  • 2,对平滑图像进行抽样(采样)

1.1  高斯金字塔

  高斯金字塔用于下采样。高斯金字塔是最基本的图像塔。原理:首先将原图像作为最底层图像 level0(高斯金字塔的第0层),利用高斯核(5*5)对其进行卷积,然后对卷积后的图像进行下采样(去除偶数行和列)得到上一层图像G1,将此图像作为输入,重复卷积和下采样操作得到更上一层的图像,反复迭代多次,形成一个金字塔形的图像数据结构,即高斯金字塔。

  高斯金字塔是通过高斯平滑和亚采样获取一些列下采样图像,也就是说第K层高斯金字塔通过平滑,亚采样就可以获得K+1 层高斯图像,高斯金字塔包含了一系列低通滤波器,其截止频率从上一层到下一层是以因子 2 逐渐增加,所以高斯金字塔可以跨越很大的频率范围。

1.2 高斯金字塔的构建过程

  高斯金字塔是在sift算子中提出来的概念,首先高斯金字塔并不是一个金字塔,而是由很多组(Octave)金字塔构成,并且每组金字塔都包含若干层(Interval)。

  高斯金字塔的构建过程:

  1,先将原图像扩大一倍之后作为高斯金字塔的第1组第1层,将第1组第1层图像经高斯卷积(高斯平滑或称高斯滤波)之后作为第1组金字塔的第2层,高斯卷积函数为:

  对于参数 σ,在SIFT算子中取的是固定值 1.6

  2,将 σ 乘以一个比例系数k,等到一个新的平滑因子 σ = k*σ,用它来平滑第1组第2层图像,结果图像作为第3层。

  3,如此重复,最后得到L层图像,在同一组中,每一层图像的尺寸都是一样的,只是平滑系数不一样。它们对应的平滑系数分别为:0,σ,kσ,k^2σ,k^3σ……k^(L-2)σ。

  4,将第1组导数第三层图像作为比例因子为2的降采样,得到的图像作为第2组的第1层,然后对第2组的第1层图像作平滑因子为 σ 的高斯平滑,得到第2组的第2层,就像步骤2中一样,如此得到第2组的L层图像,同组内它们的尺寸是一样的,对应的平滑系数分别为:0,σ,kσ,k^2σ,k^3σ……k^(L-2)σ。但是在尺寸方面第2组是第1组图像的一半。

  这样反复执行,就可以得到一共O组,每组L层,共计O*L个图像,这些图像一起就构成了高斯金字塔,结构如下:

  在同一组内,不同层图像的尺寸是一样的,后一层图像的高斯平滑因子σ是前一层图像平滑因子的k倍;

  在不同组内,后一组第一个图像是前一组倒数第三个图像的二分之一采样,图像大小是前一组的一半;

1.3 尺度空间

  在一定的范围内,无论物体是大是小,人眼都可以分辨出来,然而计算机要有相同的能力却很难,所以要让机器能够对物体在不同尺度下有一个统一的认知,就需要考虑图像在不同尺度下都存在的特点。

  所以在很多时候,我们会将图像构建为一系列不同尺度的图像集,在不同尺度中取检测我们感兴趣的特征。比如:在Harris特征检测人脸的时候,因为我们并不知道图像中人脸的尺寸,所以需要生成一个不同大小的图像组成的金字塔,扫描其中每一幅图来寻找可能出现的特征。

  尺度空间的获取通常使用高斯模糊来实现:

  其中G是高斯函数,如下:

  即给定一个图像的不同点分配不同的权重,当不同Sigma的高斯函数决定了对图像的平滑程度,越大的Sigma值对应的图像越模糊,下面以lena为例:

  图像的尺度空间解决的问题是如何对图像在所有尺度下描述的问题。

  在高斯金字塔中一共生成O组L层不同尺度的图像,这两个量合起来(O,L)就构成了高斯金字塔的尺度空间,也就是说以高斯金字塔的组O作为二维坐标系的一个坐标,不同层L作为另一个坐标,则给定的一组坐标(O,L)就可以唯一确定高斯金字塔中的一幅图像。

  尺度空间的形象表述:

  上图中尺度空间中k前的系数n表示的是第一组图像尺寸是当前组图像尺寸的n倍。

  将上面两个图结合起来,如下:

1.4 DOG金字塔

  差分金字塔,DOG(Difference of Gaussian)金字塔是在高斯金字塔的基础上构建起来的,其实生成高斯金字塔的目的就是为了构建DOG金字塔。

  DOG金字塔的第1组第1层是由高斯金字塔的第1组第2层减第1组第1层得到的。以此类推,逐组逐层生成每一个差分图像,所有差分图像构成差分金字塔。概括为DOG金字塔的第 o 组第 l 层图像是有高斯金字塔的第 o 组第 l+1 层减第 o 组第 l 层得到的。

  DOG金字塔的构建可以用下图描述:

  每一组在层数上,DOG金字塔比高斯金字塔少一层。后续Sift特征点的提取都是在DOG金字塔上进行的。

  DOG定义公式:

1.5  拉普拉斯金字塔

  拉普拉斯金字塔用于重建图形,也就是预测残差,对图像进行最大程度的还原。比如一幅小图像重建为一幅大图。原理:用高斯金字塔的每一层图像减去其上一层图像上采样并高斯卷积之后的预测图像,得到一系列的差值图像,即为Laplacian分解图像。

  拉普拉斯图像的形成过程大致为对原图像进行低通滤波和降采样得到一个粗尺度的近似图像,即分解得到的低通近似图像,把这个近似图像经过插值,滤波,再计算它和原图像的插值,就得到分解的带通分量。下一级分解是在得到的低通近似图像上进行,迭代完成多尺度分解。可以看出拉普拉斯金字塔的分解过程包括四个步骤:

  • 1,低通滤波
  • 2,降采样(缩小尺寸)
  • 3,内插(放大尺寸)
  • 4,带通滤波(图像相减)

  拉普拉斯图像突出图像中的高频分量,注意的是拉普拉斯的最后一层是低通滤波图像,不是带通滤波图像。

  我们对图像进行缩放可以用图像金字塔,也可以使用resize函数进行缩放,后者效果更好(我们后面补充resize图像缩放)。这里只是对图像金字塔做一些简单了解

  下面分别学习图像向下取样和向上取样(下采样就是图片缩小,上采样就是图片放大)。

2,图像向下取样

2.1 高斯金字塔——向下采样(缩小)

  在图像向下取样中,使用最多的是高斯金字塔。它将堆图像Gi进行高斯核卷积,并删除原图中所有的偶数行和列,最终缩小图像。其中,高斯核卷积运算就是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值(券种不同)经过加权平均后得到。常见的 3*3 和 5*5 高斯核如下:

   高斯核卷积让临近中心的像素点具有更高的重要度,对周围像素计算加权平均值,如下图所示,其中心位置权重最高为 0.4。

   显而易见,原始图像 Gi 具有 M*N 个像素,进行向下采样之后,所得到的图像 Gi+1 具有 M/2 * N/2 个像素,只有原图的四分之一。通过对输入的原始图像不停迭代以上步骤就会得到整个金字塔。注意,由于每次向下取样会删除偶数行和列,所以它会不停地丢失图像的信息。

  在OpenCV中,向下取样使用的函数为 pyrDown() ,其函数原型如下:

dst = pyrDown(src[, dst[, dstsize[, borderType]]])

  cv2.pyrDown 使用Gaussian金字塔分解对输入图像向下采样。首先它对输入图像用指定滤波器进行卷积,然后通过拒绝偶数的行和列来下采样图像。

  其参数意思如下:

  • src表示输入图像,
  • dst表示输出图像,和输入图像具有一样的尺寸和类型
  • dstsize表示输出图像的大小,默认值为Size(5*5)
  • borderType表示像素外推方法,详见cv::bordertypes

  实现代码如下:

# _*_coding:utf-8 _*_
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取原始图片
img = cv2.imread('kd2.jpg')

# 图像向下取样
r = cv2.pyrDown(img)

# # 显示图形
# cv2.imshow('origin image', img)
# cv2.imshow('processing image', r)
# cv2.waitKey(0)
# cv2.destroyAllWindows()

# 为了方便将两张图对比,我们使用matplotlib
titles = ['origin', 'pyrDown']
images = [img, r]
for i in np.arange(2):
    plt.subplot(1, 2, i+1), plt.imshow(images[i])
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

   我们可以先看OpenCV画的图,然后看两张图放一样大的效果图。

  我们从上图可以看出,向下采样将原始图像压缩成原图的四分之一。

   很明显比起第一张图,第二张图有点模糊了。

  多次向下取样的代码:

# _*_coding:utf-8 _*_
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取原始图片
img = cv2.imread('kd2.jpg')

# 图像向下取样
r1 = cv2.pyrDown(img)
r2 = cv2.pyrDown(r1)
r3 = cv2.pyrDown(r2)

# 为了方便将两张图对比,我们使用matplotlib
titles = ['origin', 'pyrDown1', 'pyrDown2', 'pyrDown3']
images = [img, r1, r2, r3]
for i in np.arange(4):
    plt.subplot(2, 2, i+1), plt.imshow(images[i])
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

   结果如图所示:

  虽然我将图像展示的一样大小,但是我们可以很清楚的看到图像向下采样越多,图像越模糊。

3,图像向上取样

3.1  高斯金字塔——向上采样(放大)

  在图像向上取样是由小图像不断放图像的过程,它将图像在每个方向上扩大为原图像的2倍,新增的行和列均使用0来填充,并使用于“向下取样”相同的卷积核乘以4,再与放大后的图像进行卷积运算,以获得“新增像素”的新值,如下所示,它在原始像素45, 123, 89, 149之间各新增了一行和一列值为0的像素。

   在OpenCV中,向上取样使用的函数为 pyrUp(),其原型如下所示:

dst = pyrUp(src[, dst[, dstsize[, borderType]]])

  cv2.PyrUp() 是使用Gaussian金字塔分解对输入图像向上采样。首先通过在图像中插入 0 偶数行和偶数列,然后对得到的图像用指定的滤波器进行高斯卷积。其中滤波器乘以4做插值。所以输出图像是输入图像的2倍大小。

  • src表示输入图像,
  • dst表示输出图像,和输入图像具有一样的尺寸和类型
  • dstsize表示输出图像的大小,默认值为Size()
  • borderType表示像素外推方法,详见cv::bordertypes

  实现代码如下:

# _*_coding:utf-8 _*_
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取原始图片
img = cv2.imread('kd2.jpg')

# 图像向下取样
r1 = cv2.pyrUp(img)

# 显示图形
cv2.imshow('origin image', img)
cv2.imshow('processing image1', r1)
cv2.waitKey(0)
cv2.destroyAllWindows()

   由于放大了四倍,图太大,我就这样放了:

  多次向上取样的代码如下:

# _*_coding:utf-8 _*_
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取原始图片
img = cv2.imread('kd2.jpg')

# 图像向下取样
r1 = cv2.pyrUp(img)
r2 = cv2.pyrUp(r1)
r3 = cv2.pyrUp(r2)

# 为了方便将两张图对比,我们使用matplotlib
titles = ['origin', 'pyrUp1', 'pyrUp2', 'pyrUp3']
images = [img, r1, r2, r3]
for i in np.arange(4):
    plt.subplot(2, 2, i+1), plt.imshow(images[i])
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

   结果如下:

   每次向上取样均为上次图像的四倍,但图像的清晰度会降低。

4,拉普拉斯金字塔

  图像的拉普拉斯金字塔可以由图像的高斯金字塔得到,没有单独的函数。拉普拉斯金字塔图像是边缘图片,大部分元素是零,它被用在图像压缩上,拉普拉斯金字塔的一级是由那一级的高斯金字塔和它的更高一级高斯金字塔的图像差别来生成的。

  转换的公式为:

  拉普拉斯金字塔的代码实现:

import cv2
import numpy as np

img = cv2.imread('kd2.jpg')
down = cv2.pyrDown(img)
down_up = cv2.pyrUp(down)

l_1 = img - down_up
cv2.imshow('laplacian', l_1)
cv2.waitKey(0)
cv2.destroyAllWindows()

  效果图如下:

 

   拉普拉斯金字塔的图像看起来就像是边界图。经常被用在图像压缩中。

 

5,总结上采样和下采样

5.1  为什么用高斯核

  图像的金字塔化能高效的(计算效率也较高)对图像进行多尺度的表达,但它缺乏坚实的理论基础,不能分析图像中物体的各种尺度(虽然我们原图像的金字塔图像,但是我们还是不知道原图像内人物的大小)

  信号的尺度空间刚提出是通过一系列单参数,宽度递增的高斯滤波器将原始信号滤波得到组低频信号。那么一个很明显的疑问是:除了高斯滤波之外,其他带有参数 t 的低通滤波器是否也可以用来生成一个尺度空间。

  后来Koenerink、Lindeberg[Scale-space theory in computer vision]、Florack等人用精确的数学形式通过不同的途径都证明了高斯核是实现尺度变换的唯一变换核。

  虽然很多研究者从可分性,旋转不变性,因果性等特征推出高斯滤波器是建立线性尺度空间的最优滤波器。然而在数字图像处理中,需要对核函数进行采样,离散的高斯函数并不满足连续高斯函数的一些优良的性质。所以后来就出现了一些非线性的滤波器组来建立尺度空间,如B样条核函数。

5.2 使用高斯核进行尺度空间金字塔的构建,则具有如下性质

1,加权平均和有限孔径效应

  信号在尺度 t 上的表达可以看成是原信号在空间上一系列加权平均,权重就是具有不同尺度参数的高斯核。

  信号在尺度 t 上的表达也对应于一个无方向性的孔径函数(特征长度为 σ = √ t)来观测信号的结果。这时候信号中特征长度小于 σ 的波动会被平滑掉。

2,层叠平滑

  层叠平滑也叫高斯核族的半群(Semi-Group)性质:两个高斯核的卷积等同于另外一个不同核参数的高斯核卷积。

 

  这个性质的意思就是说不同的高斯核对图像的平滑是连续的。

3,局部极值递性

  这个特征可以从人眼的视觉原理去理解,人在看一件物体时,离的越远,物体的细节看到的越少,细节特征是在减少的。

  高斯核对图像进行滤波具有压制细节的性质。

4,尺度伸缩不变性

  这里只是一个公式推导的问题,对原来的信号加一个变换函数,对变换后的信号再进行高斯核的尺度空间生成,新的信号的极值点等特征是不变的。

5.3  总结上采样和下采样函数

  上面对两种采样做了代码实现,下面再赘述一次。

  上采样:就是图片放大,使用PryUp函数。上采样的步骤:先将图像在每个方向放大为原来的两倍,新增的行和列用0填充,再使用先前同样的内核与放大后的图像卷积,获得新增像素的近似值。

  下采样:就是图片缩小,使用PyrDown函数。下采样步骤:先将图片进行高斯内核卷积,再将所有偶数列去除。

  注意PryUP() 和 PyrDown() 不是互逆的,即上采样和下采样的不是互为逆操作

   总之,上,下采样都存在一个严重的问题,那就是图像变模糊了,因为缩放的过程中发生了信息丢失的问题。要解决这个问题,就得用拉普拉斯金字塔。

 

   当然也可以直接使用 cv2里面的resize()函数,resize()函数的效果更好,下面我们学习一下使用resize()函数进行图像缩放。

6, 图像缩放——resize()函数

  cv2.resize()函数是opencv中专门来调整图片的大小,改变图片尺寸。

  注意:CV2是BGR,而我们读取的图片是RGB,所以要注意一下,变换的时候注意对应。

  其函数原型如下:

def resize(src, dsize, dst=None, fx=None, fy=None, interpolation=None)

  对应的各个参数意思:

  src:输入,原图像,即待改变大小的图像;

  dsize:输出图像的大小。如果这个参数不为0,那么就代表将原图像缩放到这个Size(width,height)指定的大小;如果这个参数为0,那么原图像缩放之后的大小就要通过下面的公式来计算:

              dsize = Size(round(fx*src.cols), round(fy*src.rows))

其中,fx和fy就是下面要说的两个参数,是图像width方向和height方向的缩放比例。

  fx:width方向的缩放比例,如果它是0,那么它就会按照(double)dsize.width/src.cols来计算;

  fy:height方向的缩放比例,如果它是0,那么它就会按照(double)dsize.height/src.rows来计算;

  interpolation:这个是指定插值的方式,图像缩放之后,肯定像素要进行重新计算的,就靠这个参数来指定重新计算像素的方式,有以下几种:

  • INTER_NEAREST                 - 最邻近插值
  • INTER_LINEAR                     - 双线性插值,如果最后一个参数你不指定,默认使用这种方法
  • INTER_AREA                        -  区域插值(使用像素区域关系进行重采样)    resampling using pixel area relation. It may be a preferred method for image decimation, as it gives moire’-free results. But when the image is zoomed, it is similar to the INTER_NEAREST method.
  • INTER_CUBIC                      - 三次样条插值 (超过4x4像素邻域内的双立方插值)
  • INTER_LANCZOS4              -  Lanczos插值(超过8x8像素邻域内的Lanczos插值)

  对于插值方法,正常情况下使用默认的双线性插值法就够了。不过这里还是有建议的:若要缩小图像,一般情形下最好用 CV_INTER_AREA 来插值,而若要放大图像,一般情况下最好用  CV_INTER_CUBIC (效率不高,慢,不推荐使用)或 CV_INTER_LINEAR (效率较高,速度较快,推荐使用)

几种常用方法的效率为:

  最邻近插值>双线性插值>双立方插值>Lanczos插值

  但是效率和效果是反比的,所以根据自己的情况酌情使用。

  注意:输出的尺寸格式为(宽,高)

  示例:

# _*_coding:utf-8_*_
import cv2
import numpy as np

image = cv2.imread('cat.jpg')
# 对图片进行灰度化,注意这里变换!!
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
 
crop_img = cv2.resize(gray, (224, 224), interpolation=cv2.INTER_LANCZOS4)
print(image.shape, gray.shape, crop_img.shape)
# (414, 500, 3) (414, 500) (224, 224)


cv2.imshow('result', crop_img)
cv2.waitKey()
cv2.detroyAllWindows()

   

  效果如下:

  对图像缩放,做一个完整的示例:

import cv2
import os
import numpy as np
import matplotlib.pyplot as plt


img = cv2.imread("cat.jpg")
# resize的插值方法:正常情况下使用默认的双线性插值法
res_img = cv2.resize(img, (200, 100))
res_fx = cv2.resize(img, (0, 0), fx=0.5, fy=1)
res_fy = cv2.resize(img, (0, 0), fx=1, fy=0.5)
print('origin image shape is ',img.shape)
print('resize 200*100 image shape is ',res_img.shape)
print('resize  0.5:1 shape is ',res_fx.shape)
print('resize  1:0.5  image shape is ',res_fy.shape)
'''
origin image shape is  (414, 500, 3)
resize 200*100 image shape is  (100, 200, 3)
resize  0.5:1 shape is  (414, 250, 3)
resize  1:0.5  image shape is  (207, 500, 3)
'''

# 标题
title = ['Origin Image', 'resize200*100', 'resize_fx/2', 'resize_fy/2']
# 对应的图像
image = [img, res_img, res_fx, res_fy]

for i in range(len(image)):
    plt.subplot(2, 2, i+1), plt.imshow(image[i])
    plt.title(title[i])
    plt.xticks([]), plt.yticks([])
plt.show()

   效果图如下:

  

参考文献:https://blog.csdn.net/Eastmount/article/details/89341077

 https://www.cnblogs.com/FHC1994/p/9128005.html

https://www.cnblogs.com/zsb517/archive/2012/06/10/2543739.html

https://blog.csdn.net/dcrmg/article/details/52561656

https://www.cnblogs.com/ronny/p/3886013.html

posted @ 2020-10-22 15:37  战争热诚  阅读(16746)  评论(3编辑  收藏  举报