Faster R-CNN源码阅读之七:Faster R-CNN/lib/rpn_msr/anchor_target_layer_tf.py

Faster R-CNN源码阅读之七:Faster R-CNN/lib/rpn_msr/anchor_target_layer_tf.py

一、介绍

   本demo由Faster R-CNN官方提供,我只是在官方的代码上增加了注释,一方面方便我自己学习,另一方面贴出来和大家一起交流。
   该文件中的函数的主要目的是产生anchors并结合gt boxes(ground truth boxes)给这些anchors进行标记labels(前景还是背景),然后生成这些anchors的权重信息,并产生bbox的RPN网络回归结果目标。

二、代码以及注释
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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# -*- 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 os
import yaml
from fast_rcnn.config import cfg
import numpy as np
import numpy.random as npr
from generate_anchors import generate_anchors
from utils.cython_bbox import bbox_overlaps
from fast_rcnn.bbox_transform import bbox_transform
import pdb

DEBUG = False


def anchor_target_layer(rpn_cls_score, gt_boxes, im_info, data, _feat_stride=[16, ], anchor_scales=[4, 8, 16, 32]):
"""
Assign anchors to ground-truth targets. Produces anchor classification
labels and bounding-box regression targets.
"""

# 产生作为基准的若干个anchors,并获取这些anchors的个数。
_anchors = generate_anchors(scales=np.array(anchor_scales))
_num_anchors = _anchors.shape[0]

# if DEBUG代码块仅用作调试时使用,输出某些特定的信息,没有实际的帮助,下同。
if DEBUG:
print 'anchors:'
print _anchors
print 'anchor shapes:'
print np.hstack((
_anchors[:, 2::4] - _anchors[:, 0::4],
_anchors[:, 3::4] - _anchors[:, 1::4],
))
_counts = cfg.EPS
_sums = np.zeros((1, 4))
_squared_sums = np.zeros((1, 4))
_fg_sum = 0
_bg_sum = 0
_count = 0

# allow boxes to sit over the edge by a small amount
# 允许boxes(anchors)超过图像实际边界的某个余量,这里设置为0,表示不允许boxes超过图像边界。
_allowed_border = 0
# map of shape (..., H, W)
# height, width = rpn_cls_score.shape[1:3]

# 获取图像的信息
im_info = im_info[0]

# Algorithm:
#
# for each (H, W) location i
# generate 9 anchor boxes centered on cell i
# apply predicted bbox deltas at cell i to each of the 9 anchors
# filter out-of-image anchors
# measure GT overlap

# 算法:

# 断言,目前版本的算法仅能允许每次feed一张图片
assert rpn_cls_score.shape[0] == 1, \
'Only single item batches are supported'

# map of shape (..., H, W)
height, width = rpn_cls_score.shape[1:3]

if DEBUG:
print 'AnchorTargetLayer: height', height, 'width', width
print ''
print 'im_size: ({}, {})'.format(im_info[0], im_info[1])
print 'scale: {}'.format(im_info[2])
print 'height, width: ({}, {})'.format(height, width)
print 'rpn: gt_boxes.shape', gt_boxes.shape
print 'rpn: gt_boxes', gt_boxes

# 1. Generate proposals from bbox deltas and shifted anchors
# 1. 生成所有的加入偏移量之后的anchors,具体过程可以参考proposal_layer_tf.py文件。
shift_x = np.arange(0, width) * _feat_stride
shift_y = np.arange(0, height) * _feat_stride
shift_x, shift_y = np.meshgrid(shift_x, shift_y)
shifts = np.vstack((shift_x.ravel(), shift_y.ravel(),
shift_x.ravel(), shift_y.ravel())).transpose()
# add A anchors (1, A, 4) to
# cell K shifts (K, 1, 4) to get
# shift anchors (K, A, 4)
# reshape to (K*A, 4) shifted anchors
A = _num_anchors
K = shifts.shape[0]
all_anchors = (_anchors.reshape((1, A, 4)) +
shifts.reshape((1, K, 4)).transpose((1, 0, 2)))
all_anchors = all_anchors.reshape((K * A, 4))
total_anchors = int(K * A)

