博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Faster-RCNN代码+理论——2
阅读量:2032 次
发布时间:2019-04-28

本文共 8621 字,大约阅读时间需要 28 分钟。

接着上面,继续分析,下面接着rpn之后的内容开始分析。

前面,我们分析了RPN,得到了一些框和背景。按照下图,把RPN的输出输入给RoI pooling进行一系列操作。

这里写图片描述

① 定义输入数据RPN,将RPN的输出输入到RoI

#coding:UTF-8from __future__ import divisionimport randomimport pprintimport sysimport timeimport numpy as npfrom optparse import OptionParserimport picklefrom keras import backend as Kfrom keras.optimizers import Adam, SGD, RMSpropfrom keras.layers import Inputfrom keras.models import Modelfrom keras_frcnn import config, data_generatorsfrom keras_frcnn import losses as lossesimport keras_frcnn.roi_helpers as roi_helpersfrom keras.utils import generic_utils# 输入尺度(以backend为Tensorflow为例)input_shape_img = (None, None, 3)img_input = Input(shape=input_shape_img)# 关于rpn函数的内容,请查看Faster-RCNN代码+理论——1rpn = nn.rpn(shared_layers, num_anchors)# 定义model_rpnmodel_rpn = Model(img_input, rpn[:2])...# 简化的训练过程(这里相比keras代码的内容进行了简化)num_epochs = 2000for epoch_num in range(num_epochs):    # Progbar是生成进度条(这是一个武大的兄弟告诉我的,表示感谢)    progbar = generic_utils.Progbar(epoch_length)    print('Epoch {}/{}'.format(epoch_num + 1, num_epochs))    while True:        # data_gen_train是一个迭代器。返回的是 np.copy(x_img), [np.copy(y_rpn_cls), np.copy(y_rpn_regr)], img_data_aug(我们这里假设数据没有进行水平翻转等操作。那么,x_img = img_data_aug),y_rpn_cls和y_rpn_regr是RPN的两个损失函数。        X, Y, img_data = next(data_gen_train)        loss_rpn = model_rpn.train_on_batch(X, Y)        P_rpn = model_rpn.predict_on_batch(X)        # 得到了region proposals,接下来另一个重要的思想就是ROI,        # 可将不同shape的特征图转化为固定shape,送到全连接层进行最终的预测。        # rpn_to_roi接收的是每张图片的预测输出,返回的R = [boxes, probs]        R = roi_helpers.rpn_to_roi(P_rpn[0], P_rpn[1], C, K.image_dim_ordering(), use_regr=True, overlap_thresh=0.7, max_boxes=300)        # note: calc_iou converts from (x1,y1,x2,y2) to (x,y,w,h) format        # 通过calc_iou()找出剩下的不多的region对应ground truth里重合度最高的bbox,从而获得model_classifier的数据和标签。        # X2保留所有的背景和match bbox的框; Y1 是类别one-hot转码; Y2是对应类别的标签及回归要学习的坐标位置; IouS是debug用的。        X2, Y1, Y2, IouS = roi_helpers.calc_iou(R, img_data, C, class_mapping)

下面简单叙述一下rpn_to_roi和calc_iou的作用。

② 函数rpn_to_roi & calc_iou分析

从上面的代码可以看出,rpn_to_roi输出作为calc_iou的输入。那么按照顺序先来分析一下rpn_to_roi,此函数的主要作用是:把由RPN输出的所有可能的框过滤掉重合度高的框,降低计算复杂度。

其中,涉及到一个算法:non_max_suppression(非极大值抑制)

下面关于非极大值抑制这个算法的介绍来自

因为经过RPN之后,可能会从一张图片中找出很多个可能是物体的矩形框,然后为每个矩形框为做类别分类概率:

这里写图片描述

以上面的图片为例,目标是要定位一个车辆,最后算法就找出了一堆的方框,我们需要判别哪些矩形框是没用的。非极大值抑制的意思就是:先假设有6个矩形框,根据分类器类别分类概率做排序,从小到大分别属于车辆的概率分别为A、B、C、D、E、F。

(1)从最大概率矩形框F开始,分别判断A~E与F的重叠度IOU是否大于某个设定的阈值;

(2)假设B、D与F的重叠度超过阈值,那么就扔掉B、D;并标记第一个矩形框F,是我们保留下来的。

(3)从剩下的矩形框A、C、E中,选择概率最大的E,然后判断E与A、C的重叠度,重叠度大于一定的阈值,那么就扔掉;并标记E是我们保留下来的第二个矩形框。

就这样一直重复,找到所有被保留下来的矩形框。

