tf-faster-rcnn 代码详细解读

一.理解fast-rcnn到faster-rcnn的结构

如果不理解faster-rcnn的结构就去看代码是不可能看懂的,faster-rcnn是在fast-rcnn的基础上改进的。

那么我们就先从fast-rcnn开始

tf-faster-rcnn 代码详细解读_第1张图片

(文字是跟着图片描述的)

1. 选择性搜索Selective Search(SS)在图片中获得大约2000个候选框,使用的方法是Selective Search(SS)(Region Proposa)

2. 用vgg16 前五个阶段是conv + relu + pooling的多层的卷积网络得到一些特征映射(Feature Map)

3. 得到feature map,根据之前RoI框选择出对应的区域(既可以理解为将feature map映射回原图像)

4.通过ROI Pooling得到固定大小的特征,再把这些特征输入全连接层4096。

5.最后是21和84的两个全连接层(这两个全连接层是并列的,不是前后关系),前者是分类的输出,代表每个region proposal属于每个类别(21类)的得分,后者是回归的输出,代表每个region proposal的四个坐标。

6.最后是两个损失层,分类的是softmaxWithLoss,输入是label和分类层输出的得分;回归的是SmoothL1Loss,输入是回归层的输出和target坐标及weight。

7.最后对每个类别采用NMS(non-maximun suppression)

(下面这个图细分了fast-rcnn的构造)

tf-faster-rcnn 代码详细解读_第2张图片

(下面这个是损失部分的)

tf-faster-rcnn 代码详细解读_第3张图片

接下来是faster-rcnn的结构理解

(下面这个是faster-rcnn的结构图)

tf-faster-rcnn 代码详细解读_第4张图片

faster-rcnn就是在fast-rcnn的基础上用RPN替换掉Selective Search来生成ROI(注意图中的 区域建议 是虚线的)

RPN网络输入的特征图经过RPN网络得到区域建议和区域得分,并对区域得分采用非极大值抑制【阈值为0.7】,输出其Top-N得分的区域建议给RoI池化层

(了解到这里就可以理解代码了,其余的不详解结构,给出几个网站有兴趣的可以看看:

https://www.cnblogs.com/zyly/p/9247863.html

https://www.cnblogs.com/zyly/p/9246418.html

https://www.cnblogs.com/CZiFan/p/9903518.html

https://blog.csdn.net/shenxiaolu1984/article/details/51036677

https://blog.csdn.net/gentelyang/article/details/80469553

)

二.代码解读

给出github代码链接:https://github.com/endernewton/tf-faster-rcnn

这个tf-faster-rcnn代码想要训练起来一定要花大量的时间去解决问题,非常有耐心才可以(这里不教解决bug,自己上网查进行解决)。

一般代码都有demo文件,让你初步运行查看结果和测试环境。

安装

1.在设置脚本中更新-arch以匹配GPU

cd tf-faster-rcnn/lib
# Change the GPU architecture (-arch) if necessary
vim setup.py

2.构建Cython模块

make clean
make
cd ..

3.安装Python COCO API。代码要求API访问COCO dataset

cd data
git clone https://github.com/pdollar/coco.git
cd coco/PythonAPI
make
cd ../../..

5.下载模型

./data/scripts/fetch_faster_rcnn_models.sh

4.创建一个文件夹和一个软链接来使用预训练的模型

NET=res101
TRAIN_IMDB=voc_2007_trainval+voc_2012_trainval
mkdir -p output/${NET}/${TRAIN_IMDB}
cd output/${NET}/${TRAIN_IMDB}
ln -s ../../../data/voc_2007_trainval+voc_2012_trainval ./default
cd ../../..

在路径下:tf-faster-rcnn-master/output/res101/voc_2007_trainval+voc_2012_trainval/default/   我们可以看到如下模型

tf-faster-rcnn 代码详细解读_第5张图片

我们也从这里开始:

在目录tf-faster-rcnn-master下运行:

GPU_ID=0
CUDA_VISIBLE_DEVICES=${GPU_ID} ./tools/demo.py

一般来说是可以运行的但失败了呢?不要紧,接下来看代码,理解了代码就可以解决问题

(没必要的代码我并没有放进来)

demo.py

因为代码的跳转问题,图文讲述特别麻烦,我接下来按照这个main进行跳转

