主页
文章
分类
标签
关于
windows下yolov5的环境配置以及基础训练
发布于: 2025-2-28   更新于: 2025-2-28   收录于: yolo
文章字数: 3688   阅读时间: 8 分钟   阅读量:

前言

硬件环境:

  • 笔记本电脑 win11 23h2
  • cpu: amd r7 6800H
  • gpu: rtx3060 6g laptop
  • 32g内存

软件环境:

  • python 3.8
  • yolov5 6.0

yolov5 6.0的源码请到官方github下载整个Source code压缩包 点我跳转

python环境搭建

python环境这里我使用conda进行虚拟环境的搭建,conda的具体使用教程请参考文章

我们打开powershell或者Anaconda Prompt,输入代码创建python3.8的虚拟环境

1
2
# 创建py3.8虚拟环境
conda create -n yolov5_60 python==3.8

结束之后,我们切换到上述环境,并验证python环境和python库情况

1
2
3
4
5
6
7
8
# 切换环境
conda activate yolov5_60
# 进入python,查看是否为py3.8
python
# 退出python
exit()
# 查看当前第三方库,是否干净,有无串库情况(如果以前没下过py3.8的第三方库,不会出现这种情况)
pip list

安装完新的py环境后,我们进入到yolov5的文件夹,安装指定的py库

1
2
# 在yolov5文件夹中运行
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

注意这里的pytorch默认安装的是cpu版本,如果需要gpu的请自行下载。 同时pytorch版本可能会过高导致一些bug,具体bug请看下面注意事项

这里还有个问题 默认下载的numpy 版本太高,有些东西被弃用,在后面运行代码的时候会出错,所以我们安装旧版本的

1
2
pip uninstall numpy
pip install numpy==1.22

当全部安装完成,我们的py环境配置到此结束

运行案例

我们可以运行detect.py来检验一下运行效果

1
2
# 在yolov5文件夹中运行
python detect.py --source data/images/bus.jpg --weights yolov5s.pt --img 640

在刚开始的情况下我们会下载yolov5s,但如果你的网速不佳的话,可以手动下载,去前面下载代码的地方下载模型。

当全部运行结束,它会提示我们result保存在了run/detect下,我们打开里面的图片,就是推测结果。

模型训练

这里我采用的是摔倒检测的训练集,点我跳转下载地址

下载完数据集后,我们将数据集放在yolov5文件夹下的dataset当中,具体位置如下所示: alt text

防止好训练集后,我们要对训练集进行清洗处理和划分处理

清洗数据集

大致浏览数据集,有img、txt、xml三个文件夹,我们主要用到img和txt两个文件夹。

下载地址上并没有告知我们有多少类别,我们可以写个脚本,自行检索获得,新建一个scripts/nc_nums.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import os
from tqdm import tqdm

# 标注文件所在的目录
label_dir = 'xxx'

# 用于存储所有出现的类别索引
class_indices = set()
# 用于存储每个类别出现的次数
class_count = {}

# 获取所有以 .txt 结尾的文件
txt_files = [filename for filename in os.listdir(label_dir) if filename.endswith('.txt')]

# 使用 tqdm 创建进度条
for filename in tqdm(txt_files, desc="Processing annotation files", unit="file"):
    file_path = os.path.join(label_dir, filename)
    try:
        with open(file_path, 'r') as f:
            lines = f.readlines()
            for line in lines:
                # 分割每行数据,获取第一个数字(类别索引)
                parts = line.strip().split()
                if parts:
                    class_index = int(parts[0])
                    class_indices.add(class_index)
                    # 统计每个类别出现的次数
                    if class_index in class_count:
                        class_count[class_index] += 1
                    else:
                        class_count[class_index] = 1
    except Exception as e:
        print(f"读取文件 {file_path} 时出错: {e}")

# 类别数量为最大索引加 1
num_classes = max(class_indices) + 1 if class_indices else 0

# 输出类别数量
print(f"类别数量: {num_classes}")

# 输出每个类别出现的次数
print("每个类别出现的次数:")
for class_index, count in sorted(class_count.items()):
    print(f"类别 {class_index}: {count} 次")

填入自己的数据集位置,然后进行检索,我们可以得知该数据集总共有2个类别,但是这还是有问题的,实际上只有一个类别。 因为txt中只有标签1,并没有标签0,所以识别成2个类别,我们正常训练的数据集都是从类别0开始的,所以这个数据有点问题,但我们也可以写一个脚本,将里面的所有类别1转化成类别0,新建一个scripts/change1to1.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import os

