文章目录
- 13.5. 多尺度目标检测
- 13.5.1. 多尺度锚框
- 13.5.2. 多尺度检测
- 13.5.3. 小结
- 13.6. 目标检测数据集
- 13.6.2. 读取数据集
- 13.6.3. 演示
- 13.7. 单发多框检测(SSD)
- 13.7.1. 模型
- 13.7.1.1. 类别预测层
- 13.7.1.2. 边界框预测层
- 13.7.1.3. 连结多尺度的预测
- 13.7.1.4. 高和宽减半块
- 13.7.1.5. 基本网络块
- 13.7.1.6. 完整的模型
- 13.7.2. 训练模型
- 13.7.2.1. 读取数据集和初始化
- 13.7.2.2. 定义损失函数和评价函数
- 13.7.2.3. 训练模型
- 13.7.3. 预测目标
- 13.7.4. 小结
- 13.8. 区域卷积神经网络(R-CNN)系列
- 13.8.1. R-CNN
- 13.8.2. Fast R-CNN
- 13.8.3. Faster R-CNN
- 13.8.4. Mask R-CNN
- 13.8.5. 小结
13.5. 多尺度目标检测
在 13.4节中,我们以输入图像的每个像素为中心,生成了多个锚框。 基本而言,这些锚框代表了图像不同区域的样本。 然而,如果为每个像素都生成的锚框,我们最终可能会得到太多需要计算的锚框。 想象一个 (561, 728)的输入图像,如果以每个像素为中心生成五个形状不同的锚框,就需要在图像上标记和预测超过200万个锚框(561, 728, 5)。
13.5.1. 多尺度锚框
减少图像上的锚框数量并不困难。 比如,我们可以在输入图像中均匀采样一小部分像素,并以它们为中心生成锚框。 此外,在不同尺度下,我们可以生成不同数量和不同大小的锚框。 直观地说,比起较大的目标,较小的目标在图像上出现的可能性更多样。 例如, 1x1、1x2 和 2x2 的目标可以分别以4、2和1种可能的方式出现在 2x2 图像上。 因此,当使用较小的锚框检测较小的物体时,我们可以采样更多的区域,而对于较大的物体,我们可以采样较少的区域。
回想一下,在 6.2节中,我们将卷积图层的二维数组输出称为特征图。 通过定义特征图的形状,我们可以确定任何图像上均匀采样锚框的中心。
display_anchors函数定义如下。 我们在特征图(fmap)上生成锚框(anchors),每个单位(像素)作为锚框的中心。 由于锚框中的 (x, y)轴坐标值(anchors)已经被除以特征图(fmap)的宽度和高度,因此这些值介于0和1之间,表示特征图中锚框的相对位置。
由于锚框(anchors)的中心分布于特征图(fmap)上的所有单位,因此这些中心必须根据其相对空间位置在任何输入图像上均匀分布。 更具体地说,给定特征图的宽度和高度fmap_w和fmap_h,以下函数将均匀地对任何输入图像中fmap_h行和fmap_w列中的像素进行采样。 以这些均匀采样的像素为中心,将会生成大小为s(假设列表s的长度为1)且宽高比(ratios)不同的锚框。
def display_anchors(fmap_w, fmap_h, s):d2l.set_figsize()# 前两个维度上的值不影响输出fmap = torch.zeros((1, 10, fmap_h, fmap_w))# multibox_priors是在输入的fmap每个像素上生成锚框,并且坐标在[0, 1]之间anchors = d2l.multibox_prior(fmap, sizes=s, ratios=[1, 2, 0.5])# 这里再*真实图像宽高, 就变成了在真实图像上均匀分布的锚框,并且上面fmap的宽高就是锚框在每行列上的个数bbox_scale = torch.tensor((w, h, w, h))d2l.show_bboxes(d2l.plt.imshow(img).axes,anchors[0] * bbox_scale)
13.5.2. 多尺度检测
13.5.3. 小结
- 在多个尺度下,我们可以生成不同尺寸的锚框来检测不同尺寸的目标。
- 通过定义特征图的形状,我们可以决定任何图像上均匀采样的锚框的中心。
- 我们使用输入图像在某个感受野区域内的信息,来预测输入图像上与该区域位置相近的锚框类别和偏移量。
- 我们可以通过深入学习,在多个层次上的图像分层表示进行多尺度目标检测。
13.6. 目标检测数据集
目标检测领域没有像MNIST和Fashion-MNIST那样的小数据集。 为了快速测试目标检测模型,我们收集并标记了一个小型数据集。 首先,我们拍摄了一组香蕉的照片,并生成了1000张不同角度和大小的香蕉图像。 然后,我们在一些背景图片的随机位置上放一张香蕉的图像。 最后,我们在图片上为这些香蕉标记了边界框。
13.6.2. 读取数据集
通过read_data_bananas函数,我们读取香蕉检测数据集。 该数据集包括一个的CSV文件,内含目标类别标签和位于左上角和右下角的真实边界框坐标。
#@save
def read_data_bananas(is_train=True):"""读取香蕉检测数据集中的图像和标签"""data_dir = d2l.download_extract('banana-detection')csv_fname = os.path.join(data_dir, 'bananas_train' if is_trainelse '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():images.append(torchvision.io.read_image(os.path.join(data_dir, 'bananas_train' if is_train else'bananas_val', 'images', f'{img_name}')))# 这里的target包含(类别,左上角x,左上角y,右下角x,右下角y),# 其中所有图像都具有相同的香蕉类(索引为0)targets.append(list(target))return images, torch.tensor(targets).unsqueeze(1) / 256
解释:
- csv_data = csv_data.set_index(‘img_name’) 和 for img_name, target in csv_data.iterrows(): 配合使用的作用:
- 最后返回
torch.tensor(targets).unsqueeze(1) / 256
的作用:unsqueeze 是模拟一张图像里面有多个目标检测框,这里为了简单直接都设置为1个检测框。
13.6.3. 演示
13.7. 单发多框检测(SSD)
在 13.3节— 13.6节中,我们分别介绍了边界框、锚框、多尺度目标检测和用于目标检测的数据集。 现在我们已经准备好使用这样的背景知识来设计一个目标检测模型:单发多框检测(SSD) (Liu et al., 2016)。 该模型简单、快速且被广泛使用。尽管这只是其中一种目标检测模型,但本节中的一些设计原则和实现细节也适用于其他模型。
13.7.1. 模型
图13.7.1描述了单发多框检测模型的设计。 此模型主要由基础网络组成,其后是几个多尺度特征块。 基本网络用于从输入图像中提取特征,因此它可以使用深度卷积神经网络。 单发多框检测论文中选用了在分类层之前截断的VGG (Liu et al., 2016),现在也常用ResNet替代。 我们可以设计基础网络,使它输出的高和宽较大。 这样一来,基于该特征图生成的锚框数量较多,可以用来检测尺寸较小的目标。 接下来的每个多尺度特征块将上一层提供的特征图的高和宽缩小(如减半),并使特征图中每个单元在输入图像上的感受野变得更广阔。
回想一下在 13.5节中,通过深度神经网络分层表示图像的多尺度目标检测的设计。 由于接近 图13.7.1顶部的多尺度特征图较小,但具有较大的感受野,它们适合检测较少但较大的物体。 简而言之,通过多尺度特征块,单发多框检测生成不同大小的锚框,并通过预测边界框的类别和偏移量来检测大小不同的目标,因此这是一个多尺度目标检测模型。
13.7.1.1. 类别预测层
设目标类别的数量为 q 。这样一来,锚框有 q+1 个类别,其中0类是背景。 在某个尺度下,设特征图的高和宽分别为 h 和 w。 如果以其中每个单元为中心生成 a 个锚框,那么我们需要对 hwa 个锚框进行分类。 如果使用全连接层作为输出,很容易导致模型参数过多。 回忆 7.3节一节介绍的使用卷积层的通道来输出类别预测的方法, 单发多框检测采用同样的方法来降低模型复杂度。
具体来说,类别预测层使用一个保持输入高和宽的卷积层。 这样一来,输出和输入在特征图宽和高上的空间坐标一一对应。 考虑输出和输入同一空间坐标(x, y):输出特征图上(x, y)坐标的通道里包含了以输入特征图(x, y)坐标为中心生成的所有锚框的类别预测。 因此输出通道数为 a(q+1),其中索引为 i(q+1) + j的通道代表了索引为 i 的锚框有关类别索引为 j 的预测。
在下面,我们定义了这样一个类别预测层,通过参数num_anchors和num_classes分别指定了 a 和 q。 该图层使用填充为1的 3x3 的卷积层。此卷积层的输入和输出的宽度和高度保持不变。
%matplotlib inline
import torch
import torchvision
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2ldef cls_predictor(num_inputs, num_anchors, num_classes):return nn.Conv2d(num_inputs, num_anchors * (num_classes + 1),kernel_size=3, padding=1)
13.7.1.2. 边界框预测层
边界框预测层的设计与类别预测层的设计类似。 唯一不同的是,这里需要为每个锚框预测4个偏移量,而不是 q+1 个类别。
def bbox_predictor(num_inputs, num_anchors):return nn.Conv2d(num_inputs, num_anchors * 4, kernel_size=3, padding=1)
13.7.1.3. 连结多尺度的预测
正如我们所提到的,单发多框检测使用多尺度特征图来生成锚框并预测其类别和偏移量。 在不同的尺度下,特征图的形状或以同一单元为中心的锚框的数量可能会有所不同。 因此,不同尺度下预测输出的形状可能会有所不同。
在以下示例中,我们为同一个小批量构建两个不同比例(Y1和Y2)的特征图,其中Y2的高度和宽度是Y1的一半。 以类别预测为例,假设Y1和Y2的每个单元分别生成了 5 个和 3 个锚框。 进一步假设目标类别的数量为 10,对于特征图Y1和Y2,类别预测输出中的通道数分别为 5x(10+1) = 55 和 3x(10+1) = 33,其中任一输出的形状是(批量大小,通道数,高度,宽度)。
def forward(x, block):return block(x)Y1 = forward(torch.zeros((2, 8, 20, 20)), cls_predictor(8, 5, 10))
Y2 = forward(torch.zeros((2, 16, 10, 10)), cls_predictor(16, 3, 10))
Y1.shape, Y2.shape
# 打印结果:
# (torch.Size([2, 55, 20, 20]), torch.Size([2, 33, 10, 10]))
正如我们所看到的,除了批量大小这一维度外,其他三个维度都具有不同的尺寸。 为了将这两个预测输出链接起来以提高计算效率,我们将把这些张量转换为更一致的格式。
通道维包含中心相同的锚框的预测结果。我们首先将通道维移到最后一维。 因为不同尺度下批量大小仍保持不变,我们可以将预测结果转成二维的(批量大小,高x宽x通道数)的格式,以方便之后在维度 1 上的连结。
def flatten_pred(pred):return torch.flatten(pred.permute(0, 2, 3, 1), start_dim=1)def concat_preds(preds):return torch.cat([flatten_pred(p) for p in preds], dim=1)
这样一来,尽管Y1和Y2在通道数、高度和宽度方面具有不同的大小,我们仍然可以在同一个小批量的两个不同尺度上连接这两个预测输出。
concat_preds([Y1, Y2]).shape
# 打印结果
# torch.Size([2, 25300])
13.7.1.4. 高和宽减半块
13.7.1.5. 基本网络块
13.7.1.6. 完整的模型
完整的单发多框检测模型由五个模块组成。每个块生成的特征图既用于生成锚框,又用于预测这些锚框的类别和偏移量。在这五个模块中,第一个是基本网络块,第二个到第四个是高和宽减半块,最后一个模块使用全局最大池将高度和宽度都降到1。从技术上讲,第二到第五个区块都是 图13.7.1中的多尺度特征块。
def get_blk(i):if i == 0:blk = base_net()elif i == 1:blk = down_sample_blk(64, 128)elif i == 4:blk = nn.AdaptiveMaxPool2d((1,1))else:blk = down_sample_blk(128, 128)return blk
现在我们为每个块定义前向传播。与图像分类任务不同,此处的输出包括:CNN特征图Y;在当前尺度下根据Y生成的锚框;预测的这些锚框的类别和偏移量(基于Y)。
def blk_forward(X, blk, size, ratio, cls_predictor, bbox_predictor):Y = blk(X)# 基于当前的特征图大小,生成的不同大小和宽高比的初始anchoranchors = d2l.multibox_prior(Y, sizes=size, ratios=ratio)# 基于当前特征图,再过一层conv预测的每个anchor的类别cls_preds = cls_predictor(Y)# 基于当前特征图,再过一层conv预测的每个anchor的偏移量bbox_preds = bbox_predictor(Y)return (Y, anchors, cls_preds, bbox_preds)
疑问:较大值为什么是这么计算的?
现在,我们就可以按如下方式定义完整的模型TinySSD了。
class TinySSD(nn.Module):def __init__(self, num_classes, **kwargs):super(TinySSD, self).__init__(**kwargs)self.num_classes = num_classes# 最开始的basenet输出64通道,剩下的卷积都是输出128通道idx_to_in_channels = [64, 128, 128, 128, 128]for i in range(5):# 即赋值语句self.blk_i=get_blk(i)setattr(self, f'blk_{i}', get_blk(i))setattr(self, f'cls_{i}', cls_predictor(idx_to_in_channels[i],num_anchors, num_classes))setattr(self, f'bbox_{i}', bbox_predictor(idx_to_in_channels[i],num_anchors))def forward(self, X):# 5个不同尺度特征图上的输出anchors, cls_preds, bbox_preds = [None] * 5, [None] * 5, [None] * 5for i in range(5):# getattr(self,'blk_%d'%i)即访问self.blk_iX, anchors[i], cls_preds[i], bbox_preds[i] = blk_forward(X, getattr(self, f'blk_{i}'), sizes[i], ratios[i],getattr(self, f'cls_{i}'), getattr(self, f'bbox_{i}'))# 每个anchor: (1, N, 4), 1是unsqueeze固定为1, N是当前尺度特征图的anchor数量anchors = torch.cat(anchors, dim=1) # (1, N', 4)# list中每个预测: (b, c, h, w), c=4x(1+1), 4是每个像素anchor数,(1+1)是目标类别+背景类别# 最后concat_preds之后:(b, h'w'c')cls_preds = concat_preds(cls_preds)# (b, h'w', c'), 也就是不同尺度的每个像素上anchor的类别cls_preds = cls_preds.reshape(cls_preds.shape[0], -1, self.num_classes + 1)# 同上,最后是(b, h'w'x4), 4是每个anchor固定的4个坐标偏移bbox_preds = concat_preds(bbox_preds)return anchors, cls_preds, bbox_preds
我们创建一个模型实例,然后使用它对一个 256x256 像素的小批量图像X执行前向传播。
如本节前面部分所示,第一个模块输出特征图的形状为 32x32。 回想一下,第二到第四个模块为高和宽减半块,第五个模块为全局汇聚层。 由于以特征图的每个单元为中心有 4(size=3, ratio=2, 3+2-1)个锚框生成,因此在所有五个尺度下,每个图像总共生成 (322 + 162 + 82 + 42 + 1) x4 = 5444个锚框。注意第五个模块为全局汇聚层,所以直接缩小到(1x1)像素。
net = TinySSD(num_classes=1)
X = torch.zeros((32, 3, 256, 256))
anchors, cls_preds, bbox_preds = net(X)print('output anchors:', anchors.shape)
print('output class preds:', cls_preds.shape)
print('output bbox preds:', bbox_preds.shape)# 打印输出
# output anchors: torch.Size([1, 5444, 4])
# output class preds: torch.Size([32, 5444, 2])
# output bbox preds: torch.Size([32, 21776])
13.7.2. 训练模型
现在,我们将描述如何训练用于目标检测的单发多框检测模型。
13.7.2.1. 读取数据集和初始化
13.7.2.2. 定义损失函数和评价函数
目标检测有两种类型的损失。 第一种有关锚框类别的损失:我们可以简单地复用之前图像分类问题里一直使用的交叉熵损失函数来计算; 第二种有关正类锚框偏移量的损失:预测偏移量是一个回归问题。 但是,对于这个回归问题,我们在这里不使用 3.1.3节中描述的平方损失,而是使用 L 1 L_1 L1 范数损失,即预测值和真实值之差的绝对值。 掩码变量bbox_masks令负类锚框和填充锚框不参与损失的计算。 最后,我们将锚框类别和偏移量的损失相加,以获得模型的最终损失函数。
cls_loss = nn.CrossEntropyLoss(reduction='none')
bbox_loss = nn.L1Loss(reduction='none')def calc_loss(cls_preds, cls_labels, bbox_preds, bbox_labels, bbox_masks):batch_size, num_classes = cls_preds.shape[0], cls_preds.shape[2]cls = cls_loss(cls_preds.reshape(-1, num_classes),cls_labels.reshape(-1)).reshape(batch_size, -1).mean(dim=1)bbox = bbox_loss(bbox_preds * bbox_masks,bbox_labels * bbox_masks).mean(dim=1)return cls + bbox
我们可以沿用准确率评价分类结果。 由于偏移量使用了
范数损失,我们使用平均绝对误差来评价边界框的预测结果。这些预测结果是从生成的锚框及其预测偏移量中获得的。
def cls_eval(cls_preds, cls_labels):# 由于类别预测结果放在最后一维,argmax需要指定最后一维。return float((cls_preds.argmax(dim=-1).type(cls_labels.dtype) == cls_labels).sum())def bbox_eval(bbox_preds, bbox_labels, bbox_masks):return float((torch.abs((bbox_labels - bbox_preds) * bbox_masks)).sum())
13.7.2.3. 训练模型
在训练模型时,我们需要在模型的前向传播过程中生成多尺度锚框(anchors),并预测其类别(cls_preds)和偏移量(bbox_preds)。 然后,我们根据标签信息Y为生成的锚框标记类别(cls_labels)和偏移量(bbox_labels)。 最后,我们根据类别和偏移量的预测和标注值计算损失函数。为了代码简洁,这里没有评价测试数据集。
num_epochs, timer = 20, d2l.Timer()
animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],legend=['class error', 'bbox mae'])
net = net.to(device)
for epoch in range(num_epochs):# 训练精确度的和,训练精确度的和中的示例数# 绝对误差的和,绝对误差的和中的示例数metric = d2l.Accumulator(4)net.train()for features, target in train_iter:timer.start()trainer.zero_grad()X, Y = features.to(device), target.to(device)# 生成多尺度的锚框,为每个锚框预测类别和偏移量anchors, cls_preds, bbox_preds = net(X)# 为每个锚框标注类别和偏移量bbox_labels, bbox_masks, cls_labels = d2l.multibox_target(anchors, Y)# 根据类别和偏移量的预测和标注值计算损失函数l = calc_loss(cls_preds, cls_labels, bbox_preds, bbox_labels,bbox_masks)l.mean().backward()trainer.step()metric.add(cls_eval(cls_preds, cls_labels), cls_labels.numel(),bbox_eval(bbox_preds, bbox_labels, bbox_masks),bbox_labels.numel())cls_err, bbox_mae = 1 - metric[0] / metric[1], metric[2] / metric[3]animator.add(epoch + 1, (cls_err, bbox_mae))
print(f'class err {cls_err:.2e}, bbox mae {bbox_mae:.2e}')
print(f'{len(train_iter.dataset) / timer.stop():.1f} examples/sec on 'f'{str(device)}')
13.7.3. 预测目标
13.7.4. 小结
- 单发多框检测是一种多尺度目标检测模型。基于基础网络块和各个多尺度特征块,单发多框检测生成不同数量和不同大小的锚框,并通过预测这些锚框的类别和偏移量检测不同大小的目标。
- 在训练单发多框检测模型时,损失函数是根据锚框的类别和偏移量的预测及标注值计算得出的。
13.8. 区域卷积神经网络(R-CNN)系列
除了 13.7节中描述的单发多框检测之外, 区域卷积神经网络(region-based CNN或regions with CNN features,R-CNN) (Girshick et al., 2014)也是将深度模型应用于目标检测的开创性工作之一。 本节将介绍R-CNN及其一系列改进方法:快速的R-CNN(Fast R-CNN) (Girshick, 2015)、更快的R-CNN(Faster R-CNN) (Ren et al., 2015)和掩码R-CNN(Mask R-CNN) (He et al., 2017)。 限于篇幅,我们只着重介绍这些模型的设计思路。
13.8.1. R-CNN
R-CNN首先从输入图像中选取若干(例如2000个)提议区域(如锚框也是一种选取方法),并标注它们的类别和边界框(如偏移量)。 (Girshick et al., 2014)然后,用卷积神经网络对每个提议区域进行前向传播以抽取其特征。 接下来,我们用每个提议区域的特征来预测类别和边界框。
图13.8.1展示了R-CNN模型。具体来说,R-CNN包括以下四个步骤:
- 对输入图像使用选择性搜索来选取多个高质量的提议区域 (Uijlings et al., 2013)。这些提议区域通常是在多个尺度下选取的,并具有不同的形状和大小。每个提议区域都将被标注类别和真实边界框;
- 选择一个预训练的卷积神经网络,并将其在输出层之前截断。将每个提议区域变形为网络需要的输入尺寸,并通过前向传播输出抽取的提议区域特征;
- 将每个提议区域的特征连同其标注的类别作为一个样本。训练多个支持向量机对目标分类,其中每个支持向量机用来判断样本是否属于某一个类别;
- 将每个提议区域的特征连同其标注的边界框作为一个样本,训练线性回归模型来预测真实边界框。
尽管R-CNN模型通过预训练的卷积神经网络有效地抽取了图像特征,但它的速度很慢。 想象一下,我们可能从一张图像中选出上千个提议区域,这需要上千次的卷积神经网络的前向传播来执行目标检测。 这种庞大的计算量使得R-CNN在现实世界中难以被广泛应用。
13.8.2. Fast R-CNN
R-CNN的主要性能瓶颈在于,对每个提议区域,卷积神经网络的前向传播是独立的,而没有共享计算。 由于这些区域通常有重叠,独立的特征抽取会导致重复的计算。 Fast R-CNN (Girshick, 2015)对R-CNN的主要改进之一,是仅在整张图象上执行卷积神经网络的前向传播。
图13.8.2中描述了Fast R-CNN模型。它的主要计算如下:
- 与R-CNN相比,Fast R-CNN用来提取特征的卷积神经网络的输入是整个图像,而不是各个提议区域。此外,这个网络通常会参与训练。设输入为一张图像,将卷积神经网络的输出的形状记为 1 × c × h 1 × w 1 1 \times c \times h_1 \times w_1 1×c×h1×w1;
- 假设选择性搜索生成了 n 个提议区域。这些形状各异的提议区域在卷积神经网络的输出上分别标出了形状各异的兴趣区域。然后,这些感兴趣的区域需要进一步抽取出形状相同的特征(比如指定高度 h 2 h_2 h2 和宽度 w 2 w_2 w2),以便于连结后输出。为了实现这一目标,Fast R-CNN引入了兴趣区域汇聚层(RoI pooling):将卷积神经网络的输出和提议区域作为输入,输出连结后的各个提议区域抽取的特征,形状为 n × c × h 2 × w 2 n \times c \times h_2 \times w_2 n×c×h2×w2;
- 通过全连接层将输出形状变换为 n × d n \times d n×d,其中超参数 d
取决于模型设计; - 预测 n 个提议区域中每个区域的类别和边界框。更具体地说,在预测类别和边界框时,将全连接层的输出分别转换为形状为 n × q n \times q n×q(q是类别的数量)的输出和形状为 n × 4 n \times 4 n×4 的输出。其中预测类别时使用softmax回归。
下面,我们演示了兴趣区域汇聚层的计算方法。 假设卷积神经网络抽取的特征X的高度和宽度都是4,且只有单通道。
import torch
import torchvisionX = torch.arange(16.).reshape(1, 1, 4, 4)
X
# 打印结果
# tensor([[[[ 0., 1., 2., 3.],
# [ 4., 5., 6., 7.],
# [ 8., 9., 10., 11.],
# [12., 13., 14., 15.]]]])
让我们进一步假设输入图像的高度和宽度都是40像素,且选择性搜索在此图像上生成了两个提议区域。 每个区域由5个元素表示:区域目标类别、左上角和右下角的 (x, y) 坐标。
rois = torch.Tensor([[0, 0, 0, 20, 20], [0, 0, 10, 30, 30]])
由于X的高和宽是输入图像高和宽的 1/10,因此,两个提议区域的坐标先按spatial_scale乘以0.1。 然后,在X上分别标出这两个兴趣区域X[:, :, 0:3, 0:3]和X[:, :, 1:4, 0:4]。 最后,在 2x2 的兴趣区域汇聚层中,每个兴趣区域被划分为子窗口网格,并进一步抽取相同形状 2x2 的特征。
torchvision.ops.roi_pool(X, rois, output_size=(2, 2), spatial_scale=0.1)# 打印结果
# tensor([[[[ 5., 6.],[ 9., 10.]]],[[[ 9., 11.],[13., 15.]]]])
13.8.3. Faster R-CNN
为了较精确地检测目标结果,Fast R-CNN模型通常需要在选择性搜索中生成大量的提议区域。 Faster R-CNN (Ren et al., 2015)提出将选择性搜索替换为区域提议网络(region proposal network),从而减少提议区域的生成数量,并保证目标检测的精度。
图13.8.4描述了Faster R-CNN模型。 与Fast R-CNN相比,Faster R-CNN只将生成提议区域的方法从选择性搜索改为了区域提议网络,模型的其余部分保持不变。具体来说,区域提议网络的计算步骤如下:
- 使用填充为1的 3x3 的卷积层变换卷积神经网络的输出,并将输出通道数记为 c 。这样,卷积神经网络为图像抽取的特征图中的每个单元均得到一个长度为 c的新特征。
- 以特征图的每个像素为中心,生成多个不同大小和宽高比的锚框并标注它们。
- 使用锚框中心单元长度为 c 的特征,分别预测该锚框的二元类别(含目标还是背景)和边界框。
- 使用非极大值抑制,从预测类别为目标的预测边界框中移除相似的结果。最终输出的预测边界框即是兴趣区域汇聚层所需的提议区域。
值得一提的是,区域提议网络作为Faster R-CNN模型的一部分,是和整个模型一起训练得到的。 换句话说,Faster R-CNN的目标函数不仅包括目标检测中的类别和边界框预测,还包括区域提议网络中锚框的二元类别和边界框预测。 作为端到端训练的结果,区域提议网络能够学习到如何生成高质量的提议区域,从而在减少了从数据中学习的提议区域的数量的情况下,仍保持目标检测的精度。
13.8.4. Mask R-CNN
如果在训练集中还标注了每个目标在图像上的像素级位置,那么Mask R-CNN (He et al., 2017)能够有效地利用这些详尽的标注信息进一步提升目标检测的精度。
如 图13.8.5所示,Mask R-CNN是基于Faster R-CNN修改而来的。 具体来说,Mask R-CNN将兴趣区域汇聚层替换为了 兴趣区域对齐层,使用双线性插值(bilinear interpolation)来保留特征图上的空间信息,从而更适于像素级预测。 兴趣区域对齐层的输出包含了所有与兴趣区域的形状相同的特征图。 它们不仅被用于预测每个兴趣区域的类别和边界框,还通过额外的全卷积网络预测目标的像素级位置。 本章的后续章节将更详细地介绍如何使用全卷积网络预测图像中像素级的语义。
13.8.5. 小结
- R-CNN对图像选取若干提议区域,使用卷积神经网络对每个提议区域执行前向传播以抽取其特征,然后再用这些特征来预测提议区域的类别和边界框。
- Fast R-CNN对R-CNN的一个主要改进:只对整个图像做卷积神经网络的前向传播。它还引入了兴趣区域汇聚层,从而为具有不同形状的兴趣区域抽取相同形状的特征。
- Faster R-CNN将Fast R-CNN中使用的选择性搜索替换为参与训练的区域提议网络,这样后者可以在减少提议区域数量的情况下仍保证目标检测的精度。
- Mask R-CNN在Faster R-CNN的基础上引入了一个全卷积网络,从而借助目标的像素级位置进一步提升目标检测的精度。