if __name__ == '__main__':
    cfg.TEST.HAS_RPN = True  # 将RPN用于提案
    args = parse_args()

    # model path
    demonet = args.demo_net
    dataset = args.dataset
    # 模型路径
    tfmodel = os.path.join('../output', demonet, DATASETS[dataset][0], 'default', NETS[demonet][0]) 

    if not os.path.isfile(tfmodel + '.meta'):
        raise IOError(('{:s} not found.\nDid you download the proper networks from '
                       'our server and place them properly?').format(tfmodel + '.meta'))

    # set config
    tfconfig = tf.ConfigProto(allow_soft_placement=True)
    tfconfig.gpu_options.allow_growth = True

    # init session
    sess = tf.Session(config=tfconfig)
    # load network
    if demonet == 'vgg16':
        net = vgg16()
    elif demonet == 'res101':
        net = resnetv1(num_layers=101)
    else:
        raise NotImplementedError
    net.create_architecture("TEST", 21,
                            tag='default', anchor_scales=[8, 16, 32])
    saver = tf.train.Saver()
    # 加载模型
    saver.restore(sess, tfmodel)

    print('Loaded network {:s}'.format(tfmodel))
    # 验证图片集
    im_names = ['000456.jpg', '000542.jpg', '001150.jpg', '001763.jpg', '004545.jpg']

    for im_name in im_names:
        print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
        print('Demo for data/demo/{}'.format(im_name))
        # 验证函数进入
        demo(sess, net, im_name)
        # 保存图片
        plt.savefig(im_name)
plt.show()
parse_args()跳转
def parse_args():
    """Parse input arguments."""
    parser = argparse.ArgumentParser(description='Tensorflow Faster R-CNN demo')
    parser.add_argument('--net', dest='demo_net', help='Network to use [vgg16 res101]',
                        choices=NETS.keys(), default='res101')
    parser.add_argument('--dataset', dest='dataset', help='Trained dataset [pascal_voc pascal_voc_0712]',
                        choices=DATASETS.keys(), default='pascal_voc_0712')
    args = parser.parse_args()

    return args

可以看到网络(net)用的是res101,数据集(dataset)用的是pascal_voc_0712.

(注意,没用到数据集,只是拼接路径用到pascal_voc_0712)

 

CLASSES = ('__background__',
           'aeroplane', 'bicycle', 'bird', 'boat',
           'bottle', 'bus', 'car', 'cat', 'chair',
           'cow', 'diningtable', 'dog', 'horse',
           'motorbike', 'person', 'pottedplant',
           'sheep', 'sofa', 'train', 'tvmonitor')

NETS = {'vgg16': ('vgg16_faster_rcnn_iter_70000.ckpt',), 'res101': ('res101_faster_rcnn_iter_110000.ckpt',)}
DATASETS = {'pascal_voc': ('voc_2007_trainval',), 'pascal_voc_0712': ('voc_2007_trainval+voc_2012_trainval',)}

CLASSES : 是数据的类别名称

NETS : 是使用的网络,这里有vgg16 、res101 代码中还提供了mobile、res50、res152,也可以使用,也提供了相应的模型下载链接https://drive.google.com/drive/folders/0B1_fAEgxdnvJSmF3YUlZcHFqWTQ下载后解压放到tf-faster-rcnn-master/output/res101/voc_2007_trainval+voc_2012_trainval/default/ 路径,根据相应的进行修改代码,即可使用不同的模型

DATASETS :这里是使用相应的数据,提供我们下载的模型中有使用, voc_2007_trainval+voc_2012_trainval和coco_2014_train+coco_2014_valminusminival数据的,没有voc_2007_trainval的模型,毕竟数据越大对验证越好,这里有两种格式voc和coco的数据,代码中只提供了voc数据的模型,如果要用到coco的自行修改。

用pascal_voc代表使用数据voc_2007_trainval  ( 在我们训练自己的数据的时候使用这个比较方便)

这里使用pascal_voc_0712代表使用数据voc_2007_trainval+voc_2012_trainval (当然这里只代表模型是用这个数据集进行训练的,现在还不用提供数据集)

接下来我们看到模型路径tfmodel,根据上面是数据这里拼接出模型的路径:
output/res101/voc_2007_trainval/default/res101_faster_rcnn_iter_110000.ckpt.meta