# only keep anchors inside the image
# 获取所有的边界都在图像边界(加上余量之后)的范围内的anchors的索引。
inds_inside = np.where(
(all_anchors[:, 0] >= -_allowed_border) &
(all_anchors[:, 1] >= -_allowed_border) &
(all_anchors[:, 2] < im_info[1] + _allowed_border) & # width
(all_anchors[:, 3] < im_info[0] + _allowed_border) # height
)[0]

if DEBUG:
print 'total_anchors', total_anchors
print 'inds_inside', len(inds_inside)

# keep only inside anchors
# 获取上述求得的边界均满足条件的anchors。
anchors = all_anchors[inds_inside, :]
if DEBUG:
print 'anchors.shape', anchors.shape

# label: 1 is positive, 0 is negative, -1 is don't care
# 初始化label,1表示正样本,0表示负样本,-1表示不关心。
# 因此刚开始,我们将所有的label设置为-1。
labels = np.empty((len(inds_inside),), dtype=np.float32)
labels.fill(-1)

# overlaps between the anchors and the gt boxes
# overlaps (ex, gt)
# 计算每一个anchor和每一个gt boxes(ground truth boxes)之间的overlap
# 假设anchors的数目为N,gt boxes的数目为K,
# 则bbox_overlaps会返回一个shape为[N, K]的数组array,里面依次保存着第i个anchor和第j个gt box之间的IOU。
overlaps = bbox_overlaps(
np.ascontiguousarray(anchors, dtype=np.float),
np.ascontiguousarray(gt_boxes, dtype=np.float))

# 横向比较,为每一个anchor找到与其拥有最高的IOU的gt box,并返回这些gt box的索引(这里是列索引)。
argmax_overlaps = overlaps.argmax(axis=1)
# 保存的是每一个anchor与其拥有最高IOU的gt box的IOU值。
max_overlaps = overlaps[np.arange(len(inds_inside)), argmax_overlaps]

# 纵向比较,为每一个gt box找到与其拥有最高的IOU的anchor,并返回这些anchors的索引(这里是行索引)。
gt_argmax_overlaps = overlaps.argmax(axis=0)
# 保存的是每一个gt box与其拥有最高IOU的anchor的IOU值。
# 注意这里gt_argmax_overlaps和np.arange(overlaps.shape[1])的顺序。
# (个人认为以下的两行代码都更为简便的写法,实际上第二行代码可以省略)
gt_max_overlaps = overlaps[gt_argmax_overlaps,
np.arange(overlaps.shape[1])]
# 这里按行的顺序获取最大值的索引,本质上是对gt_argmax_overlaps进行从小到大的排序
gt_argmax_overlaps = np.where(overlaps == gt_max_overlaps)[0]

if not cfg.TRAIN.RPN_CLOBBER_POSITIVES:
# assign bg labels first so that positive labels can clobber them
# 首先对那些小于一定阈值的anchor进行label的赋值,把它们标记为背景anchor(label标记为0)。
labels[max_overlaps < cfg.TRAIN.RPN_NEGATIVE_OVERLAP] = 0

# fg label: for each gt, anchor with highest overlap
# 标记前景anchors的label,与某一个gt box有最高IOU的anchor的label标记为1。
# 之所以这么做是因为有时候IOU高于某一阈值的anchor不存在,仅考虑使用阈值标记label就会出现错误。
labels[gt_argmax_overlaps] = 1

# fg label: above threshold IOU
# 与某一个gt box的IOU大于某一阈值的anchor的label标记为1。
labels[max_overlaps >= cfg.TRAIN.RPN_POSITIVE_OVERLAP] = 1

