欢迎关注『youcans动手学模型』系列
本专栏内容和资源同步到 GitHub/youcans
【youcans动手学模型】目标检测之 RCNN 模型
- 1. R-CNN 目标检测
 - 1.1 论文摘要
 - 1.2 技术背景
 - 1.3 基本方法
 - 1.4 算法实现
 - 1.5 总结
 
- 2. 使用 PyTorch 实现 RCNN 目标检测
 - 2.1 训练 AlexNet 模型
 - 2.2 微调 AlexNet 预训练模型
 - 2.3 训练 SVM 分类器
 - 2.4 训练 BBox 回归器
 - 2.5 模型预测
 
本文介绍 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













![数据结构07:查找[C++][B树Btree]](https://img-blog.csdnimg.cn/5e97b730d4854a36bfcc169a8957be92.png)