后面这个模型文件是训练完成后才会保存的(也可以直接去下载模型https://drive.google.com/drive/folders/0B1_fAEgxdnvJSmF3YUlZcHFqWTQ 这个网站提供了各种模型google drive外网的)。

 tfmodel = os.path.join('../output', demonet, DATASETS[dataset][0], 'default', NETS[demonet][0])

选择对应的网络结构

    if demonet == 'vgg16':
        net = vgg16()
    elif demonet == 'res101':
        net = resnetv1(num_layers=101)
    else:
        raise NotImplementedError

进入测试

net.create_architecture("TEST", 21,
                            tag='default', anchor_scales=[8, 16, 32])

这里就不进一步了解了,不然走得太深,训练模型的时候再详细了解,跳转看一下参数就ok了。

跳转network.py 中 create_architecture函数

def create_architecture(self, mode, num_classes, tag=None,
                          anchor_scales=(8, 16, 32), anchor_ratios=(0.5, 1, 2)):

可以看到参数:

mode="TEST"\"TRAIN"(训练还是测试),

num_classes=类别数,

anchor_scales  (不懂看一下RPN之anchors生成)

anchor_ratios (不懂看一下RPN之anchors生成)

接下来是循环输入图片调用函数预测

im_names = ['000456.jpg', '000542.jpg', '001150.jpg', '001763.jpg', '004545.jpg']


    for im_name in im_names:
        print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
        print('Demo for data/demo/{}'.format(im_name))
        demo(sess, net, im_name)
        plt.savefig(im_name)

跳转demo函数

在demo函数里也就只有一个重要的函数im_detect

调用这个函数返回boxes和scores,因为一张图可能有多个物体,每个物体识别到的返回的box不止一个,之后的代码就是对结果进行分类和找出最好的box。最后通过函数 vis_detections 进行可视化。

def demo(sess, net, image_name):
    """Detect object classes in an image using pre-computed object proposals."""

    # 加载演示图像
    im_file = os.path.join(cfg.DATA_DIR, 'demo', image_name)
    im = cv2.imread(im_file)

    # 计算时间
    timer = Timer()
    timer.tic()
    # 验证返回边框和分数
    scores, boxes = im_detect(sess, net, im)
    timer.toc()
    print('Detection took {:.3f}s for {:d} object proposals'.format(timer.total_time, boxes.shape[0]))

    # 可视化检测每个类
    CONF_THRESH = 0.8
    NMS_THRESH = 0.3

    # 将vis_detections 函数中for 循环之前的3行代码移动到这里
    # 展示图片的初始设置
    im = im[:, :, (2, 1, 0)]
    fig, ax = plt.subplots(figsize=(12, 12))
    ax.imshow(im, aspect='equal')

    for cls_ind, cls in enumerate(CLASSES[1:]):
        cls_ind += 1  # because we skipped background
        cls_boxes = boxes[:, 4 * cls_ind:4 * (cls_ind + 1)]
        cls_scores = scores[:, cls_ind]
        #  np.newaxis 把每个数弄成一个维度
        # np.hstack 按列合并
        dets = np.hstack((cls_boxes, cls_scores[:, np.newaxis])).astype(np.float32)
        # 最大抑制值
        keep = nms(dets, NMS_THRESH)
        dets = dets[keep, :]
        # 将ax做为参数传入vis_detections,即增加第4项
        vis_detections(im, cls, dets, ax, thresh=CONF_THRESH)
    # 将vis_detections 函数中for 循环之后的3行代码移动到这里
    plt.axis('off')
    plt.tight_layout()
    plt.draw()

跳转函数 在test.py中 im_detect :

def im_detect(sess, net, im):
  blobs, im_scales = _get_blobs(im)
  assert len(im_scales) == 1, "Only single-image batch implemented"

  im_blob = blobs['data']
  blobs['im_info'] = np.array([im_blob.shape[1], im_blob.shape[2], im_scales[0]], dtype=np.float32)

  _, scores, bbox_pred, rois = net.test_image(sess, blobs['data'], blobs['im_info'])
  
  boxes = rois[:, 1:5] / im_scales[0]
  scores = np.reshape(scores, [scores.shape[0], -1])
  bbox_pred = np.reshape(bbox_pred, [bbox_pred.shape[0], -1])
  if cfg.TEST.BBOX_REG:
    # Apply bounding-box regression deltas
    box_deltas = bbox_pred
    pred_boxes = bbox_transform_inv(boxes, box_deltas)
    pred_boxes = _clip_boxes(pred_boxes, im.shape)
  else:
    # Simply repeat the boxes, once for each class
    pred_boxes = np.tile(boxes, (1, scores.shape[1]))

  return scores, pred_boxes

跳转函数 _get_blobs:

def _get_blobs(im):
  """Convert an image and RoIs within that image into network inputs."""
  blobs = {}
  blobs['data'], im_scale_factors = _get_image_blob(im)

  return blobs, im_scale_factors

跳转函数 _get_image_blob:

MAX_SIZE  1000
SCALES  (600,)

根据 600/最小边 = im_scale, 对边进行改变。

例如:输入(500,500), 600/500=1.2 ,改边 500*1.2=600,改边500*1.2

def _get_image_blob(im):
 
  im_orig = im.astype(np.float32, copy=True)
  im_orig -= cfg.PIXEL_MEANS

  im_shape = im_orig.shape
  im_size_min = np.min(im_shape[0:2])
  im_size_max = np.max(im_shape[0:2])

  processed_ims = []
  im_scale_factors = []

  for target_size in (600,):
    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 = cv2.resize(im_orig, None, None, fx=im_scale, fy=im_scale,
            interpolation=cv2.INTER_LINEAR)
    im_scale_factors.append(im_scale)
    processed_ims.append(im)

  # 创建一个blob来保存输入图像
  blob = im_list_to_blob(processed_ims)

  return blob, np.array(im_scale_factors)

函数 test_image

输入 图片信息和图片,输出scores=21*300分数、boxes=300*84框

最后在im_detect 函数中进行处理,来到demo.

demo函数里循环匹配,在nms里挑选出比较优良的例如:33*5个  那5个分别是box和score。

在函数 vis_detections 里挑选出最好的进行画框:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(计算机视觉)