Faster R-CNN源码阅读之八:Faster R-CNN/lib/rpn_msr/proposal_target_layer_tf.py

Faster R-CNN源码阅读之八:Faster R-CNN/lib/rpn_msr/proposal_target_layer_tf.py

一、介绍

   本demo由Faster R-CNN官方提供,我只是在官方的代码上增加了注释,一方面方便我自己学习,另一方面贴出来和大家一起交流。
   该文件中的函数的主要目的是根据所传入的参数rpn rois和gt boxes等信息对rois尽心采样,并确定每一个roi的labels标签和bbox回归目标。

二、代码以及注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# coding=utf-8
# --------------------------------------------------------
# Faster R-CNN
# Copyright (c) 2015 Microsoft
# Licensed under The MIT License [see LICENSE for details]
# Written by Ross Girshick and Sean Bell
# --------------------------------------------------------

import yaml
import numpy as np
import numpy.random as npr
from fast_rcnn.config import cfg
from fast_rcnn.bbox_transform import bbox_transform
from utils.cython_bbox import bbox_overlaps
import pdb

DEBUG = False

# 该函数将rois和gt boxes结合起来,对产生的rois进行筛选和分类(每一个roi中的目标属于哪一种类别)。
# 同时产生bbox inside weights和bbox outside weights,用以loss值的确定。
def proposal_target_layer(rpn_rois, gt_boxes, _num_classes):
"""
Assign object detection proposals to ground-truth targets.
Produces proposal classification labels and bounding-box regression targets.
将之前产生的目标检测的proposals和ground-truth目标进行匹配对齐,从而产生proposals的分类labels和bbox的回归目标。
:param rpn_rois:blob, shape为[N, 5],每一行的组成为[proposals的输入图片的索引(1),proposals坐标(4)]。
(由于每次值feed一张图片,这里的图片索引一般为0)
:param gt_boxes: ground truth boxes,shape为[M, 5],每一行的前四个元素表示gt box的坐标,最后一个元素表示类别。
:param _num_classes: 类别的总数目,包括背景,这里一本为21,(Pascal VOC的类别数目为21)。
:returns:
"""

# Proposal ROIs (0, x1, y1, x2, y2) coming from RPN
# (i.e., rpn.proposal_layer.ProposalLayer), or any other source
# 重新给变量命名
all_rois = rpn_rois
# TODO(rbg): it's annoying that sometimes I have extra info before
# and other times after box coordinates -- normalize to one format

# Include ground-truth boxes in the set of candidate rois
# 定义一个shape为(gt_boxes.shape[0], 1)的全0矩阵,用以标注gt boxes的图片索引。
zeros = np.zeros((gt_boxes.shape[0], 1), dtype=gt_boxes.dtype)
# 这一步将生成的proposals和gt boxes结合在一起。
# gt_boxes[:, :-1]表示取出每一行的除了最后一个元素之外的所有元素。
all_rois = np.vstack((all_rois, np.hstack((zeros, gt_boxes[:, :-1]))))

# Sanity check: single batch only
# 因为每次值feed进网络一张图片,因此图片索引必定为0。
assert np.all(all_rois[:, 0] == 0), 'Only single item batches are supported'

# 图片数目,一般情况下都为1
num_images = 1
# 平均每张图片上的rois数目。cfg.TRAIN.BATCH_SIZE一般取值128。
rois_per_image = cfg.TRAIN.BATCH_SIZE / num_images
# 平均每张图片上的前景rois数目。cfg.TRAIN.FG_FRACTION一般取值0.25,表示前景的比例。
fg_rois_per_image = np.round(cfg.TRAIN.FG_FRACTION * rois_per_image)

# Sample rois with classification labels and bounding box regression targets
# 对所有的rois进行采样,选区其中的一部分作为前景rois,背景rois,
# 返回他们的labels标签,rois,bbox回归的目标矩阵和bbox inside weights
labels, rois, bbox_targets, bbox_inside_weights = _sample_rois(
all_rois, # 所有的rois,包括产生的proposals和gt boxes
gt_boxes, # ground truth boxes
fg_rois_per_image, # 每张图片的前景rois数目
rois_per_image, # 平均每张图片上的rois总数目
_num_classes) # 类别总数目

# 输出调试信息
if DEBUG:
print 'num fg: {}'.format((labels > 0).sum())
print 'num bg: {}'.format((labels == 0).sum())
_count += 1
_fg_num += (labels > 0).sum()
_bg_num += (labels == 0).sum()
print 'num fg avg: {}'.format(_fg_num / _count)
print 'num bg avg: {}'.format(_bg_num / _count)
print 'ratio: {:.3f}'.format(float(_fg_num) / float(_bg_num))

# 对上面产生的矩阵进行重新整理
# rois整理成N x 5 的矩阵,第一列表示图片索引,一般为0。
rois = rois.reshape(-1, 5)
labels = labels.reshape(-1, 1)
bbox_targets = bbox_targets.reshape(-1, _num_classes * 4)
bbox_inside_weights = bbox_inside_weights.reshape(-1, _num_classes * 4)

