物体检测和数据集

3 minute read

Published:

本文介绍图像分类、目标检测和实例分割的区别,说明边界框的表示方法与目标检测数据集的组织方式,并给出边界框绘制和数据读取示例。

🎦 本节课程视频地址 👇

Bilibil

目标检测(Object Detection)

图片分类、目标检测和实例分割

Object Detection

图像分类任务中,我们假设图像中只有一个主要物体对象,我们只关注如何识别其类别。 然而,很多时候图像里有多个我们感兴趣的目标,我们不仅想知道它们的类别,还想得到它们在图像中的具体位置。 在计算机视觉里,我们将这类任务称为目标检测(object detection)或目标识别(object recognition)。

目标检测在多个领域中被广泛使用。 例如,在无人驾驶里,我们需要通过识别拍摄到的视频图像里的车辆、行人、道路和障碍物的位置来规划行进线路。 机器人也常通过该任务来检测感兴趣的目标。安防领域则需要检测异常目标,如歹徒或者炸弹。

边界框(bounding box)

一个边界框可以用 4 个数字定义

  • 左上 x,左上 y,右下 x,右下 y
  • 左上 x,左上 y,宽,高

目标识别的数据集通常比图片分类的数据集小很多。

目标检测数据集

  • 每行表示一个物体,若一张图里有 n 个物体,则重复 n 行

    如:图片文件名,物体类别,边缘框(x1,y1,x2,y2)

  • COCO 数据集

    80 类别,330K 图片,1.5M 物体

代码实现

%matplotlib inline
import torch
from d2l import torch as d2l

d2l.set_figsize()
img = d2l.plt.imread('../Image/Lions.png')
d2l.plt.imshow(img)
  • 定义两种边界框表示函数
def box_corner_to_center(boxes):
    """从(左上,右下)转换到(中间,宽度,高度)"""
    x1, y1, x2, y2 = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
    #行数>1,表示多个框
    cx = (x1 + x2) / 2
    cy = (y1 + y2) / 2
    w = x2 - x1
    h = y2 - y1
    boxes = torch.stack((cx, cy, w, h), axis=-1)
    #torch.stack(),tensor的concatenate
    #Concatenates a sequence of tensors along a new dimension.
    return boxes

def box_center_to_corner(boxes):
    """从(中间,宽度,高度)转换到(左上,右下)"""
    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

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

# bbox是边界框的英文缩写
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
# Out:
# tensor([[True, True, True, True],
#         [True, True, True, True]])
  • 画出边界框
def bbox_to_rect(bbox, color):
    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'))
output_bounding-box
  • 目标检测数据集
%matplotlib inline
import os
import pandas as pd
import torch
import torchvision
from d2l import torch as d2l

d2l.DATA_HUB['banana-dection'] = (
    d2l.DATA_URL + 'banana-detection.zip',
    '5de26c8fce5ccdea9f91267273464dc968d20d72')

def read_data_bananas(is_train=True):
    data_dir = d2l.download_extract('banana-detection', folder='../data')
    csv_fname = os.path.join('../data/banana-detection', 'bananas_train' if is_train else 'bananas_val', 'label.csv')
    csv_data = pd.read_csv(csv_fname)
    csv_data = csv_data.set_index('img_name')
    images, targets = [], []
    for img_name, target in csv_data.iterrows():
    #iterrows()进行行索引,返回索引和内容
        images.append(
            torchvision.io.read_image(
                os.path.join('../data/banana-detection', 'bananas_train' if is_train else
                             'bananas_val', 'images', f'{img_name}')))
            #Reads a JPEG or PNG image into a 3 dimensional RGB Tensor.
            #把图片张量加入images,也就是说把所有图片都读到内存里
            #因为数据集小
        targets.append(list(target))
        #把所识别内容加入targets
    return images, torch.tensor(targets).unsqueeze(1) / 256
    #unsqueeze(1),在第一维增加维度
    #Returns a new tensor with a dimension of size one inserted at the specified position.
    #原来是df的(1000, 5),现在是(1000, 1, 5)
    #target是香蕉的框
    #tensor(target)/256相当于把框正则化,变成关于一个像素的位置
  • 创建自定义 Dataset 实例
class BananasDataset(torch.utils.data.Dataset):
    def __init__(self, is_train):
        self.features, self.labels = read_data_bananas(is_train)
        print('read' + str(len(self.features)) + (f' training examples' if is_train else f' validation examples'))
        print(self.labels.shape)

    def __getitem__(self, idx):
    # __getitem__(self, idx)定义了类似于索引,去实现可索引的功能
    # 需要更复杂的逻辑去定义切片、顺序、步长等
        return (self.features[idx].float(), self.labels[idx])

    def __len__(self):
        return len(self.features)
  • 定义数据迭代器
def load_data_bananas(batch_size):
    train_iter = torch.utils.data.DataLoader(BananasDataset(is_train=True),
                                             batch_size, shuffle=True)
    val_iter = torch.utils.data.DataLoader(BananasDataset(is_train=False),
                                           batch_size)

    return train_iter, val_iter
  • 测试
batch_size, edge_size = 32, 256 #图片大小256
train_iter, _ = load_data_bananas(batch_size)
batch = next(iter(train_iter))
batch[0].shape, batch[1].shape
# Out:
# (torch.Size([32, 3, 256, 256]), torch.Size([32, 1, 5]))
# 训练iter(batch_size, RBG_channals, height, width)
# 标号iter(batch_size, 每张图片最多物体数, 标号+边界框4个坐标)

imgs = (batch[0][0:10].permute(0, 2, 3, 1)) / 255
#batch[0]是imgs
#permute()函数改变tensor的维度顺序
print(imgs.shape)
axes = d2l.show_images(imgs, 2, 5, scale=2)
#Plot a list of images.

for ax, label in zip(axes, batch[1][0:10]):
    #batch[1]是boxes,tensor.shape = (32, 1, 5)
    #bacth[1][0:10]就是前十个boxes
    #label.shape=(1, 5)
    d2l.show_bboxes(ax, [label[0][1:5] * edge_size], colors=['w'])

Python 模块参考文档


Q&A🤓

Q:怎样标注新的数据集并使用?

🙋‍♂️:一般来说不用大规模的从头标注一个大数据集,通常会使用迁移模型,标注少量的目标数据集后,在已有的目标检测模型上做 fine-tuning,然后选出结果置信度不高的样本,再进行标注、微调,使用这种方式进行迭代直到效果满意为止。

配图说明:部分旧版图片链接已失效,现采用教材对应章节中的 PyTorch 示例图;图中数值可能与笔记保留的历史运行输出略有不同。