YOLOv9 安全帽检测(一)

YOLOv9

YOLOv9是近期由 YOLOv7 团队提出的,引入了 PGI 概念来应对深度网络实现多个目标所需的各种变化。PGI 可以为目标任务计算目标函数提供完整的输入信息,从而获得可靠的梯度信息来更新网络权值。同期发布的还有一种新的轻量级网络架构GELAN。 GELAN 的架构证实了 PGI 在轻量级模型上取得了优异的结果。

https://github.com/WongKinYiu/yolov9

在这里插入图片描述

本文记录了使用YOLOv9在安全帽检测上的训练,使用ubuntu22.04,RTX4090

准备环境

yolov9官方提供了一个docker的拉取代码

# create the docker container, you can change the share memory size if you have more.
nvidia-docker run --name yolov9 -it -v your_coco_path/:/coco/ -v your_code_path/:/yolov9 --shm-size=64g nvcr.io/nvidia/pytorch:21.11-py3

# apt install required packages
apt update
apt install -y zip htop screen libgl1-mesa-glx

# pip install required packages
pip install seaborn thop

# go to code folder
cd /yolov9

​也可以自己写一个Dockerfile

FROM python:3.10

ENV PYTHONBUFFERED=1
ENV NVIDIA_VISIBLE_DEVICES=all
ENV NVIDIA_DRIVER_CAPABILITIES=compute,utility
WORKDIR /usr/src/
RUN apt-get update
RUN python3.10 -m pip install --upgrade pip

然后将GPU和yolov9目录挂载进去

进入环境后拉取项目
在这里插入图片描述
创建虚拟环境安装依赖

python3 -m venv yolov9
source yolov9/bin/activate
pip3 install -r requirement.txt

在这里插入图片描述

目前yolov9还只能下在-c和-e参数量的模型
在这里插入图片描述
这里选取YOLOv9-C来训练,下载后放入容器该目录下
(这里这个是错误的,下载的是yolov9-c-convert)
在这里插入图片描述

模型应该在这

准备数据集

这里使用了开源数据集

https://github.com/njvisionpower/Safety-Helmet-Wearing-Dataset

也可以在各大平台上搜,挺多的(百度云是真的逆天的慢,这个有Google的链接快很多)

在这里插入图片描述
这个数据集是VOC格式的,需要转化为yolo数据集格式,

VOC格式:

在这里插入图片描述

YOLO格式:

在这里插入图片描述
用到了https://github.com/ssaru/convert2Yolo这个项目

同样的,先拉区项目,创建虚拟环境,安装依赖

git clone https://github.com/ssaru/convert2Yolo.git
python3 -m venv convert2Yolo
source convert2Yolo/bin/activate
pip3 install -r requirement.txt

项目提供了使用示例

python3 example.py --datasets VOC --img_path ~/VOCdevkit/VOC2012/JPEGImages/ --label ~/VOCdevkit/VOC2012/Annotations/ --convert_output_path ~/YOLO/ --img_type ".jpg" --manifest_path ./ --cls_list_file ~/VOC/voc.names

>>
VOC Parsing:   |████████████████████████████████████████| 100.0% (17125/17125) Complete
YOLO Generating:|████████████████████████████████████████| 100.0% (17125/17125)Complete
YOLO Saving:   |████████████████████████████████████████| 100.0% (17125/17125) Complete

如果报错类似这种


YOLO Generating Result : False, msg : ERROR : 'hat' is not in list, moreInfo : <class 'ValueError'>     Format.py       704

在这里插入图片描述
就按照这个步骤创建完文件后,编辑文件添加上去就好了

在这里插入图片描述

运行完后检查看看label文件对不对

在这里插入图片描述
最好看看同目录下的manifest.txt文件,我在使用过程中会有点问题,这个项目是很早之前的了,对文件划分的方法有点bug,会导致和数据集图片的命名不同(这一个bug还没细看,后面再看看他的代码),如果报错了,就找其他项目来转换

https://github.com/yurizzzzz/Helmet-Detection-YoloV5这个项目也有转换代码,目前还没尝试

