第一次通过Tensorflow对象检测API了解对象检测。它很容易使用。传入了一张海滩的图片,作为回报,API在它识别的对象上绘制了方框。这似乎很神奇。
很好奇,想剖析API,了解它到底是如何在幕后工作的。这很难,我失败了。Tensorflow对象检测API支持经过数十年研究的最先进模型。它们被复杂地编织成代码,就像钟表匠如何将微小的齿轮组合在一起,它们可以连贯地移动。
然而,目前大多数最先进的模型都建立在Faster RCNN模型的基础之上,即使在今天,该模型仍然是计算机视觉领域被引用最多的论文之一。因此,理解它至关重要。
在本文中,我们将分解Faster RCNN论文,了解其工作原理,并在PyTorch中部分构建它,以了解其中的细微差别。
Faster R-CNN概述
对于物体检测,我们需要建立一个模型,并教它学会识别和定位图像中的物体。
Faster R-CNN模型采用以下方法:图像首先通过主干网络获得输出特征图,主干网络通常是卷积网络,如ResNet或VGG16。输出特征图是表示图像的学习特征的空间密集张量。接下来,我们生成多个不同大小和形状的框。这些定位框的目的是捕获图像中的对象。
我们使用1x1卷积网络来预测所有锚盒的类别和偏移。在训练期间,我们对与标签重叠最多的锚框进行采样。这些被称为阳性或正锚框。我们还对与标签锚框几乎没有重叠的负锚框进行了采样。
网络学习使用二进制交叉熵损失对锚盒进行分类。现在,正锚框可能与标签锚框不完全对齐。因此,我们训练了一个类似的1x1卷积网络,以学习从标签锚框预测偏移。当应用于锚框时,这些偏移会使它们更接近标签锚框。
我们使用L2回归损失来学习偏移。使用预测的偏移来变换锚框,并将其称为区域建议,并且上述网络称为区域提议网络。这是探测器的第一阶段。Faster RCNN是一个两级检测器。还有另一个阶段。
第2阶段的输入是从第1阶段生成的区域建议。在第2阶段,我们学习使用简单的卷积网络预测区域建议中的对象类别。现在,建议的框大小不同,因此我们使用一种称为ROI池的技术在通过网络之前调整它们的大小。该网络学习使用交叉熵损失来预测多个类别。
我们使用另一个网络来预测来自标签锚框的区域提议的偏移量。这一网络进一步试图使预测的框与标签锚框保持一致。这使用L2回归损失。最后,我们对两种损失进行加权组合,以计算最终损失。在第二阶段,我们学习预测类别和偏移量。这被称为多任务学习。
所有这些都发生在训练期间。在推断过程中,我们通过主干网络传递图像并生成锚框-与之前相同。然而,这一次我们只选择在第一阶段中获得高分类分数的前300个框,并使它们有资格进入第二阶段。
在第二阶段,我们预测最终类别和偏移量。此外,我们还执行了一个额外的后处理步骤,使用一种称为非最大抑制的技术来删除重复的边界框。如果一切按预期运行,探测器会识别并在图像中的对象上绘制方框,如下所示:
这是两阶段Faster RCNN网络的简要概述。在接下来的部分中,我们将深入探讨每个部分。
设置环境
使用的所有代码都可以在此GitHub存储库中找到。我们不需要很多依赖项,因为我们将从头开始构建。仅在标准anaconda环境中安装PyTorch库就足够了。
https://github.com/wingedrasengan927/pytorch-tutorials/tree/master/Object%20Detection
这是我们要使用的主要笔记本
https://gist.github.com/wingedrasengan927/3d5eb6f1b0d4fb3acbf2550f9db8daf0#file-faster-r-cnn-ipynb
%load_ext autoreload
%autoreload 2
import numpy as np
from skimage import io
from skimage.transform import resize
import matplotlib.pyplot as plt
import random
import matplotlib.patches as patches
from utils import *
from model import *
import os
import torch
import torchvision
from torchvision import ops
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader, Dataset
from torch.nn.utils.rnn import pad_sequence
准备和加载数据
首先,我们需要使用一些示例图像。这里我从这里下载了两张高分辨率图像。
接下来,我们需要标记这些图像。CVAT是目前流行的开源标签工具之一。
你只需将图像加载到工具中,在相关对象周围绘制框,并标记其类别,如下所示:
完成后,可以将注释导出为首选格式。在这里,我已经将它们导出为CVAT for images 1.1 xml格式。
注释文件包含有关图像、标记类和边界框坐标的所有信息。
PyTorch数据集和DataLoader
在PyTorch中,创建一个继承自PyTorch的Dataset类的类来加载数据被认为是最佳实践。这将使我们对数据有更多的控制,并有助于保持代码模块化。此外,我们可以从数据集实例创建PyTorch DataLoader,它可以自动处理数据的批处理、混洗和采样。
class ObjectDetectionDataset(Dataset):
'''
A Pytorch Dataset class to load the images and their corresponding annotations.
Returns
------------
images: torch.Tensor of size (B, C, H, W)
gt bboxes: torch.Tensor of size (B, max_objects, 4)
gt classes: torch.Tensor of size (B, max_objects)
'''
def __init__(self, annotation_path, img_dir, img_size, name2idx):
self.annotation_path = annotation_path
self.img_dir = img_dir
self.img_size = img_size
self.name2idx = name2idx
self.img_data_all, self.gt_bboxes_all, self.gt_classes_all = self.get_data()
def __len__(self):
return self.img_data_all.size(dim=0)
def __getitem__(self, idx):
return self.img_data_all[idx], self.gt_bboxes_all[idx], self.gt_classes_all[idx]
def get_data(self):
img_data_all = []
gt_idxs_all = []
gt_boxes_all, gt_classes_all, img_paths = parse_annotation(self.annotation_path, self.img_dir, self.img_size)
for i, img_path in enumerate(img_paths):
# skip if the image path is not valid
if (not img_path) or (not os.path.exists(img_path)):
continue
# read and resize image
img = io.imread(img_path)
img = resize(img, self.img_size)
# convert image to torch tensor and reshape it so channels come first
img_tensor = torch.from_numpy(img).permute(2, 0, 1)
# encode class names as integers
gt_classes = gt_classes_all[i]
gt_idx = torch.Tensor([self.name2idx[name] for name in gt_classes])
img_data_all.append(img_tensor)
gt_idxs_all.append(gt_idx)
# pad bounding boxes and classes so they are of the same size
gt_bboxes_pad = pad_sequence(gt_boxes_all, batch_first=True, padding_value=-1)
gt_classes_pad = pad_sequence(gt_idxs_all, batch_first=True, padding_value=-1)
# stack all images
img_data_stacked = torch.stack(img_data_all, dim=0)
return img_data_stacked.to(dtype=torch.float32), gt_bboxes_pad, gt_classes_pad
在上面的类中,我们定义了一个名为get_data的函数,该函数加载注释文件并解析它以提取图像路径、标记类和边界框坐标,然后将其转换为PyTorch的Tensor对象。图像将被重塑为固定大小。
注意,我们正在填充边界框。这与调整大小相结合,允许我们将图像批处理在一起。
我们可以从DataLoader中获取一些图像并将其可视化,如下所示:
主干网络
这里我们将使用ResNet 50作为主干网络。记住,ResNet 50中的单个块由瓶颈层的堆栈组成。在沿空间维度的每个块之后,图像会减少一半,而通道的数量会增加一倍。瓶颈层由三个卷积层以及跳跃连接组成,如下所示:
我们将使用ResNet 50的前四个块作为主干网络。
一旦图像通过主干网络,它就会沿着空间维度向下采样。输出是图像的特征丰富的表示。
如果我们通过主干网络传递大小(640、480)的图像,我们将得到大小(15、20)的输出特征图。因此,图像已缩小(32,32)。
生成锚点
我们将特征图中的每个点视为锚点。因此,锚点将只是表示沿宽度和高度维度的坐标的数组。