GitHub 水项目之 快速上手 YOLOV5

前言

先前咱们已经懂得了如何快速上手pytorch,并且搭建一个简单的神经网络,不过哪里依然有一些小问题,那就是我们还没有从自制数据集实现一个分类网络,所以后面有时间的话我会在总结一篇如何基于LeNet做一个简单的自定义的分类神经网络小dome。并且我们模仿 YOLO 的项目结构自己也来把这个小dome进行“规范”项目化。

不过我们当前的任务是如何去使用GitHub使用部署一个开源的深度学习项目,当然这个是基于Pytorch的。

在本篇文章当中将简单地介绍如何使用YoloV5,使用这个玩意儿做点好玩的事情,之后是如何使用训练我们自己的模型,来实现我们的一些功能。

环境准备

下载项目

打开gayhub
GitHub 水项目之 快速上手 YOLOV5
GitHub 水项目之 快速上手 YOLOV5
下载并解压
然后打开你的Pytorch 。

这里很贴心,有一个环境依赖文件
GitHub 水项目之 快速上手 YOLOV5
所以打开项目后,我们只需要在控制台输入这个命令
GitHub 水项目之 快速上手 YOLOV5
当然pycharm也有提示,不过如果你想让它自带给你下载安装环境的话,建议你换一个镜像,或者科学上网。

下载权重文件

如果我们不想从0开始的话,我们还需要去下载他们训练好的权重文件,这样一来我们就能快速上手。
GitHub 水项目之 快速上手 YOLOV5

下载后放在这里
GitHub 水项目之 快速上手 YOLOV5

获得

为方便读者,我已将项目上传至百度云盘。需要自取

链接:https://pan.baidu.com/s/1tXbtecPGki_QyyohrlRjig
提取码:6666

项目结构

任何机器学习项目,或者深度学习项目,其实无非就是几步
GitHub 水项目之 快速上手 YOLOV5
之后,该项目提供了一个接口来加载和使用我们训练好的模型。

嗯,这也不例外
GitHub 水项目之 快速上手 YOLOV5

第一个“Hello World”

接下来我们来看一下如何使用YOLOV5。

我们首先进入detect.py这个文件。我们首先在这里发现了几个超参数的设置
GitHub 水项目之 快速上手 YOLOV5
关键是两者都可以看到。
一个是 weights 这个是设置咱们的权重模型,这个很明显默认实在咱们的项目根目录下。
另一个是我们的资源文件。打开那个文件夹,我们发现里面有图片的文件
GitHub 水项目之 快速上手 YOLOV5

ok,接下来啥也不管,我们先直接运行一下我们的这个文件
控制台输出这个
GitHub 水项目之 快速上手 YOLOV5
我们在run这个文件夹下找到了咱们运行后的结果
GitHub 水项目之 快速上手 YOLOV5
到此我们的第一个Hello World 就完成了。

参数设定(detect)

然后我们可以好好看看可以设置哪些参数。

 parser = argparse.ArgumentParser()
    parser.add_argument('--weights', nargs='+', type=str, default='yolov5s.pt', help='model.pt path(s)')
    parser.add_argument('--source', type=str, default='data/images', help='source')  # file/folder, 0 for webcam
    parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')
    parser.add_argument('--conf-thres', type=float, default=0.25, help='object confidence threshold')
    parser.add_argument('--iou-thres', type=float, default=0.45, help='IOU threshold for NMS')
    parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
    parser.add_argument('--view-img', action='store_true', help='display results')
    parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
    parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels')
    parser.add_argument('--nosave', action='store_true', help='do not save images/videos')
    parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --class 0, or --class 0 2 3')
    parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')
    parser.add_argument('--augment', action='store_true', help='augmented inference')
    parser.add_argument('--update', action='store_true', help='update all models')
    parser.add_argument('--project', default='runs/detect', help='save results to project/name')
    parser.add_argument('--name', default='exp', help='save results to project/name')
    parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
    opt = parser.parse_args()
    print(opt)
    check_requirements(exclude=('pycocotools', 'thop'))

这里我们可以参考这篇文章
https://cloud.tencent.com/developer/article/1874301
有关于参数的详细设置。

实时检测