划分数据集

数据集下好后就划分数据集,这里用的是其他作者的https://blog.csdn.net/heart_warmonger/article/details/136249119
这个是项目链接,可以自行查看

数据集不是特别好,所以对划分数据集的代码进行了一点点修改,添加了异常跳过,
在这里插入图片描述

代码:

import shutil
import random
import os

# 原始路径
image_original_path = "/usr/src/yolov9/data/datasets1/datasets/"
label_original_path = "/usr/src/yolov9/data/datasets1/datasets/"

cur_path = os.getcwd()

# 训练集路径
train_image_path = os.path.join(cur_path, "/usr/src/yolov9/data/datasets/images/train/")
train_label_path = os.path.join(cur_path, "/usr/src/yolov9/data/datasets/labels/train/")

# 验证集路径
val_image_path = os.path.join(cur_path, "/usr/src/yolov9/data/datasets/images/val/")
val_label_path = os.path.join(cur_path, "/usr/src/yolov9/data/datasets/labels/val/")

# 测试集路径
test_image_path = os.path.join(cur_path, "/usr/src/yolov9/data/datasets/images/test/")
test_label_path = os.path.join(cur_path, "/usr/src/yolov9/data/datasets/labels/test/")

# 训练集目录
list_train = os.path.join(cur_path, "/usr/src/yolov9/data/datasets/sets/train.txt")
list_val = os.path.join(cur_path, "/usr/src/yolov9/data/datasets/sets/val.txt")
list_test = os.path.join(cur_path, "/usr/src/yolov9/data/datasets/sets/test.txt")

train_percent = 0.8
val_percent = 0.1
test_percent = 0.1

def del_file(path):
    for i in os.listdir(path):
        file_data = os.path.join(path, i)
        os.remove(file_data)

def mkdir():
    paths = [train_image_path, train_label_path, val_image_path, val_label_path, test_image_path, test_label_path]
    for path in paths:
        if not os.path.exists(path):
            os.makedirs(path)
        else:
            del_file(path)

def clearfile():
    paths = [list_train, list_val, list_test]
    for path in paths:
        if os.path.exists(path):
            os.remove(path)

def main():
    mkdir()
    clearfile()

    file_train = open(list_train, 'w')
    file_val = open(list_val, 'w')
    file_test = open(list_test, 'w')

    total_txt = os.listdir(label_original_path)
    num_txt = len(total_txt)
    list_all_txt = range(num_txt)

    num_train = int(num_txt * train_percent)
    num_val = int(num_txt * val_percent)
    num_test = num_txt - num_train - num_val

    train = random.sample(list_all_txt, num_train)
    val_test = [i for i in list_all_txt if not i in train]
    val = random.sample(val_test, num_val)

    print("训练集数目:{}, 验证集数目:{}, 测试集数目:{}".format(len(train), len(val), len(val_test) - len(val)))
    for i in list_all_txt:
        name = total_txt[i][:-4]

        srcImage = os.path.join(image_original_path, name + '.jpg')
        srcLabel = os.path.join(label_original_path, name + ".txt")

        try:
            if i in train:
                dst_train_Image = os.path.join(train_image_path, name + '.jpg')
                dst_train_Label = os.path.join(train_label_path, name + '.txt')
                shutil.copyfile(srcImage, dst_train_Image)
                shutil.copyfile(srcLabel, dst_train_Label)
                file_train.write(dst_train_Image + '\n')
            elif i in val:
                dst_val_Image = os.path.join(val_image_path, name + '.jpg')
                dst_val_Label = os.path.join(val_label_path, name + '.txt')
                shutil.copyfile(srcImage, dst_val_Image)
                shutil.copyfile(srcLabel, dst_val_Label)
                file_val.write(dst_val_Image + '\n')
            else:
                dst_test_Image = os.path.join(test_image_path, name + '.jpg')
                dst_test_Label = os.path.join(test_label_path, name + '.txt')
                shutil.copyfile(srcImage, dst_test_Image)
                shutil.copyfile(srcLabel, dst_test_Label)
                file_test.write(dst_test_Image + '\n')
        except Exception as e:
            print(f"Error copying files: {e}")
            continue

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

