33从传统算法到深度学习:目标检测入门实战 --图像金字塔

图像金字塔的作用及实现

图像金字塔简单来说就是用多个不同的尺寸来表示一张图片。如下图,最左边的图片是原始图片,然后从左向右图片的尺寸依次缩小直到图片的尺寸达到一个阈值,这个阈值就是多次缩小图片的最小尺寸,不会有比这更小尺寸的图片了,像这种图片的尺寸逐步递增或递减的多张图层就是图像金字塔,每张不同尺寸的图片都称为图像金字塔的一层。图像金字塔的目的就是寻找图片中出现的不同尺寸的目标(物体、动物等)。


image.png

下面我们通过几行简单的代码来实现图像金字塔。首先执行下面命令下载我们将会用到的图片。

!wget https://labfile.oss.aliyuncs.com/courses/3096/pets.jpg

然后导入 cv2 模块用于处理图片,分别从 matplotlib 导入 pyplot 和从 IPython 中导入 display 模块用于显示图片。然后我们使用 %matplotlib inline 魔法函数让图片在页面中显示。

import cv2
from matplotlib import pyplot as plt
from IPython import display
%matplotlib inline

创建了一个名为 pyramid 函数,这个函数将用来生成图像金字塔,这个函数有三个参数,如下所示。
第一个参数 image 是要进行图像金字塔操作的原始图片。
第二个参数 top 是图像将会被缩小的最小尺寸,我们将这个参数设置一个默认值为 (128, 128),第一个 128 表示图片的高,第二个 128 表示图片的宽。
第三个参数 ratio 表示每次图像将会被缩小 ratio 倍,我们给这个参数设置了一个默认值为 1.2。

def pyramid(image, top = (128, 128), ratio = 1.2):
    yield image
    
    while True:
        (w, h) = (int(image.shape[1] / ratio), int(image.shape[0] / ratio))
        image = cv2.resize(image, (w, h), interpolation = cv2.INTER_AREA)
        
        if w < top[1] or h < top[0]:
            break
        
        yield image

在函数内我们首先使用 yield 生成器返回原始图片,因为在图像金字塔的最底端我们需要一张原始图片。然后使用 while 循环来不断缩小图片尺寸。直到缩小后图片的尺寸比前面的 top 参数小为止。在循环内 (w, h) 表示图像金字塔前一层的图像缩小 ratio 倍的宽和高。我们使用 cv2.resize 方法将前一层图片进行缩放,我们将 (w, h) 作为函数的第二个参数,表示缩放后图片的宽和高的值。
随着图片的尺寸不断缩小,我们使用 if 语句判断图片的尺寸是否已经到达了设定的最小尺寸,将每次图片缩放后的宽和高与设定的最小尺寸 top 进行对比,如果小于最小尺寸则使用 break 结束循环。最后使用 yield 生成器返回每次缩放后的图片。至此图像金字塔的函数就构建完成了。
下面让我们来调用这个函数看下结果。首先我们使用 cv2.imread 函数读取图片。

image = cv2.imread("pets.jpg")

然后我们使用一个 for 循环获取每次缩放的图片,这里 pyramid 的第一个参数 image 就是需要逐层缩小的图片。第二个参数 top 不传入值,表示使用默认的最小尺寸。第三个参数 ratio 表示每次将图片缩小 1.5 倍。接下来我们使用切片方法 i[:,:,::-1] 调整图片的通道顺序以便使用 plt.imshow 方法显示图片,然后使用 plt.pause(0.3) 让每张图片显示暂停 0.3 秒,最后使用 display 的 clear_output(wait=True) 方法清除当前显示的图片为显示下一张图片做准备。

for i in pyramid(image, ratio = 1.5):
    i = i[:,:,::-1]
    plt.imshow(i)
    plt.pause(0.3) 
    display.clear_output(wait=True)

执行上面代码可以看到坐标轴上的数字在不断缩小,说明图片的尺寸已经按照我们传递给 pyramid 函数的参数来缩小了。通过下图可以更直观的看出图片被缩放后的效果。


image.png

图像金字塔结合滑动窗口

在传统的目标检测方法中,使用图像金字塔和滑动窗口相结合的方式来检测出图片中不同位置和不同尺寸的目标。使用滑动窗口的方法时,在图片上滑动的矩形框尺寸是固定的,这就导致了如果目标的尺寸相对于矩形框太大或太小都会导致我们无法检测到目标。这种情况下,我们可以在图像金字塔的每层图片上进行滑动窗口的操作来解决这个问题。如下图左边的图片,狗并不能完全被矩形框包围,矩形框只能包住狗的部分面部区域;下图右边的图片,通过运用图像金字塔和滑动窗口相结合的方法,矩形框的尺寸没有变化,但是在经过缩小后的图片中狗完全被矩形框包裹住了。


image.png

为了实现上述操作,首先创建实验 1 中学习的 sliding_window 函数。其每一步的具体含义可以参考实验 1 中的说明。

def sliding_window(image, window, step):
    for y in range(0, image.shape[0] - window[1], step):
        for x in range(0, image.shape[1] - window[0], step):
            yield (x, y, image[y:y + window[1], x:x + window[0]])

然后我们定义滑动窗口的宽 window_w 为 128 个像素,滑动窗口的高 window_h 为 128 个像素。

(window_w, window_h) = (128, 128)

接下来我们使用一个 for 循环获取每层缩放的图片,在循环内再使用一个 for 循环进行滑动窗口的操作,其中 sliding_window 函数的参数意义如下所示。
第一个参数 i 是图像金字塔每层的图片。
第二个参数 (128, 128) 表示滑动窗口的宽和高都是 128。
第三个参数 100 表示滑动窗口将每次滑动的步长为 100 个像素。

for i in pyramid(image, ratio = 1.5):
    for (x, y, window) in sliding_window(i, (window_w, window_h), 100):
        if window.shape[0] != window_w or window.shape[1] != window_h:
            continue
            
        clone = i.copy()
        cv2.rectangle(clone, (x, y), (x + window_w, y + window_h), (0, 255, 0), 2)
        clone = clone[:,:,::-1]
        plt.imshow(clone)
        plt.pause(0.01) 
        display.clear_output(wait=True)

我们使用一个 if 语句来判断获得的滑动窗口和我们设定的滑动窗口大小是否一致。如果滑动窗口截取的区域与设定的 (window_w, window_h) 中任意一个元素不同,则执行 continue 跳过该滑动窗口。最后就是在图片上绘制出滑动窗口。
下图就是我们运行脚本后得到的部分图片。可以看到我们在图像金字塔的每一层都使用滑动窗口,虽然矩形框的尺寸保持不变,但是随着图片地不断缩小,矩形框逐渐包裹住目标。


image.png

你可能感兴趣的:(33从传统算法到深度学习:目标检测入门实战 --图像金字塔)