- Faster R-CNN源码阅读之零:写在前面
- Faster R-CNN源码阅读之一:Faster R-CNN/lib/networks/network.py
- Faster R-CNN源码阅读之二:Faster R-CNN/lib/networks/factory.py
- Faster R-CNN源码阅读之三:Faster R-CNN/lib/networks/VGGnet_test.py
- Faster R-CNN源码阅读之四:Faster R-CNN/lib/rpn_msr/generate_anchors.py
- Faster R-CNN源码阅读之五:Faster R-CNN/lib/rpn_msr/proposal_layer_tf.py
- Faster R-CNN源码阅读之六:Faster R-CNN/lib/fast_rcnn/bbox_transform.py
- Faster R-CNN源码阅读之七:Faster R-CNN/lib/rpn_msr/anchor_target_layer_tf.py
- Faster R-CNN源码阅读之八:Faster R-CNN/lib/rpn_msr/proposal_target_layer_tf.py
- Faster R-CNN源码阅读之九:Faster R-CNN/tools/train_net.py
- Faster R-CNN源码阅读之十:Faster R-CNN/lib/fast_rcnn/train.py
- Faster R-CNN源码阅读之十一:Faster R-CNN预测demo代码补完
- Faster R-CNN源码阅读之十二:写在最后
一、介绍
本demo由Faster R-CNN官方提供,我只是在官方的代码上增加了注释,一方面方便我自己学习,另一方面贴出来和大家一起交流。
该文件中的函数都是与anchors的变换相关,包括正向变换,反向变换。
二、代码以及注释
# -*- coding:utf-8 -*-
# --------------------------------------------------------
# Fast R-CNN
# Copyright (c) 2015 Microsoft
# Licensed under The MIT License [see LICENSE for details]
# Written by Ross Girshick
# --------------------------------------------------------
import numpy as np
'''
该文件中的函数都是与anchors的变换相关,包括正向变换,反向变换。
'''
def bbox_transform(ex_rois, gt_rois):
'''
计算两个N * 4的矩阵之间的相关回归矩阵。
本质上是在求解每一个anchor相对于它的对应gt box的(dx, dy, dw, dh)的四个回归值,返回结果的shape为[N, 4]。
:param ex_rois: shape为[N, 4]的数组,一般传入的anchors的信息。
:param gt_rois: shape为[N, 4]的数组,一般传入的gt boxes(ground truth boxes)的信息。每一个gt roi都与一个ex roi相对应。
:return: 本质上是在求解每一个anchor相对于它的对应gt box的(dx, dy, dw, dh)的四个回归值,返回结果的shape为[N, 4]。
'''
# 求出每一个ex_roi的宽度高度和中心坐标
ex_widths = ex_rois[:, 2] - ex_rois[:, 0] + 1.0
ex_heights = ex_rois[:, 3] - ex_rois[:, 1] + 1.0
ex_ctr_x = ex_rois[:, 0] + 0.5 * ex_widths
ex_ctr_y = ex_rois[:, 1] + 0.5 * ex_heights
# 求解每一个gt box的宽度高度和中心坐标
gt_widths = gt_rois[:, 2] - gt_rois[:, 0] + 1.0
gt_heights = gt_rois[:, 3] - gt_rois[:, 1] + 1.0
gt_ctr_x = gt_rois[:, 0] + 0.5 * gt_widths
gt_ctr_y = gt_rois[:, 1] + 0.5 * gt_heights
# 这里本质上是在反向求解RPN网络应该生成的数据,即bbox进行回归操作需要的4个变量。
# 参考bbox_transform_inv在demo中的使用,可以看出,targets本质上就是bbox_transform_inv参数中的deltas。
# 在bbox_transform_inv中,正向使用了deltas,而在该函数中,我们需要根据gt boxes信息,反向求解出deltas。
# 我们所需要的就是RPN(rpn_cls_pred)根据我们返回的目标targets产生合适的deltas。
# 下面是bbox_transform_inv中的anchors的变换代码,正好和targets的生成代码相反。
# pred_ctr_x = dx * widths[:, np.newaxis] + ctr_x[:, np.newaxis]
# pred_ctr_y = dy * heights[:, np.newaxis] + ctr_y[:, np.newaxis]
# pred_w = np.exp(dw) * widths[:, np.newaxis]
# pred_h = np.exp(dh) * heights[:, np.newaxis]
targets_dx = (gt_ctr_x - ex_ctr_x) / ex_widths
targets_dy = (gt_ctr_y - ex_ctr_y) / ex_heights
targets_dw = np.log(gt_widths / ex_widths)
targets_dh = np.log(gt_heights / ex_heights)
# 将所有的信息组合成一个矩阵返回
# vstack将所有的向量组合成shape为[4, N]的矩阵,最后transpose,矩阵的shape变成[N, 4]。
targets = np.vstack(
(targets_dx, targets_dy, targets_dw, targets_dh)).transpose()
# 返回
return targets
def bbox_transform_inv(boxes, deltas):
'''
将boxes使用rpn网络产生的deltas进行变换处理,求出变换后的boxes,即预测的proposals。
此处boxes一般表示原始anchors,即未经任何处理仅仅是经过平移之后产生测anchors。
:param boxes: 一般表示原始anchors,即未经任何处理仅仅是经过平移之后产生测anchors,shape为[N, 4],N表示anchors的数目。
:param deltas: RPN网络产生的数据,shape为[N, (1 + classes) * 4],classes表示类别数目,1 表示背景,N表示anchors的数目。
:return: 预测的变换之后的proposals(或者叫anchors)
'''
if boxes.shape[0] == 0:
return np.zeros((0, deltas.shape[1]), dtype=deltas.dtype)
# 进行类型转换
boxes = boxes.astype(deltas.dtype, copy=False)
# 求出每一个box的宽度高度和中心点,下面四个变量的shape均为[N],(一位数组,长度为N),N为anchors的数目
widths = boxes[:, 2] - boxes[:, 0] + 1.0
heights = boxes[:, 3] - boxes[:, 1] + 1.0
ctr_x = boxes[:, 0] + 0.5 * widths
ctr_y = boxes[:, 1] + 0.5 * heights
# 获取每一个类别的deltas的信息,每一个类别的deltas的信息是顺序存储的,
# 即第一个类别的四个信息(dx, dy, dw, dh)存储完成后才接着另一个类别。
# 下面四个变量的shape均为[N, classes + 1],N表示anchors数目,classes表示类别数目(此处为20),1表示背景。
dx = deltas[:, 0::4]
dy = deltas[:, 1::4]
dw = deltas[:, 2::4]
dh = deltas[:, 3::4]
# 具体的变换过程,这里采用了以e为底数的指数运算方式,(据说)方便求导。
# 下面四个变量的shape均为[N, classes + 1],N表示anchors数目,classes表示类别数目(此处为20),1表示背景。
# 对于每一个box(anchor),都对其预测每一个类别,即一个anchor对应于每一个类别。
pred_ctr_x = dx * widths[:, np.newaxis] + ctr_x[:, np.newaxis]
pred_ctr_y = dy * heights[:, np.newaxis] + ctr_y[:, np.newaxis]
pred_w = np.exp(dw) * widths[:, np.newaxis]
pred_h = np.exp(dh) * heights[:, np.newaxis]
# 变换完成后,对每一个box(anchor)都重新生成预测之后的每一个类别的左上角和右下角的坐标。
# pred_boxes和deltas的shape相同,均为[N, (1 + classes) * 4],classes表示类别数目,1 表示背景,N表示anchors的数目。
pred_boxes = np.zeros(deltas.shape, dtype=deltas.dtype)
# 进行赋值操作
# x1
pred_boxes[:, 0::4] = pred_ctr_x - 0.5 * pred_w
# y1
pred_boxes[:, 1::4] = pred_ctr_y - 0.5 * pred_h
# x2
pred_boxes[:, 2::4] = pred_ctr_x + 0.5 * pred_w
# y2
pred_boxes[:, 3::4] = pred_ctr_y + 0.5 * pred_h
# 返回
return pred_boxes
def clip_boxes(boxes, im_shape):
"""
Clip boxes to image boundaries.
将boxes的边框进行裁剪,使得每一条边框都在图片大小的范围之内。
:param boxes: 等待裁剪的boxes,一般是一系列的anchors。
:param im_shape: 图片的大小形状
:return: 裁减之后的boxes,每一条边都在图片大小的范围内
"""
# x1 >= 0
boxes[:, 0::4] = np.maximum(np.minimum(boxes[:, 0::4], im_shape[1] - 1), 0)
# y1 >= 0
boxes[:, 1::4] = np.maximum(np.minimum(boxes[:, 1::4], im_shape[0] - 1), 0)
# x2 < im_shape[1]
boxes[:, 2::4] = np.maximum(np.minimum(boxes[:, 2::4], im_shape[1] - 1), 0)
# y2 < im_shape[0]
boxes[:, 3::4] = np.maximum(np.minimum(boxes[:, 3::4], im_shape[0] - 1), 0)
return boxes