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

 

人工智能学习离不开实践的验证,推荐大家可以多在FlyAI-AI竞赛服务平台多参加训练和竞赛,以此来提升自己的能力。FlyAI是为AI开发者提供数据竞赛并支持GPU离线训练的一站式服务平台。每周免费提供项目开源算法样例,支持算法能力变现以及快速的迭代算法模型。

 传送门:请点击我

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

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

 

1,图像金字塔

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

   生成图像金字塔主要包括两种方式——向下取样,向上取样,在上图中,将level0级别的图像转换为 level1,level2,level3,level4,图像分辨率不断降低的过程称为向下取样;将level4级别的图像转换为 level3,level2,level1,leve0,图像分辨率不断增大的过程称为向上取样

1.1  高斯金字塔

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

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

1.2 拉普拉斯金字塔

  拉普拉斯金字塔用于重建图形,也就是预测残差,对图像进行最大程度的还原。比如一幅小图像重建为一幅大图。原理:用高斯金字塔的每一层图像减去其上一层图像上采样并高斯卷积之后的预测图像,得到一系列的差值图像,即为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() ,其函数原型如下:

1

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

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

  其参数意思如下:

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

  实现代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

# _*_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 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画的图,然后看两张图放一样大的效果图。

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

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

  多次向下取样的代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

# _*_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 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(),其原型如下所示:

1

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

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

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

  实现代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

# _*_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()

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

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

# _*_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 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,拉普拉斯金字塔

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

  转换的公式为:

 

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

1

2

3

4

5

6

7

8

9

10

11

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,总结上采样和下采样

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

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

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

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

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

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

 

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

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

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

  其函数原型如下:

1

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插值

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

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

  示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

# _*_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.detrosyAllWindows()

   效果如下:

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

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

更多精彩内容请访问FlyAI-AI竞赛服务平台;为AI开发者提供数据竞赛并支持GPU离线训练的一站式服务平台;每周免费提供项目开源算法样例,支持算法能力变现以及快速的迭代算法模型。

挑战者,都在FlyAI!!!