14.3. 目标检测和边界框
在 Colab 中打开 Notebook
在 Colab 中打开 Notebook
在 Colab 中打开 Notebook
在 Colab 中打开 Notebook
在 SageMaker Studio Lab 中打开 Notebook

在前面的章节(例如, 第 8.1 节第 8.4 节)中,我们介绍了各种用于图像分类的模型。在图像分类任务中,我们假设图像中只有*一个*主要对象,我们只关注如何识别其类别。然而,现实世界中的图像通常包含*多个*我们感兴趣的对象。我们不仅想知道它们的类别,还想知道它们在图像中的具体位置。在计算机视觉里,我们将这类任务称为*目标检测*(object detection)或*物体识别*(object recognition)。

目标检测在许多领域都有广泛的应用。例如,在无人驾驶中,我们需要通过识别拍摄的视频图像里车辆、行人、道路和障碍物的位置来规划行驶路线。此外,机器人也可能使用这项技术在复杂的环境中导航并与现实世界进行交互。安防系统同样需要检测异常目标,如闯入者或炸弹。

在接下来的几节中,我们将介绍几种用于目标检测的深度学习方法。我们将从介绍物体的*位置*信息开始。

%matplotlib inline
import torch
from d2l import torch as d2l
%matplotlib inline
from mxnet import image, np, npx
from d2l import mxnet as d2l

npx.set_np()
%matplotlib inline
import tensorflow as tf
from d2l import tensorflow as d2l

我们将加载本节将使用的示例图像。可以看到图像左侧是狗,右侧是猫。它们是这张图像中的两个主要目标。

d2l.set_figsize()
img = d2l.plt.imread('../img/catdog.jpg')
d2l.plt.imshow(img);
../_images/output_bounding-box_d6b70e_15_0.svg
d2l.set_figsize()
img = image.imread('../img/catdog.jpg').asnumpy()
d2l.plt.imshow(img);
[21:49:41] ../src/storage/storage.cc:196: Using Pooled (Naive) StorageManager for CPU
../_images/output_bounding-box_d6b70e_18_1.svg
d2l.set_figsize()
img = d2l.plt.imread('../img/catdog.jpg')
d2l.plt.imshow(img);
../_images/output_bounding-box_d6b70e_21_0.svg

14.3.1. 边界框

在目标检测中,我们通常使用*边界框*(bounding box)来描述对象的空间位置。边界框是矩形的,由矩形左上角的\(x\)\(y\)坐标与右下角的\(x\)\(y\)坐标决定。另一种常用的边界框表示是边界框中心的\((x, y)\)轴坐标以及框的宽度和高度。

在这里,我们定义了在这两种表示法之间进行转换的函数:box_corner_to_center从两角表示法转换为中心-宽度-高度表示法,而box_center_to_corner则相反。输入参数boxes可以是一个形状为(\(n\), 4)的二维张量,其中\(n\)是边界框的数量。

#@save
def box_corner_to_center(boxes):
    """Convert from (upper-left, lower-right) to (center, width, height)."""
    x1, y1, x2, y2 = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
    cx = (x1 + x2) / 2
    cy = (y1 + y2) / 2
    w = x2 - x1
    h = y2 - y1
    boxes = torch.stack((cx, cy, w, h), axis=-1)
    return boxes

#@save
def box_center_to_corner(boxes):
    """Convert from (center, width, height) to (upper-left, lower-right)."""
    cx, cy, w, h = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
    x1 = cx - 0.5 * w
    y1 = cy - 0.5 * h
    x2 = cx + 0.5 * w
    y2 = cy + 0.5 * h
    boxes = torch.stack((x1, y1, x2, y2), axis=-1)
    return boxes
#@save
def box_corner_to_center(boxes):
    """Convert from (upper-left, lower-right) to (center, width, height)."""
    x1, y1, x2, y2 = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
    cx = (x1 + x2) / 2
    cy = (y1 + y2) / 2
    w = x2 - x1
    h = y2 - y1
    boxes = np.stack((cx, cy, w, h), axis=-1)
    return boxes

#@save
def box_center_to_corner(boxes):
    """Convert from (center, width, height) to (upper-left, lower-right)."""
    cx, cy, w, h = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
    x1 = cx - 0.5 * w
    y1 = cy - 0.5 * h
    x2 = cx + 0.5 * w
    y2 = cy + 0.5 * h
    boxes = np.stack((x1, y1, x2, y2), axis=-1)
    return boxes
#@save
def box_corner_to_center(boxes):
    """Convert from (upper-left, lower-right) to (center, width, height)."""
    x1, y1, x2, y2 = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
    cx = (x1 + x2) / 2
    cy = (y1 + y2) / 2
    w = x2 - x1
    h = y2 - y1
    boxes = tf.stack((cx, cy, w, h), axis=-1)
    return boxes

