保姆级 Keras 实现 Faster R-CNN 四

保姆级 Keras 实现 Faster R-CNN 四

  • 一. 数据增强
  • 二. 读入训练数据 Generator

上一篇文章 中讲了如何为每一个 anchor box 打标签, 那打完了标签是不是就能马上训练了呢, 别急, 还有一些准备工作还没有完成

一. 数据增强

VOC 数据集 数据集虽然图像感觉有那么多, 但是如果不做增强处理的话, 训练的效果还是没有那么好. 先做简单点的, 包括左右翻转, 上下翻转, 左右上下一起翻转, 至于旋转变形什么的, 你就自己处理了, 只是要注意数据增强的时候, 标签也是要一起做相应处理的

# 数据增强函数, 包括左右, 上下, 左右上翻转
# data_pair: data_set_path 返回的数据元素
# train_num: 一次参数训练的 anchor 的数量
def data_augment(data_pair, train_num):
    augmented = [] # 返回增强后的数据
    
    img_src = cv.imread(data_pair[0])
    img_new, scale = new_size_image(img_src, SHORT_SIZE)
    feature_size = (img_new.shape[0] // FEATURE_STRIDE, img_new.shape[1] // FEATURE_STRIDE)
    anchors = create_train_anchors(feature_size, base_anchors, FEATURE_STRIDE)
    
    # 原始图像与标签------------------------------------------------------
    ground_truth = get_ground_truth(data_pair[1], data_pair[2], CATEGORIES)    
    # ground_truth 要做相应的缩放
    for gt in ground_truth:
        gt[0][0] = round(gt[0][0] * scale)
        gt[0][1] = round(gt[0][1] * scale)
        gt[0][2] = round(gt[0][2] * scale)
        gt[0][3] = round(gt[0][3] * scale)
    
    rpn_cls_label, gt_boxes = get_rpn_cls_label(img_new.shape, anchors, ground_truth, train_num = train_num)
    augmented.append([img_new, rpn_cls_label, gt_boxes])
    # 原始图像与标签------------------------------------------------------
    
    # 左右翻转与标签------------------------------------------------------
    # 复制一份,后面的操作在备份上操作
    gt_copy = copy.deepcopy(ground_truth)
    x_flip = cv.flip(img_new, 1) # 左右翻转图像
    for gt in gt_copy: # 左右翻转标签
        gt[0][0] = x_flip.shape[1] - 1 - gt[0][0]
        gt[0][2] = x_flip.shape[1] - 1 - gt[0][2]
        gt[0][0], gt[0][2] = gt[0][2], gt[0][0]
        
    rpn_cls_label, gt_boxes = get_rpn_cls_label(x_flip.shape, anchors, gt_copy, train_num = train_num)
    augmented.append([x_flip, rpn_cls_label, gt_boxes])
    # 左右翻转与标签------------------------------------------------------
    
    # 上下翻转与标签------------------------------------------------------
    # 复制一份,后面的操作在备份上操作
    gt_copy = copy.deepcopy(ground_truth)
    y_flip = cv.flip(img_new, 0) # 左右翻转图像
    for gt in gt_copy: # 上下翻转标签
        gt[0][1] = y_flip.shape[0] - 1 - gt[0][1]
        gt[0][3] = y_flip.shape[0] - 1 - gt[0][3]
        gt[0][1], gt[0][3] = gt[0][3], gt[0][1]
        
    rpn_cls_label, gt_boxes = get_rpn_cls_label(y_flip.shape, anchors, gt_copy, train_num = train_num)
    augmented.append([y_flip, rpn_cls_label, gt_boxes])
    # 上下翻转与标签------------------------------------------------------
    
    # 左右上下翻转与标签--------------------------------------------------
    # 复制一份,后面的操作在备份上操作
    gt_copy = copy.deepcopy(ground_truth)
    xy_flip = cv.flip(img_new, -1) # 左右翻转图像
    for gt in gt_copy: # 左右上下翻转标签
        gt[0][0] = xy_flip.shape[1] - 1 - gt[0][0]
        gt[0][1] = xy_flip.shape[0] - 1 - gt[0][1]
        gt[0][2] = xy_flip.shape[1] - 1 - gt[0][2]
        gt[0][3] = xy_flip.shape[0] - 1 - gt[0][3]
        
        gt[0][0], gt[0][2] = gt[0][2], gt[0][0]
        gt[0][1], gt[0][3] = gt[0][3], gt[0][1]
        
    rpn_cls_label, gt_boxes = get_rpn_cls_label(xy_flip.shape, anchors, gt_copy, train_num = train_num)
    augmented.append([xy_flip, rpn_cls_label, gt_boxes])
    # 左右上下翻转与标签--------------------------------------------------
    
    return augmented

现在增强一张图像来看效果

# 测试 data_augment
titles = ["original", "x_filip", "y_flip", "xy_flip"]
plt.figure("augmented", figsize = (12, 8))

print(train_set[idx]) # idx 是 保姆级 Keras 实现 Faster R-CNN 二 中生成的随机数
augmented = data_augment(train_set[idx], train_num = 32)

for i, data in enumerate(augmented):
    img_copy = data[0].copy()
    feature_size = (img_copy.shape[0] // FEATURE_STRIDE, img_copy.shape[1] // FEATURE_STRIDE)
    anchors = create_train_anchors(feature_size, base_anchors, FEATURE_STRIDE)
    
    for j, a in enumerate(anchors):
        if POS_VAL == data[1][j]:
            gt = data[2][j]
            # 测试 get_rpn_cls_label 带出来的 gt 是否正确
            cv.rectangle(img_copy, (gt[0], gt[1]), (gt[2], gt[3]), (255, 55, 55), 2)
            cv.rectangle(img_copy, (a[0], a[1]), (a[2], a[3]), (0, 255, 0), 2)
        # 这里不显示负样本方观察
        elif NEG_VAL == data[1][j]:
            cv.rectangle(img_copy, (a[0], a[1]), (a[2], a[3]), (0, 0, random.randint(128, 256)), 1)
        
    plt.subplot(2, 2, i + 1)
    plt.title(titles[i], color = 'gray')
    plt.imshow(img_copy[..., : : -1]) # 这里的通道要反过来显示才正常
plt.show()
('data_set\\007152.jpg', 'data_set\\007152.xml', 'xml')

保姆级 Keras 实现 Faster R-CNN 四_第1张图片
因为负样本比较多, 是随机生成的, 所以负样本不是对称的. 正样本少, 每次都会取到, 所以是对称的

二. 读入训练数据 Generator

数据增强已完成, 是时候考虑如何向网络送入训练的数据了

VOC 数据集 数据集图像还是比较多的, 加上还要做一些增强处理, 一下子读到内存的话, 训练起来可能你的机器受不了. 所以需要用 Generator 的方式来读取训练数据, 需要多少读多少. 其次是要理解 Generator 的话, 一定要先理解并掌握 yield 这个神奇的关键字

# 网络输入数据 generator
# data_set: 训练或测试数据列表
# categories: 类别列表
# train_num: 参加训练的 anchor 的数量
# batch_size: 一次输入训练的图像数量
# augment_fun: 数据增强函数
# train_mode: True: 训练模式, False: 测试模式
# shuffle_enable: 打乱标记
# 返回图像和标签
def input_reader(data_set, categories, batch_size = 1, train_num = TRAIN_NUM,
                 augment_fun = None, train_mode = True, shuffle_enable = True):
    assert(isinstance(data_set, tuple) or isinstance(data_set, list))
    
    stop_now = False
    data_nums = len(data_set)
    index_list = [x for x in range(data_nums)] # 用这个列表序号来打乱 data_set 排序
    
    while False == stop_now:
        if train_mode and shuffle_enable:
            shuffle(index_list)
            
        x = []     # 返回图像
        y_cls = [] # 返回分类标签
        
        max_rows = 0 # 记录一个 batch 中图像的最大行数
        max_cols = 0 # 记录一个 batch 中图像的最大列数
        
        for i in index_list:
            if train_mode:
                augmented = augment_fun(data_set[i], train_num)
                for data in augmented:
                    x.append(data[0])
                    y_cls.append(data[1])                 
                    max_rows = max(max_rows, x[-1].shape[0])
                    max_cols = max(max_cols, x[-1].shape[1])
                
                    if len(x) >= batch_size:
                        # 一个 batch 中图像的尺寸不一样是不能一起训练的, 所以要将其统一到相同的尺寸
                        # 行数小于最大行数在图像下方填充 0, 列数小于最大列数在图像右方填充 0
                        # 图像填充的同时标签也要填充
                        new_shape = (max_rows // FEATURE_STRIDE, max_cols // FEATURE_STRIDE)
                        
                        for j, img in enumerate(x):
                            # 原图对应的特征图尺寸
                            old_shape = (img.shape[0] // FEATURE_STRIDE, img.shape[1] // FEATURE_STRIDE)
                            # 这里 = 号前要用 x[j] 不能用 img
                            x[j] = cv.copyMakeBorder(img,
                                                     0, max_rows - img.shape[0], 0, max_cols - img.shape[1],
                                                     cv.BORDER_CONSTANT, (0, 0, 0))
                        
                            # 行方向填充数据
                            if new_shape[0] - old_shape[0] > 0:
                                pad_num = (new_shape[0] - old_shape[0]) * old_shape[1] * ANCHOR_NUM
                                y_pad = [NEUTRAL] * pad_num
                                y_cls[j].extend(y_pad)
                                
                            # 列方向填充
                            # 行方向时直接加在末尾, 而列方向是不连续的, 所以一行一行加在末尾
                            if new_shape[1] - old_shape[1] > 0:
                                pad_pos = old_shape[1] * ANCHOR_NUM 
                                                                    
                                pad_num = (new_shape[1] - old_shape[1]) * ANCHOR_NUM
                                y_pad = [NEUTRAL] * pad_num
                                for r in range(new_shape[0]):
                                    # 这里不能用 insert 函数, insert 会把 y_pad 整体当成一个元素
                                    y_cls[j][pad_pos: pad_pos] = y_pad
                                    pad_pos += (pad_num + old_shape[1] * ANCHOR_NUM)
                                    
                        # 返回数据
                        x = np.array(x).astype(np.float32) / 255.0
                        y_cls = np.array(y_cls).astype(np.float32)
                        y_cls = y_cls.reshape((-1, new_shape[0], new_shape[1], ANCHOR_NUM))
                        yield x, y_cls
                        x = []
                        y_cls = []
                        max_rows = 0
                        max_cols = 0
            else: # 如果不是训练模式, 就把 batch_size 默认为 1 了
                # 如果 3 == data_set[i], 表示带标签输入, 否则只有图像
                img_src = cv.imread(data_set[i][0] if 3 == len(data_set[i]) else data_set[i])
                img_new, scale = new_size_image(img_src, SHORT_SIZE)
                feature_size = (img_new.shape[0] // FEATURE_STRIDE, img_new.shape[1] // FEATURE_STRIDE)
                x.append(img_new)
                
                if 3 == len(data_set[i]):
                    anchors = create_train_anchors(feature_size, base_anchors, FEATURE_STRIDE)
                    ground_truth = get_ground_truth(data_set[i][1], data_set[i][2], CATEGORIES)    
                    # ground_truth 要做相应的缩放
                    for gt in ground_truth:
                        gt[0][0] = round(gt[0][0] * scale)
                        gt[0][1] = round(gt[0][1] * scale)
                        gt[0][2] = round(gt[0][2] * scale)
                        gt[0][3] = round(gt[0][3] * scale)
    
                    rpn_cls_label, gt_boxes = get_rpn_cls_label(img_new.shape, anchors,
                                                                ground_truth, train_num = train_num)
                    y_cls.append(rpn_cls_label)
            
                else: # 只有图像, 没有标签
                    y_cls.append([NEUTRAL] * feature_size[0] * feature_size[1] * ANCHOR_NUM)
                    
                x = np.array(x).astype(np.float32) / 255.0
                y_cls = np.array(y_cls).astype(np.float32)
                y_cls = y_cls.reshape((-1, feature_size[0], feature_size[1], ANCHOR_NUM))
                        
                yield x, y_cls
                x = []
                y_cls = []
                
        if False == train_mode:
            stop_now = True

input_reader 中要注意的要点是当 batch_size > 4 时, 读入的图像尺寸不一定一样, 所以要将其填充到相同的尺寸, 标签也要做相应的填充, 结合下面的测试输出图像更容易明白

# 测试 input_reader
# 这里设置成 32 方向显示, 要不然密密麻麻的框
show_reader = input_reader(train_set, CATEGORIES, batch_size = 8, train_num = 32,
                           augment_fun = data_augment)
# 测试 input_reader
x, y = next(show_reader)
batch_size = x.shape[0]
print("train image shape: ", x.shape)
print("label shape: ", y.shape)

SHOW_COLUMNS = 4
SHOW_ROWS = max(1, batch_size // SHOW_COLUMNS) + 1
plt.figure("batch_images", figsize = (12, SHOW_ROWS * 3))

for i in range(batch_size):
    feature_size = (x[0].shape[0] // FEATURE_STRIDE, x[0].shape[1] // FEATURE_STRIDE)
    anchors = create_train_anchors(feature_size, base_anchors, FEATURE_STRIDE)
    
    if 0 == i:
        print("\nanchrors in single image: ", len(anchors))
        
    positives = 0
    idxs = tf.where(K.not_equal(y[i], NEUTRAL))
    for idx in idxs:
        idx = (i, int(idx[0]), int(idx[1]), int(idx[2]))
        rgb = (0, 255, 0) if POS_VAL == y[idx] else (0, 0, 255)
        positives = positives + 1 if POS_VAL == y[idx] else positives

        idx = int(idx[1] * feature_size[1] * ANCHOR_NUM + idx[2] * ANCHOR_NUM + idx[3])
        a = anchors[idx]
        cv.rectangle(x[i], (a[0], a[1]), (a[2], a[3]), rgb, 2)
        
    plt.subplot(SHOW_ROWS, SHOW_COLUMNS, i + 1)
    plt.title("positive = " + str(positives), color = 'gray')
    plt.imshow(x[i][..., : : -1])
plt.show()
train image shape:  (8, 400, 400, 3)
label shape:  (8, 25, 25, 9)

anchrors in single image:  5625

保姆级 Keras 实现 Faster R-CNN 四_第2张图片
输出的图像中只在下方和右方进行了填充, 这样方便标签的处理, 当然你要用强迫症可以在四边都填充

这里有一个问题是 anchor box 画到了填充的黑色区域, 是因为在测试的循环中我们重新生成了 anchor box, 并没有做舍去或者截断处理, 真正训练的时候是没有的

上一篇: 保姆级 Keras 实现 Faster R-CNN 三
下一篇: 保姆级 Keras 实现 Faster R-CNN 五

你可能感兴趣的:(Object,Detect,Keras,深度学习,keras,faster_rcnn)