开源的yolov5算法的使用

1. 源码的下载及算法环境的配置

1.1 源码的下载

在github官网上搜索yolov5,链接
开源的yolov5算法的使用_第1张图片
注意:yolov5有好几个版本,本文主要采用yolov5-V2.0的版本作为此次的使用,因此最好不要下错了,注意选取版本。

pycharm下得目录结构为:
开源的yolov5算法的使用_第2张图片

1.2 docker环境的配置

根据代码中自带的Dockerfile文件,在装有Docker环境的系统中运行:docker build -t 镜像名:版本号 .

注意:这里的镜像名和版本号是自己起的。

2. yolov5的训练

注意: 在训练之前我们需要去查看自己的算法运行环境是否正确,避免出现后续的环境调试问题。

先下载一个测试环境的权重文件,文件如下所示:
开源的yolov5算法的使用_第3张图片

  • 在本地的环境调试中(即pycharm中),我们直接运行detect.py文件就行了,如果环境和权重有无误的话会出现如下内容:
    开源的yolov5算法的使用_第4张图片
    在项目路径的 inference/output中生成如下内容:
    开源的yolov5算法的使用_第5张图片

  • 在linux的docker环境下,直接运行python detect.py文件即可(需要先挂载容器哦,详细见下一个步骤)

2. 1 容器的挂载

挂载容器,无非是需要挂载两个目录路径:

  • 数据集的路径
  • yolov5-V2.0的代码路径

运行:

sudo docker run --runtime=nvidia --name=yolov5-v2.0_test  -v 数据集目录/:/yolov5/data -v 代码路径/:/yolov5 -it 镜像id /bin/bash
解释说明: 通过-v 将数据集目录挂载到容器根目录下的:yolov5/data的目录中
		  通过-v将代码挂载到容器根目录下得yolov5目录中。

2. 2 关于数据集的处理

由于yolov5和传统的VOC数据集存在差异,yolov5有自己的标签存储方式,所以针对标准的VOC数据集来说需要转换成yolo格式的标签存储。

  • 传统的VOC目录格式如下:
    开源的yolov5算法的使用_第6张图片
  • 将VOC格式的目录转换成yolo格式代码如下:
# -*- coding: utf-8 -*-
import xml.etree.ElementTree as ET
import os
from os import getcwd

sets = ['train', 'val']    #ImageSets目录下得txt文件
classes = ["10"]  # 改成自己的类别
root_dir = r"D:\Dataset\one_pointer_sum\VOC2007"    #VOC数据集的根目录

def convert(size, box):
    dw = 1. / (size[0])
    dh = 1. / (size[1])
    x = (box[0] + box[1]) / 2.0 - 1
    y = (box[2] + box[3]) / 2.0 - 1
    w = box[1] - box[0]
    h = box[3] - box[2]
    x = x * dw
    w = w * dw
    y = y * dh
    h = h * dh
    return x, y, w, h


def convert_annotation(image_id):
    in_file = open(root_dir + '/Annotations/%s.xml' % (image_id), encoding='UTF-8')
    out_file = open(root_dir + '/labels/%s.txt' % (image_id), 'w')
    tree = ET.parse(in_file)
    root = tree.getroot()
    size = root.find('size')
    w = int(size.find('width').text)
    h = int(size.find('height').text)
    for obj in root.iter('object'):
        if obj.find('difficult'):
            difficult = float(obj.find('difficult').text)
        else:
            difficult = 0
        # difficult = obj.find('Difficult').text
        cls = obj.find('name').text
        if cls not in classes or int(difficult) == 1:
            continue
        cls_id = classes.index(cls)
        xmlbox = obj.find('bndbox')
        b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),
             float(xmlbox.find('ymax').text))
        b1, b2, b3, b4 = b
        # 标注越界修正
        if b2 > w:
            b2 = w
        if b4 > h:
            b4 = h
        b = (b1, b2, b3, b4)
        bb = convert((w, h), b)
        out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')


wd = getcwd()
for image_set in sets:
    if not os.path.exists(root_dir + '/labels/'):
        os.makedirs(root_dir + '/labels/')
    image_ids = open(root_dir + '/ImageSets/Main/%s.txt' % (image_set)).read().strip().split()

    if not os.path.exists(root_dir + '/dataSet_path/'):
        os.makedirs(root_dir + '/dataSet_path/')
    list_file = open(root_dir + '/dataSet_path/%s.txt' % (image_set), 'w')
    for image_id in image_ids:
        list_file.write(root_dir + '/images/%s.jpg\n' % (image_id))
        convert_annotation(image_id)
    list_file.close()

运行上述代码后,VOC的根目录格式变为如下所示。
开源的yolov5算法的使用_第7张图片
其中labels中存放的是VOC数据集的XML标签转换为yolo格式标签。
开源的yolov5算法的使用_第8张图片
dataSet_path中存放的是train和val,区别于VOC数据的是VOC中存放的train和val是文件名,而yolo格式中的train和val是每个文件的路径
开源的yolov5算法的使用_第9张图片

