R-FCN问题记录
前段时间用到了目标检测网络R-FCN,对于整体框架有了大概的了解.
然后被问了关于网络输入层im_info的问题:输入层有data层为什么还要有个im_info层,两者的区别在哪,作用是什么?在此记录下过程。
在test的prototxt中确实有两个输入层:
input: “data”
input_shape {
dim: 1
dim: 3
dim: 224
dim: 224
}
input: “im_info”
input_shape {
dim: 1
dim: 3
}
首先在prototxt.txt中查看了im_info的去处:
layer {
name: ‘proposal’
type: ‘Python’
bottom: ‘rpn_cls_prob_reshape’
bottom: ‘rpn_bbox_pred’
bottom: ‘im_info’
top: ‘rois’
python_param {
module: ‘rpn.proposal_layer’
layer: ‘ProposalLayer’
param_str: “‘feat_stride’: 16” }
}
发现im_info层仅用于proposal层的输入,查看proposal层的python实现代码proposal_layer.py:
im_info = bottom[2].data[0, :]
if DEBUG:
print 'im_size: ({}, {})'.format(im_info[0], im_info[1])
print 'scale: {}'.format(im_info[2])
这里的赋值是将proposal层的第三个bottom层的data信息给到im_info变量中,也就是im_info层的信息.
下面的debug模式表明可以输出im_info[0],im_info[1],im_info[2],然而im_info层的初始输入参数只有两个:input_shape {dim: 1 dim: 3}
所以继续往上查看im_info变量的输入,测试时通常调用lib/fast_rcnn/test.py,在test.py中搜索了im_info变量:
if cfg.TEST.HAS_RPN:
im_blob = blobs['data']
blobs['im_info'] = np.array([[im_blob.shape[2],im_blob.shape[3], im_scales[0]]], dtype=np.float32)
可以看到变量im_info包含三个值,其中两个是从blobs[‘data’]传递过来的:im_blob.shape[2]和im_blob.shape[3],第三个值为im_scales[0].
继续查看im_scales变量:
blobs, im_scales = _get_blobs(im, boxes)
从内部函数_get_blobs()中获得:
def _get_blobs(im, rois):
blobs = {'data' : None, 'rois' : None}
blobs['data'], im_scale_factors = _get_image_blob(im)
if not cfg.TEST.HAS_RPN:
blobs['rois'] = _get_rois_blob(rois, im_scale_factors)
return blobs, im_scale_factors
继续查看im_scale_factors变量,从函数_get_image_blob()处获得:
im_scale_factors = []
for target_size in cfg.TEST.SCALES:
im_scale = float(target_size) / float(im_size_min)
# Prevent the biggest axis from being more than MAX_SIZE
if np.round(im_scale * im_size_max) > cfg.TEST.MAX_SIZE:
im_scale = float(cfg.TEST.MAX_SIZE) / float(im_size_max)
im_scale_factors.append(im_scale)
return blob, np.array(im_scale_factors)
可以看到im_scale_factors是一个数组,里面存放的值是im_scale.
在config.py文件中有参数设定,TEST.SCALES存放了图片最短边的长度,可以是一个数组. TEST.MAX_SIZE存放了图片最长边的长度.
# Each scale is the pixel size of an image's shortest side
__C.TEST.SCALES = (600,)
# Max pixel size of the longest side of a scaled input image
__C.TEST.MAX_SIZE = 1000
所以im_scale的含义是:
进入网络时图片假设resize参数设定(config.py)为600 * 1000,图片A真实大小为size = 1080 * 1920,那么im_scale = float(600) / float(1080),并且如果np.round(im_scale * im_size_max) > cfg.TEST.MAX_SIZE,那么im_scale = float(1000) / float(1920),即im_scale = min{长边比,短边比}
回到test.py,得到缩放比例后,将原始图片按照该比例缩放:
im = cv2.resize(im_orig, None, None, fx=im_scale, fy=im_scale, interpolation=cv2.INTER_LINEAR)
然后将该图片存入blob:
processed_ims.append(im)
# Create a blob to hold the input images
blob = im_list_to_blob(processed_ims)
最后使用im_info存储resize后图片的宽高和缩放比例:
blobs['im_info'] = np.array([[im_blob.shape[2],im_blob.shape[3],
im_scales[0]]], dtype=np.float32)
以图片A为例,resize参数设定为600 * 1000,图片A真实大小为size = 1080 * 1920,im_scale计算出来为im_scale = 1000 / 1920 ≈ 0.521,因此图片进入网络时resize为(1080 * im_scale) * (1920 * im_scale) = 562.5 * 1000大小.
在命令行中将im_info变量输出能得到:
im_blob.shape[2] = 562.5
im_blob.shape[3] = 1000
im_scales[0] = 0.52083333
因此实际上im_info层的意义是存储了每张图片进入网络后原图片大小被resize至设定图片大小的比例和resize后图片的宽高.
使用是在proposal层:
im_info = bottom[2].data[0, :]
...
min_size = cfg[cfg_key].RPN_MIN_SIZE
...
#clip predicted boxes to image
proposals = clip_boxes(proposals, im_info[:2])
#remove predicted boxes with either height or width < threshold
#convert min_size to input image scale stored in im_info[2])
keep = _filter_boxes(proposals, min_size * im_info[2])
proposals = proposals[keep, :]
scores = scores[keep]
im_info[:2]为resize后的宽高,将得到的proposals侯选框进行修剪,去除超出图片边界的框.
im_info[2]是scale比例,用于去除proposals中比设定的最小min_size乘以对应比例后还小的侯选框,例如__C.TEST.RPN_MIN_SIZE = 16,则去除proposals中长或宽比16 * 0.521 ≈ 8.3 还要小的侯选框.
ps:去除方法可以查看lib/fast_rcnn/bbox_transform.py中的clip_boxes函数定义和proposal.py中的_filter_boxes函数定义.
后续:im_scales可以是多个短边值组成的数组,但im_info直接使用了im_scales[0].
对于这个问题刚开始没想通,后发现test.py中:
if cfg.DEDUP_BOXES > 0 and not cfg.TEST.HAS_RPN:
v = np.array([1, 1e3, 1e6, 1e9, 1e12])
hashes = np.round(blobs['rois'] * cfg.DEDUP_BOXES).dot(v)
_, index, inv_index = np.unique(hashes, return_index=True,
return_inverse=True)
blobs['rois'] = blobs['rois'][index, :]
boxes = boxes[index, :]
if cfg.TEST.HAS_RPN:
forward_kwargs['im_info'] = blobs['im_info'].astype(np.float32, copy=False)
else:
forward_kwargs['rois'] = blobs['rois'].astype(np.float32, copy=False)
并且有:
if cfg.TEST.HAS_RPN:
assert len(im_scales) == 1, "Only single-image batch implemented"
rois = net.blobs['rois'].data.copy()
# unscale back to raw image space
boxes = rois[:, 1:5] / im_scales[0]
所以当config.py中参数__C.TEST.HAS_RPN = True时,len(im_scales)只能为1,也就是说使用RPN生成侯选框时只能有一个size值;
不使用RPN的话是R-FCN的另一种ohem模式,而非end-to-end模式,此时使用rois层作为输入,可以支持多个size图片同时进入网络,models里ohem模式的test.pt中输入层如下:
input: “data”
input_shape {
dim: 1
dim: 3
dim: 224
dim: 224
}
input: “rois”
input_shape {
dim: 1 # to be changed on-the-fly to num ROIs
dim: 5 # [batch ind, x1, y1, x2, y2] zero-based indexing
}
对于im_info层的详解大致如上,牵扯到挺多细节点,对于源码的理解和钻研还不够,有理解不对的地方请多多指教~.