计算机视觉与物体检测

本文深入解析Faster R-CNN模型,介绍物体检测的基本流程,包括主干网络、锚点生成、数据准备、模型构建、训练过程。通过PyTorch实现部分代码,帮助理解Faster R-CNN的工作原理。最后,讨论了模型训练和实际应用中的非最大抑制技术。
摘要由CSDN通过智能技术生成

f19ccdcd2df9d9af8d8aea2668a53957.jpeg

第一次通过Tensorflow对象检测API了解对象检测。它很容易使用。传入了一张海滩的图片,作为回报,API在它识别的对象上绘制了方框。这似乎很神奇。

很好奇,想剖析API,了解它到底是如何在幕后工作的。这很难,我失败了。Tensorflow对象检测API支持经过数十年研究的最先进模型。它们被复杂地编织成代码,就像钟表匠如何将微小的齿轮组合在一起,它们可以连贯地移动。

然而,目前大多数最先进的模型都建立在Faster RCNN模型的基础之上,即使在今天,该模型仍然是计算机视觉领域被引用最多的论文之一。因此,理解它至关重要。

在本文中,我们将分解Faster RCNN论文,了解其工作原理,并在PyTorch中部分构建它,以了解其中的细微差别。


Faster R-CNN概述

c9f1ac7257f0cb7a63e052c2cf0d2a92.png

对于物体检测,我们需要建立一个模型,并教它学会识别和定位图像中的物体。

Faster R-CNN模型采用以下方法:图像首先通过主干网络获得输出特征图,主干网络通常是卷积网络,如ResNet或VGG16。输出特征图是表示图像的学习特征的空间密集张量。接下来,我们生成多个不同大小和形状的框。这些定位框的目的是捕获图像中的对象。

我们使用1x1卷积网络来预测所有锚盒的类别和偏移。在训练期间,我们对与标签重叠最多的锚框进行采样。这些被称为阳性或正锚框。我们还对与标签锚框几乎没有重叠的负锚框进行了采样。

网络学习使用二进制交叉熵损失对锚盒进行分类。现在,正锚框可能与标签锚框不完全对齐。因此,我们训练了一个类似的1x1卷积网络,以学习从标签锚框预测偏移。当应用于锚框时,这些偏移会使它们更接近标签锚框。

我们使用L2回归损失来学习偏移。使用预测的偏移来变换锚框,并将其称为区域建议,并且上述网络称为区域提议网络。这是探测器的第一阶段。Faster RCNN是一个两级检测器。还有另一个阶段。

第2阶段的输入是从第1阶段生成的区域建议。在第2阶段,我们学习使用简单的卷积网络预测区域建议中的对象类别。现在,建议的框大小不同,因此我们使用一种称为ROI池的技术在通过网络之前调整它们的大小。该网络学习使用交叉熵损失来预测多个类别。

我们使用另一个网络来预测来自标签锚框的区域提议的偏移量。这一网络进一步试图使预测的框与标签锚框保持一致。这使用L2回归损失。最后,我们对两种损失进行加权组合,以计算最终损失。在第二阶段,我们学习预测类别和偏移量。这被称为多任务学习。

所有这些都发生在训练期间。在推断过程中,我们通过主干网络传递图像并生成锚框-与之前相同。然而,这一次我们只选择在第一阶段中获得高分类分数的前300个框,并使它们有资格进入第二阶段。

在第二阶段,我们预测最终类别和偏移量。此外,我们还执行了一个额外的后处理步骤,使用一种称为非最大抑制的技术来删除重复的边界框。如果一切按预期运行,探测器会识别并在图像中的对象上绘制方框,如下所示:

96992ac1a5ce36279351e06087869bfd.png

这是两阶段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是目前流行的开源标签工具之一。

你只需将图像加载到工具中,在相关对象周围绘制框,并标记其类别,如下所示:

2bbbf5f351fda36c03766c639bd6f597.png

完成后,可以将注释导出为首选格式。在这里,我已经将它们导出为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对象。图像将被重塑为固定大小。

注意,我们正在填充边界框。这与调整大小相结合,允许我们将图像批处理在一起。

c82d8c1ae22b613c1e1141a69f345ce1.png

我们可以从DataLoader中获取一些图像并将其可视化,如下所示:

827f3e553a39eacfc1073bd58a00bc30.png

主干网络

这里我们将使用ResNet 50作为主干网络。记住,ResNet 50中的单个块由瓶颈层的堆栈组成。在沿空间维度的每个块之后,图像会减少一半,而通道的数量会增加一倍。瓶颈层由三个卷积层以及跳跃连接组成,如下所示:

44ced5d1f80859de6461694171f38658.png

我们将使用ResNet 50的前四个块作为主干网络。

8e3cd2ea50abc78cd709741cb6336614.png

一旦图像通过主干网络,它就会沿着空间维度向下采样。输出是图像的特征丰富的表示。

c745115442b5ba6a90d052dc6f7278bf.png

如果我们通过主干网络传递大小(640、480)的图像,我们将得到大小(15、20)的输出特征图。因此,图像已缩小(32,32)。


生成锚点

我们将特征图中的每个点视为锚点。因此,锚点将只是表示沿宽度和高度维度的坐标的数组。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值