【youcans动手学模型】目标检测之 RCNN 模型

欢迎关注『youcans动手学模型』系列
本专栏内容和资源同步到 GitHub/youcans



本文介绍 RCNN 目标检测方法,并使用 PyTorch 实现 RCNN 方法。


1. R-CNN 目标检测

R-CNN(Regions with Convolutional Neural Network Features) 是目标检测任务的经典模型,属于两阶段(two-stage)目标检测方法。

论文发表于 2014年 CVPR。

Ross Girshick, Jeff Donahue, Trevor Darrell, Jitendra Malik. Rich feature hierarchies for accurate object detection and semantic segmentation. 2014 CVPR
【下载地址】: https://arxiv.org/abs/1311.2524
【GitHub地址】:
https://github.com/rbgirshick/rcnn
https://github.com/bigbrother33/Deep-Learning

在这里插入图片描述


1.1 论文摘要

近年来基于 PASCAL VOC 标准数据集的目标检测任务的性能比较平稳。性能最好的方法是复杂的集成系统,通常将多个图像特征与上下文相结合。在本文中,我们提出了一种简单且可扩展的检测算法,该算法将平均检测精度(mAP)提高了 30% 以上(VOC 2012 的最佳结果为53.3%)。

本文的方法结合了两个关键技术:

(1)在候选区域(region proposal)自下而上地使用卷积神经网络(CNNs),进行定位和分割对象;

(2)当训练数据不足时,先针对辅助任务进行有监督的预训练,再进行特定任务的微调,可以显著提高性能。

我们将这种候选区域与 CNN 相结合的方法称为 R-CNN: Regions with CNN features 。

我们将 R-CNN 与基于 CNN架构的滑动窗口检测器 OverFeat 进行比较,在 ILSVRC2013检测数据集上 R-CNN的性能大大优于OverFeat。


1.2 技术背景

图像分类、目标检测和图像分割

图像分类、目标检测和图像分割都是计算机视觉领域最基础、最常用、发展最迅速的任务。我们首先看看这几个任务之间的区别。

  • 图像分类:输入图像中通常只有一个物体,目的是判断图像中物体的类别,属于图像级别的任务。
  • 目标检测:输入图像中通常有一个或多个物体,目的是判断每个物体的位置与类别,属于计算机视觉中的核心任务。
  • 图像分割:输入图像中通常有一个或多个物体,目的是判断图像中每一个像素属于哪一个类别,属于像素级的分类。

在这里插入图片描述

图像分类任务针对图像中只有一个目标的情况。分类目标可以有多种类别(例如猫、狗等),但输入图像中通常只有一个类别的实例。但是,大多数图像中可能有多个/多种目标,需要找到目标的位置,并对它们进行分类。这种情况就是目标检测。

在目标检测中,我们不仅对输入图像中的目标感兴趣,还关心目标在输入图像中的位置。目标检测比图像分类问题更复杂,计算时间通常是后者的数百倍。因此,对于目标的位置不重要的问题,应该使用图像分类算法。


现有的目标检测方法

特征很重要。在过去十年中,各类视觉识别任务基本都建立在对 SIFT 和 HOG 特征的使用,但性能的进展比较缓慢。例如,基于 HOG 的可变性部件优化模型(deformable part model,DPM),可以视为 HOG+SVM 的扩展和改进,连续获得了 2007~2009 的目标检测任务冠军 。

卷积神经网络在1990年代提出,2012年 AlexNet 模型在 ImageNet 挑战赛获得冠军,使卷积神经网络受到了广泛关注。但是,HOG-like 特征简单明确、容易理解;而 CNN 所提取的特征,可视化和可解释性很差。

一个核心问题是:在 ImageNet 上训练的 CNN 分类模型,能否及如何应用到物体检测任务上?我们关注两个问题:(1)使用深度卷积网络定位物体,(2)在小规模的数据集上进行网络模型的训练。

目标检测需要在图像中定位物体(可能有多个)。一种方法是将边界框的定位视为回归问题,但其性能并不好;另一种方法是构造滑动窗口检测器,但是由于网络层次很深,输入图片的感受野(195×195)和步长(32×32)很大,使滑动窗口方法充满挑战。