我们先来看看效果
GitHub 水项目之 快速上手 YOLOV5
全程我啥也没干,我只是打开了我都手机摄像头,和一个IP摄像头软件。

下载软件

GitHub 水项目之 快速上手 YOLOV5

打开服务器

然后记录下你局域网的IP地址

例如我在这里
GitHub 水项目之 快速上手 YOLOV5

GitHub 水项目之 快速上手 YOLOV5
然后进入你的detect文件里面,你可以直接在参数里面设置,也可以python 运行单个文件的时候设置
而已。

然后单击运行。

不过我们实际上想要做的没有那么简单。而且说实话YOLO以我目前接触的东西来说,这玩意压根不算框架,只是一个Dome,一个实现好了的 用于目标检测的 残差神经网络dome。 所以为了方便运用和改写,我们需要去仔细阅读它的源码,方便后面改造这个框架。这个框架我简单看了一下从工程角度上来说,不复杂,比Spring简单多了,当然它的难度也不是工程难度,而是专业理论。

自我训练

然后终于到了自己训练小工具的时候了。

但在此之前,您需要下载一个标记软件。当然,你也可以上网使用,不过需要科学上网,这里就不用我介绍了。

所以咱们这里使用的是LabelImg。

使用LabelImg

关于这个软件,使用起来很简单,主要是安装和使用比较麻烦。

先下载源代码。后面我会给出百度云盘链接。
由于我是coda环境,所以我只需要先下载源码,解压
GitHub 水项目之 快速上手 YOLOV5

然后去这个文件夹
安装 pyqt

conda install pyqt=5
pyrcc5 -o resources.py resources.qrc

这时候还不够,大概率会出问题。所以你需要把
这个生成的文件,移动到libs里面
GitHub 水项目之 快速上手 YOLOV5
GitHub 水项目之 快速上手 YOLOV5
最后运行
GitHub 水项目之 快速上手 YOLOV5

一切正常,下一步是获取此软件
链接:https://pan.baidu.com/s/14y-0vqU7u9JkDBaAw7Odxg
提取码:6666

大喊

在这里,我只是拍几张照片来娱乐一下。
GitHub 水项目之 快速上手 YOLOV5

制作数据集

前面我们其实还只是完成了准备工作,接下来才是比较复杂的点,那就是制作数据集,这个LabelImg打包好之后的数据集的格式其实是VOC格式的,当然那里也可以切换为yolo的格式,不过后面都还是要再转换的。

让我们看看我们的初始数据集是什么样的
GitHub 水项目之 快速上手 YOLOV5

让我们看看最终的文件是什么样的
GitHub 水项目之 快速上手 YOLOV5

接下来我依次介绍这些脚本和处理操作。 这些代码都是copy的,目标只有一个制作一个标准的VOC数据集,然后给Yolo识别。
由于只是演示,所以我的图片的数据集不会太多。
GitHub 水项目之 快速上手 YOLOV5

关于图片的获取,可以自己写爬虫吧?一定要注意这里的图片大小一样(我在后面翻了)

划分训练文件

在这种情况下,我们主要运行这个脚本。
GitHub 水项目之 快速上手 YOLOV5

# coding:utf-8

import os
import random
import argparse

parser = argparse.ArgumentParser()
#xml文件的地址,根据自己的数据进行修改 xml一般存放在Annotations下
parser.add_argument('--xml_path', default='Annotations', type=str, help='input xml label path')
#数据集的划分,地址选择自己数据下的ImageSets/Main
parser.add_argument('--txt_path', default='ImageSets/Main', type=str, help='output txt label path')
opt = parser.parse_args()

trainval_percent = 1.0  # 训练集和验证集所占比例。 这里没有划分测试集
train_percent = 0.9     # 训练集所占比例,可自己进行调整
xmlfilepath = opt.xml_path
txtsavepath = opt.txt_path
total_xml = os.listdir(xmlfilepath)
if not os.path.exists(txtsavepath):
    os.makedirs(txtsavepath)

num = len(total_xml)
list_index = range(num)
tv = int(num * trainval_percent)
tr = int(tv * train_percent)
trainval = random.sample(list_index, tv)
train = random.sample(trainval, tr)