而calc_iou的作用是,通过calc_iou()找出剩下的不多的region对应ground truth里重合度最高的bbox,从而获得model_classifier的数据和标签。

X2保留所有的背景和match bbox的框; Y1 是类别one-hot转码; Y2是对应类别的标签及回归要学习的坐标位置; IouS是debug用的。
X2, Y1, Y2, IouS = roi_helpers.calc_iou(R, img_data, C, class_mapping)

# 通过calc_iou()找出剩下的不多的region对应ground truth里重合度最高的bbox,# 从而获得model_classifier的目标和标签。def calc_iou(R, img_data, C, class_mapping):    bboxes = img_data['bboxes']    (width, height) = (img_data['width'], img_data['height'])    # get image dimensions for resizing    (resized_width, resized_height) = data_generators.get_new_img_size(width, height, C.im_size)    # 这里跟calc_rpn基本一致    gta = np.zeros((len(bboxes), 4))    for bbox_num, bbox in enumerate(bboxes):        # get the GT box coordinates, and resize to account for image resizing        gta[bbox_num, 0] = int(round(bbox['x1'] * (resized_width / float(width))/C.rpn_stride))        gta[bbox_num, 1] = int(round(bbox['x2'] * (resized_width / float(width))/C.rpn_stride))        gta[bbox_num, 2] = int(round(bbox['y1'] * (resized_height / float(height))/C.rpn_stride))        gta[bbox_num, 3] = int(round(bbox['y2'] * (resized_height / float(height))/C.rpn_stride))    x_roi = []    y_class_num = []    y_class_regr_coords = []    y_class_regr_label = []    IoUs = [] # for debugging only    # R = [boxes, probs]    for ix in range(R.shape[0]):        (x1, y1, x2, y2) = R[ix, :]        x1 = int(round(x1))        y1 = int(round(y1))        x2 = int(round(x2))        y2 = int(round(y2))        best_iou = 0.0        best_bbox = -1        for bbox_num in range(len(bboxes)):            # x1 x2 y1 y2是生成的框,gta是相对于原图缩小比例的bbox            curr_iou = data_generators.iou([gta[bbox_num, 0], gta[bbox_num, 2], gta[bbox_num, 1], gta[bbox_num, 3]], [x1, y1, x2, y2])            if curr_iou > best_iou:                best_iou = curr_iou                best_bbox = bbox_num        # 如果对于某个框,其匹配现有的bbox重叠率小于0.3,那么这个框就扔掉        if best_iou < C.classifier_min_overlap:                continue        else:            w = x2 - x1            h = y2 - y1            x_roi.append([x1, y1, w, h])            IoUs.append(best_iou)            if C.classifier_min_overlap <= best_iou < C.classifier_max_overlap:                # hard negative example                cls_name = 'bg'            elif C.classifier_max_overlap <= best_iou:                cls_name = bboxes[best_bbox]['class']                cxg = (gta[best_bbox, 0] + gta[best_bbox, 1]) / 2.0                cyg = (gta[best_bbox, 2] + gta[best_bbox, 3]) / 2.0                cx = x1 + w / 2.0                cy = y1 + h / 2.0                tx = (cxg - cx) / float(w)                ty = (cyg - cy) / float(h)                tw = np.log((gta[best_bbox, 1] - gta[best_bbox, 0]) / float(w))                th = np.log((gta[best_bbox, 3] - gta[best_bbox, 2]) / float(h))            else:                print('roi = {}'.format(best_iou))                raise RuntimeError        # 找到class对应的类别的数字标签:0,1,2...        class_num = class_mapping[cls_name]        # One-Hot        class_label = len(class_mapping) * [0]        class_label[class_num] = 1        y_class_num.append(copy.deepcopy(class_label))        coords = [0] * 4 * (len(class_mapping) - 1)        labels = [0] * 4 * (len(class_mapping) - 1)        if cls_name != 'bg':            label_pos = 4 * class_num            sx, sy, sw, sh = C.classifier_regr_std            # coords: 坐标调整:相当于coords是回归要学习的内容            coords[label_pos:4+label_pos] = [sx*tx, sy*ty, sw*tw, sh*th]            labels[label_pos:4+label_pos] = [1, 1, 1, 1]            y_class_regr_coords.append(copy.deepcopy(coords))            y_class_regr_label.append(copy.deepcopy(labels))        else:            y_class_regr_coords.append(copy.deepcopy(coords))            y_class_regr_label.append(copy.deepcopy(labels))    if len(x_roi) == 0:        return None, None, None, None    # X保留所有的背景和match bbox的框; Y1 是类别one-hot转码; Y2是对应类别的标签及回归要学习的坐标位置    X = np.array(x_roi)    Y1 = np.array(y_class_num)    Y2 = np.concatenate([np.array(y_class_regr_label),np.array(y_class_regr_coords)],axis=1)    # expand_dims:增加一个通道    return np.expand_dims(X, axis=0), np.expand_dims(Y1, axis=0), np.expand_dims(Y2, axis=0), IoUs