if cfg.TRAIN.RPN_CLOBBER_POSITIVES:
# assign bg labels last so that negative labels can clobber positives
# 最后对背景anchor进行赋值。
# 和上面的if not cfg.TRAIN.RPN_CLOBBER_POSITIVES代码块功能类似,就是看positive和negative谁比较强。
# 这两部分的代码在实际过程中只能执行一个。
# 前一部分代码现将一部分label设置为0,则默认为positive比negative强,因为在前面的两行label赋值代码中有可能anchor的label会从0变成1。
# 这一部分代码则与前面的if代码块作用相反,默认为negative比positive强,因为在这一部分代码中,可能有已经被标记为1的label会被重新标记为0。
labels[max_overlaps < cfg.TRAIN.RPN_NEGATIVE_OVERLAP] = 0

# subsample positive labels if we have too many
# 如果有过多的正样本label,即过多的前景anchors,随机选择一些,剩下的未被选择的标记为-1。
# 需要的最大前景数目
num_fg = int(cfg.TRAIN.RPN_FG_FRACTION * cfg.TRAIN.RPN_BATCHSIZE)
# 获取label为1即所有前景anchors的索引
fg_inds = np.where(labels == 1)[0]
# 如果前景数目过多
if len(fg_inds) > num_fg:
# 随机选择一些前景,数目为总前景数目减去所需要的前景数目
disable_inds = npr.choice(
fg_inds, size=(len(fg_inds) - num_fg), replace=False)
# 把这些前景anchors的label变更为-1,表示不关心。
labels[disable_inds] = -1

# subsample negative labels if we have too many
# 如果有过多的背景数目,也是同理筛选一些保留。
num_bg = cfg.TRAIN.RPN_BATCHSIZE - np.sum(labels == 1)
bg_inds = np.where(labels == 0)[0]
if len(bg_inds) > num_bg:
disable_inds = npr.choice(
bg_inds, size=(len(bg_inds) - num_bg), replace=False)
labels[disable_inds] = -1
# print "was %s inds, disabling %s, now %s inds" % (
# len(bg_inds), len(disable_inds), np.sum(labels == 0))

# 创建一个shape为[len(inds_inside), 4]大小的全0数组,用来存储等会生成的anchors的回归目标
bbox_targets = np.zeros((len(inds_inside), 4), dtype=np.float32)
# _compute_targets函数返回一个用于anchors回归成targets的包含每个anchor回归值(dx、dy、dw、dh)的array,
# 形状((len(inds_inside), 4),即(anchors.shape[0],4)
# 第二个参数gt_boxes[argmax_overlaps, :]括号内部的取值可以保证每一个anchor都对应于与之拥有最高IOU的gt box。
# bbox_targets本质上是RPN应该生成的数据,用以bbox的回归操作。即bbox targets是网络在训练的时候需要生成的目标。
bbox_targets = _compute_targets(anchors, gt_boxes[argmax_overlaps, :])

# 定义一个全0的(len(inds_inside), 4)二维数组,表示bbox inside weights,并对对应的label为1的bbox inside weights进行赋值
bbox_inside_weights = np.zeros((len(inds_inside), 4), dtype=np.float32)
# 这里将对应label为1的bbox inside weights赋值为cfg.TRAIN.RPN_BBOX_INSIDE_WEIGHTS,
# cfg.TRAIN.RPN_BBOX_INSIDE_WEIGHTS一般是一个长度为4的全有1组成的一维数组。
bbox_inside_weights[labels == 1, :] = np.array(cfg.TRAIN.RPN_BBOX_INSIDE_WEIGHTS)

# 定义一个全0的(len(inds_inside), 4)二维数组,表示bbox outside weights。
bbox_outside_weights = np.zeros((len(inds_inside), 4), dtype=np.float32)
# cfg.TRAIN.RPN_POSITIVE_WEIGHT一般设为-1
if cfg.TRAIN.RPN_POSITIVE_WEIGHT < 0:
# uniform weighting of examples (given non-uniform sampling)
# 统一的样本(anchors)配置权重方法,else代码块中为非统一样本配置权重方法。
# 在统一的方法中正样本(anchors)和负样本(anchors)的bbox outside weights均被赋值为正负样本数目之后的倒数。

