根据对应的 roi_data 模块可以处理 对应模型的 minibatch blobs.
fast_rcnn.pymask_rcnn.pykeypoint_rcnn.pyrpn.pyretinanet.py构建用于 Fast R-CNN 训练的 minibatches.
""" 处理 Fast R-CNN 所涉及的 minibatch blobs. """ from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals import logging import numpy as np import numpy.random as npr from core.config import cfg import modeling.FPN as fpn import roi_data.keypoint_rcnn import roi_data.mask_rcnn import utils.blob as blob_utils import utils.boxes as box_utils logger = logging.getLogger(__name__) def get_fast_rcnn_blob_names(is_training=True): """ Fast R-CNN blob names. """ """ rois blob: R 个 RoIs(regions of interest), 每个 blob 是 5-tuple:(batch_idx, x1, y1, x2, y2), - batch_idx: 图片 batch index - (x1, y1, x2, y2):矩形框 """ blob_names = ['rois'] if is_training: # labels_int32 blob: # R categorical labels in [0, ..., K] for K foreground classes plus background # K 个前景类 + 1 个背景类. blob_names += ['labels_int32'] if is_training: # bbox_targets blob: # R bounding-box regression targets with 4 targets per class blob_names += ['bbox_targets'] # bbox_inside_weights blob: # 每个 roi 最多 4 个 targets 被激活,该二值向量表示了激活 targets 的subset. blob_names += ['bbox_inside_weights'] blob_names += ['bbox_outside_weights'] if is_training and cfg.MODEL.MASK_ON: # 'mask_rois': # 训练 mask 预测分支所采样的 RoIs # Shape is (#masks, 5) in format (batch_idx, x1, y1, x2, y2). blob_names += ['mask_rois'] # 'roi_has_mask': # rois 中指定的 RoIs 的二值标签(binart labels),表示每个 RoI 是否有 mask. # 注:某些情况, *bg* RoI 会有一个值都为 -1(ignore) 的 mask,此时,没有 fg RoIs 可采样. # Shape is (batchsize). blob_names += ['roi_has_mask_int32'] # 'masks_int32': # 'mask_rois' 中指定的 RoIs的二值masks. # Shape is (#fg, M * M) where M is the ground truth mask size. blob_names += ['masks_int32'] if is_training and cfg.MODEL.KEYPOINTS_ON: # 'keypoint_rois': # 训练 keypoint 预测分支所采样的 RoIs # Shape is (#instances, 5) in format (batch_idx, x1, y1, x2, y2). blob_names += ['keypoint_rois'] # 'keypoint_locations_int32': # KRCNN.HEATMAP_SIZE**2 大小的 array 中 keypoint 的索引index. # Shape is (#instances). Used in SoftmaxWithLoss. blob_names += ['keypoint_locations_int32'] # 'keypoint_weights': # 'keypoint_locations_int32' 中每个 target 的权重weight # Shape is (#instances). Used in SoftmaxWithLoss. blob_names += ['keypoint_weights'] # 'keypoint_loss_normalizer': # 可选参数,如果 cfg.KRCNN.NORMALIZE_BY_VISIBLE_KEYPOINTS = False, # 使用归一化因子. blob_names += ['keypoint_loss_normalizer'] if cfg.FPN.FPN_ON and cfg.FPN.MULTILEVEL_ROIS: """ 支持 FPN multi-level rois without bbox reg isn't implemented (... and may never be implemented) """ k_max = cfg.FPN.ROI_MAX_LEVEL k_min = cfg.FPN.ROI_MIN_LEVEL # Same format as rois blob, but one per FPN level for lvl in range(k_min, k_max + 1): blob_names += ['rois_fpn' + str(lvl)] blob_names += ['rois_idx_restore_int32'] if is_training: if cfg.MODEL.MASK_ON: for lvl in range(k_min, k_max + 1): blob_names += ['mask_rois_fpn' + str(lvl)] blob_names += ['mask_rois_idx_restore_int32'] if cfg.MODEL.KEYPOINTS_ON: for lvl in range(k_min, k_max + 1): blob_names += ['keypoint_rois_fpn' + str(lvl)] blob_names += ['keypoint_rois_idx_restore_int32'] return blob_names def add_fast_rcnn_blobs(blobs, im_scales, roidb): """ 添加 blobs ,用于训练 Fast R-CNN style models. """ # 从每张图片采样训练 RoIs,并添加到 blob 列表lists for im_i, entry in enumerate(roidb): frcn_blobs = _sample_rois(entry, im_scales[im_i], im_i) for k, v in frcn_blobs.items(): blobs[k].append(v) # 将 blob lists 连接为 tensors for k, v in blobs.items(): if isinstance(v, list) and len(v) > 0: blobs[k] = np.concatenate(v) # 添加 FPN multilevel training RoIs, if configured if cfg.FPN.FPN_ON and cfg.FPN.MULTILEVEL_ROIS: _add_multilevel_rois(blobs) # 在处理完所有的 minibatch 图片后,进行安全性检查. valid = True if cfg.MODEL.KEYPOINTS_ON: valid = roi_data.keypoint_rcnn.finalize_keypoint_minibatch(blobs, valid) return valid def _sample_rois(roidb, im_scale, batch_idx): """ 生成由 foreground 和 background 样本组成的 RoIs 的随机采样. """ rois_per_image = int(cfg.TRAIN.BATCH_SIZE_PER_IM) fg_rois_per_image = int(np.round(cfg.TRAIN.FG_FRACTION * rois_per_image)) max_overlaps = roidb['max_overlaps'] # 选择 foreground RoIs,overlap >= FG_THRESH 的 fg_inds = np.where(max_overlaps >= cfg.TRAIN.FG_THRESH)[0] # 避免出现的情况: # 图片中的 foreground RoIs 的数量小于 fg_rois_per_image fg_rois_per_this_image = np.minimum(fg_rois_per_image, fg_inds.size) # 无替换地(without replacement)采样 foreground 区域 if fg_inds.size > 0: fg_inds = npr.choice(fg_inds, size=fg_rois_per_this_image, replace=False) # 选择 background RoIs, overlap 在 [BG_THRESH_LO, BG_THRESH_HI) 之间的 bg_inds = np.where((max_overlaps < cfg.TRAIN.BG_THRESH_HI) & (max_overlaps >= cfg.TRAIN.BG_THRESH_LO) )[0] # 计算从图片中选择的 background RoIs 数量 # (避免数量太少) bg_rois_per_this_image = rois_per_image - fg_rois_per_this_image bg_rois_per_this_image = np.minimum(bg_rois_per_this_image, bg_inds.size) # 无替换地(without replacement)采样 background 区域 if bg_inds.size > 0: bg_inds = npr.choice(bg_inds, size=bg_rois_per_this_image, replace=False) # 所选择的 indices (both fg and bg) keep_inds = np.append(fg_inds, bg_inds) # Label 是与每个 RoI 具有最大 overlap 的类别class sampled_labels = roidb['max_classes'][keep_inds] sampled_labels[fg_rois_per_this_image:] = 0 # Label bg RoIs with class 0 sampled_boxes = roidb['boxes'][keep_inds] if 'bbox_targets' not in roidb: gt_inds = np.where(roidb['gt_classes'] > 0)[0] gt_boxes = roidb['boxes'][gt_inds, :] gt_assignments = gt_inds[roidb['box_to_gt_ind_map'][keep_inds]] bbox_targets = _compute_targets(sampled_boxes, gt_boxes[gt_assignments, :], sampled_labels) bbox_targets, bbox_inside_weights = _expand_bbox_targets(bbox_targets) else: bbox_targets, bbox_inside_weights = _expand_bbox_targets(roidb['bbox_targets'][keep_inds, :]) bbox_outside_weights = np.array(bbox_inside_weights > 0, dtype=bbox_inside_weights.dtype) # 缩放Scale rois,并格式化为: (batch_idx, x1, y1, x2, y2) sampled_rois = sampled_boxes * im_scale repeated_batch_idx = batch_idx * blob_utils.ones((sampled_rois.shape[0], 1)) sampled_rois = np.hstack((repeated_batch_idx, sampled_rois)) # Base Fast R-CNN blobs blob_dict = dict(labels_int32=sampled_labels.astype(np.int32, copy=False), rois=sampled_rois, bbox_targets=bbox_targets, bbox_inside_weights=bbox_inside_weights, bbox_outside_weights=bbox_outside_weights ) # Optionally add Mask R-CNN blobs if cfg.MODEL.MASK_ON: roi_data.mask_rcnn.add_mask_rcnn_blobs( blob_dict, sampled_boxes, roidb, im_scale, batch_idx ) # Optionally add Keypoint R-CNN blobs if cfg.MODEL.KEYPOINTS_ON: roi_data.keypoint_rcnn.add_keypoint_rcnn_blobs( blob_dict, roidb, fg_rois_per_image, fg_inds, im_scale, batch_idx) return blob_dict def _compute_targets(ex_rois, gt_rois, labels): """ 计算图片的边界框回归目标值bounding-box regression targets. """ assert ex_rois.shape[0] == gt_rois.shape[0] assert ex_rois.shape[1] == 4 assert gt_rois.shape[1] == 4 targets = box_utils.bbox_transform_inv(ex_rois, gt_rois, cfg.MODEL.BBOX_REG_WEIGHTS) return np.hstack((labels[:, np.newaxis], targets)).astype(np.float32, copy=False ) def _expand_bbox_targets(bbox_target_data): """ 边界框回归目标值以紧凑形式存储在 roidb 中. 该函数将 targets 展开为网所使用的 4-of-4*K 表示. (i.e. 只有一个类别class 具有 non-zero targets). 类似地,loss weights 也进行展开. 返回值: bbox_target_data (ndarray): N x 4K blob of regression targets bbox_inside_weights (ndarray): N x 4K blob of loss weights """ num_bbox_reg_classes = cfg.MODEL.NUM_CLASSES if cfg.MODEL.CLS_AGNOSTIC_BBOX_REG: num_bbox_reg_classes = 2 # bg and fg clss = bbox_target_data[:, 0] bbox_targets = blob_utils.zeros((clss.size, 4 * num_bbox_reg_classes)) bbox_inside_weights = blob_utils.zeros(bbox_targets.shape) inds = np.where(clss > 0)[0] for ind in inds: cls = int(clss[ind]) start = 4 * cls end = start + 4 bbox_targets[ind, start:end] = bbox_target_data[ind, 1:] bbox_inside_weights[ind, start:end] = (1.0, 1.0, 1.0, 1.0) return bbox_targets, bbox_inside_weights def _add_multilevel_rois(blobs): """ 默认情况,只对单 feature map level 添加训练 RoIs. 当使用 FPN时,RoIs 必须根据 level 设置启发式来分配到不同的 FPN levels. (参见: modeling.FPN.map_rois_to_fpn_levels). """ lvl_min = cfg.FPN.ROI_MIN_LEVEL lvl_max = cfg.FPN.ROI_MAX_LEVEL def _distribute_rois_over_fpn_levels(rois_blob_name): """ 分配 rois 到不同的 FPN levels. """ # 获取每个 roi 的 target level # blob rois 格式为:(batch_idx, x1, y1, x2, y2), 因此,取1:5 列的 box 坐标 target_lvls = fpn.map_rois_to_fpn_levels(blobs[rois_blob_name][:, 1:5], lvl_min, lvl_max ) # Add per FPN level roi blobs named like: <rois_blob_name>_fpn<lvl> fpn.add_multilevel_roi_blobs(blobs, rois_blob_name, blobs[rois_blob_name], target_lvls, lvl_min, lvl_max) _distribute_rois_over_fpn_levels('rois') if cfg.MODEL.MASK_ON: _distribute_rois_over_fpn_levels('mask_rois') if cfg.MODEL.KEYPOINTS_ON: _distribute_rois_over_fpn_levels('keypoint_rois')构建 Mask R-CNN 训练的 minibatches.
""" 处理 Mask R-CNN 的 minibatch blobs. """ from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals import logging import numpy as np from core.config import cfg import utils.blob as blob_utils import utils.boxes as box_utils import utils.segms as segm_utils logger = logging.getLogger(__name__) def add_mask_rcnn_blobs(blobs, sampled_boxes, roidb, im_scale, batch_idx): """ 添加 Mask R-CNN 特有的 blobs 到 input blob dictionary. """ """ 准备 mask targets: 将一个 gt mask 关联到每个具有 fg 类别标签(non-bg class label)的训练 roi, """ M = cfg.MRCNN.RESOLUTION polys_gt_inds = np.where((roidb['gt_classes'] > 0) & (roidb['is_crowd'] == 0))[0] polys_gt = [roidb['segms'][i] for i in polys_gt_inds] boxes_from_polys = segm_utils.polys_to_boxes(polys_gt) fg_inds = np.where(blobs['labels_int32'] > 0)[0] roi_has_mask = blobs['labels_int32'].copy() roi_has_mask[roi_has_mask > 0] = 1 if fg_inds.shape[0] > 0: # foreground rois 的类别标签 mask_class_labels = blobs['labels_int32'][fg_inds] masks = blob_utils.zeros((fg_inds.shape[0], M**2), int32=True) # 寻找所有的 foreground rois 与边界框之间的重叠区域,封闭区域. rois_fg = sampled_boxes[fg_inds] overlaps_bbfg_bbpolys = box_utils.bbox_overlaps( rois_fg.astype(np.float32, copy=False), boxes_from_polys.astype(np.float32, copy=False) ) # 将每个 fg rois 映射到 highest overlap 的mask. # (衡量标准: bbox overlap) fg_polys_inds = np.argmax(overlaps_bbfg_bbpolys, axis=1) # 添加 fg targets for i in range(rois_fg.shape[0]): fg_polys_ind = fg_polys_inds[i] poly_gt = polys_gt[fg_polys_ind] roi_fg = rois_fg[i] # 将给定 fg roi 中的多边形 mask 转换为 MxM 的二值图像. mask = segm_utils.polys_to_mask_wrt_box(poly_gt, roi_fg, M) # 确保 mask 是二值的binary mask = np.array(mask > 0, dtype=np.int32) masks[i, :] = np.reshape(mask, M**2) else: # 如果没有 fg masks # 网络不能处理空 blobs,因此,需要提供一个 mask. # 简单采用第一个 bg roi,并给定其一个都是 -1(ignore label) 值的 mask, # 且其类别标签为 0 (bg). bg_inds = np.where(blobs['labels_int32'] == 0)[0] # rois_fg 实际上是一个 background roi, but that's ok because ... rois_fg = sampled_boxes[bg_inds[0]].reshape((1, -1)) # 设定一个 -1's blob (ignore label) masks = -blob_utils.ones((1, M**2), int32=True) # 设定其类别标签 class = 0 (background) mask_class_labels = blob_utils.zeros((1, )) # 确保第一个 roi 有一个 mask roi_has_mask[0] = 1 if cfg.MRCNN.CLS_SPECIFIC_MASK: masks = _expand_to_class_specific_mask_targets(masks, mask_class_labels) # 缩放Scale rois_fg,并格式化为: (batch_idx, x1, y1, x2, y2) rois_fg *= im_scale repeated_batch_idx = batch_idx * blob_utils.ones((rois_fg.shape[0], 1)) rois_fg = np.hstack((repeated_batch_idx, rois_fg)) # Update blobs dict with Mask R-CNN blobs blobs['mask_rois'] = rois_fg blobs['roi_has_mask_int32'] = roi_has_mask blobs['masks_int32'] = masks def _expand_to_class_specific_mask_targets(masks, mask_class_labels): """ 将 masks 由 shape (#masks, M ** 2) 展开到 (#masks, #classes * M ** 2), 以表示类别已知的 mask targets. """ assert masks.shape[0] == mask_class_labels.shape[0] M = cfg.MRCNN.RESOLUTION # Target values of -1 are "don't care" / ignore labels mask_targets = -blob_utils.ones((masks.shape[0], cfg.MODEL.NUM_CLASSES * M**2), int32=True ) for i in range(masks.shape[0]): cls = int(mask_class_labels[i]) start = M**2 * cls end = start + M**2 # 忽略 background 实例instance # (只有图片中没有 fg 样本是才会发生) if cls > 0: mask_targets[i, start:end] = masks[i, :] return mask_targets构建 Mask R-CNN 关于 keypoints 训练的 minibatches.
""" 处理 Mask R-CNN 中关于 keypoint 检测分支训练的 minibatch blobs. """ from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals import logging import numpy as np from core.config import cfg import utils.blob as blob_utils import utils.keypoints as keypoint_utils logger = logging.getLogger(__name__) def add_keypoint_rcnn_blobs(blobs, roidb, fg_rois_per_image, fg_inds, im_scale, batch_idx): """ 添加 Mask R-CNN keypoint 相关的 blobs 到给定的 blobs dictionary. """ """ 注: gt_inds 必须与 datasets.json_dataset._merge_proposal_boxes_into_roidb 中的计算一致. """ gt_inds = np.where(roidb['gt_classes'] > 0)[0] max_overlaps = roidb['max_overlaps'] gt_keypoints = roidb['gt_keypoints'] ind_kp = gt_inds[roidb['box_to_gt_ind_map']] within_box = _within_box(gt_keypoints[ind_kp, :, :], roidb['boxes']) vis_kp = gt_keypoints[ind_kp, 2, :] > 0 is_visible = np.sum(np.logical_and(vis_kp, within_box), axis=1) > 0 kp_fg_inds = np.where(np.logical_and(max_overlaps >= cfg.TRAIN.FG_THRESH, is_visible) )[0] kp_fg_rois_per_this_image = np.minimum(fg_rois_per_image, kp_fg_inds.size) if kp_fg_inds.size > kp_fg_rois_per_this_image: kp_fg_inds = np.random.choice(kp_fg_inds, size=kp_fg_rois_per_this_image, replace=False ) sampled_fg_rois = roidb['boxes'][kp_fg_inds] box_to_gt_ind_map = roidb['box_to_gt_ind_map'][kp_fg_inds] num_keypoints = gt_keypoints.shape[2] sampled_keypoints = -np.ones((len(sampled_fg_rois), gt_keypoints.shape[1], num_keypoints), dtype=gt_keypoints.dtype ) for ii in range(len(sampled_fg_rois)): ind = box_to_gt_ind_map[ii] if ind >= 0: sampled_keypoints[ii, :, :] = gt_keypoints[gt_inds[ind], :, :] assert np.sum(sampled_keypoints[ii, 2, :]) > 0 heats, weights = keypoint_utils.keypoints_to_heatmap_labels( sampled_keypoints, sampled_fg_rois ) shape = (sampled_fg_rois.shape[0] * cfg.KRCNN.NUM_KEYPOINTS, 1) heats = heats.reshape(shape) weights = weights.reshape(shape) sampled_fg_rois *= im_scale repeated_batch_idx = batch_idx * blob_utils.ones( (sampled_fg_rois.shape[0], 1) ) sampled_fg_rois = np.hstack((repeated_batch_idx, sampled_fg_rois)) blobs['keypoint_rois'] = sampled_fg_rois blobs['keypoint_locations_int32'] = heats.astype(np.int32, copy=False) blobs['keypoint_weights'] = weights def finalize_keypoint_minibatch(blobs, valid): """ 当所有的 minibatch 图片 blobs 处理完以后,定型 minibatch. """ min_count = cfg.KRCNN.MIN_KEYPOINT_COUNT_FOR_VALID_MINIBATCH num_visible_keypoints = np.sum(blobs['keypoint_weights']) valid = (valid and len(blobs['keypoint_weights']) > 0 and num_visible_keypoints > min_count ) # Normalizer to use if cfg.KRCNN.NORMALIZE_BY_VISIBLE_KEYPOINTS is False. # See modeling.model_builder.add_keypoint_losses norm = num_visible_keypoints / ( cfg.TRAIN.IMS_PER_BATCH * cfg.TRAIN.BATCH_SIZE_PER_IM * cfg.TRAIN.FG_FRACTION * cfg.KRCNN.NUM_KEYPOINTS ) blobs['keypoint_loss_normalizer'] = np.array(norm, dtype=np.float32) return valid def _within_box(points, boxes): """ 确认在给定 box 中的 keypoints. points: Nx2xK boxes: Nx4 output: NxK """ x_within = np.logical_and( points[:, 0, :] >= np.expand_dims(boxes[:, 0], axis=1), points[:, 0, :] <= np.expand_dims(boxes[:, 2], axis=1) ) y_within = np.logical_and( points[:, 1, :] >= np.expand_dims(boxes[:, 1], axis=1), points[:, 1, :] <= np.expand_dims(boxes[:, 3], axis=1) ) return np.logical_and(x_within, y_within)