file_trainval = open(txtsavepath + '/trainval.txt', 'w')
file_test = open(txtsavepath + '/test.txt', 'w')
file_train = open(txtsavepath + '/train.txt', 'w')
file_val = open(txtsavepath + '/val.txt', 'w')

for i in list_index:
    name = total_xml[i][:-4] + '\n'
    if i in trainval:
        file_trainval.write(name)
        if i in train:
            file_train.write(name)
        else:
            file_val.write(name)
    else:
        file_test.write(name)

file_trainval.close()
file_train.close()
file_val.close()
file_test.close()

运行后会出现这个文件
GitHub 水项目之 快速上手 YOLOV5

生成标签

之后是生成我们的标签。
GitHub 水项目之 快速上手 YOLOV5

下面的路径看变化

# -*- coding: utf-8 -*-
import xml.etree.ElementTree as ET
import os
from os import getcwd

sets = ['train', 'val', 'test']
classes = ["猫羽雫", "喵喵~","女孩"]  # 改成自己的类别
abs_path = os.getcwd()
print(abs_path)


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('F:\projects\PythonProject\yolov5-5.0\mydata\Annotations\%s.xml' % (image_id), encoding='UTF-8')
    out_file = open('F:\projects\PythonProject\yolov5-5.0\mydata\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'):
        difficult = obj.find('difficult').text
        # 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('F:\projects\PythonProject\yolov5-5.0\mydata\labels'):
        os.makedirs('F:\projects\PythonProject\yolov5-5.0\mydata\labels')
    image_ids = open('F:\projects\PythonProject\yolov5-5.0\mydata\/ImageSets/Main/%s.txt' % (image_set)).read().strip().split()

    if not os.path.exists('F:\projects\PythonProject\yolov5-5.0\mydata\dataSet_path/'):
        os.makedirs('F:\projects\PythonProject\yolov5-5.0\mydata\dataSet_path/')

    list_file = open('dataSet_path/%s.txt' % (image_set), 'w')
    # 这行路径不需更改,这是相对路径
    for image_id in image_ids:
        list_file.write('F:\projects\PythonProject\yolov5-5.0\mydata/images/%s.jpg\n' % (image_id))
        convert_annotation(image_id)
    list_file.close()

然后会出现两个文件夹
GitHub 水项目之 快速上手 YOLOV5

其中一个是我们图片的真实存储地址,另一个是标签文本。

聚合操作

这主要用于设置我们的目标框架的大小。这里有两个脚本。这两个脚本的目的是计算我们在数据集中手动构图的帧的平均大小。目的是:我们得到训练好的模型后,它给我们的box的大小不会太奇怪,并且控制在一个合适的范围内。

帮助脚本

GitHub 水项目之 快速上手 YOLOV5

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))

这个脚本不需要运行,它是一个工具类。

需要运行的脚本

GitHub 水项目之 快速上手 YOLOV5

这里还要注意路径修改

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

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

FILE_ROOT = "F:\projects\PythonProject\yolov5-5.0\mydata/"     # 根路径
ANNOTATION_ROOT = "Annotations"   # 数据集标签文件夹路径
ANNOTATION_PATH = FILE_ROOT + ANNOTATION_ROOT

ANCHORS_TXT_PATH = "F:\projects\PythonProject\yolov5-5.0\mydata/anchors.txt"   #anchors文件保存位置

CLUSTERS = 9
CLASS_NAMES = ['猫羽雫', '喵喵~','女孩']   #类别名称

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()

然后会生成一个这样的文件
GitHub 水项目之 快速上手 YOLOV5

开始训练

首先打开我们的文件夹
GitHub 水项目之 快速上手 YOLOV5
这里有四种训练方法,不同的方法意味着不同的准确率和训练时间
GitHub 水项目之 快速上手 YOLOV5
我们这里默认的就是 5s,所以这里我就用5s做使用。

参数设置

首先我们需要在data文件下,或者其他的文件夹下你能够找到就行。
GitHub 水项目之 快速上手 YOLOV5

train: F:\projects\PythonProject\yolov5-5.0\mydata\dataSet_path\train.txt
val: F:\projects\PythonProject\yolov5-5.0\mydata\dataSet_path\val.txt