选择性搜索产生候选区域

候选区域是可能的边界框的列表,但它包含检测目标的可能性很小,而且并不检测目标的类别。

通过选择性搜索(Selective Search, SS)基于颜色、纹理、大小和形状的一致性计算相似区域的分层分组,从一张图片上提取若干候选区域(region proposal)。
选择性搜索算法的主要步骤为:
(1)基于颜色、纹理、尺度等特征,计算所有邻近区域之间的相似性;
(2)合并相似度最高的区域;
(3)计算合并区域和临近区域的相似度;
(4)重复以上过程,直到整个图片合并为一个区域。

在每次迭代中,形成更大的区域并将其添加到候选区域集合中。通过这种自下而上的方式,可以创建从小到大的不同尺度和形状的候选区域。

在这里插入图片描述


1.3 基本方法

我们基于区域识别(recognition using regions)处理卷积神经网络的定位问题。

先对每张图片产生约2000个候选区域(region proposal),再对每个区域使用 CNN 生成固定长度的特征向量,最后对每个区域用 SVM 分类器进行分类。这种方法结合了 Region proposals 和 CNN,所以将其称为 R-CNN:Regions with CNN features。

在这里插入图片描述

R-CNN 目标检测主要分为 4个阶段:

(1)候选区域。

R-CNN 并不依赖于特定的候选区域算法,我们使用选择性搜索,以便与先前的研究进行比较。
使用选择性搜索(SS)方法,对每张图片产生约 2000个候选区域(region proposal)。这些候选区域的边界框的位置、尺寸和宽高比各不不同,而且大多数候选区域中并不包含任何目标。

(2)特征提取。

使用卷积神经网络模型,从每个候选区域中提取 4096 维特征向量。
以 AlexNet 网络为例,输入图片为 227*227像素。对于大小和形状不同的候选区域,我们直接将不同尺寸的候选区域通过缩放调整到 227*227像素。

(3)类别判定。

使用 SVM 分类器,判断每个候选区域属于某个类别或背景。以检测 20 个类别物体为例,另有输入图像中没有物体的情况作为背景类,共有 21类。将 2000*4096 维特征向量送入 SVM 分类器,得到 2000*21 维输出矩阵,表示每个候选区域属于某类别的概率。
在 2000个候选区域中,存在大量重叠的候选区域,可以使用非极大值抑制(NMS)方法消除冗余的候选框。

(4)精细定位。

经过 NMS 筛选得到的候选区域,定位精度通常并不高,需要进一步的精细定位。
建立并训练一个边界框回归模型(bbox regressor),可以提高候选区域的定位精度。


1.4 算法实现

使用 PyTorch 建立、训练和使用 RCNN 进行目标检测的基本步骤如下。

(1)训练卷积神经网络。

论文中使用 AlexNet 网络架构,但也可以使用其它网络架构,例如 VGG16 网络。经过测试 AlexNet网络的精度为58.5%,而 VGG16 网络的精度为66%,但 VGG的计算量是 AlexNet 的 7 倍。

(2)微调预训练模型。

很多预训练模型是在 ImageNet 数据集进行训练,有 1000 个分类,模型比较庞大。
为了让预训练模型适应新的任务和新的领域,我们使用缩放后的候选窗口作为输入,对预训练模型参数进行微调。我们把预训练模型中的 1000类的分类器,用一个 21类的分类层替代(VOC数据集的20类别+背景类别),而将模型中的卷积层的结构和参数固定不变。

(3)训练 SVM 分类器。

思考一下检测汽车的二分类器。很显然,一个图像区域紧紧包裹着一辆汽车应该就是正例。同样的,没有汽车的就是背景区域,也就是负例。较为不明确的是怎样标注哪些只和汽车部分重叠的区域。我们使用IoU重叠阈值来解决这个问题,低于这个阈值的就是负例。这个阈值我们选择了0.3,是在验证集上基于{0, 0.1, … 0.5}通过网格搜索得到的。

(4)训练 BBox 回归器。