③ 总训练(结合四个损失函数)

这里写图片描述

如图,因为Faster-RCNN有四个损失函数:
  • RPN calssification(anchor good.bad)
  • RPN regression(anchor->propoasal)
  • Fast R-CNN classification(over classes)
  • Fast R-CNN regression(proposal ->box)
现在,我们结合第②步的输出和原始输入,来训练总的网络。

# sel_samples表示所有匹配Bbox的框(pos)及背景(neg)sel_samples = selected_pos_samples + selected_neg_samplesloss_class = model_classifier.train_on_batch([X, X2[:, sel_samples, :]], [Y1[:, sel_samples, :], Y2[:, sel_samples, :]])

这里,

# 输入roi_input = Input(shape=(None, 4)) # roi框的位置,故为4input_shape_img = (None, None, 3)img_input = Input(shape=input_shape_img)# classifier是什么?# classes_count {} 每一个类的数量:{'cow': 4, 'dog': 10, ...}# C.num_rois每次取的感兴趣区域,默认为32# roi_input = Input(shape=(None, 4)) 框框# classifier是faster rcnn的两个损失函数[out_class, out_reg]# shared_layers是Faster-RCNN代码+理论——1里面vgg的输出feature mapclassifier = nn.classifier(shared_layers, roi_input, C.num_rois, nb_classes=len(classes_count), trainable=True)model_classifier = Model([img_input, roi_input], classifier)

那么,这个nn.classifier()是什么呢?请看下图:

这里写图片描述

这里,RoiPoolingConv一个自定义的keras layer,下面大家可能会问,为什么用TimeDistributed这个DD呢?这个不是用在RNN里面的吗?

答:
在最后Faster RCNN的结构中进行类别判断和bbox框的回归时,需要对设置的num_rois个感兴趣区域进行回归处理,由于每一个区域的处理是相对独立的,便等价于此时的时间步为num_rois,因此用TimeDistributed来wrap。

最后,产生num_rois个out_class和out_reg。也就是上面的四个损失函数中的下面两个:Fast R-CNN classification和Fast R-CNN regression(proposal ->box)。

总结

这里,我将结合图片来解释一下流程:

① 输入数据:

图片地址 左上角横坐标 左上角纵坐标 右下角横坐标 右下角纵坐标 Label
xxx.jpg x11 y11 x21 y21 dog
xxx.jpg x12 y12 x22 y22 cat

这里写图片描述

② 经过VGG/Resnet等分类模型产生特征图后,进行RPN网络的训练:

注意:这里重点来了,RPN网络的输入 X 是原图:xxx.jpg;而其对应的label

Y
则是由 keras代码
data_generators里面对应的get_anchor_gt生产的新的label,而不仅仅是①中的两个Bbox。
这步产生的输出可能是:(其中:绿色代表狗,红色代表猫,紫色代表背景。
这里写图片描述
注意:RPN的回归是回归这些乱78糟的由锚点生产的框,而不是回归原始label对应的框!

③ 经过一系列处理(包括非极大值抑制),得到合适的框和标签:

这一步见之前函数calc_iou的返回值。

这里写图片描述

④ 最后经过把rpn和roiPoolingConv合并起来的Faster-RCNN来进行判别和修正:

此步将不展示背景:

这里写图片描述

关于损失函数和RoiPoolingConv等内容,这里不再细述。希望这两篇文章对大家有帮助!

参考资料

[1]

[2]
[3]

你可能感兴趣的文章
使用2个MR计算
查看>>
Linux,du、df统计磁盘情况不一致
查看>>
springmvc事务回滚失效
查看>>
Java 8的五大开发技巧
查看>>
多线程中的注意点
查看>>
netty4 Handler的执行顺序
查看>>
ZooKeeper原理及使用
查看>>
guava学习--事件驱动模型
查看>>
guava学习--hashing
查看>>
guava学习--AsyncFunction
查看>>
guava学习--monitor
查看>>
guava学习--FutureCallback
查看>>
golang的数据类型之布尔类型
查看>>
golang的数据类型之字符类型
查看>>
安装MySQL
查看>>
golang简介
查看>>
golang的数据类型之整型类型
查看>>
安装go版本
查看>>
golang的数据类型之基本数据类型的默认值和转换
查看>>
golang的数据类型之浮点类型
查看>>