zoukankan      html  css  js  c++  java
  • YoloV4当中的Mosaic数据增强方法(附代码详细讲解)码农的后花园

    上一期中讲解了图像分类和目标检测中的数据增强的区别和联系,这期讲解数据增强的进阶版- yolov4中的Mosaic数据增强方法以及CutMix。

    前言

    Yolov4的mosaic数据增强参考了CutMix数据增强方式, 是CutMix数据增强方法的改进版。不同于一般的数据增强的方式是对一张图片进行扭曲、翻转、色域变化,CutMix数据增强方式是对两张图片进行拼接变为一张新的图片,然后将拼接好了的图片传入到神经网络中去学习,如下图。

    YoloV4当中的Mosaic数据增强方法(附代码详细讲解)码农的后花园

     

    CutMix的处理方式比较简单,对一对图片做操作,简单讲就是随机生成一个裁剪框Box,裁剪掉A图的相应位置,然后用B图片相应位置的ROI放到A图中被裁剪的区域形成新的样本,计算损失时同样采用加权求和的方式进行求解。就是将图A一部分区域cut掉但不填充0像素,然后随机填充训练集中的其他数据的区域像素值,分类结果按一定的比例分配。

    下图是使用CutMix方法对常见的数据集进行数据增强的表现,可以看到有明显的提升。

    YoloV4当中的Mosaic数据增强方法(附代码详细讲解)码农的后花园

     

    Mosaic数据增强方法

    mosaic数据增强则利用了四张图片,对四张图片进行拼接,每一张图片都有其对应的框框,将四张图片拼接之后就获得一张新的图片,同时也获得这张图片对应的框框,然后我们将这样一张新的图片传入到神经网络当中去学习,相当于一下子传入四张图片进行学习了。论文中说这极大丰富了检测物体的背景!且在标准化BN计算的时候一下子会计算四张图片的数据!如下图所示:

    YoloV4当中的Mosaic数据增强方法(附代码详细讲解)码农的后花园

     

    实现过程

    这里以对Voc2007数据集进行随机数据增强为例进行讲解,大致分为四步:

    第一步:从Voc数据集中每次随机读取四张图片

    YoloV4当中的Mosaic数据增强方法(附代码详细讲解)码农的后花园

     

    第二步:分别对四张图片进行翻转(对原始图片进行左右的翻转)、缩放(对原始图片进行大小的缩放)、色域变化(对原始图片的明亮度、饱和度、色调进行改变)等操作。

    操作完成之后然后再将原始图片按照 第一张图片摆放在左上,第二张图片摆放在左下,第三张图片摆放在右下,第四张图片摆放在右上四个方向位置摆好。

    YoloV4当中的Mosaic数据增强方法(附代码详细讲解)码农的后花园

     

    3、进行图片的组合和框的组合

    完成四张图片的摆放之后,我们利用矩阵的方式将四张图片它固定的区域截取下来,然后将它们拼接起来,拼接成一 张新的图片,新的图片上含有框框等一系列的内容。

    YoloV4当中的Mosaic数据增强方法(附代码详细讲解)码农的后花园

     

    如上图可以看到我们将四张图片进行拼接的时候有很明显的边缘,横线和竖线就是分割的线,这个分割线是由我们人为事先预先设定好了的,在代码中由min_offset_x和min_offset_y去选取分割的线。

    拼接完成之后得到的新的一张图片,我们可以看到拼接的图片的左上角的图像对于原图来说是少了的,因为拼接的时候被它右边的图覆盖掉了,拼接的时候很有可能也会把另外的图中的框框给覆盖掉,这些问题都会在最后的对框框进行处理:当图片的框框(或者图片本身)超出两张图片之间的边缘(也就是我们设置的分割线)的时候,我们就需要把这个超出分割线的部分框框或者图片的部分)处理掉,进行边缘处理

    代码实现

    from PIL import Image, ImageDraw
    import numpy as np
    from matplotlib.colors import rgb_to_hsv, hsv_to_rgb
    import math


    def rand(a=0, b=1):
    return np.random.rand() * (b - a) + a


    def merge_bboxes(bboxes, cutx, cuty):
    merge_bbox = []
    for i in range(len(bboxes)):
    for box in bboxes[i]:
    tmp_box = []
    x1, y1, x2, y2 = box[0], box[1], box[2], box[3]

    if i == 0:
    if y1 > cuty or x1 > cutx:
    continue
    if y2 >= cuty and y1 <= cuty:
    y2 = cuty
    if y2 - y1 < 5:
    continue
    if x2 >= cutx and x1 <= cutx:
    x2 = cutx
    if x2 - x1 < 5:
    continue
    if i == 1:
    if y2 < cuty or x1 > cutx:
    continue
    if y2 >= cuty and y1 <= cuty:
    y1 = cuty
    if y2 - y1 < 5:
    continue
    if x2 >= cutx and x1 <= cutx:
    x2 = cutx
    if x2 - x1 < 5:
    continue
    if i == 2:
    if y2 < cuty or x2 < cutx:
    continue
    if y2 >= cuty and y1 <= cuty:
    y1 = cuty
    if y2 - y1 < 5:
    continue
    if x2 >= cutx and x1 <= cutx:
    x1 = cutx
    if x2 - x1 < 5:
    continue
    if i == 3:
    if y1 > cuty or x2 < cutx:
    continue
    if y2 >= cuty and y1 <= cuty:
    y2 = cuty
    if y2 - y1 < 5:
    continue
    if x2 >= cutx and x1 <= cutx:
    x1 = cutx
    if x2 - x1 < 5:
    continue
    tmp_box.append(x1)
    tmp_box.append(y1)
    tmp_box.append(x2)
    tmp_box.append(y2)
    tmp_box.append(box[-1])
    merge_bbox.append(tmp_box)
    return merge_bbox


    def get_random_data(annotation_line, input_shape, random=True, hue=.1, sat=1.5, val=1.5, proc_img=True):
    '''random preprocessing for real-time data augmentation'''
    h, w = input_shape
    min_offset_x = 0.4
    min_offset_y = 0.4
    scale_low = 1 - min(min_offset_x, min_offset_y)
    scale_high = scale_low + 0.2
    image_datas = []
    box_datas = []
    index = 0
    place_x = [0, 0, int(w * min_offset_x), int(w * min_offset_x)]
    place_y = [0, int(h * min_offset_y), int(w * min_offset_y), 0]
    for line in annotation_line:
    # 每一行进行分割
    line_content = line.split()
    # 打开图片
    image = Image.open(line_content[0])
    image = image.convert("RGB")
    # 图片的大小
    iw, ih = image.size
    # 保存框的位置
    box = np.array([np.array(list(map(int, box.split(',')))) for box in line_content[1:]])

    # image.save(str(index)+".jpg")
    # 是否翻转图片
    flip = rand() < .5
    if flip and len(box) > 0:
    image = image.transpose(Image.FLIP_LEFT_RIGHT)
    box[:, [0, 2]] = iw - box[:, [2, 0]]

    # 对输入进来的图片进行缩放
    new_ar = w / h
    scale = rand(scale_low, scale_high)
    if new_ar < 1:
    nh = int(scale * h)
    nw = int(nh * new_ar)
    else:
    nw = int(scale * w)
    nh = int(nw / new_ar)
    image = image.resize((nw, nh), Image.BICUBIC)

    # 进行色域变换
    hue = rand(-hue, hue)
    sat = rand(1, sat) if rand() < .5 else 1 / rand(1, sat)
    val = rand(1, val) if rand() < .5 else 1 / rand(1, val)
    x = rgb_to_hsv(np.array(image) / 255.)
    x[..., 0] += hue
    x[..., 0][x[..., 0] > 1] -= 1
    x[..., 0][x[..., 0] < 0] += 1
    x[..., 1] *= sat
    x[..., 2] *= val
    x[x > 1] = 1
    x[x < 0] = 0
    image = hsv_to_rgb(x)

    image = Image.fromarray((image * 255).astype(np.uint8))
    # 将图片进行放置,分别对应四张分割图片的位置
    dx = place_x[index]
    dy = place_y[index]
    new_image = Image.new('RGB', (w, h), (128, 128, 128))
    new_image.paste(image, (dx, dy))
    image_data = np.array(new_image) / 255
    # Image.fromarray((image_data*255).astype(np.uint8)).save(str(index)+"distort.jpg")
    index = index + 1
    box_data = []
    # 对box进行重新处理
    if len(box) > 0:
    np.random.shuffle(box)
    box[:, [0, 2]] = box[:, [0, 2]] * nw / iw + dx
    box[:, [1, 3]] = box[:, [1, 3]] * nh / ih + dy
    box[:, 0:2][box[:, 0:2] < 0] = 0
    box[:, 2][box[:, 2] > w] = w
    box[:, 3][box[:, 3] > h] = h
    box_w = box[:, 2] - box[:, 0]
    box_h = box[:, 3] - box[:, 1]
    box = box[np.logical_and(box_w > 1, box_h > 1)]
    box_data = np.zeros((len(box), 5))
    box_data[:len(box)] = box

    image_datas.append(image_data)
    box_datas.append(box_data)

    img = Image.fromarray((image_data * 255).astype(np.uint8))
    for j in range(len(box_data)):
    thickness = 3
    left, top, right, bottom = box_data[j][0:4]
    draw = ImageDraw.Draw(img)
    for i in range(thickness):
    draw.rectangle([left + i, top + i, right - i, bottom - i], outline=(255, 255, 255))
    img.show()

    # 将图片分割,放在一起
    cutx = np.random.randint(int(w * min_offset_x), int(w * (1 - min_offset_x)))
    cuty = np.random.randint(int(h * min_offset_y), int(h * (1 - min_offset_y)))

    new_image = np.zeros([h, w, 3])
    new_image[:cuty, :cutx, :] = image_datas[0][:cuty, :cutx, :]
    new_image[cuty:, :cutx, :] = image_datas[1][cuty:, :cutx, :]
    new_image[cuty:, cutx:, :] = image_datas[2][cuty:, cutx:, :]
    new_image[:cuty, cutx:, :] = image_datas[3][:cuty, cutx:, :]

    # 对框进行进一步的处理
    new_boxes = merge_bboxes(box_datas, cutx, cuty)

    return new_image, new_boxes


    def normal_(annotation_line, input_shape):
    '''random preprocessing for real-time data augmentation'''
    line = annotation_line.split()
    image = Image.open(line[0])
    box = np.array([np.array(list(map(int, box.split(',')))) for box in line[1:]])

    iw, ih = image.size
    image = image.transpose(Image.FLIP_LEFT_RIGHT)
    box[:, [0, 2]] = iw - box[:, [2, 0]]

    return image, box


    if __name__ == "__main__":
    with open("2007_train.txt") as f:
    lines = f.readlines()
    a = np.random.randint(0, len(lines))
    # index = 0
    # line_all = lines[a:a+4]
    # for line in line_all:
    # image_data, box_data = normal_(line,[416,416])
    # img = image_data
    # for j in range(len(box_data)):
    # thickness = 3
    # left, top, right, bottom = box_data[j][0:4]
    # draw = ImageDraw.Draw(img)
    # for i in range(thickness):
    # draw.rectangle([left + i, top + i, right - i, bottom - i],outline=(255,255,255))
    # img.show()
    # # img.save(str(index)+"box.jpg")
    # index = index+1
    # 传入四张图片
    # line = lines[a:a + 4]
    line = lines[0:4]
    image_data, box_data = get_random_data(line, [416, 416])
    img = Image.fromarray((image_data * 255).astype(np.uint8))
    for j in range(len(box_data)):
    thickness = 3
    left, top, right, bottom = box_data[j][0:4]
    draw = ImageDraw.Draw(img)
    for i in range(thickness):
    draw.rectangle([left + i, top + i, right - i, bottom - i], outline=(255, 255, 255))
    img.show()
    # img.save("box_all.jpg")

    所有实现代码以及完整注释,关注我下载使用,更多有关python、深度学习和计算机编程和电脑知识的精彩内容,可以关注微信公众号:码农的后花园

  • 相关阅读:
    网络编程(二)——TCP协议、基于tcp协议的套接字socket
    网络编程(一)——网络编程介绍
    吴裕雄--天生自然JAVA开发JSP-Servlet学习笔记:指定JSP页面的错误提示页
    吴裕雄--天生自然JAVA开发JSP-Servlet学习笔记:指定JSP页面的描述信息
    吴裕雄--天生自然JAVA开发JSP-Servlet学习笔记:JSP脚本
    吴裕雄--天生自然JAVA开发JSP-Servlet学习笔记:输出JSP表达式
    吴裕雄--天生自然JAVA开发JSP-Servlet学习笔记:JSP声明
    吴裕雄--天生自然JAVA开发JSP-Servlet学习笔记:JSP的基本原理
    吴裕雄--天生自然JAVA开发JSP-Servlet学习笔记:构建WEB应用
    吴裕雄--天生自然 JAVA-ORACLE学习笔记:JAVA连接ORACLE操作
  • 原文地址:https://www.cnblogs.com/xiamuzi/p/13471396.html
Copyright © 2011-2022 走看看