# bbox outside weights的产生和赋值,这里将bbox inside weights大于0的部分设置为1.0,其余部分设置为0.0。
bbox_outside_weights = np.array(bbox_inside_weights > 0).astype(np.float32)

# 返回
return rois, labels, bbox_targets, bbox_inside_weights, bbox_outside_weights


def _get_bbox_regression_labels(bbox_target_data, num_classes):
"""
Bounding-box regression targets (bbox_target_data) are stored in a
compact form N x (class, tx, ty, tw, th)

This function expands those targets into the 4-of-4*K representation used
by the network (i.e. only one class has non-zero targets).

这个函数目的一个是将bbox targets扩展转换成类似one-hot的形式,另一个目的是返回bbox inside weights。
:param bbox_target_data: _compute_targets函数生成的labels和bbox回归目标的关联矩阵,形如N x (class, tx, ty, tw, th)。
:param num_classes: 类别数目。

Returns:
bbox_target (ndarray): N x 4K blob of regression targets
N × 4K的矩阵,表示bbox回归的目标。
bbox_inside_weights (ndarray): N x 4K blob of loss weights
N × 4K的矩阵,用以产生loss值。
"""

# 获取所有的类别信息
clss = np.array(bbox_target_data[:, 0], dtype=np.uint16, copy=True)
# 产生一个全0的矩阵,用以储存bbox的回归目标信息,注意这里矩阵的形状是(clss.size, 4 * num_classes)。
bbox_targets = np.zeros((clss.size, 4 * num_classes), dtype=np.float32)
# 产生一个全0的矩阵,用以存储bbox inside weights,这里矩阵的形状也是(clss.size, 4 * num_classes)。
bbox_inside_weights = np.zeros(bbox_targets.shape, dtype=np.float32)
# 获取所有的前景目标的索引,0表示背景
inds = np.where(clss > 0)[0]
# 对每一个前景目标索引
for ind in inds:
# 获取这个前景目标的类别
cls = clss[ind]
# 计算初始列和结束列
start = 4 * cls
end = start + 4
# 在合适的列上进行赋值,行数有ind指定。这里把bbox的回归目标进行赋值。
bbox_targets[ind, start:end] = bbox_target_data[ind, 1:]
# 同上,这里将bbox的bbox inside weights进行赋值。cfg.TRAIN.BBOX_INSIDE_WEIGHTS一般取值(1.0, 1.0, 1.0, 1.0)。
bbox_inside_weights[ind, start:end] = cfg.TRAIN.BBOX_INSIDE_WEIGHTS

# 返回
return bbox_targets, bbox_inside_weights


def _compute_targets(ex_rois, gt_rois, labels):
"""
Compute bounding-box regression targets for an image.
计算bbox的回归目标
:param ex_rois: 经过一系列计算保留下来的rois。
:param gt_rois: 和ex_rois拥有最大IOU的gt box,该参数中的gt box和前面额ex rois一一对应。
:param labels: 前面ex rois的labels
:return: bbox的labels和回归目标共同组成的二维数组。
"""

# 保证ex rois和gt rois的形状符合要求。
assert ex_rois.shape[0] == gt_rois.shape[0]
assert ex_rois.shape[1] == 4
assert gt_rois.shape[1] == 4

# 计算bbox的回归目标,该函数的具体含义可以参考lib/fast_rcnn/bbox_transform.py文件。
targets = bbox_transform(ex_rois, gt_rois)

# 是否进行正则化,默认是False。
if cfg.TRAIN.BBOX_NORMALIZE_TARGETS_PRECOMPUTED:
# Optionally normalize targets by a precomputed mean and stddev
# 对targets进行正则化,参数取值一般如下
# cfg.TRAIN.BBOX_NORMALIZE_MEAN = (0.0, 0.0, 0.0, 0.0)
# cfg.TRAIN.BBOX_NORMALIZE_STDS = (0.1, 0.1, 0.2, 0.2)
targets = ((targets - np.array(cfg.TRAIN.BBOX_NORMALIZE_MEANS)) / np.array(cfg.TRAIN.BBOX_NORMALIZE_STDS))

# 将labels信息和bbox回归目标结合起来。
return np.hstack((labels[:, np.newaxis], targets)).astype(np.float32, copy=False)


def _sample_rois(all_rois, gt_boxes, fg_rois_per_image, rois_per_image, num_classes):
"""
Generate a random sample of RoIs comprising foreground and background examples.
生成包含前景和背景示例的RoI的随机样本。
:param all_rois: 所有的rois,包括产生的proposals和gt boxes
:param gt_boxes: ground truth boxes
:param fg_rois_per_image: 每张图片的前景rois数目,该值一般为32
:param rois_per_image: 平均每张图片上的rois总数目,该值一般为128
:param num_classes: 类别总数目,该值一般为21,包括背景
:returns:
"""
# overlaps: (rois x gt_boxes)
# 计算所有的产生的rois和gt boxes之间的overlaps(IOU)。
# overlaps是一个shape为[N, K]的二维数组,N表示所有的rois的数目,K表示gt boxes的数目。
# 对应overlap[i, j]存放的是第i个rois和第j个gt boxes之间的IOU。
overlaps = bbox_overlaps(
np.ascontiguousarray(all_rois[:, 1:5], dtype=np.float),
np.ascontiguousarray(gt_boxes[:, :4], dtype=np.float))