我们使用一种简单的回归方法减小定位误差。受到 DPM 中的约束框回归训练的启发,我们训练了一个线性回归模型在给定一个选择区域的 pool5 特征时,预测一个新的检测窗口。
BBox 回归方法简单,修复了大量的检测错位,使检测性能提升了 3-4 个百分点。

(5)使用非最大值抑制方法(NMS)对结果进行筛选,消除冗余的边界框 。

(6)模型预测。


1.5 总结

R-CNN 将卷积神经网络引入目标检测领域,与传统方法相比检测性能显著提高。其后的系列文章 Fast RCNN, Faster RCNN 在此基础上不断改进,开拓和引领了目标检测领域新的研究方向。

R-CNN 的不足在于:
(1)训练阶段多,步骤繁琐。先要预训练 CNN,然后微调 CNN,再训练 SVM,训练回归器,还要用 NMS 筛选。
(2)训练耗时,占用磁盘空间大。
(3)处理速度慢,需要对 2000 个候选区域分别提取特征,有很多重复的计算。
(4)对候选区域的高宽进行不同比例的缩放,容易引起物体变形。

在这里插入图片描述


2. 使用 PyTorch 实现 RCNN 目标检测

为了简单起见,选择 17flowers 的小型数据集,而不是 PASCAL VOC 2012。17flowers 数据集可以从官网下载:http://www.robots.ox.ac.uk/~vgg/data/flowers/17/


2.1 训练 AlexNet 模型

使用 17flowers 数据集训练 Alexnet 网络,得到分类任务的预训练模型。

$ python train_step1.py

train_step1.py 例程如下。

from __future__ import division
from data.dataset_factory import DatasetFactory
from models.model_factory import ModelsFactory
from options.train_options import TrainOptions



class Train_step1:
    def __init__(self):
        self._opt = TrainOptions().parse()

        self._dataset_train = DatasetFactory.get_by_name("AlexnetDataset", self._opt)
        self._dataset_train_size = len(self._dataset_train)
        print('#train images = %d' % self._dataset_train_size)

        self._model = ModelsFactory.get_by_name("AlexModel", self._opt, is_train=True)

        self._train()

    def _train(self):
        self._steps_per_epoch = int (self._dataset_train_size / self._opt.batch_size)
        
        for i_epoch in range(self._opt.load_alex_epoch + 1, self._opt.total_epoch + 1):
            # train epoch
            self._train_epoch(i_epoch)

            # save model
            if i_epoch % 20 == 0:
                print('saving the model at the end of epoch %d' % i_epoch)
                self._model.save(i_epoch)

    def _train_epoch(self, i_epoch):

        for step in range(1, self._steps_per_epoch+1):
            input, labels = self._dataset_train.get_batch()

            # train model
            self._model.set_input(input, labels)
            self._model.optimize_parameters()

            # display terminal
            self._display_terminal_train(i_epoch, step)

    def _display_terminal_train(self, i_epoch, i_train_batch):
        errors = self._model.get_current_errors()
        message = '(epoch: %d, it: %d/%d) ' % (i_epoch, i_train_batch, self._steps_per_epoch)
        for k, v in errors.items():
            message += '%s:%.3f ' % (k, v)

        print(message)

if __name__ == "__main__":
    Train_step1()

2.2 微调 AlexNet 预训练模型

使用 2flowers 数据集对 Alexnet 预训练网络进行微调,得到微调模型。

$ python train_step2.py --batch_size 128 --load_alex_epoch 100 --options_dir finetune

train_step2.py 例程如下。

from __future__ import division
from data.dataset_factory import DatasetFactory
from models.model_factory import ModelsFactory
from options.train_options import TrainOptions