2. 3 关于代码中部分配置文件的修改

熟悉目标检测算法的都知道,运行github上的代码主要做配置文件的修改一般只有两个地方

  • 关于数据集目录对应位置的修改(需要将数据集对应)
  • 关于训练模型配置文件的修改(选取需要训练哪个模型,yolov5包含好几个网络模型如:yolov5s,yolov5l等),以及配置文件中部分参数的设置。

2. 3. 1 关于数据集对应的yaml文件修改

在项目根目录下得data/voc.yaml或coco.yaml中需要修改的地方如下所示:
开源的yolov5算法的使用_第10张图片
其中需要修改的地方有四处。

  1. 为训练集的位置,如,在上述的数据集结构中可以表示为 D:\Dataset\one_pointer_sum\VOC2007\dataSet_path/train.txt
  2. 为验证集的位置,如,在上述的数据集结构中可以表示为 D:\Dataset\one_pointer_sum\VOC2007\dataSet_path/val.txt
  3. nc为训练的类别数,需要改为自己的训练类别数
  4. name为训练的类别标签列表,需要自行更改。

注意:在改完后要注意冒号":"后面要有一个空格,不然会出错,如"train:"后面不能直接写路径,需要添加一个空格,然后写路径。

2. 3. 2 关于模型配置文件的yaml文件修改

在项目根目录下的model/目录下有可以选取的网络模型,本文选取yolov5s.yaml做测试。
开源的yolov5算法的使用_第11张图片
需要更改的地方主要有两处,其中第二处可改可不改.

  1. 训练的类别数,nc需要修改为自己训练的类别数
  2. 训练中的初始anchors大小,虽然yolov5在训练过程中会自适应的迭代更改anchors的大小,但是建议最好自己选取初始的anchors大小,建议的anchors大小代码如下所示:

新建一个py文件,并命名为clauculate.py,文件的内容如下所示:

# -*- coding: utf-8 -*-
# 根据标签文件求先验框

import os
import numpy as np
import xml.etree.cElementTree as et
from kmeans import kmeans, avg_iou

FILE_ROOT = r"E:\2021年无人机缺陷识别比赛数据\大金具/"     # 根路径
ANNOTATION_PATH = FILE_ROOT + "Annotations"

ANCHORS_TXT_PATH = FILE_ROOT + "/anchors.txt"   #anchors文件保存位置

CLUSTERS = 10
CLASS_NAMES = ['xcxjzc', 'fzczc', 'fzchy', 'fzcxs', 'zczc', 'xcxjpy', 'fzcpy', 'pbhxs', 'fzctl', 'zcxs']   #类别名称

def load_data(anno_dir, class_names):
    xml_names = os.listdir(anno_dir)
    boxes = []
    for xml_name in xml_names:
        xml_pth = os.path.join(anno_dir, xml_name)
        tree = et.parse(xml_pth)

        width = float(tree.findtext("./size/width"))
        height = float(tree.findtext("./size/height"))

        for obj in tree.findall("./object"):
            cls_name = obj.findtext("name")
            if cls_name in class_names:
                xmin = float(obj.findtext("bndbox/xmin")) / width
                ymin = float(obj.findtext("bndbox/ymin")) / height
                xmax = float(obj.findtext("bndbox/xmax")) / width
                ymax = float(obj.findtext("bndbox/ymax")) / height

                box = [xmax - xmin, ymax - ymin]
                boxes.append(box)
            else:
                continue
    return np.array(boxes)

if __name__ == '__main__':

    anchors_txt = open(ANCHORS_TXT_PATH, "w")

    train_boxes = load_data(ANNOTATION_PATH, CLASS_NAMES)
    count = 1
    best_accuracy = 0
    best_anchors = []
    best_ratios = []

    for i in range(10):      ##### 可以修改,不要太大,否则时间很长
        anchors_tmp = []
        clusters = kmeans(train_boxes, k=CLUSTERS)
        idx = clusters[:, 0].argsort()
        clusters = clusters[idx]
        # print(clusters)

        for j in range(CLUSTERS):
            anchor = [round(clusters[j][0] * 640, 2), round(clusters[j][1] * 640, 2)]
            anchors_tmp.append(anchor)
            print(f"Anchors:{anchor}")

        temp_accuracy = avg_iou(train_boxes, clusters) * 100
        print("Train_Accuracy:{:.2f}%".format(temp_accuracy))

        ratios = np.around(clusters[:, 0] / clusters[:, 1], decimals=2).tolist()
        ratios.sort()
        print("Ratios:{}".format(ratios))
        print(20 * "*" + " {} ".format(count) + 20 * "*")

        count += 1

        if temp_accuracy > best_accuracy:
            best_accuracy = temp_accuracy
            best_anchors = anchors_tmp
            best_ratios = ratios

    anchors_txt.write("Best Accuracy = " + str(round(best_accuracy, 2)) + '%' + "\r\n")
    anchors_txt.write("Best Anchors = " + str(best_anchors) + "\r\n")
    anchors_txt.write("Best Ratios = " + str(best_ratios))
    anchors_txt.close()