#@save
def box_center_to_corner(boxes):
    """Convert from (center, width, height) to (upper-left, lower-right)."""
    cx, cy, w, h = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
    x1 = cx - 0.5 * w
    y1 = cy - 0.5 * h
    x2 = cx + 0.5 * w
    y2 = cy + 0.5 * h
    boxes = tf.stack((x1, y1, x2, y2), axis=-1)
    return boxes

我们将根据坐标信息定义图像中狗和猫的边界框。图像中坐标系的原点是图像的左上角,向右和向下分别是\(x\)\(y\)轴的正方向。

# Here `bbox` is the abbreviation for bounding box
dog_bbox, cat_bbox = [60.0, 45.0, 378.0, 516.0], [400.0, 112.0, 655.0, 493.0]
# Here `bbox` is the abbreviation for bounding box
dog_bbox, cat_bbox = [60.0, 45.0, 378.0, 516.0], [400.0, 112.0, 655.0, 493.0]
# Here `bbox` is the abbreviation for bounding box
dog_bbox, cat_bbox = [60.0, 45.0, 378.0, 516.0], [400.0, 112.0, 655.0, 493.0]

我们可以通过两次转换来验证这两个边界框转换函数的正确性。

boxes = torch.tensor((dog_bbox, cat_bbox))
box_center_to_corner(box_corner_to_center(boxes)) == boxes
tensor([[True, True, True, True],
        [True, True, True, True]])
boxes = np.array((dog_bbox, cat_bbox))
box_center_to_corner(box_corner_to_center(boxes)) == boxes
array([[ True,  True,  True,  True],
       [ True,  True,  True,  True]])
boxes = tf.constant((dog_bbox, cat_bbox))
box_center_to_corner(box_corner_to_center(boxes)) == boxes
<tf.Tensor: shape=(2, 4), dtype=bool, numpy=
array([[ True,  True,  True,  True],
       [ True,  True,  True,  True]])>

让我们在图像中绘制边界框,以检查它们是否准确。在绘制之前,我们将定义一个辅助函数bbox_to_rect。它以matplotlib包的边界框格式表示边界框。

#@save
def bbox_to_rect(bbox, color):
    """Convert bounding box to matplotlib format."""
    # Convert the bounding box (upper-left x, upper-left y, lower-right x,
    # lower-right y) format to the matplotlib format: ((upper-left x,
    # upper-left y), width, height)
    return d2l.plt.Rectangle(
        xy=(bbox[0], bbox[1]), width=bbox[2]-bbox[0], height=bbox[3]-bbox[1],
        fill=False, edgecolor=color, linewidth=2)
#@save
def bbox_to_rect(bbox, color):
    """Convert bounding box to matplotlib format."""
    # Convert the bounding box (upper-left x, upper-left y, lower-right x,
    # lower-right y) format to the matplotlib format: ((upper-left x,
    # upper-left y), width, height)
    return d2l.plt.Rectangle(
        xy=(bbox[0], bbox[1]), width=bbox[2]-bbox[0], height=bbox[3]-bbox[1],
        fill=False, edgecolor=color, linewidth=2)
#@save
def bbox_to_rect(bbox, color):
    """Convert bounding box to matplotlib format."""
    # Convert the bounding box (upper-left x, upper-left y, lower-right x,
    # lower-right y) format to the matplotlib format: ((upper-left x,
    # upper-left y), width, height)
    return d2l.plt.Rectangle(
        xy=(bbox[0], bbox[1]), width=bbox[2]-bbox[0], height=bbox[3]-bbox[1],
        fill=False, edgecolor=color, linewidth=2)

在图像上添加边界框后,我们可以看到两个目标的主体部分基本上在两个框内。

fig = d2l.plt.imshow(img)
fig.axes.add_patch(bbox_to_rect(dog_bbox, 'blue'))
fig.axes.add_patch(bbox_to_rect(cat_bbox, 'red'));
../_images/output_bounding-box_d6b70e_75_0.svg
fig = d2l.plt.imshow(img)
fig.axes.add_patch(bbox_to_rect(dog_bbox, 'blue'))
fig.axes.add_patch(bbox_to_rect(cat_bbox, 'red'));
../_images/output_bounding-box_d6b70e_78_0.svg
fig = d2l.plt.imshow(img)
fig.axes.add_patch(bbox_to_rect(dog_bbox, 'blue'))
fig.axes.add_patch(bbox_to_rect(cat_bbox, 'red'));
../_images/output_bounding-box_d6b70e_81_0.svg

14.3.2. 小结

  • 目标检测不仅可以识别图像中所有感兴趣的物体,还能识别它们的位置,位置通常由矩形边界框表示。

  • 我们可以在两种常用的边界框表示之间进行转换。

14.3.3. 练习

  1. 找到另一张图片,并尝试标记一个包含目标的边界框。比较标注边界框和标注类别:哪个通常需要更长的时间?

  2. 为什么box_corner_to_centerbox_center_to_corner的输入参数boxes的最内层维度总是4?