# 这一步是获取正负样本(anchors)的总数目(1表示正样本,0表示负样本,-1表示不关心)
num_examples = np.sum(labels >= 0)

# 赋值
positive_weights = np.ones((1, 4)) * 1.0 / num_examples
negative_weights = np.ones((1, 4)) * 1.0 / num_examples
else:
# 保证cfg.TRAIN.RPN_POSITIVE_WEIGHT 在0和1之间
assert ((cfg.TRAIN.RPN_POSITIVE_WEIGHT > 0) & (cfg.TRAIN.RPN_POSITIVE_WEIGHT < 1))
# 正样本的权重
positive_weights = (cfg.TRAIN.RPN_POSITIVE_WEIGHT / np.sum(labels == 1))
# 负样本的权重
negative_weights = ((1.0 - cfg.TRAIN.RPN_POSITIVE_WEIGHT) / np.sum(labels == 0))

# 对正负样本(anchors)的bbox outside weights分别进行赋值。
bbox_outside_weights[labels == 1, :] = positive_weights
bbox_outside_weights[labels == 0, :] = negative_weights

if DEBUG:
_sums += bbox_targets[labels == 1, :].sum(axis=0)
_squared_sums += (bbox_targets[labels == 1, :] ** 2).sum(axis=0)
_counts += np.sum(labels == 1)
means = _sums / _counts
stds = np.sqrt(_squared_sums / _counts - means ** 2)
print 'means:'
print means
print 'stdevs:'
print stds

# map up to original set of anchors
# 从获取到存在图片内部的anchors之后,我们一直在对这些anchors进行操作,而剩下的相当多的anchors就被直接忽略掉了,
# 现在我们需要考虑利用上这一部分anchors。
# 注:inds_inside变量保存的是存在于图片内部的anchors在生成的全部的anchors中的索引,
# 而在后续的操作过程中,我们并没有改变提取出来的anchors的顺序,因此这些索引还是和anchors,labels等一一对应。

# 将剩下的anchors进行label的标注,由于这些anchors并不是全部存在于图片内部,因此这里将他们的label设置为-1,表示不关心。
# total_anchors是一个整数,表示之前生成的最最原始的anchors的数目。下同
# inds_inside表示存在于图片内部的anchors在所有原始anchors中的索引。下同
labels = _unmap(labels, total_anchors, inds_inside, fill=-1)
# 下面的三行代码也是同理,将那些原始的anchors的信息也添加进去,由于没有经过上面的计算过程,因此这些信息全部被设置为0。
bbox_targets = _unmap(bbox_targets, total_anchors, inds_inside, fill=0)
bbox_inside_weights = _unmap(bbox_inside_weights, total_anchors, inds_inside, fill=0)
bbox_outside_weights = _unmap(bbox_outside_weights, total_anchors, inds_inside, fill=0)

if DEBUG:
print 'rpn: max max_overlap', np.max(max_overlaps)
print 'rpn: num_positive', np.sum(labels == 1)
print 'rpn: num_negative', np.sum(labels == 0)
_fg_sum += np.sum(labels == 1)
_bg_sum += np.sum(labels == 0)
_count += 1
print 'rpn: num_positive avg', _fg_sum / _count
print 'rpn: num_negative avg', _bg_sum / _count

# labels
# pdb.set_trace()
# 在前面anchors的产生过程中可以看出,其本质上也是在特征图上滑窗操作,在每个特征图的点上生成若干个anchors。
# 这里height,width分别表示特征图的高度和宽度,A表示在每个位置产生的anchors的数目,reshape之后就和特征图上的位置一一对应。
# 之后transpose(0, 3, 1, 2),此时最精确信息为width,此时以width信息进行fastest聚类。(??)
labels = labels.reshape((1, height, width, A)).transpose(0, 3, 1, 2)
labels = labels.reshape((1, 1, A * height, width))
rpn_labels = labels

