工程目录:
D:\Edge\download\yolov5-v6.0\yolov5-v6.0环境:Python 3.8 + YOLOv5 v6.0;依赖安装pip install -r requirements.txt部署目标:泰山派(RK3566)NPU
目录
一、数据集长什么样
dataset/
├─ train/{images,labels} # 训练集
├─ valid/{images,labels} # 验证集
├─ test/{images,labels} # 测试集
├─ data.yaml # 类别定义
└─ yolov5s.pt # 预训练权重(可选放这里)
- 图片和标签同名一一对应(
a.jpg↔a.txt)。 - 标签为 YOLO 格式,一行一个目标,数值全部归一化到
0~1:
<class_id> <x_center> <y_center> <width> <height>
- 没有目标的图片:标签文件留空(不要删文件)。
data.yaml决定类别:
path: D:/Edge/download/yolov5-v6.0/yolov5-v6.0/dataset
train: train/images
val: valid/images
test: test/images
nc: 6
names: ['person', 'hardhat', 'no-hardhat', 'safety-vest', 'no-safety-vest', 'fire']
names的下标 = 标签里的class_id,这个对应关系是全部工作的基准,别弄错。
二、怎么训练
1. 直接开跑(CPU 友好版)
python train.py --weights dataset/yolov5s.pt --cfg models/yolov5s.yaml --data dataset/data.yaml --epochs 50 --batch-size 8 --img 416 --device cpu --workers 4 --patience 15 --name my_v1
有独显就把 --device cpu 改成 --device 0,并把 --img 640 --batch-size 16 加回去。
2. 参数说明
| 参数 | 含义 | 建议 |
|---|---|---|
--weights |
预训练权重 | yolov5s.pt,迁移学习收敛快 |
--cfg |
模型结构 | models/yolov5s.yaml |
--data |
数据集配置 | 指向 data.yaml |
--epochs |
训练轮数 | GPU 100;CPU 40~50 |
--batch-size |
批大小 | 内存不够就调小 |
--img |
输入尺寸 | CPU 用 416,GPU 用 640 |
--device |
设备 | 0 = GPU,cpu = CPU |
--workers |
数据加载线程 | CPU 训练用 4 |
--patience |
早停轮数 | 15(默认 100 形同不早停) |
--name |
输出文件夹名 | 自定义,方便区分 |
3. ⚠️ CPU 训练时间(重点,别踩)
实测(约 400 张图、yolov5s、CPU):
| 配置 | 每轮耗时 | 100 轮总计 |
|---|---|---|
img 640 + batch 16 |
~8.8 min | ≈ 14.7 小时 |
img 416 + batch 8 |
~3.7 min | ≈ 6 小时(50 轮 ≈ 3 小时) |
结论:CPU 训练一律用 --img 416 --epochs 50,别用 640/100 轮。 输入尺寸降到 416,计算量只有 640 的约 42%。
4. 训练结果在哪
runs/train/<name>/:
weights/best.pt→ 推理 / 部署用(验证集最优)weights/last.pt→ 断点续训用(最后一轮)results.csv→ 每轮的 loss / mAP 数值(可直接看)results.png/PR_curve.png/confusion_matrix.png→ 评估图,训练跑完才生成
5. 续训
python train.py --resume runs/train/my_v1/weights/last.pt
三、怎么评估
1. 看每类指标(最重要)
python val.py --weights runs/train/my_v1/weights/best.pt --data dataset/data.yaml --img 416
输出示例:
Class Images Labels P R mAP@.5 mAP@.5:.95
all 228 1161 0.742 0.672 0.705 0.399
person 228 155 0.416 0.194 0.188 0.079 ← 偏低
hardhat 228 372 0.819 0.704 0.762 0.41
fire 228 77 0.805 0.701 0.717 0.434
看 mAP@.5:
| 数值 | 评价 |
|---|---|
| ≥ 0.7 | 好,可用 |
| 0.4 ~ 0.7 | 一般,可接受 |
| < 0.4 | 差,需要补数据或重训 |
2. 看画面(可视化)
# 整个测试集
python detect.py --weights runs/train/my_v1/weights/best.pt --source dataset/test/images --img 416
# 单张图
python detect.py --weights runs/train/my_v1/weights/best.pt --source 你的图.jpg --img 416
# 摄像头实时
python detect.py --weights runs/train/my_v1/weights/best.pt --source 0 --img 416 --view-img
结果在 runs/detect/expN/。
3. 只看某个类
python val.py --weights runs/train/my_v1/weights/best.pt --data dataset/data.yaml --img 416 --classes 5
四、新增识别目标要改哪些地方 ⭐
核心原则:类别总数 nc 和类别顺序,在下面几处必须完全一致。
以「原来 5 类,加一个 fire → 6 类」为例:
1)改 data.yaml(必改)
nc: 6
names: ['person', 'hardhat', 'no-hardhat', 'safety-vest', 'no-safety-vest', 'fire']
names 的下标就是标签里的 class_id。
2)改标签 class_id(必改)
新类别编号必须是 0 ~ nc-1 内、且与 names 下标对应、不能和已有类别冲突。
例:火灾标签原来是 0/1/2,统一改成 5(= fire 在 names 中的下标):
0 0.51 0.44 0.31 0.22 → 5 0.51 0.44 0.31 0.22
3)删缓存(改过标签就删)
删除 dataset/train/labels.cache、dataset/valid/labels.cache,否则 YOLOv5 读旧缓存,新标签不生效。
4)重新训练(必做)
nc 一变,网络检测头输出维度就变了,旧权重不能直接配新 data.yaml,必须重训。
5)部署端同步改(必改)
RKNN 转换脚本和板端后处理里的类别表要一致,否则类别名会错位:
CLASSES = ("person", "hardhat", "no-hardhat", "safety-vest", "no-safety-vest", "fire")
五、数据不够 / 类别不平衡怎么办
1. 先看差多少
用 val.py 看每类 mAP,再数各类样本数。经验值:
| 类别目标 | 数量 |
|---|---|
| 每类 train | ≥ 200 张(越均衡越好) |
| 每类 valid / test | 各 train 的 1/3 ~ 1/8 |
train : valid : test = 7 : 2 : 1(或 8:1:1),按每类 200 张 train 算,每类总量约 286 张。
2. mAP 低的常见原因
| 现象 | 原因 | 对策 |
|---|---|---|
| 某类样本远少于其他类 | 类别不平衡 | 补该类数据,或让它配额更高 |
| 召回低、漏检多 | 样本不够 / 目标小 | 补数据;或提高 --img |
| 精度低、误检多 | 标注不一致(不同数据集标准不同) | 清洗数据,统一标注口径 |
| 多个类互相混淆 | 类间差异小(如"戴帽/没戴帽") | 每类都要足够多的正反样本 |
3. 去哪找数据
优先找已标注的数据集,直接合并:
- Roboflow Universe:https://universe.roboflow.com/search?q=PPE
搜
PPE detection、safety helmet、safety vest、no hard hat、fire detection等; 下载时选 YOLOv5 / YOLOv8 (txt) 格式。 - Kaggle:搜同名关键词。
4. 合并多个数据集(关键)
不同数据集的类别顺序几乎肯定不一样,必须重映射 class_id 后再合并。步骤:
- 建一张映射表(源类别 → 目标类别号),未列出的源类别丢弃;
- 图片+标签拷入
dataset/train|valid|images|labels,同名成对; - 按上表改写标签的
class_id; - 删
labels.cache; - 重新训练。
两个必须处理的细节:
- 防数据泄漏:Roboflow 会导出同一张原图的增强副本(文件名前缀相同、只有
.rf.<hash>不同)。切分 train/valid 时,同一原图的所有副本必须进同一个 split,否则验证集分数虚高。 - Windows 超长路径:某些来源的文件名超过 260 字符,Python 读取会报错。读写时用
\\?\前缀("\\\\?\\" + os.path.abspath(p)),并另存为短文件名。
六、部署链路:pt → onnx → rknn → 泰山派
泰山派芯片 RK3566,只能用
.rknn模型。转换必须在 PC(x86_64 Linux) 上做,板子上装不了转换工具。
1. 导出 ONNX(在 Windows 上)
先改两个文件(一次性):
models/yolo.py:Detect.forward增加 RKNN 分支,输出 3 个原始张量:
def forward(self, x):
z = []
if os.getenv('RKNN_model_hack', '0') != '0': # RKNN 导出路径
for i in range(self.nl):
x[i] = torch.sigmoid(self.m[i](x[i]))
return x
# ... 下面是原有逻辑,保持不变
export.py:在run()里加一行
os.environ['RKNN_model_hack'] = 'npu_2'
然后导出:
python export.py --weights runs/train/my_v1/weights/best.pt --img 416 --batch 1 --include onnx --opset 12
产物 best.onnx,输出应为 3 个张量(以 416 为例):
input: [1, 3, 416, 416]
outputs: [1, 33, 52, 52] [1, 33, 26, 26] [1, 33, 13, 13]
# 33 = 3锚点 × (5 + nc=6)
2. 准备转换材料
需要 4 样东西(放同一个文件夹):
| 文件 | 说明 |
|---|---|
best.onnx |
上一步产物 |
calib/ + calib.txt |
100~300 张校准图 + 清单(每行一个路径) |
rknn_convert.py |
转换脚本 |
classes.txt |
类别名(板端后处理用) |
3. Ubuntu 上转 RKNN
环境(conda 或 venv,Python 3.8):
# 用现有环境即可,例如:
source ~/Val-Env/RK3566/bin/activate
python -c "import rknn; print(rknn.__file__)"
若没有环境:
python3.8 -m venv ~/rknn_env
source ~/rknn_env/bin/activate
pip install -U pip -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install rknn-toolkit2 -i https://pypi.tuna.tsinghua.edu.cn/simple
⚠️ 版本必须匹配:
rknn-toolkit2版本要 ≤ 板端 runtime 版本(板端用strings librknnrt.so | grep version查),否则板子上加载会报版本不匹配。板端是 1.5.x,就用rknn-toolkit2==1.5.2。
转换脚本:
import os
from rknn.api import RKNN
ONNX, RKNN_OUT, DATASET = 'best.onnx', 'best.rknn', 'calib.txt'
rknn = RKNN(verbose=True)
rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]],
target_platform='rk3566', # 泰山派
quantized_dtype='asymmetric_quantized-8')
assert rknn.load_onnx(model=ONNX) == 0, 'load_onnx failed'
assert rknn.build(do_quantization=True, dataset=DATASET) == 0, 'build failed'
assert rknn.export_rknn(RKNN_OUT) == 0, 'export failed'
rknn.release()
print('OK ->', RKNN_OUT, '%.1f MB' % (os.path.getsize(RKNN_OUT) / 1048576))
python rknn_convert.py # 生成 best.rknn(YOLOv5s int8 约 4~8 MB)
4. 泰山派上运行
① 板子实况
| 项 | 值 |
|---|---|
| 芯片 | RK3566(泰山派) |
| 系统 | Ubuntu 20.04.6 LTS aarch64 |
| 内核 | 4.19.232 |
| runtime 库 | /usr/lib/librknnrt.so |
| 现成 demo | ~/rknn_yolov5/(rknn_yolov5_cam.cpp + 已编译的 rknn_yolov5_cam) |
② 查 runtime 版本(确认与 toolkit 匹配)
strings /usr/lib/librknnrt.so | grep -i "librknnrt version"
板端 ≥ 1.5.2 即可直接用 1.5.2 转出的模型(runtime 向下兼容旧模型)。
③ 传模型
# 在 Ubuntu(PC) 上执行;板子 IP 用 ip addr 查
scp ~/YOLO/my_all6/best.rknn huangwenbo@<板子IP>:~/rknn_yolov5/
④ 改 demo 的类别定义(在 rknn_yolov5_cam.cpp 里)
#define OBJ_CLASS_NUM 6 /* 必须等于模型的 nc */
static const char *CLASSES[] = {
"person", "hardhat", "no-hardhat",
"safety-vest", "no-safety-vest", "fire"
};
const char *model_path = "./best.rknn"; /* 指向新模型 */
⑤ 后处理加类别过滤(只看火;模型不用改、不用重训)
/* 只保留 fire(class_id = 5),其余丢弃 */
if (class_id != 5) continue;
也可做成"火 + 安全帽":
if (class_id != 1 && class_id != 5) continue; /* 1=hardhat, 5=fire */
⑥ 编译并运行(沿用你原来的编译方式 / CMakeLists)
cd ~/rknn_yolov5
./rknn_yolov5_cam # 或按原步骤重新编译后再跑
首次运行留意打印的 model input height/width,确认是 416(与导出一致)。
若换用 Rockchip 官方例程:
rknpu2/examples/rknn_yolov5_demo,export LD_LIBRARY_PATH=./lib && ./rknn_yolov5_demo model/RK356X/best.rknn 图.jpg
七、常见操作速查
| 我要做的事 | 改哪里 |
|---|---|
| 新增类别 | data.yaml(nc/names) + 标签 class_id + 删 cache + 重训 + 部署端 CLASSES |
| 合并类别(3 类合成 1 类) | 标签 class_id 统一 + data.yaml + 删 cache + 重训 + 部署端 CLASSES |
| 只加数据、不加类别 | 图片/标签丢进对应目录 + 删 cache + 重训(nc 不变) |
| 换数据集路径 | 只改 data.yaml 的 path/train/val/test |
| 某类 mAP 低 | 补该类数据(按 7:2:1 配额)+ 重训 |
| 只想检测部分类别 | 推理时加 --classes(如 --classes 5),板端后处理加白名单 |
| 模型上板 | 导出 onnx → 转 rknn → 传板子 → 跑 demo |
八、常见坑(实战踩坑)
训练类
- mAP 一直是 0:标签
class_id超出nc范围,或nc与标签对不上。 - 新标签不生效:忘了删
labels.cache。 - 检测框类别名错位:
names顺序与标注时的类别顺序不一致。 - 图片被忽略:图片没有同名
.txt标签,会被当成负样本或跳过。 - 改了 nc 却用旧权重:必须重新训练,不能只改 yaml。
- CPU 训练跑到天荒地老:用了
img 640 + epochs 100,务必改img 416 + epochs 50。 - 验证分数虚高:Roboflow 增强副本被拆到了 train 和 valid 两边(数据泄漏),切分时要按原图分组。
部署类
- 在板子上装 rknn-toolkit2:装不了,转换只能在 PC(x86_64) 上做;板子只有 runtime。
- 板端加载报 version mismatch:toolkit 版本高于板端 runtime,降 toolkit 版本重转。
python找不到 rknn 包:环境没激活。用find ~ -maxdepth 4 -name activate -path "*/bin/*"找已建好的 venv,再source .../bin/activate。- RKNN 默认目标平台是 rk3588:必须显式写
target_platform='rk3566',否则板端跑不了。 - ONNX 输出只有一个张量:忘了改
models/yolo.py的Detect.forward或没在export.py里设RKNN_model_hack。 - 量化后精度掉了:校准图太少或不具代表性,建议 100~300 张、覆盖各场景。
- 只想看火却一堆框:后处理没做类别过滤,加上
if (class_id != 5) continue;。
九、完整流程清单(照做即可)
阶段 0 · 环境准备
cd D:\Edge\download\yolov5-v6.0\yolov5-v6.0
pip install -r requirements.txt
阶段 1 · 准备数据集
- 按 一、数据集结构 组织
dataset/ - 确认
data.yaml的nc与names - 每类数量尽量均衡(train : valid : test = 7 : 2 : 1,每类 train ≥ 200)
阶段 2 · 训练
python train.py --weights dataset/yolov5s.pt --cfg models/yolov5s.yaml --data dataset/data.yaml --epochs 50 --batch-size 8 --img 416 --device cpu --workers 4 --patience 15 --name my_v1
阶段 3 · 评估
python val.py --weights runs/train/my_v1/weights/best.pt --data dataset/data.yaml --img 416
python detect.py --weights runs/train/my_v1/weights/best.pt --source dataset/test/images --img 416
mAP 达标(≥0.6)再往下走。
阶段 4 · 导出 ONNX(Windows)
- 改
models/yolo.py的Detect.forward(加 RKNN 分支) - 改
export.py(加os.environ['RKNN_model_hack'] = 'npu_2')
python export.py --weights runs/train/my_v1/weights/best.pt --img 416 --batch 1 --include onnx --opset 12
阶段 5 · 转 RKNN(Ubuntu x86_64)
- 新建工作目录,放入
best.onnx+calib/+calib.txt+rknn_convert.py - 激活已有环境:
source ~/Val-Env/RK3566/bin/activate(rknn-toolkit2 1.5.2) - 转换:
cd ~/YOLO/my_all6
python rknn_convert.py # 产物 best.rknn,约 4~8 MB
阶段 6 · 板端部署(泰山派)
scp best.rknn huangwenbo@<板子IP>:~/rknn_yolov5/- 改
rknn_yolov5_cam.cpp:OBJ_CLASS_NUM/CLASSES[]/ 模型路径 - 后处理加类别过滤:
if (class_id != 5) continue; - 编译运行,确认输入尺寸 416、框和类别名正确
十、本项目实际档案(可作参照)
| 项 | 实际值 |
|---|---|
| 芯片 / 板子 | RK3566 / 泰山派 |
| 类别 | 6 类:person, hardhat, no-hardhat, safety-vest, no-safety-vest, fire |
| 数据集规模 | train 639 / valid 228 / test 115 张;每类 train 200 / valid 57 / test 29 |
| 训练配置 | yolov5s,img 416,batch 8,epochs 50,CPU |
| 训练耗时 | 约 5 小时(403 张时约 3 小时;640+100轮则会到 14.7 小时) |
| 最终指标 | mAP@0.5 = 0.705,mAP@0.5:0.95 = 0.399 |
| 各类 mAP@0.5 | safety-vest 0.923|no-hardhat 0.884|hardhat 0.762|no-safety-vest 0.755|fire 0.717|person 0.188 |
| ONNX | opset 12,输入 [1,3,416,416],输出 3 个张量 |
| RKNN | rknn-toolkit2 1.5.2,target_platform='rk3566',int8,产物 7.6 MB |
| 板端 | Ubuntu 20.04.6 aarch64,/usr/lib/librknnrt.so,demo 在 ~/rknn_yolov5/ |
经验结论:
- CPU 训练一律
img 416 + epochs 50,能省 3~5 倍时间; - 数据均衡比堆量更重要:把每类拉到 200 张后,整体 mAP 从 0.403 → 0.705;
- 稀少/异构的类(如 person)光靠堆量不够,其瓶颈在标注口径不一致;
- 模型保持 6 类、在部署端做类别过滤,比"重训单类模型"更灵活;
- toolkit 版本必须 ≤ 板端 runtime,建议先在板子上
strings librknnrt.so | grep version确认。