darknet-yolov4的训练(linux)+imgaug数据增强

参考文章:https://www.cnblogs.com/pprp/p/9525508.html

如果成功遍历caffe后,编译darknet就很方便,而且caffe的编译与darknet 不相互影响

数据增强

目标检测的数据增强,主要针对voc格式的,可参考:https://zhuanlan.zhihu.com/p/89679274

使用下列方式安装imgaug包

pip install imgaug

将标记好的图片和标注放好,修改下列imgaug.py文件的路径,执行,就可以生成增强后的图片和相应的annotation。但由于目标检测的图片旋转等形式后,会造成框的位置不那么拟合,所以还是需要使用labelImg来微调一下的。但是不微调,影响也不是特别大。

import xml.etree.ElementTree as ET
import pickle
import os
from os import getcwd
import numpy as np
from PIL import Image
import shutil
import matplotlib.pyplot as plt

import imgaug as ia
from imgaug import augmenters as iaa


ia.seed(1)


def read_xml_annotation(root, image_id):
    in_file = open(os.path.join(root, image_id))
    tree = ET.parse(in_file)
    root = tree.getroot()
    bndboxlist = []

    for object in root.findall('object'):  # 找到root节点下的所有country节点
        bndbox = object.find('bndbox')  # 子节点下节点rank的值

        xmin = int(bndbox.find('xmin').text)
        xmax = int(bndbox.find('xmax').text)
        ymin = int(bndbox.find('ymin').text)
        ymax = int(bndbox.find('ymax').text)
        # print(xmin,ymin,xmax,ymax)
        bndboxlist.append([xmin, ymin, xmax, ymax])
        # print(bndboxlist)

    bndbox = root.find('object').find('bndbox')
    return bndboxlist


# (506.0000, 330.0000, 528.0000, 348.0000) -> (520.4747, 381.5080, 540.5596, 398.6603)
def change_xml_annotation(root, image_id, new_target):
    new_xmin = new_target[0]
    new_ymin = new_target[1]
    new_xmax = new_target[2]
    new_ymax = new_target[3]

    in_file = open(os.path.join(root, str(image_id) + '.xml'))  # 这里root分别由两个意思
    tree = ET.parse(in_file)
    xmlroot = tree.getroot()
    object = xmlroot.find('object')
    bndbox = object.find('bndbox')
    xmin = bndbox.find('xmin')
    xmin.text = str(new_xmin)
    ymin = bndbox.find('ymin')
    ymin.text = str(new_ymin)
    xmax = bndbox.find('xmax')
    xmax.text = str(new_xmax)
    ymax = bndbox.find('ymax')
    ymax.text = str(new_ymax)
    tree.write(os.path.join(root, str("%06d" % (str(id) + '.xml'))))


def change_xml_list_annotation(root, image_id, new_target, saveroot, id):
    in_file = open(os.path.join(root, str(image_id) + '.xml'))  # 这里root分别由两个意思
    tree = ET.parse(in_file)
    elem = tree.find('filename')
    elem.text = (str("%06d" % int(id)) + '.jpg')
    xmlroot = tree.getroot()
    index = 0

    for object in xmlroot.findall('object'):  # 找到root节点下的所有country节点
        bndbox = object.find('bndbox')  # 子节点下节点rank的值

        # xmin = int(bndbox.find('xmin').text)
        # xmax = int(bndbox.find('xmax').text)
        # ymin = int(bndbox.find('ymin').text)
        # ymax = int(bndbox.find('ymax').text)

        new_xmin = new_target[index][0]
        new_ymin = new_target[index][1]
        new_xmax = new_target[index][2]
        new_ymax = new_target[index][3]

        xmin = bndbox.find('xmin')
        xmin.text = str(new_xmin)
        ymin = bndbox.find('ymin')
        ymin.text = str(new_ymin)
        xmax = bndbox.find('xmax')
        xmax.text = str(new_xmax)
        ymax = bndbox.find('ymax')
        ymax.text = str(new_ymax)

        index = index + 1

    tree.write(os.path.join(saveroot, str("%06d" % int(id)) + '.xml'))


