本文共 8621 字,大约阅读时间需要 28 分钟。
接着上面,继续分析,下面接着rpn之后的内容开始分析。
前面,我们分析了RPN,得到了一些框和背景。按照下图,把RPN的输出输入给RoI pooling进行一系列操作。
#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,此函数的主要作用是:把由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 |
注意:这里重点来了,RPN网络的输入 X 是原图:xxx.jpg;而其对应的label
这一步见之前函数calc_iou的返回值。
此步将不展示背景:
关于损失函数和RoiPoolingConv等内容,这里不再细述。希望这两篇文章对大家有帮助!
[1]
[2] [3]