# 将以下的三个变量的通道顺序更改为[N, C, H, W],N为样本维,C表示通道维,H表示height,W表示宽度。
# 重新给变量命名,并结合上述的rpn_labels一并返回。

# bbox_targets
bbox_targets = bbox_targets.reshape((1, height, width, A * 4)).transpose(0, 3, 1, 2)

rpn_bbox_targets = bbox_targets
# bbox_inside_weights
bbox_inside_weights = bbox_inside_weights.reshape((1, height, width, A * 4)).transpose(0, 3, 1, 2)
# assert bbox_inside_weights.shape[2] == height
# assert bbox_inside_weights.shape[3] == width

rpn_bbox_inside_weights = bbox_inside_weights

# bbox_outside_weights
bbox_outside_weights = bbox_outside_weights.reshape((1, height, width, A * 4)).transpose(0, 3, 1, 2)
# assert bbox_outside_weights.shape[2] == height
# assert bbox_outside_weights.shape[3] == width

rpn_bbox_outside_weights = bbox_outside_weights

return rpn_labels, rpn_bbox_targets, rpn_bbox_inside_weights, rpn_bbox_outside_weights


def _unmap(data, count, inds, fill=0):
""" Unmap a subset of item (data) back to the original set of items (of size count)
在之前的处理过程中,我们一直在处理存在于图片边界内部的anchors,还有相当多的anchors在图片边界外部或者在图片边界上。
而这一部分我们直接忽略掉了,现在利用这个函数将他们也进行简单处理,赋予他们所需要的label和weights信息。
:param data: 该参数有两种形式,一个是图片内部anchors的labels,另一个是图片内部anchors的RPN回归目标以及权重等信息。
labels是一个一维数组,其他的都是shape为[N, 4]形状的二维数组。
:param count:原始anchors的数目,这里包括图片边缘内部的,也包括存在于图片边缘上的和外部的。
:param inds: 存在于图片边缘内部的anchors在原始anchors序列中的索引,也是一个一维数组。
:param fill: 默认填充的数值,在图片边缘上的和外部的anchors没有经过前面的计算过程,这些参数只用fill提供的值默认填充。
:return: 补完之后的信息,这里的信息是所有anchors的信息,包括图片内部的,边界上的和边界外的、
"""

# 长度为1,表示这里补完的是labels信息
if len(data.shape) == 1:
# 定义矩阵。使用fill填充
ret = np.empty((count,), dtype=np.float32)
ret.fill(fill)
# 将图片内部anchors的信息重新赋值给矩阵,位置信息由inds提供。
ret[inds] = data
else:
# 这里补完的是bbox回归目标,权值等信息,和填充labels的过程类似。
ret = np.empty((count,) + data.shape[1:], dtype=np.float32)
ret.fill(fill)
ret[inds, :] = data
return ret


def _compute_targets(ex_rois, gt_rois):
"""
Compute bounding-box regression targets for an image.
:param ex_rois: 待处理的rois,一般是一系列anchors
:param gt_rois: ground truth boxes, 与每一个ex_rois(anchor)一一对应,每一行都是与当前ex_roi(anchor)其拥有最大IOU的gt box。
:return:
"""

# 确保每一个ex_roi都有一个gt box对应
assert ex_rois.shape[0] == gt_rois.shape[0]
# anchor长度为4
assert ex_rois.shape[1] == 4
# gt box的长度为5,分别表示实际bbox的坐标(4)和类别(1)
assert gt_rois.shape[1] == 5

# 分别计算bbox transform,这里只用到了gt box的坐标部分
return bbox_transform(ex_rois, gt_rois[:, :4]).astype(np.float32, copy=False)

Comments

Your browser is out-of-date!

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

×