def mkdir(path):
    # 去除首位空格
    path = path.strip()
    # 去除尾部 \ 符号
    path = path.rstrip("\\")
    # 判断路径是否存在
    # 存在     True
    # 不存在   False
    isExists = os.path.exists(path)
    # 判断结果
    if not isExists:
        # 如果不存在则创建目录
        # 创建目录操作函数
        os.makedirs(path)
        print(path + ' 创建成功')
        return True
    else:
        # 如果目录存在则不创建,并提示目录已存在
        print(path + ' 目录已存在')
        return False


if __name__ == "__main__":

    IMG_DIR = r"F:\cardata\car_finish\car_pwd\car_jpg"
    XML_DIR = r"F:\cardata\car_finish\car_pwd\annotation"

    AUG_XML_DIR =r"F:\cardata\car_finish\car_pwd\data_aug/Annotations"  # 存储增强后的XML文件夹路径
    try:
        shutil.rmtree(AUG_XML_DIR)
    except FileNotFoundError as e:
        a = 1
    mkdir(AUG_XML_DIR)

    AUG_IMG_DIR = r"F:\cardata\car_finish\car_pwd\data_aug/JPEGImages"  # 存储增强后的影像文件夹路径
    try:
        shutil.rmtree(AUG_IMG_DIR)
    except FileNotFoundError as e:
        a = 1
    mkdir(AUG_IMG_DIR)

    AUGLOOP = 50  # 每张影像增强的数量 20

    boxes_img_aug_list = []
    new_bndbox = []
    new_bndbox_list = []

    # 影像增强
    seq1 = iaa.Sequential([
        iaa.Flipud(0.5),  # vertically flip 20% of all images
        iaa.Fliplr(0.5),  # 镜像
        iaa.Multiply((1.2, 1.5)),  # change brightness, doesn't affect BBs
        iaa.GaussianBlur(sigma=(0, 3.0)),  # iaa.GaussianBlur(0.5),
        iaa.Affine( #仿射
            translate_px={"x": 15, "y": 15},
            scale=(0.8, 0.95),
            rotate=(-30, 30)
        )  # translate by 40/60px on x/y axis, and scale to 50-70%, affects BBs
    ])
    matrix1 = np.array([[0, -1, 0],
                       [-1, 4, -1],
                       [0, -1, 0]])
    sometimes = lambda aug: iaa.Sometimes(0.5, aug)  # 建立lambda表达式,
    sometimes2 = lambda aug: iaa.Sometimes(0.05, aug)  # 建立lambda表达式,
    seq = iaa.Sequential([
        iaa.Flipud(0.3),  # vertically flip 20% of all images
        iaa.Fliplr(0.5),  # 镜像
        # iaa.Multiply((1.2, 1.5)),  # change brightness, doesn't affect BBs
        # iaa.GaussianBlur(sigma=(0, 3.0)),  # iaa.GaussianBlur(0.5),
        # #iaa.Crop(percent=(0, 0.2)),#这个是指删除的部分
        # iaa.AdditiveGaussianNoise(scale=(0, 15)),

        sometimes(iaa.Crop(percent=(0, 0.2))),
        # 这里沿袭我们上面提到的sometimes,对随机的一部分图像做crop操作
        # crop的幅度为0到10%
        sometimes(iaa.Affine(  # 对一部分图像做仿射变换
            scale={"x": (0.8, 1.2), "y": (0.8, 1.2)},  # 图像缩放为80%到120%之间
            translate_percent={"x": (-0.2, 0.2), "y": (-0.2, 0.2)},  # 平移±20%之间
            rotate=(-45, 45),  # 旋转±45度之间
            shear=(-16, 16),  # 剪切变换±16度,(矩形变平行四边形)
            order=[0, 1],  # 使用最邻近差值或者双线性差值
            cval=(0, 255),  # 全白全黑填充0到255的任意像素随意填充
            mode=ia.ALL  # 定义填充图像外区域的方法
        )),

        iaa.SomeOf((0, 5),
                   [
                       # 将部分图像进行超像素的表示。o(╥﹏╥)o用超像素增强作者还是第一次见,比较孤陋寡闻
                       #每个图像生成16到128个超像素。 用平均像素颜色替换每个超像素的概率在10到100%之间(每个图像采样一次)
                       #aug = iaa.Superpixels(p_replace=(0.1, 1.0), n_segments=(16, 128))
                       sometimes2(
                           iaa.Superpixels(
                               p_replace=(0.005, 0.5),
                               n_segments=(5, 20)
                           )
                       ),

                       # 用高斯模糊,均值模糊,中值模糊中的一种增强。注意OneOf的用法
                       iaa.OneOf([
                           iaa.GaussianBlur((0, 3.0)),
                           iaa.AverageBlur(k=(2, 7)),  # 核大小2~7之间,k=((5, 7), (1, 3))时,核高度5~7,宽度1~3
                           iaa.MedianBlur(k=(3, 11)),
                       ]),
                       # 改变颜色空间增强器,从RGB更改为HSV,然后将50-100添加到第一个通道,然后转换回RGB。 这增加了每个图像的色调值。
                        iaa.Sequential([
                            iaa.ChangeColorspace(from_colorspace="RGB", to_colorspace="HSV"),
                            iaa.WithChannels(0, iaa.Add((50, 100))),
                            iaa.ChangeColorspace(from_colorspace="HSV", to_colorspace="RGB")
                        ]),
                       # 锐化处理
                       iaa.Sharpen(alpha=(0, 1.0), lightness=(0.75, 1.5)),

                       # 浮雕效果
                       iaa.Emboss(alpha=(0, 1.0), strength=(0, 2.0)),

                       # 边缘检测,将检测到的赋值0或者255然后叠在原图上
                       sometimes(iaa.OneOf([
                           iaa.EdgeDetect(alpha=(0, 0.7)),
                           iaa.DirectedEdgeDetect(
                               alpha=(0, 0.7), direction=(0.0, 1.0)
                           ),
                       ])),
                       #卷积增强
                       # sometimes2(
                       #     iaa.Convolve(matrix=matrix1)
                       # ),#效果很不好,就不用了,图片一篇黑

                       # 加入高斯噪声
                       iaa.AdditiveGaussianNoise(
                           loc=0, scale=(0.0, 0.05 * 255), per_channel=0.5
                       ),

                       # 将1%到10%的像素设置为黑色
                       # 或者将3%到15%的像素用原图大小2%到5%的黑色方块覆盖
                       iaa.OneOf([
                           iaa.Dropout((0.01, 0.1), per_channel=0.5),
                           iaa.CoarseDropout(
                               (0.03, 0.10), size_percent=(0.02, 0.05),
                               per_channel=0.2
                           ),
                       ]),

                       # 5%的概率反转像素的强度,即原来的强度为v那么现在的就是255-v
                       iaa.Invert(0.05, per_channel=True),

                       # 每个像素随机加减-10到10之间的数
                       iaa.Add((-10, 10), per_channel=0.5),

                       #相邻像素加法增强器,在图像中添加-40到40之间的随机值,每个像素采样一次
                       iaa.AddElementwise((-40, 40)),

                        # 像素乘上0.5或者1.5之间的数字.
                       iaa.Multiply((0.5, 1.5), per_channel=0.5),

                       # 将整个图像的对比度变为原来的一半或者二倍,这个对比度是否有效?
                       iaa.ContrastNormalization((0.5, 2.0), per_channel=0.5),

                       # 将RGB变成灰度图然后乘alpha加在原图上
                       iaa.Grayscale(alpha=(0.0, 1.0)),

                       # 把像素移动到周围的地方。这个方法在mnist数据集增强中有见到
                       sometimes(
                           iaa.ElasticTransformation(alpha=(0.5, 3.5), sigma=0.25)
                       ),

                       # 扭曲图像的局部区域
                       sometimes(iaa.PiecewiseAffine(scale=(0.01, 0.05)))
                   ],random_order=True)  # 随机的顺序把这些操作用在图像上
    ],random_order=True)

    for root, sub_folders, files in os.walk(XML_DIR):

        for name in files:

            bndbox = read_xml_annotation(XML_DIR, name)
            shutil.copy(os.path.join(XML_DIR, name), AUG_XML_DIR)
            shutil.copy(os.path.join(IMG_DIR, name[:-4] + '.jpg'), AUG_IMG_DIR)

            for epoch in range(AUGLOOP):
                seq_det = seq.to_deterministic()  # 保持坐标和图像同步改变,而不是随机
                #
                img = Image.open(os.path.join(IMG_DIR, name[:-4] + '.jpg'))
                # sp = img.size
                img = np.asarray(img)
                # bndbox 坐标增强
                for i in range(len(bndbox)):
                    bbs = ia.BoundingBoxesOnImage([
                        ia.BoundingBox(x1=bndbox[i][0], y1=bndbox[i][1], x2=bndbox[i][2], y2=bndbox[i][3]),
                    ], shape=img.shape)

                    bbs_aug = seq_det.augment_bounding_boxes([bbs])[0]
                    boxes_img_aug_list.append(bbs_aug)

                    # new_bndbox_list:[[x1,y1,x2,y2],...[],[]]
                    n_x1 = int(max(1, min(img.shape[1], bbs_aug.bounding_boxes[0].x1)))
                    n_y1 = int(max(1, min(img.shape[0], bbs_aug.bounding_boxes[0].y1)))
                    n_x2 = int(max(1, min(img.shape[1], bbs_aug.bounding_boxes[0].x2)))
                    n_y2 = int(max(1, min(img.shape[0], bbs_aug.bounding_boxes[0].y2)))
                    if n_x1 == 1 and n_x1 == n_x2:
                        n_x2 += 1
                    if n_y1 == 1 and n_y2 == n_y1:
                        n_y2 += 1
                    if n_x1 >= n_x2 or n_y1 >= n_y2:
                        print('error', name)
                    new_bndbox_list.append([n_x1, n_y1, n_x2, n_y2])
                # 存储变化后的图片
                image_aug = seq_det.augment_images([img])[0]
                path = os.path.join(AUG_IMG_DIR,
                                    # str("%06d" % (len(files) + int(name[:-4]) + epoch * 250)) + '.jpg')
                                    str("%06d" % (len(files) + int(name[:-4]) + epoch * 250)) + '.jpg')
                image_auged = bbs.draw_on_image(image_aug, thickness=0)
                Image.fromarray(image_auged).save(path)

                # 存储变化后的XML
                change_xml_list_annotation(XML_DIR, name[:-4], new_bndbox_list, AUG_XML_DIR,
                                           len(files) + int(name[:-4]) + epoch * 250)
                print(str("%06d" % (len(files) + int(name[:-4]) + epoch * 250)) + '.jpg')
                new_bndbox_list = []

