目标检测简介
目标检测是计算机视觉中一个重要的研究方向。人眼可以轻松、准确地识别出图片中的物体是什么、这个物体在图片中的哪个位置。
例如,当我们看到下图左边的图片时,我们可以轻松的识别出图片中的动物是猫和狗,并且知道它们在图片中所处的位置。但是对于计算机来说,在以数字形式表示的图片中寻找目标物体,并判断这个物体是什么,这是一件困难的事情。目标检测的目的就是使计算机能够识别图片中的目标(物体、动物等)是什么、这个目标的位置在哪里。如下右边的图片所示,通过检测算法的帮助,计算机不仅能识别出图中的动物,还可以标记出其所处的位置。
目标检测的发展可以分为两个阶段:传统检测算法和基于深度学习的检测算法。
传统的目标检测算法是通过将人为设计的目标特征和机器学习的分类器相结合来实现的。
基于深度学习的检测算法可分为两类,一类是使用基于候选区域(Region Proposal)的方法先找出图片中可能存在目标的区域,然后通过卷积神经网络对该区域进行分类;另一类是直接使用卷积神经网络预测目标所属类别的概率和其在图片中的位置坐标。
由于深度学习的崛起,推动了目标检测的快速发展和应用,如今目标检测已经广泛的应用于我们的日常生活中,如自动驾驶、安保监控、医疗影像、机器视觉等领域。虽然目标检测在发展过程中取得了很多成果,但同时也面临很多挑战,例如目标检测在实时性、抗干扰性、工业的大规模应用等方面还存在很多阻碍。
相关的工具的介绍
OpenCV
是一个基于 C++ 编写的轻量级高效的开源许可发行的跨平台计算机视觉库,可运行在多种操作系统上:Windows、Mac OS、Linux、Android。由于其具有友好的可读性和运行的效率,故获得大量开发者的青睐,同时其还提供 Python、Ruby 等语言的接口方便开发者调用。本教程将使用 Python 语言对 OpenCV 的库函数进行调用。大家可以访问 OpenCV 官方网站 获取更多相关知识。
NumPy
是一个支持处理多维度大型矩阵的 Python 科学计算包。在对图像进行处理时经常会用到 NumPy,OpenCV 中读取存储图片都是以 NumPy 形式完成的。利用 NumPy 我们可以轻松的以多维数据的形式呈现图片,并对图片进行重组、计算、数值分析等操作。想要深入学习可以访问 NumPy 官方中文文档。
TensorFlow
是一个开源的机器学习平台,其广泛应用于机器学习和深度学习算法实现上。其可运行在多种平台上,如 PC、移动和分布式平台。支持多种计算机语言:C++、Python、Java、Go等。由于其灵活、便捷、高性能以及活跃的社区等因素使得 TensorFlow 成为目前最受欢迎的机器学习开源框架之一。本教程将使用 Python 语言对 TensorFlow 进行调用。想要更深入学习该框架可以访问 TensorFlow 官方网站。
scikit-learn
是基于 Python 的开源机器学习工具,其涵盖大部分机器学习算法,包括分类、回归、非监督分类、数据降维、数据预处理等,是一款高效的数据挖掘和分析的工具。其通过 NumPy、SciPy 和 Matplotlib 等实现多种算法并且可在各种环境中使用。关于该工具更详细的信息可以参考 scikit-learn 官方网站。
scikit-image
是一个基于 Python 的开源图像处理工具,其将图片作为 NumPy 数组,并包括分割、几何变换、过滤等处理方法。
滑动窗口(Sliding Windows)
当我们构建一个传统的目标检测方法时,首先需要提供待检测图片,然后将滑动窗口(Sliding Windows)和图像金字塔(Image Pyramid)这两种方法相结合,从图片中选择出一些区域。接下来通过一些算法提取出这些区域的特征信息,然后通过机器学习的分类器对提取的特征分类。目标检测的输出结果一般是使用矩形框标记出要检测的目标,但是使用滑动窗口和图像金字塔时在同一个目标上会标记出多个矩形框,所以我们需要使用非极大值抑制(Non-maxima suppression)来剔除多余的矩形框,确保每个目标只用一个矩形框标记。
滑动窗口(Sliding Windows)在目标检测过程中的作用是定位目标(物体、动物等)在图片中的位置。在计算机视觉中滑动窗口是一个矩形框,它沿着从左向右、从上向下的方向在图片上滑动以达到提取出图片中每一个区域的目的。对于矩形框滑过的每一区域,我们使用分类器来判断该区域中是否存在物体。
滑动窗口的实现比较简单,下面我们通过代码来实现这个方法。首先在脚本中我们导入需要用到的模块,这里我们导入 cv2 模块用于处理图片。分别从 matplotlib 导入 pyplot 和从 IPython 中导入 display 模块用于显示图片。然后我们使用 %matplotlib inline 魔法函数让图片在页面中显示。
import cv2
from matplotlib import pyplot as plt
from IPython import display
%matplotlib inline
然后我们定义一个函数 sliding_window 用于获取滑动窗口。这个函数有三个参数 image、window 和 step。
第一个参数 image 是输入函数的图片,我们将用矩形框在这个图片上滑动。
第二个参数 window 是一个元组,表示滑动的矩形框的高和宽。
第三个参数 step 表示矩形框间隔多少个像素移动一次,这里我们可以称之为步长。
下图中我们用三种颜色表示不同位置的同一矩形框,图中的 n 就表示矩形框每次移动 n 个像素的距离,step 设置的太小或太大都会对目标检测造成负面的影响,一般这个值会设置在 4 到 8 之间。
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]])
想要实现矩形框在图片上滑动,我们需要知道矩形框每次滑动位于图片中的位置。我们使用两个 for 循环获取矩形框的所有坐标位置。第一个 for 循环控制矩形框以 step 的步长在图片中上下移动,第二个 for 循环控制矩形框以 step 的步长在图中左右移动。最后通过 yield 生成器返回一个元组,其中元组的第一个元素 x 和第二个元素 y 表示矩形框左上角的坐标,元组的第三个元素 image[y:y + window[1], x:x + window[0]] 就是处在图片中不同位置的矩形框。
我们构建好了函数后,下面我们可以使用这个函数来实现滑动窗口操作了。首先我们使用以下命令来获取需要用到的图片 pets.jpg。
!wget https://labfile.oss.aliyuncs.com/courses/3096/pets.jpg
接下来我们使用 cv2.imread 函数读取图片,pets.jpg 是要读取的图片名。然后我们定义滑动窗口的宽 window_w 为 400 个像素,滑动窗口的高 window_h 为 400 个像素。我们使用 n 来表示滑动窗口的数量。
image = cv2.imread("pets.jpg")
(window_w, window_h) = (400, 400)
接下来使用一个 for 循环来遍历每一个滑动窗口,我们需要传递三个参数给 sliding_window。
第一个参数 image 是我们读取的图片。
第二个参数 (window_w, window_h) 表示滑动窗口的宽和高。
第三个参数 200 表示滑动窗口将每次滑动的步长为 200 个像素(注意这里为了演示方便将滑动窗口和步长的值设置得都很大,在实际使用中不建议将其设置过大或过小)。
然后我们使用一个 if 语句来判断获得的滑动窗口和我们设定的滑动窗口大小是否一致,如果滑动窗口截取的区域与设定的 (window_w, window_h) 中任意一个元素不同,则执行 continue 跳过该滑动窗口。
for (x, y, window) in sliding_window(image, (window_w, window_h), 200):
if window.shape[0] != window_w or window.shape[1] != window_h:
continue
clone = image.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.1)
display.clear_output(wait=True)
在循环内我们绘制出图片中每一个滑动窗口了,在绘制前使用 copy 函数复制输入函数的图片,因为接下来的画图操作将会修改源图片,接着我们使用 cv2.rectangle 在 clone 上绘制出每个滑动窗口。因为在 OpenCV 图片是以 B,G,R(蓝,绿,红)的通道顺序存储的,而在 Matplotlib 中图片是以 R,G,B 的通道顺序存储,所以我们使用 clone[:,:,::-1] 切片方法来跳转图片通道的顺序,然后使用 plt.imshow 在页面中呈现绘图后的结果。
因为我们在 for 循环内要绘制多张图片,所以使用 plt.pause(0.1) 让每张图片显示暂停一段时间,函数的参数 0.1 表示暂停 0.1 秒。最后我们使用 display 的 clear_output(wait=True) 方法清除已经显示的图片为下一张图片显示做准备。
执行代码后绘制的滑动窗口的结果如下所示。从图中我们可以看到滑动窗口按照从左向右、从上向下在图片上滑动。
下图是执行代码后每个滑动窗口内的区域,与上图对照可以看出下图中每一小块图片对应上图中每个矩形框内的区域。