新建一个py文件,并命名为kemans.py,文件的内容如下所示:

import numpy as np

def iou(box, clusters):
    """
    Calculates the Intersection over Union (IoU) between a box and k clusters.
    :param box: tuple or array, shifted to the origin (i. e. width and height)
    :param clusters: numpy array of shape (k, 2) where k is the number of clusters
    :return: numpy array of shape (k, 0) where k is the number of clusters
    """
    x = np.minimum(clusters[:, 0], box[0])
    y = np.minimum(clusters[:, 1], box[1])
    if np.count_nonzero(x == 0) > 0 or np.count_nonzero(y == 0) > 0:
        raise ValueError("Box has no area")                 # 如果报这个错,可以把这行改成pass即可

    intersection = x * y
    box_area = box[0] * box[1]
    cluster_area = clusters[:, 0] * clusters[:, 1]

    iou_ = intersection / (box_area + cluster_area - intersection)

    return iou_

def avg_iou(boxes, clusters):
    """
    Calculates the average Intersection over Union (IoU) between a numpy array of boxes and k clusters.
    :param boxes: numpy array of shape (r, 2), where r is the number of rows
    :param clusters: numpy array of shape (k, 2) where k is the number of clusters
    :return: average IoU as a single float
    """
    return np.mean([np.max(iou(boxes[i], clusters)) for i in range(boxes.shape[0])])

def translate_boxes(boxes):
    """
    Translates all the boxes to the origin.
    :param boxes: numpy array of shape (r, 4)
    :return: numpy array of shape (r, 2)
    """
    new_boxes = boxes.copy()
    for row in range(new_boxes.shape[0]):
        new_boxes[row][2] = np.abs(new_boxes[row][2] - new_boxes[row][0])
        new_boxes[row][3] = np.abs(new_boxes[row][3] - new_boxes[row][1])
    return np.delete(new_boxes, [0, 1], axis=1)


def kmeans(boxes, k, dist=np.median):
    """
    Calculates k-means clustering with the Intersection over Union (IoU) metric.
    :param boxes: numpy array of shape (r, 2), where r is the number of rows
    :param k: number of clusters
    :param dist: distance function
    :return: numpy array of shape (k, 2)
    """
    rows = boxes.shape[0]

    distances = np.empty((rows, k))
    last_clusters = np.zeros((rows,))

    np.random.seed()

    # the Forgy method will fail if the whole array contains the same rows
    clusters = boxes[np.random.choice(rows, k, replace=False)]

    while True:
        for row in range(rows):
            distances[row] = 1 - iou(boxes[row], clusters)

        nearest_clusters = np.argmin(distances, axis=1)

        if (last_clusters == nearest_clusters).all():
            break

        for cluster in range(k):
            clusters[cluster] = dist(boxes[nearest_clusters == cluster], axis=0)

        last_clusters = nearest_clusters

    return clusters

if __name__ == '__main__':
    a = np.array([[1, 2, 3, 4], [5, 7, 6, 8]])
    print(translate_boxes(a))

运行clauculate.py后,会生成一个txt文件,该文件中写入的是anchors的建议框大小,只需要将建议框大小取整写入即可。

2. 3 模型的训练

在训练之前通过观看,train.py文件,可以看到,需要注意的几个地方如下所示:
开源的yolov5算法的使用_第12张图片

  1. “–cfg” 这是选取的模型的yaml文件,写入时请自行更改为自己所需训练模型yaml文件的路径。
  2. “–data” 这是数据集的yaml文件,注意自行更改
  3. “–epochs” 这是训练的轮次,自行更改。
  4. “–batch-size”,这是每一次训练的batchsize,注意修改,值得注意的是,这个batchsize是所有GPU总和的batch,假设你的batch-size是16,当你有一个GPU时,是一张卡训练16,当你有l两个GPU是一张卡训练8.
  5. “–img-size”,这是训练时图片的大小,自行更改。
  6. “–device”,这是训练时的GPU编号,当然,当你没有GPU时那就是CPU了。

多GPU训练:

Python -m torch.distributed.launch --nporc_per_node 训练卡数 train.py --img 训练图片大小 --batch 训练的batchsize 
   --epoch 训练的轮次 --data 数据集的yaml文件路径 --cfg 训练的模型yaml文件路径,--weights 预训练权重路径
    --device gpu设备的id号

同理,当你采用单卡训练时,只需要将训练卡数设置为1,并且将gpu 的id号写对就行。

CPU训练的话,不需要给训练的卡数就行。

你可能感兴趣的:(目标检测,算法,pycharm,python)