class Train_step2:
    def __init__(self):
        self._opt = TrainOptions().parse()

        self._dataset_train = DatasetFactory.get_by_name("FinetuneDataset", self._opt)
        self._dataset_train_size = len(self._dataset_train)
        print('#train images = %d' % self._dataset_train_size)

        self._model = ModelsFactory.get_by_name("FineModel", self._opt, is_train=True)
        self._train()

    def _train(self):
        self._steps_per_epoch = int (self._dataset_train_size / self._opt.batch_size)
        
        for i_epoch in range(self._opt.load_finetune_epoch + 1, self._opt.total_epoch + 1):
            # train epoch
            self._train_epoch(i_epoch)

            # save model
            if i_epoch % 20 == 0:
                print('saving the model at the end of epoch %d' % i_epoch)
                self._model.save(i_epoch)

    def _train_epoch(self, i_epoch):

        for step in range(1, self._steps_per_epoch+1):
            input, labels = self._dataset_train.get_batch()

            # train model
            self._model.set_input(input, labels)
            self._model.optimize_parameters()

            # display terminal
            self._display_terminal_train(i_epoch, step)

    def _display_terminal_train(self, i_epoch, i_train_batch):
        errors = self._model.get_current_errors()
        message = '(epoch: %d, it: %d/%d) ' % (i_epoch, i_train_batch, self._steps_per_epoch)
        for k, v in errors.items():
            message += '%s:%.3f ' % (k, v)

        print(message)

if __name__ == "__main__":
    Train_step2()

2.3 训练 SVM 分类器

使用从微调模型中提取的特征来训练 SVM,得到分类任务模型。

$ python train_step3.py --load_finetune_epoch 100 --options_dir svm

train_step3.py 例程如下。

from __future__ import division
from data.dataset_factory import DatasetFactory
from models.model_factory import ModelsFactory
from options.train_options import TrainOptions
import numpy as np

class Train_step3:
    def __init__(self):
        self._opt = TrainOptions().parse()

        self._dataset_train = DatasetFactory.get_by_name("SVMDataset", self._opt)
        self._dataset_train_size = len(self._dataset_train)
        print('#train images = %d' % self._dataset_train_size)

        self.classA_features, self.classA_labels, self.classB_features, self.classB_labels = self._dataset_train.get_datas()

        self._modelA = ModelsFactory.get_by_name("SvmModel", self._opt, is_train=True)
        self._modelB = ModelsFactory.get_by_name("SvmModel", self._opt, is_train=True)

        self._train(self._modelA, self.classA_features, self.classA_labels, "A")
        self._train(self._modelB, self.classB_features, self.classB_labels, "B")

    def _train(self, model, features, labels, name):
        model.train(features, labels)
        model.save(name)
        pred = model.predict(features)

        print (labels)
        print (pred)
        
        

if __name__ == "__main__":
    Train_step3()  

2.4 训练 BBox 回归器

训练一个回归网络,用于边界框的精细定位。

$ python train_step4.py --decay_rate 0.5 --options_dir regression --batch_size 512

train_step4.py 例程如下。

from __future__ import division
from data.dataset_factory import DatasetFactory
from models.model_factory import ModelsFactory
from options.train_options import TrainOptions

class Train_step4:
    def __init__(self):
        self._opt = TrainOptions().parse()

        self._dataset_train = DatasetFactory.get_by_name("RegDataset", self._opt)
        self._dataset_train_size = len(self._dataset_train)
        print('#train images = %d' % self._dataset_train_size)

        self._model = ModelsFactory.get_by_name("RegModel", self._opt, is_train=True)

        self._train()

    def _train(self):
        self._steps_per_epoch = int (self._dataset_train_size / self._opt.batch_size)
        
        for i_epoch in range(self._opt.load_reg_epoch + 1, self._opt.total_epoch + 1):
            # train epoch
            self._train_epoch(i_epoch)

            # save model
            if i_epoch % 20 == 0:
                print('saving the model at the end of epoch %d' % i_epoch)
                self._model.save(i_epoch)

    def _train_epoch(self, i_epoch):

        for step in range(1, self._steps_per_epoch+1):
            input, labels = self._dataset_train.get_batch()

            # train model
            self._model.set_input(input, labels)
            self._model.optimize_parameters()

            # display terminal
            self._display_terminal_train(i_epoch, step)

    def _display_terminal_train(self, i_epoch, i_train_batch):
        errors = self._model.get_current_errors()
        message = '(epoch: %d, it: %d/%d) ' % (i_epoch, i_train_batch, self._steps_per_epoch)
        for k, v in errors.items():
            message += '%s:%.3f ' % (k, v)

        print(message)