# 横向比较,找到与每个roi拥有最高IOU的gt box的索引。
gt_assignment = overlaps.argmax(axis=1)
# 横向比较,找到与每个roi拥有最高IOU的gt box的IOU值。
max_overlaps = overlaps.max(axis=1)
# 根据gt_assignment信息取出gt boxes的第4列(即labels标签列),此时相当于是在给all rois设置labels标签。
labels = gt_boxes[gt_assignment, 4]

# Select foreground RoIs as those with >= FG_THRESH overlap
# 找到和某个gt boxes拥有等于或者高于cfg.TRAIN.FG_THRESH阈值的all rois的索引。
fg_inds = np.where(max_overlaps >= cfg.TRAIN.FG_THRESH)[0]
# Guard against the case when an image has fewer than fg_rois_per_image foreground RoIs
# 控制前景rois的数目不超过fg_rois_per_image。因为有时候经过cfg.TRAIN.FG_THRESH的阈值控制,剩下的rois数目还是过多。
# 这个时候需要对rois的数目进行控制,让其不能超过fg_rois_per_image。
# 如果本身前景rois的数目就没有超过fg_rois_per_image,则直接全部保留。
# 因此这里取fg_rois_per_image和fg_inds.size之间的最小值。
fg_rois_per_this_image = int(min(fg_rois_per_image, fg_inds.size))
# Sample foreground regions without replacement
# 如果有有效的前景rois
if fg_inds.size > 0:
# 当fg_rois_per_this_image取值是fg_inds.size时,说明前景anchors数目不超过fg_rois_per_image,
# 这个时候由于replace=False,npr.choice的作用仅仅相当于打乱顺序。
# 当fg_rois_per_this_image取值是fg_rois_per_image时,说明前景anchors数目超过了fg_rois_per_image,
# 这个时候就从fg_inds中随机选择fg_rois_per_image个前景rois的索引。
# 将取出的索引存入fg_inds。
fg_inds = npr.choice(fg_inds, size=fg_rois_per_this_image, replace=False)

# 和选择前景rois采用类似的方法进行背景rois的选择。
# Select background RoIs as those within [BG_THRESH_LO, BG_THRESH_HI)
# 选择那些最大IOU在[BG_THRESH_LO, BG_THRESH_HI)(一般取值BG_THRESH_LO=0.1, BG_THRESH_HI=0.5)之间的proposals的索引。
bg_inds = np.where((max_overlaps < cfg.TRAIN.BG_THRESH_HI) &
(max_overlaps >= cfg.TRAIN.BG_THRESH_LO))[0]
# Compute number of background RoIs to take from this image (guarding against there being fewer than desired)
# 和上面的前景rois选择类似,这里也是为了防止产生过多的背景rois。
bg_rois_per_this_image = rois_per_image - fg_rois_per_this_image
bg_rois_per_this_image = min(bg_rois_per_this_image, bg_inds.size)
# Sample background regions without replacement
if bg_inds.size > 0:
bg_inds = npr.choice(bg_inds, size=bg_rois_per_this_image, replace=False)

# The indices that we're selecting (both fg and bg)
# 将前景rois和背景rois的索引连接起来,作为保留下来的rois
keep_inds = np.append(fg_inds, bg_inds)
# Select sampled values from various arrays:
# 获取保留下来的rois的labels标签。
labels = labels[keep_inds]
# Clamp labels for the background RoIs to 0
# 前面的labels中保存的是每个rois的何其拥有最大IOU的gt box的label,均为大于0的labels,
# 刚刚我们计算出了应该被设置为背景的rois的索引,并把这些rois保存在了keep_inds的后半部分,
# 因此我们需要将后半部分的rois的labels设置为0,表明他们为背景rois。
labels[fg_rois_per_this_image:] = 0
# 取出保留下来的rois
rois = all_rois[keep_inds]

# rois的第1列表示的是图片索引,一般为0,使用不到。
# gt_boxes[gt_assignment[keep_inds], :4]的目的是取出和前面的rois一一对应的gt box的坐标(前4列,最后一列为labels标签。)
# labels为rois的labels,也适合rois一一对应的。
# 该函数产生bbox的回归目标。形如N × 5。
bbox_target_data = _compute_targets(rois[:, 1:5], gt_boxes[gt_assignment[keep_inds], :4], labels)

# 结合上面产生的回归目标和类别总数目,产生扩展变换后的bbox回归目标和bbox inside weights。
bbox_targets, bbox_inside_weights = _get_bbox_regression_labels(bbox_target_data, num_classes)

# 返回
return labels, rois, bbox_targets, bbox_inside_weights

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×