# 标注文件所在的目录
label_dir = 'xxxx'

# 遍历标注文件目录
for filename in os.listdir(label_dir):
    if filename.endswith('.txt'):
        file_path = os.path.join(label_dir, filename)
        new_lines = []
        try:
            # 读取文件内容
            with open(file_path, 'r') as f:
                lines = f.readlines()
                for line in lines:
                    parts = line.strip().split()
                    if parts:
                        class_index = int(parts[0])
                        if class_index == 1:
                            # 将类别标签 1 修改为 0
                            parts[0] = '0'
                        new_lines.append(' '.join(parts) + '\n')

            # 写入修改后的内容
            with open(file_path, 'w') as f:
                f.writelines(new_lines)
            print(f"文件 {file_path} 已修改。")
        except Exception as e:
            print(f"处理文件 {file_path} 时出错: {e}")

填入数据集位置,运行脚本,将所有的类别1转为类别0,运行结束,我们再重新运行前面的检测脚本,就会发现只有一个类别了。

划分训练集

我们将这些训练集按照8:1:1来划分为训练集、验证集和测试集,我们新建脚本scripts/split.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import os
import random
import shutil
from tqdm import tqdm
import logging

# 配置日志记录
logging.basicConfig(filename='data_split.log', level=logging.INFO,
                    format='%(asctime)s - %(levelname)s - %(message)s')

# 数据集路径
data_dir = 'xxx\\yolov5-6.0\\dataset\\FallDataset'
img_dir = os.path.join(data_dir, 'img')
txt_dir = os.path.join(data_dir, 'txt')

# 输出路径
output_dir = 'xxx\\yolov5-6.0\\dataset\\FallDataset_split'
train_img_dir = os.path.join(output_dir, 'images', 'train')
val_img_dir = os.path.join(output_dir, 'images', 'val')
test_img_dir = os.path.join(output_dir, 'images', 'test')
train_label_dir = os.path.join(output_dir, 'labels', 'train')
val_label_dir = os.path.join(output_dir, 'labels', 'val')
test_label_dir = os.path.join(output_dir, 'labels', 'test')

# 创建输出文件夹
os.makedirs(train_img_dir, exist_ok=True)
os.makedirs(val_img_dir, exist_ok=True)
os.makedirs(test_img_dir, exist_ok=True)
os.makedirs(train_label_dir, exist_ok=True)
os.makedirs(val_label_dir, exist_ok=True)
os.makedirs(test_label_dir, exist_ok=True)

# 获取所有图像文件
img_files = os.listdir(img_dir)
random.shuffle(img_files)

# 划分数据集
train_size = int(len(img_files) * 0.8)
val_size = int(len(img_files) * 0.1)
test_size = len(img_files) - train_size - val_size

train_files = img_files[:train_size]
val_files = img_files[train_size:train_size + val_size]
test_files = img_files[train_size + val_size:]

# 复制图像和标注文件到相应的文件夹
def copy_files(files, img_dest, label_dest):
    for file in tqdm(files, desc=f"Copying files to {img_dest}", unit="file"):
        img_src = os.path.join(img_dir, file)
        label_name = os.path.splitext(file)[0] + '.txt'
        label_src = os.path.join(txt_dir, label_name)
        try:
            # 检查标注文件是否存在
            if os.path.exists(label_src):
                shutil.copy(img_src, img_dest)
                shutil.copy(label_src, label_dest)
            else:
                logging.warning(f"No corresponding label file found for {img_src}")
                print(f"Warning: No corresponding label file found for {img_src}")
        except Exception as e:
            logging.error(f"Error copying {img_src} or {label_src}: {e}")
            print(f"Error copying {img_src} or {label_src}: {e}")

copy_files(train_files, train_img_dir, train_label_dir)
copy_files(val_files, val_img_dir, val_label_dir)
copy_files(test_files, test_img_dir, test_label_dir)

填入数据集位置和输出位置,然后运行脚本,我们就得到了划分好的数据集。

开始训练

搞定数据集后,我们开始训练,首先需要编写一个data/falling.yaml来确定训练集位置和类别标签

1
2
3
4
5
6
7
train: xxx/yolov5-6.0/dataset/Falldataset_split/images/train
val: xxx/yolov5-6.0/dataset/Falldataset_split/images/val
test: xxx/yolov5-6.0/dataset/Falldataset_split/images/test


nc: 1  # 类别数量,只有一个类别(人体摔倒)
names: ['falling']

编写好后,我们来开始训练,在命令行输入如下命令:

