从零开始:Faster R - CNN 训练与 Single Shot Detectors 解析
1. Faster R - CNN 训练与应用
在训练 Faster R - CNN 时,可能会遇到一些问题,常见的有:
- 忘记导出 PYTHONPATH
- 配置文件中的文件路径存在拼写错误
- 生成记录文件时出现问题,例如无效的边界框
1.1 导出冻结模型图
当模型训练完成后,可使用
export_inference_graph.py
来创建一个能导入到自己脚本中的 TensorFlow 模型。具体操作步骤如下:
1. 切换到
models/research
目录:
$ cd ~/models/research
-
执行
export_inference_graph.py脚本:
$ python object_detection/export_inference_graph.py \
--input_type image_tensor \
--pipeline_config_path ~ssds_and_rcnn/lisa/experiments/training/faster_rcnn_lisa.config \
--trained_checkpoint_prefix ~ssds_and_rcnn/lisa/experiments/training/model.ckpt-50000 \
--output ~/ssds_and_rcnn/lisa/experiments/exported_model
执行上述命令后,模型检查点在第 50000 步时的结果会被导出到
exported_model
目录。可以通过以下命令查看该目录下 TensorFlow 生成的文件:
$ ls lisa/experiments/exported_model/
checkpoint
frozen_inference_graph.pb
model.ckpt.data-00000-of-00001
model.ckpt.index
model.ckpt.meta
saved_model
务必保留
exported_model
目录及其内部的文件,后续会在 Python 脚本中导入这些文件。
1.2 在图像和视频上应用 Faster R - CNN
训练并评估了 Faster R - CNN 的准确性后,接下来要将其应用到输入图像上。打开
predict.py
并插入以下代码:
# import the necessary packages
from object_detection.utils import label_map_util
import tensorflow as tf
import numpy as np
import argparse
import imutils
import cv2
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-m", "--model", required=True,
help="base path for frozen checkpoint detection graph")
ap.add_argument("-l", "--labels", required=True,
help="labels file")
ap.add_argument("-i", "--image", required=True,
help="path to input image")
ap.add_argument("-n", "--num-classes", type=int, required=True,
help="# of class labels")
ap.add_argument("-c", "--min-confidence", type=float, default=0.5,
help="minimum probability used to filter weak detections")
args = vars(ap.parse_args())
上述代码中,第 2 - 8 行导入了所需的 Python 库,其中
TensorFlow
的
label_map_util
是一个辅助函数,可方便地从磁盘加载类标签文件。第 10 - 21 行解析命令行参数,共需要四个必需的命令行参数和一个可选参数:
| 参数 | 说明 |
| ---- | ---- |
|
--model
| 指定冻结检查点检测图的路径(即 TensorFlow 模型本身) |
|
--labels
| 提供
classes.pbtxt
文件的路径,因为标签未内置在 TensorFlow 模型中 |
|
--image
| 控制要应用目标检测的输入图像的路径 |
|
--num-classes
| 手动指定类别的数量,因为 TensorFlow 无法从
--labels
文件中自动确定 |
|
--min-confidence
(可选) | 用于过滤弱检测的最小概率,默认值为 0.5 |
解析完命令行参数后,就可以从磁盘加载模型:
# initialize a set of colors for our class labels
COLORS = np.random.uniform(0, 255, size=(args["num_classes"], 3))
# initialize the model
model = tf.Graph()
# create a context manager that makes this model the default one for
# execution
with model.as_default():
# initialize the graph definition
graphDef = tf.GraphDef()
# load the graph from disk
with tf.gfile.GFile(args["model"], "rb") as f:
serializedGraph = f.read()
graphDef.ParseFromString(serializedGraph)
tf.import_graph_def(graphDef, name="")
第 24 行随机初始化了一组用于每个边界框的 RGB 颜色,第 27 行初始化了要从磁盘加载的模型,第 31 - 39 行使用 TensorFlow 的辅助工具加载序列化的 TensorFlow 网络。
接着,从磁盘加载类标签:
# load the class labels from disk
labelMap = label_map_util.load_labelmap(args["labels"])
categories = label_map_util.convert_label_map_to_categories(
labelMap, max_num_classes=args["num_classes"],
use_display_name=True)
categoryIdx = label_map_util.create_category_index(categories)
第 42 行从磁盘加载原始的
pbtxt
文件,然后使用
convert_label_map_to_categories
函数和
--numclasses
参数构建类别集合,第 46 行创建了从类标签的整数 ID 到人类可读类标签的映射。
为了预测输入图像的边界框,需要创建一个 TensorFlow 会话,并获取网络中图像、边界框、概率和类别张量的引用:
# create a session to perform inference
with model.as_default():
with tf.Session(graph=model) as sess:
# grab a reference to the input image tensor and the boxes
# tensor
imageTensor = model.get_tensor_by_name("image_tensor:0")
boxesTensor = model.get_tensor_by_name("detection_boxes:0")
# for each bounding box we would like to know the score
# (i.e., probability) and class label
scoresTensor = model.get_tensor_by_name("detection_scores:0")
classesTensor = model.get_tensor_by_name("detection_classes:0")
numDetections = model.get_tensor_by_name("num_detections:0")
这些引用能让我们在图像通过网络后访问其关联的值。
随后,从磁盘加载图像并为检测做准备:
# load the image from disk
image = cv2.imread(args["image"])
(H, W) = image.shape[:2]
# check to see if we should resize along the width
if W > H and W > 1000:
image = imutils.resize(image, width=1000)
# otherwise, check to see if we should resize along the
# height
elif H > W and H > 1000:
image = imutils.resize(image, height=1000)
# prepare the image for detection
(H, W) = image.shape[:2]
output = image.copy()
image = cv2.cvtColor(image.copy(), cv2.COLOR_BGR2RGB)
image = np.expand_dims(image, axis=0)
这段代码先从磁盘加载图像,获取其尺寸并根据情况进行缩放,最后为检测做准备。
获取边界框、概率和类标签只需调用会话的
.run
方法:
# perform inference and compute the bounding boxes,
# probabilities, and class labels
(boxes, scores, labels, N) = sess.run(
[boxesTensor, scoresTensor, classesTensor, numDetections],
feed_dict={imageTensor: image})
# squeeze the lists into a single dimension
boxes = np.squeeze(boxes)
scores = np.squeeze(scores)
labels = np.squeeze(labels)
这里将边界框、分数(即概率)、类标签和检测数量的张量列表传递给
sess.run
方法,
feed_dict
指示 TensorFlow 将
imageTensor
设置为我们的图像并进行前向传播,得到边界框、分数和类标签。由于
boxes
、
scores
和
labels
都是多维数组,使用
np.squeeze
方法将它们压缩为一维数组,以便于循环遍历。
最后,循环遍历检测结果,在输出图像上绘制它们并显示结果:
# loop over the bounding box predictions
for (box, score, label) in zip(boxes, scores, labels):
# if the predicted probability is less than the minimum
# confidence, ignore it
if score < args["min_confidence"]:
continue
# scale the bounding box from the range [0, 1] to [W, H]
(startY, startX, endY, endX) = box
startX = int(startX * W)
startY = int(startY * H)
endX = int(endX * W)
endY = int(endY * H)
# draw the prediction on the output image
label = categoryIdx[label]
idx = int(label["id"]) - 1
label = "{}: {:.2f}".format(label["name"], score)
cv2.rectangle(output, (startX, startY), (endX, endY),
COLORS[idx], 2)
y = startY - 10 if startY - 10 > 10 else startY + 10
cv2.putText(output, label, (startX, y),
cv2.FONT_HERSHEY_SIMPLEX, 0.3, COLORS[idx], 1)
# show the output image
cv2.imshow("Output", output)
cv2.waitKey(0)
要执行
predict.py
脚本,在终端中执行以下命令,并确保提供有效的输入图像路径:
$ python predict.py \
--model lisa/experiments/exported_model/frozen_inference_graph.pb \
--labels lisa/records/classes.pbtxt \
--image path/to/input/image.png \
--num-classes 3
以下是 Faster R - CNN 应用于图像的流程图:
graph LR
A[开始] --> B[导出冻结模型图]
B --> C[解析命令行参数]
C --> D[加载模型和类标签]
D --> E[加载图像并准备检测]
E --> F[执行推理]
F --> G[绘制检测结果并显示]
G --> H[结束]
2. 理解 Single Shot Detectors (SSDs)
在之前使用 Faster R - CNN 框架检测图像中的道路标志时,发现了一些问题,主要包括:
- 框架复杂,包含多个活动部件。
- 推理速度约为 7 - 10 FPS,对于基于深度学习的目标检测器来说还算合理,但无法实现真正的实时性能。
为了解决这些问题,接下来将介绍 Single Shot Detector (SSD) 框架。SSD 目标检测器是完全端到端的,没有复杂的活动部件,并且能够实现超实时性能。
2.1 动机
Girchick 等人在 R - CNN 系列论文中的工作使基于深度学习的目标检测成为现实,但 R - CNN 也存在一些问题:
-
训练阶段多
:在训练实际的分类器识别图像中的对象之前,需要先训练区域建议网络(RPN)来生成建议的边界框。虽然后来通过端到端训练整个 R - CNN 架构缓解了这个问题,但在此之前,这引入了繁琐的预训练过程。
-
训练时间长
:(Faster) R - CNN 由多个组件组成,包括区域建议网络、ROI 池化模块和最终的分类器。这些组件虽然构成了一个框架,但它们会减慢整个训练过程。
-
推理时间慢
:无法实现基于深度学习的实时目标检测。
从 SSD 的名称可以看出它是如何解决这些问题的:
-
Single Shot
:意味着在推理时,定位和检测在网络的单次前向传播中完成,网络只需“看”一次图像就能得到最终预测结果。与 R - CNN 不同,SSD 不需要从原始图像中重新获取像素或从特征图中切片,而是继续向前传播特征图,并以新颖的方式连接特征图,从而可以检测不同大小和尺度的对象。SSD 速度的根本提升在于消除了边界框建议以及像素或特征的子采样。
-
Multibox
:指的是 Szegedy 等人用于边界框回归的原始 Multibox 算法。该算法使 SSD 能够定位不同类别的对象,即使它们的边界框重叠。在许多目标检测算法中,不同类别的重叠对象通常会被抑制为一个具有最高置信度的边界框。
-
Detector
:表示不仅要定位图像中一组对象的 (x, y) 坐标,还要返回它们的类标签。
2.2 架构
和 Faster R - CNN 一样,SSD 也从一个基础网络开始。这个网络通常是在大型数据集(如 ImageNet)上预训练的,以便学习丰富的判别特征。同样会使用这个网络进行迁移学习,将输入图像传播到指定的层,获取特征图,然后进入目标检测层。
在 Liu 等人的工作中使用了 VGG16,因为在发表时,VGG 比其他流行的网络架构在迁移学习中能提供更好的准确性和结果。如今,为了获得更高的准确性,可以使用更深的 ResNet 或 DenseNet 架构;为了提高速度,可以使用 SqueezeNet 或 MobileNet。为了解释 SSD 框架,这里以 VGG 作为基础网络。
SSD 架构的工作流程如下:
1. 利用 VGG 层直到 conv_6,然后分离所有其他层,包括全连接层。
2. 在架构中添加一组新的 CONV 层,这些层使 SSD 框架成为可能。从图中可以看出,这些层也是 CONV 层,这意味着网络是全卷积的,可以接受任意大小的输入图像,不再受 VGG 的 224×224 输入要求的限制。
SSD 架构有两个重要组件:
- 像标准 CNN 一样,在更深的层中逐渐减小体积大小。
- 每个 CONV 层都连接到最终的检测层。
每个特征图连接到最终检测层这一点很重要,它使网络能够在不同尺度上检测和定位图像中的对象,并且这种尺度定位是在前向传播中完成的,不需要对特征图进行重采样,使得 SSD 能够以完全前馈的方式运行,这就是 SSD 如此快速和高效的原因。
以下是 SSD 架构的简单流程图:
graph LR
A[输入图像] --> B[基础网络(VGG)]
B --> C[获取特征图]
C --> D[添加新的 CONV 层]
D --> E[最终检测层]
E --> F[输出检测结果]
2.3 MultiBox、Priors 和 Fixed Priors
SSD 框架使用了 Szegedy 等人的 MultiBox 算法的改进版本来进行边界框建议。该算法受到 Szegedy 之前在 Inception 网络工作的启发,使用了一系列 1×1 内核来帮助减少维度(在体积宽度和高度方面),以及一系列 3×3 内核进行更丰富的特征学习。
MultiBox 算法从 priors 开始,类似于但不完全等同于 Faster R - CNN 框架中的锚点。priors 是固定大小的边界框,其尺寸是根据数据集中每个类别的真实边界框的尺寸和位置预先计算的。之所以称为“prior”,是因为依赖于贝叶斯统计推断,更具体地说是对象位置在图像中出现的先验概率分布。选择的 priors 与真实对象的交并比(IoU)大于 50%。事实证明,这种计算 priors 的方法比从输入图像中随机选择坐标更好,但问题是现在需要预训练 MultiBox 预测器,这破坏了端到端训练完整的基于深度学习的目标检测器的目标。
幸运的是,有一个解决方案:fixed priors,它涉及到与 Faster R - CNN 类似的锚点选择技术。
为了直观理解 fixed priors 的概念,假设有一个原始输入图像和真实边界框。目标是生成将输入图像离散化为单元格的特征图,这个过程会在图像通过 CNN 时自然发生,因为输出体积的空间维度会逐渐变小。
特征图中的每个单元格,类似于锚点,都有一组小的默认边界框(通常是四个),它们具有不同的宽高比。通过将输入图像离散化为不同大小的特征图,能够检测图像中不同尺度的对象。
总结
通过上述内容,我们学习了如何使用 TensorFlow 目标检测 API 训练 Faster R - CNN + ResNet - 101 架构来检测图像中的交通标志。经过 50000 步的训练,最终获得了高达 98% 的 mAP@0.5 IoU。通常,网络训练的最少步数为 20000 步,根据数据集的不同,使用 50000 - 200000 步可以获得更高的准确性。
在使用 TFOD API 训练 Faster R - CNN 后,创建了一个 Python 脚本将网络应用于自己的输入图像,这个脚本也可以用于其他项目。
同时,为了解决 Faster R - CNN 存在的框架复杂和推理速度慢的问题,介绍了 SSD 框架。SSD 框架具有端到端、无复杂活动部件和超实时性能的优点,是一种优秀的目标检测解决方案。
以下是 Faster R - CNN 和 SSD 的对比表格:
| 特性 | Faster R - CNN | SSD |
| ---- | ---- | ---- |
| 框架复杂度 | 高,包含多个组件 | 低,端到端架构 |
| 训练时间 | 长,多个组件影响 | 相对较短 |
| 推理速度 | 约 7 - 10 FPS | 可达到 59 + FPS |
| 实时性能 | 难以实现 | 可以实现超实时 |
超级会员免费看
6789

被折叠的 条评论
为什么被折叠?