if __name__ == "__main__":
    main()

检查一遍看看数据集大致数量有没有问题即可

编写配置文件

在这里插入图片描述

yolov9-yaml文件在在这里插入图片描述
在train.py文件中有启动命令各种参数的解释
这边给个中文解释

def parse_opt(known=False):
    parser = argparse.ArgumentParser()
    # parser.add_argument('--weights', type=str, default=ROOT / 'yolo.pt', help='initial weights path')
    # parser.add_argument('--cfg', type=str, default='', help='model.yaml path')
    parser.add_argument('--weights', type=str, default='', help='初始权重路径')
    parser.add_argument('--cfg', type=str, default='yolo.yaml', help='模型配置文件路径')
    parser.add_argument('--data', type=str, default=ROOT / 'data/coco128.yaml', help='数据集配置文件路径')
    parser.add_argument('--hyp', type=str, default=ROOT / 'data/hyps/hyp.scratch-low.yaml', help='超参数文件路径')
    parser.add_argument('--epochs', type=int, default=100, help='总训练周期数')
    parser.add_argument('--batch-size', type=int, default=16, help='所有 GPU 的总批量大小,-1 表示自动批量大小')
    parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=640, help='训练、验证图像大小(像素)')
    parser.add_argument('--rect', action='store_true', help='使用矩形训练')
    parser.add_argument('--resume', nargs='?', const=True, default=False, help='恢复最近的训练')
    parser.add_argument('--nosave', action='store_true', help='仅保存最终检查点')
    parser.add_argument('--noval', action='store_true', help='仅在最终周期验证')
    parser.add_argument('--noautoanchor', action='store_true', help='禁用自动锚点')
    parser.add_argument('--noplots', action='store_true', help='不保存绘图文件')
    parser.add_argument('--evolve', type=int, nargs='?', const=300, help='对 x 代进行超参数演化')
    parser.add_argument('--bucket', type=str, default='', help='gsutil 存储桶')
    parser.add_argument('--cache', type=str, nargs='?', const='ram', help='图像 --cache ram/disk')
    parser.add_argument('--image-weights', action='store_true', help='使用加权图像选择进行训练')
    parser.add_argument('--device', default='', help='cuda 设备,例如 0 或 0,1,2,3 或 cpu')
    parser.add_argument('--multi-scale', action='store_true', help='变动 img-size 的 +/- 50%%')
    parser.add_argument('--single-cls', action='store_true', help='将多类数据训练为单类')
    parser.add_argument('--optimizer', type=str, choices=['SGD', 'Adam', 'AdamW', 'LION'], default='SGD', help='优化器')
    parser.add_argument('--sync-bn', action='store_true', help='使用 SyncBatchNorm,仅在 DDP 模式下可用')
    parser.add_argument('--workers', type=int, default=8, help='最大数据加载器工作进程数(每个 RANK 在 DDP 模式下)')
    parser.add_argument('--project', default=ROOT / 'runs/train', help='保存至项目/名称')
    parser.add_argument('--name', default='exp', help='保存至项目/名称')
    parser.add_argument('--exist-ok', action='store_true', help='现有项目/名称可用,不递增')
    parser.add_argument('--quad', action='store_true', help='四重数据加载器')
    parser.add_argument('--cos-lr', action='store_true', help='余弦学习率调度器')
    parser.add_argument('--flat-cos-lr', action='store_true', help='平坦余弦学习率调度器')
    parser.add_argument('--fixed-lr', action='store_true', help='固定学习率调度器')
    parser.add_argument('--label-smoothing', type=float, default=0.0, help='标签平滑 epsilon')
    parser.add_argument('--patience', type=int, default=100, help='EarlyStopping 容忍度(不改善的周期数)')
    parser.add_argument('--freeze', nargs='+', type=int, default=[0], help='冻结层:backbone=10, first3=0 1 2')
    parser.add_argument('--save-period', type=int, default=-1, help='每 x 个周期保存一次检查点(如果 < 1 则禁用)')
    parser.add_argument('--seed', type=int, default=0, help='全局训练种子')
    parser.add_argument('--local_rank', type=int, default=-1, help='自动 DDP 多 GPU 参数,不要修改')
    parser.add_argument('--min-items', type=int, default=0, help='实验性')
    parser.add_argument('--close-mosaic', type=int, default=0, help='实验性')

    # Logger 参数
    parser.add_argument('--entity', default=None, help='实体')
    parser.add_argument('--upload_dataset', nargs='?', const=True, default=False, help='上传数据,"val" 选项')
    parser.add_argument('--bbox_interval', type=int, default=-1, help='设置边界框图像记录间隔')
    parser.add_argument('--artifact_alias', type=str, default='latest', help='要使用的数据集 artifact 版本')

    return parser.parse_known_args()[0] if known else parser.parse_args()

