欢迎关注『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