# number of classes
nc: 3

# class names
names: ['猫羽雫', '喵喵~','女孩']

之后就是修改我们的模型
我们使用的是 5s
GitHub 水项目之 快速上手 YOLOV5

这里注意到那个anchors的参数修改
GitHub 水项目之 快速上手 YOLOV5

这实际上是我们最初通过聚类创建的。

开始训练

说到这里,只能说坑不少,很多人都到了这个阶段,项目根本跑不起来,心态炸了。

ok,我这里先简单说一下如果一切正常是怎么样的。

首先,我们只需要注意这里的几个参数。
GitHub 水项目之 快速上手 YOLOV5

使用命令是这样的

python train.py --weights weights/yolov5s.pt  --cfg models/yolov5s.yaml  --data data/mydata.yaml --epoch 200 --batch-size 4   --device 0

错误处理

编码错误

第一个是这个
GitHub 水项目之 快速上手 YOLOV5
我在网上找到了很多答案,但没有奏效。
GitHub 水项目之 快速上手 YOLOV5

没有SPPF

这个呢,网上有两种解决方案,一个是说直接把那啥EPPS这个类复制到comment文件下

GitHub 水项目之 快速上手 YOLOV5


import warnings


class SPPF(nn.Module):
    # Spatial Pyramid Pooling - Fast (SPPF) layer for YOLOv5 by Glenn Jocher
    def __init__(self, c1, c2, k=5):  # equivalent to SPP(k=(5, 9, 13))
        super().__init__()
        c_ = c1 // 2  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_ * 4, c2, 1, 1)
        self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)

    def forward(self, x):
        x = self.cv1(x)
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')  # suppress torch 1.9.0 max_pool2d() warning
            y1 = self.m(x)
            y2 = self.m(y1)
            return self.cv2(torch.cat([x, y1, y2, self.m(y2)], 1))

但是,其实我发现是我杀了我,我下载了原版
GitHub 水项目之 快速上手 YOLOV5
放在了weights文件夹下面然后运行的时候路径不对,然后第一次运行自带下载然后就会报错,之后就不会了。所以不要指定好这个文件就行了。

当然第一种情况的出现主要是由于yolov5版本的问题,不过怎么说两种情况我都遇到了,所以都说一下。
如果第一个不起作用,那么第二个,如果它不起作用,就这样吧。

内存失控错误

这个有什么好说的,解决后,我们运行代码
GitHub 水项目之 快速上手 YOLOV5

你以为希望就在这里,然后,嘿嘿!

GTX1650 4GB
内存 16 GB
戴尔游匣G5
你说我的配置太垃圾? !显然不是。
在这里,我也查了很多资料,要么是电脑不好,要么是驱动版本不兼容。怎么说呢,这里不太可能出现。

到底怎么解决的,你可能不信,重启电脑? ? !那个时候我什么都不是。

正常情况

接下来是我们的正常情况
这里我输入了这样的指令(当然你直接修改train也可以,但是后面不好改回来嘛)

python train.py --weights weights/yolov5s.pt  --cfg models/yolov5s.yaml  --data data/mydata.yaml --epoch 200 --batch-size 4   --device 0

然后像这样
GitHub 水项目之 快速上手 YOLOV5
这是正常的,然后你的电脑风扇会尖叫并飞起来。如果没有,要么是您的计算机配置太好,要么是您的计算机出了问题。
训练后会告诉你输出路径
GitHub 水项目之 快速上手 YOLOV5

GitHub 水项目之 快速上手 YOLOV5

然后打开咱们的tensorboard

tensorboard --logdir=runs

这个训练的时候,默认会在runs这个文件夹下面写可视化文件(也就是tensorboard的那玩意)
咱们训练完之后打开tensorboard就能看到
GitHub 水项目之 快速上手 YOLOV5

采用

最后用起来不方便

python detect.py --weights runs/train/exp29/weights/best.pt --source 图片

但是在这里,它因为图片的大小而翻车了。我突然发现我的照片尺寸不对! ! !所以没用,我应该先用自己写的图像处理器来处理图像大小。

但过程是这样的。

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2022年4月7日 上午10:37
下一篇 2022年4月7日 上午11:08

相关推荐