其中路径类的参数有

–weights : yolov9-c.pt的绝对路径
–cfg : yolov9-c.yaml的绝对路径
–data : data.yaml的绝对路径
–project : 训练后文件保存的绝对路径

秉持着先跑起来再说的信念,就先修改这几个,其他都用默认参数

编写启动脚本

#!/bin/bash

# 设置绝对路径
WEIGHTS_PATH="/absolute/path/to/yolov9-c.pt"
CFG_PATH="/absolute/path/to/yolov9-c.yaml"
DATA_PATH="/absolute/path/to/data.yaml"
HYP_PATH="/absolute/path/to/hyp.scratch-high.yaml"
PROJECT_PATH="/absolute/path/to/save/project"

# 启动训练
python train.py --weights $WEIGHTS_PATH --cfg $CFG_PATH --data $DATA_PATH --hyp $HYP_PATH --project $PROJECT_PATH

–hyp 这个默认路径在data/hyp下面,但是下面只有一个hyp.scratch-high.yaml文件,而默认的是hyp.scratch-low.yaml,所以这里先改成hyp.scratch-high.yaml,

这里又遇到了容器共享内存的问题,这个问题在之前使用kohya的训练脚本时候也遇到了这个问题

在这里插入图片描述

docker的共享内存默认是64MB,在docker启动的时候忘记添加命令了

--shm-size 5g,或者在docker-compose下面加上  shm_size: 5gb

1.查看容器ID号:
docker ps -a

找到对应容器

2.获取完整容器名:
docker inspect <container_id> | grep Id

3.修改对应容器的配置文件:
vim /var/lib/docker/containers/<full_container_id>/hostconfig.json
找到ShmSize修改对应数值,单位为KB,默认是64M,即67108864(6410241024)

4.重启docker服务
systemctl restart docker

但是重启docker服务的代价很大,如果有很多个容器在运行建议不要重启,所以还是docker cp 把里面的东西拷贝出来,然后重新拉起一个镜像

在这里插入图片描述
成功修改,再运行一遍

报错
attributeerror: ‘FreeTypeFont‘ object has no attribute ‘getsize‘
上述划分数据集的作者也在文章中写到了
在这里插入图片描述
默认的batchsize是16,24g显存吃不消,改成8也吃不消,改成4好一些,(因为是矩阵计算,一般2乘方会好一些2,4,8,16这样的,具体内容在计算机组成原理里提到过,(我忘了TT),这次我改成了6,测试一下4和6的区别)
在这里插入图片描述
Batch-size为6时,time 2.13,mem 11g

在这里插入图片描述
Batch-size为8时 time2.10,mem 15g在这里插入图片描述
时间差不多,显存比较吃紧
100轮3个小时左右

查看结果

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/b2ae6fcbdbce4788a014373b851d134b.jpeg

在这里插入图片描述
标注全是反的。。
刚刚看了一下代码,跑的是yolov9-c-convert,应该跑yolov9-c.pt的文件,
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里更正一个地方,结果的标注有问题可能是和yaml文件配置有关,在后续文章中会重新训练

  • 40
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值