if __name__ == "__main__":
    Train_step4()        

2.5 模型预测

使用在 17flowers 数据集上训练的 RCNN 模型,输入图像进行目标检测。

$ python evaluate.py --load_finetune_epoch 100 --load_reg_epoch 40 --img_path ./sample_dataset/2flowers/jpg/1/image_1281.jpg

evaluate.py 例程如下。

from __future__ import division
from models.model_factory import ModelsFactory
from options.test_options import TestOptions
from utils.util import image_proposal
from utils.util import show_rect
import torch
import numpy as np

class Test:
    def __init__(self):
        self._opt = TestOptions().parse()
        self._img_path = self._opt.img_path
        self._img_size = self._opt.image_size

        self.fine_model = ModelsFactory.get_by_name('FineModel', self._opt, is_train=False)
        self.svm_model_A = ModelsFactory.get_by_name('SvmModel', self._opt, is_train=False)
        self.svm_model_A.load('A')
        self.svm_model_B = ModelsFactory.get_by_name('SvmModel', self._opt, is_train=False)
        self.svm_model_B.load('B')
        self.svms = [self.svm_model_A, self.svm_model_B]
        self.reg_model = ModelsFactory.get_by_name('RegModel', self._opt, is_train=False)

        self.test()

    def test(self):
        imgs, _, rects = image_proposal(self._img_path, self._img_size)

        show_rect(self._img_path, rects, ' ')

        input_data=torch.Tensor(imgs).permute(0,3,1,2)
        features, _ = self.fine_model._forward_test(input_data)
        features = features.data.cpu().numpy()

        results = []
        results_old = []
        results_label = []
        count = 0

        flower = {
    
    1:'pancy', 2:'Tulip'}

        for f in features:
            for svm in self.svms:
                pred = svm.predict([f.tolist()])
                # not background
                if pred[0] != 0:
                    results_old.append(rects[count])
                    input_data=torch.Tensor(f)
                    box = self.reg_model._forward_test(input_data)
                    box = box.data.cpu().numpy()
                    if box[0] > 0.3:
                        px, py, pw, ph = rects[count][0], rects[count][1], rects[count][2], rects[count][3]
                        old_center_x, old_center_y = px + pw / 2.0, py + ph / 2.0
                        x_ping, y_ping, w_suo, h_suo = box[1], box[2], box[3], box[4],
                        new__center_x = x_ping * pw + old_center_x
                        new__center_y = y_ping * ph + old_center_y
                        new_w = pw * np.exp(w_suo)
                        new_h = ph * np.exp(h_suo)
                        new_verts = [new__center_x, new__center_y, new_w, new_h]
                        results.append(new_verts)
                        results_label.append(pred[0])
            count += 1

        average_center_x, average_center_y, average_w,average_h = 0, 0, 0, 0
        #use average values to represent the final result
        for vert in results:
            average_center_x += vert[0]
            average_center_y += vert[1]
            average_w += vert[2]
            average_h += vert[3]
        average_center_x = average_center_x / len(results)
        average_center_y = average_center_y / len(results)
        average_w = average_w / len(results)
        average_h = average_h / len(results)
        average_result = [[average_center_x, average_center_y, average_w, average_h]]
        result_label = max(results_label, key=results_label.count)
        show_rect(self._img_path, results_old, ' ')
        show_rect(self._img_path, average_result, flower[result_label])
      

if __name__ == "__main__":
    Test()

测试结果如下:

在这里插入图片描述


本节参考以下资料:
https://github.com/cassiePython/RCNN/
https://github.com/bigbrother33/Deep-Learning

【本节完】


版权声明:
欢迎关注『youcans动手学模型』系列
转发请注明原文链接:
【youcans动手学模型】目标检测之 RCNN 模型
Copyright 2023 youcans, XUPT
Crated:2023-07-21


猜你喜欢

转载自blog.csdn.net/youcans/article/details/131843800