yolov4在darknet下面的安排

a.数据集的安排,在darknet根目录下放置如下文件夹

VOCdevkit
| VOC2007
    | Annotations 把所有的xml放入该文件夹
    | JPEGImages 放置所有测试文件
    | ImageSets
      | Main
      | train.txt
      | val.txt
      | test.txt

b.修改voc_label.py(能在scprit文件中找到),生成训练的配置文件.主要在darknet根目录下生成train.txt和test.txt

c.将配置文件,car_pwd.data,car_pwd.names,以及yolov4-kmeans.cfg放置于cfg的文件夹中,

d.训练,下载预训练文件,并将其放于weights文件夹下,执行训练.其中会在car_pwd.data的backup配置的路径中生成网络模型

	./darknet detector train cfg/car_pwd.data cfg/yolo-obj.cfg yolov4.conv.137

e.测试单张图片

   ./darknet detector test cfg/car_pwd.data cfg/yolov4-kmeans.cfg train_weights/yolov4-kmeans_final.weights

f.测试mAP
    (1)执行验证文件,在results文件夹下生成预测的结果,会生成comp4_det_test_类别名.txt

  ./darknet detector valid cfg/car_pwd.data cfg/yolov4-kmeans.cfg train_weights/yolov4-kmeans_final.weights

   (2)计算mAP要用到两个脚本voc_eval.py和reval_voc.py(能在scprit文件中找到),把两个脚本放到darknet的根目录下,修改reval_voc.py文件里的parse_args的参数配置。
      然后执行 python reval_voc.py即得到mAP

你可能感兴趣的:(darknet)