1
python train.py --img 640 --batch 8 --epochs 50 --data data/falling.yaml --cfg models/yolov5s.yaml --weights weights/yolov5s.pt

我这里的yolov5s.pt是手动下载,然后放到weights下的,默认下载是在yolov5的根目录下。

参数具体解释:

  • –img 640
    • 含义:指定训练和验证过程中输入图像的尺寸大小。在目标检测任务里,通常需要将不同尺寸的输入图像统一调整为固定大小,这样便于模型处理。这里将图像的长边或短边统一调整为 640 像素,图像的宽高比会保持不变,不足的部分会进行填充。
    • 作用:统一图像尺寸可以保证模型输入的一致性,提高训练效率和稳定性。不同的图像尺寸会影响模型的计算量和检测精度,一般来说,较大的图像尺寸能提供更多的细节信息,但会增加计算量和内存占用。
  • –batch 8
    • 含义:指定每个训练批次(batch)中包含的图像数量。一个批次的图像会一起输入到模型中进行前向传播和反向传播,更新模型的参数。
    • 作用:批量大小会影响训练的速度和模型的泛化能力。较大的批量大小可以更充分地利用 GPU 的并行计算能力,加快训练速度,但可能会导致内存不足;较小的批量大小则可以增加模型参数更新的频率,有助于模型更好地收敛,但训练速度会相对较慢。
  • –epochs 50
    • 含义:指定训练的轮数(epoch)。一个轮次表示将整个训练数据集完整地过一遍,模型会在每个轮次中对数据集中的所有图像进行训练,并更新模型的参数。
    • 作用:训练轮数会影响模型的训练效果。如果轮数太少,模型可能无法充分学习到数据集中的特征,导致欠拟合;如果轮数太多,模型可能会过度学习训练数据中的噪声,导致过拟合。
  • –data data/falling.yaml
    • 含义:指定数据集的配置文件路径。data/falling.yaml 是一个 YAML 格式的文件,其中包含了训练集、验证集和测试集的路径,以及数据集的类别数量和类别名称等信息。
    • 作用:YOLOv5 通过这个配置文件来加载和处理数据集,确保模型能够正确地读取和使用训练数据。
  • –cfg models/yolov5s.yaml
    • 含义:指定模型的配置文件路径。models/yolov5s.yaml 是一个 YAML 格式的文件,其中定义了 YOLOv5 模型的网络结构,包括卷积层、池化层、激活函数等的参数和连接方式。
    • 作用:YOLOv5 根据这个配置文件来构建模型的网络结构,不同的配置文件可以创建不同规模和复杂度的模型,例如 yolov5s.yaml 对应较小的 YOLOv5s 模型,适合在资源有限的设备上使用。
  • –weights weights/yolov5s.pt
    • 含义:指定预训练模型的权重文件路径。weights/yolov5s.pt 是一个 PyTorch 模型的权重文件,其中包含了预训练模型的参数。
    • 作用:使用预训练模型的权重可以加速模型的训练过程,因为预训练模型已经在大规模数据集上学习到了一些通用的特征。在训练新的数据集时,可以将预训练模型的权重作为初始值,然后在新数据集上进行微调,这样可以更快地收敛到较好的性能。

完成之后我们来运行train.py,刚开始它会检索数据集,将训练集和验证集搬到gpu上,这个过程上有很多的warning,不用去管它,这是因为数据集中有很多不好的图片,它会自动忽略,装载完数据集后,就开始一轮轮训练了,我们只要等待训练结束即可。

模型使用

如何使用我们训练完成之后的模型呢,与前面一样,只需要将模型的位置换成刚刚训练出来的即可:

1
python detect.py --weights runs/train/exp/weights/best.pt --source data/images

注意事项

在训练的时候如果报错:

1
RuntimeError: result type Float can‘t be cast to the desired output type __int64

这是因为pytorch的版本过高,不支持float直接转换为long int 型数据类型。

两种解决方案:

  • 安装旧版本的pytorch(但具体要自己试)
  • 手动转换数据类型

这里介绍手动转化数据类型的方法 打开yolov5文件夹下的utils/loss.py,然后找到

1
2
3
4
# 大概在173行,也可借助vscode的ctrl+h进行查找
...
gain = torch.ones(7, device=targets.device)  # normalized to gridspace gain
...

在后面加一个long(),将其替换成:

1
gain = torch.ones(7, device=targets.device).long()  # normalized to gridspace gain

这样就不会报错了。

参考资料

https://blog.csdn.net/weixin_54662911/article/details/143951389