☰
YOLOv5头盔目标检测数据集实战:从格式校验到避坑指南
2026/10/5 11:30:47 网站建设 项目流程

简介:面向YOLOv5目标检测任务的头盔识别数据集,适合需要训练工地、骑行等场景安全帽佩戴检测模型的开发者与科研人员,也适用于计算机视觉课程实验与算法验证。资源包共一百六十个文件,包含八十张真实场景图片与八十个对应的XML标注文件,每个XML文件对应一张图片并记录目标类别与边界框坐标信息,可直接转换为YOLO训练所需的txt标签,省去人工标注环节。压缩包整体仅2.86MB,体积小巧,便于快速下载与迭代测试。已有四百八十二人学习下载,数据覆盖不同角度、光照和背景下的头盔实例,图片与标注一一对应,目录结构简明,初学目标检测者也能轻松上手。利用该数据集可快速完成YOLOv5模型训练、验证与推理,用于安全帽佩戴检测、施工区域人员安全监控等实际项目,也可作为算法对比实验的基准数据。

1. yolo5头盔目标检测的数据集:先搞清楚能不能用再动手

我最早是在一次工地安全巡检项目里正经用YOLOv5跑头盔检测,那时候最耽误时间的不是调参,而是找数据。网上很多头盔检测数据集下回来是VOC格式,得自己写脚本转YOLOv5的归一化坐标,转完发现类别id对不上,再回头排查,一晚上就没了。这份yolo5头盔目标检测的数据集zip是少见的、解压后图片和txt标注都按YOLO格式排好的资源,省掉了最磨人的格式转换环节。对第一次碰YOLOv5的开发者来说,它可以当基线数据跑通整个流程;对已经在做安全帽佩戴检测的工程师来说,它能直接用来验证模型结构和训练参数。先说结论:这份数据集的底子是干净的,直接能训,但直接训练会踩掉几个典型坑——比如Mosaic增强把小目标搞丢、未戴帽类别样本偏少导致漏检。后面我按“认识结构→格式体检→划分→训练→避坑→验证落地”的顺序完整拆一遍。

2. 认识数据集:目录结构、标注格式与样本构成

2.1 解压zip:先看目录树,别急着开训

拿到zip第一件事是解压,但解压完别急着扔进YOLOv5里跑训练。先打开目录树看一眼,确认图片和标注是不是按train/val分好了。常见做法是这套结构:

helmet_dataset/ ├── images/ │ ├── train/ # 训练图片,jpg │ └── val/ # 验证图片,jpg ├── labels/ │ ├── train/ # 训练标注,txt │ └── val/ └── README.txt # 部分打包版本会带说明

这套结构跟我拿到的这份zip基本一致,图片是jpg,标注是与图片同名的txt。这里的判断标准很简单:images/train/000001.jpg必须对应labels/train/000001.txt,文件名完全一致,只有扩展名不同。

如果解压出来只有一个全量文件夹、没有train/val之分,也不用慌,第3章给的划分脚本可以直接用。但前提是先确认图片和标签的目录层级没有嵌套多一层,比如有些打包会把所有图片平铺在一个文件夹里,标签放在另一个平铺文件夹里——这种最好在划分之前先整理好,不要指望后面脚本自动处理。

2.2 一行标注拆开看:类别id和归一化坐标

打开任意一个txt,一行通常长这样:

0 0.52 0.47 0.21 0.33 1 0.36 0.71 0.12 0.19

每一行有5个数,含义分别是:类别id、目标中心点x、中心点y、目标宽、目标高。注意后四个坐标是归一化的,也就是已经除以了图片本身的宽度和高度,值域都在0到1之间。YOLOv5在训练时会将标签和resize后的图片做匹配,所以读取txt时不需要再除以图片尺寸。

关于类别id,YOLO系列统一从0开始。这份头盔数据集最常见的类别配置是0=head(未戴安全帽的人头)、1=helmet(戴了安全帽),但也有相反配置,就是0=helmet、1=head。拿到手先打开一个txt确认,再写data.yaml里的names列表,这两个地方不一致的话训练不会报错,但指标计算和可视化标签会全反,属于最隐蔽的一类错误。

2.3 样本构成:监控视角为主、近景为辅

我翻了这份数据集里的图片,大多数是工地出入口和作业区域的监控视角,相机装在高处往下拍,画面里人物偏小,安全帽往往只有几十像素;另外有一小部分是手持手机拍的近景,安全帽占画面比例很大。这个构成特点直接影响后面几个选择:一是训练尺寸要不要调大,二是Mosaic增强要不要关,三是未戴帽类别容易漏检。

从标注质量上看,这份数据集只有两个类别,没有“人”“车辆”这类干扰项,训练目标很纯粹。但有一个点需要注意:监控视角图片分辨率普遍较高,常见是1920x1080,而YOLOv5训练默认会把图片缩放到640x640再喂给网络。缩放之后,画面里远处未戴帽的人头可能只占十几个像素,属于不折不扣的小目标。这批图的标注框整体贴得比较紧,对mAP计算是个好消息,但对训练策略是个挑战。

提示:在开始训练之前,先正确定位这份数据集的适用场景——它适合做工地出入口的安全帽佩戴识别,不适合做复杂工地全景图里的密集小目标检测。场景定位错了,后面所有调参都容易白费。

2.4 十行脚本抽查可视化:标注有没有硬伤

目录结构和txt格式都确认没问题之后,我习惯随机抽10张图,把标注框画出来看一眼。这一步成本很低,但能发现靠读txt发现不了的问题,比如标注框整体偏移、类别标反、框和物体明显不贴合。下面这段脚本把归一化坐标还原到图片像素坐标并画框:

import cv2, random, glob, os # 随机抽10张训练图做可视化检查 imgs = sorted(glob.glob('helmet_dataset/images/train/*.jpg')) for img_path in random.sample(imgs, 10): label_path = img_path.replace('images/train', 'labels/train') \ .replace('.jpg', '.txt') img = cv2.imread(img_path) h, w = img.shape[:2] with open(label_path) as f: for line in f.readlines(): cls_id, cx, cy, bw, bh = map(float, line.split()) # 归一化坐标转回像素坐标 x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) color = (0, 0, 255) if cls_id == 0 else (0, 255, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.imwrite(f'check_{os.path.basename(img_path)}', img)

这段脚本不复杂,核心就一步:把归一化坐标乘以图片宽高。(cx - bw/2) * w算出框左上角的x坐标,(cy - bh/2) * h算出左上角y坐标,右下角同理。颜色上我让类别0显示红框、类别1显示绿框,这样打眼一看就能确认两个类别是否都正常出现。如果某张图的框明显偏移或者框比目标大一圈,说明这张图标注质量有问题,值得在第5章的避坑清单里对着排查。抽查之后,再做全量体检。

3. 训练前的数据准备:格式校验与train/val划分

3.1 体检脚本:缺图、空标、坏坐标一次查清

不管多急着开训,我都建议先跑一个不到30行的体检脚本。原因是YOLOv5的报错很多时候发生在训练运行半小时之后——某一张图读取失败、某个txt格式非法导致DataLoader直接崩掉、某个空标签让loss曲线出现诡异抖动。与其让训练跑到一半翻车,不如先花两分钟把问题数清楚。下面的脚本遍历训练集,逐个检查标签是否存在、是否为空、格式是否为5列浮点数、坐标是否在0到1范围内,以及图片能否正常解码:

import os, cv2, glob img_dir = 'helmet_dataset/images/train' lab_dir = 'helmet_dataset/labels/train' imgs = sorted(glob.glob(os.path.join(img_dir, '*.jpg'))) problems = [] for img_path in imgs: lab_path = os.path.join(lab_dir, os.path.basename(img_path).replace('.jpg', '.txt')) # 1. 标签文件必须存在 if not os.path.exists(lab_path): problems.append(f'missing label: {lab_path}') continue # 2. 标签文件不能是0字节 with open(lab_path) as f: lines = [ln.strip() for ln in f.readlines() if ln.strip()] if not lines: problems.append(f'empty label: {lab_path}') continue # 3. 每行必须是: 类别id + 4个归一化坐标 for ln in lines: parts = ln.split() if len(parts) != 5: problems.append(f'bad format: {lab_path}: {ln}') break try: cls_id, cx, cy, bw, bh = map(float, parts) if not (0 <= cx <= 1 and 0 <= cy <= 1 and bw > 0 and bh > 0): problems.append(f'bad coord: {lab_path}: {ln}') except ValueError: problems.append(f'not float: {lab_path}: {ln}') # 4. 图片本身是否能被OpenCV解码 img = cv2.imread(img_path) if img is None: problems.append(f'broken image: {img_path}') print(f'checked {len(imgs)} images, found {len(problems)} problems') for p in problems[:20]: print(p)

脚本本身不接收参数,改头部两个路径就行。四个检查项分别对应YOLOv5训练时最常翻车的点:缺标签会让该图片参与训练时无法计算loss;空标签会让该样本梯度为0,间接拖慢收敛;坐标值大于1说明标签是用像素坐标直接写的、没做归一化;损坏图片会让数据加载线程卡住。输出只打了前20条,问题多的话把print重定向到文件看全量。

如果发现空标签的比例在10%以内,我一般直接删对应图片,速度快且不影响大局;超过20%就要考虑换数据源了。这个阈值不是标准答案,但可以帮你快速判断数据集的脏程度。

3.2 训练/验证集划分:8:2起步,随机种子固定

有些版本的zip下载下来没有分train/val,只有一个全量文件夹,这时候必须自己划分。另外还有一种常见情况:数据集自带val目录,但这个val和训练集来自同一批次图片,分布太接近,用它验证出来的指标会虚高。我习惯的做法是合并后重新划分,固定随机种子,保证别人复现你实验时不会因为划分差异导致指标对不上。

import os, random, shutil src_img = 'helmet_dataset/images/all' src_lab = 'helmet_dataset/labels/all' train_ratio = 0.8 imgs = sorted(os.listdir(src_img)) random.seed(42) # 固定随机种子,保证可复现 random.shuffle(imgs) train_imgs = imgs[:int(len(imgs) * train_ratio)] val_imgs = imgs[int(len(imgs) * train_ratio):] for split, split_imgs in [('train', train_imgs), ('val', val_imgs)]: os.makedirs(f'helmet_dataset/images/{split}', exist_ok=True) os.makedirs(f'helmet_dataset/labels/{split}', exist_ok=True) for img_name in split_imgs: lab_name = img_name.replace('.jpg', '.txt') shutil.move(os.path.join(src_img, img_name), os.path.join(f'helmet_dataset/images/{split}', img_name)) shutil.move(os.path.join(src_lab, lab_name), os.path.join(f'helmet_dataset/labels/{split}', lab_name))

划两个要点。第一,shuffle之前必须先固定随机种子,否则每次跑结果都不一样,后面调参时根本分不清是数据划分差异还是超参改进带来的收益。第二,移动图片的同时必须把同名txt一起移动,这两个文件一旦分家,训练时就会报找不到标签。验证集比例要看数据总量:如果这个数据集的图片总量在几千张这个量级,8:2是合理的;如果总量不到一千张,建议改9:1,因为验证集太少时mAP曲线会剧烈抖动,给你造成“模型时好时坏”的假象。

划分完可以用两行命令核验数量:

echo "train images: $(ls helmet_dataset/images/train | wc -l)" echo "val images: $(ls helmet_dataset/images/val | wc -l)"

这两条命令分别统计训练集和验证集的图片数量。正常来说val大约占总量20%,如果明显偏少,回头检查是不是移动脚本执行了一半就停了。

3.3 划分前先统一后缀:jpg和png混用怎么处理

这个坑有点隐蔽。有的数据集打包时图片后缀不统一,同一个文件夹里jpg和png混着放,但标注txt全部按.jpg命名。比如一张图叫000088.png,标签却是000088.txt,用前面划分脚本的replace('.jpg', '.txt')就匹配不上了。

最省事的做法是统一后缀。实际项目中我一般不直接改扩展名,因为png改jpg只是改了容器名,编码没变,OpenCV读起来没问题,但部署到端侧推理框架时可能踩格式解析的坑。稳妥做法是用OpenCV转码:

import cv2, glob, os for png_path in glob.glob('helmet_dataset/images/all/*.png'): img = cv2.imread(png_path) jpg_path = png_path.replace('.png', '.jpg') cv2.imwrite(jpg_path, img) os.remove(png_path)

这段脚本把所有png读进来再以jpg编码写回,然后删掉原png。cv2.imwrite会按扩展名自动选择编码格式,所以路径后缀写.jpg就行。执行完后重新跑一遍3.1的体检,确认没有因为文件名替换产生新的缺失标签。

3.4 按类别统计样本:提前预判“未戴帽”漏检

头盔检测场景里最难的是“未戴帽”这个类别,因为多数工地图片是巡检视角,绝大多数工人都规范戴了帽,未戴帽样本天然少。训练前按类别统计一次样本量,能提前知道后面要不要做重采样。统计命令很简单:

cat helmet_dataset/labels/train/*.txt | awk '{print $1}' | sort | uniq -c

这条命令把训练集所有标签文件里的第一列(类别id)全部取出来,sort之后用uniq -c计数。输出会类似:

812 0 2045 1

看到这个结果就该意识到:如果0是head,head类别只有helmet类别的一半,那这个模型训练出来对未戴帽的recall大概率偏低,后面第5章的类别重采样基本是必做的。如果两类数量悬殊到1:5以上,先用第5.5节的方法处理,再进训练流程。

4. 训练头盔检测模型:data.yaml、超参和第一次跑通的命令

4.1 data.yaml:路径写绝对,names顺序别写反

把数据集整理好之后,第一步是写data.yaml。这是YOLOv5训练时的数据配置核心,决定了模型去哪里读图、预测哪几个类别。针对这份头盔数据集,配置长这样:

train: /home/user/helmet_dataset/images/train val: /home/user/helmet_dataset/images/val nc: 2 names: 0: head 1: helmet

train和val是训练集和验证集的图片路径,nc是类别数,这里是2,names是类别名列表。有个细节值得多说一句:train路径推荐写绝对路径。相对路径在本地跑没问题,但一旦换机器、换工作目录,或者放到服务器上用screen后台跑,经常因为当前目录不同找不到图片,报错信息还特别难排查,我一般在拿到数据集的第一时间就把路径粘成绝对路径。

names列表的顺序必须和txt标签里的类别id完全一致。这份数据集里id为0的对应head,那names第一个必须是head,不能先是helmet。顺序反了训练时不会报错,但val指标、可视化标签全是反的。

4.2 训练命令:用s模型起步,超参先按表来

数据配置就绪后,进入yolov5目录开始训练。针对这种几千张体量的头盔检测数据,我给出的起步命令是:

cd yolov5 python train.py --data helmet.yaml --weights yolov5s.pt \ --img 640 --batch 16 --epochs 100 --device 0 \ --project runs/helmet --name baseline

其中--weights yolov5s.pt指用s规模的预训练权重做微调。这个选择有明确理由:头盔检测属于单类物体检测,任务相对简单,s模型的容量足够拟合这份数据集;用l或者x模型跑,在小数据上非常容易过拟合,训练时间翻倍但mAP上限几乎不变。--img 640是YOLOv5默认输入尺寸,如果你的数据集中监控视角的远距离人物较多,可以改成960来保留小目标细节,代价是显存占用明显上升。--batch 16按显卡显存调整,显存6G左右降到8,4G显存降到4。--epochs设100轮比较稳,跑完看曲线再决定要不要续训。

各参数的选择逻辑可以这样记:

参数推荐值选择理由
weightsyolov5s.pt模型容量与几千张数据集匹配,不易过拟合
img640 / 960小目标多时用960,显存有限用640
batch4 / 8 / 16显存6G用8,12G以上用16
epochs100起步先跑到验证指标收敛,不够再resume
device0单卡训练;没有GPU就写cpu

训练过程会在runs/helmet/baseline/下生成weights/best.pt和weights/last.pt。前者是整个训练过程中验证集指标最好的权重,后者是最后一个epoch的权重,部署时只用best.pt,这是YOLOv5最基础的习惯。多说一句:如果你之后改用YOLOv8练头盔检测,这份数据集的目录结构和标注格式可以原样复用,只需要把训练入口换成v8的train脚本,数据侧不需要动。

4.3 训练日志与指标:mAP@0.5是主要参考

训练开始后,终端会周期性打印一行表格,包含每类loss、precision、recall和mAP。第一次跑头盔检测,不要被一堆指标绕晕,聚焦两个东西:训练loss是否持续下降,以及mAP@0.5最终能到多少。这个数据集上,mAP@0.5到0.85以上基本可以送到现场试跑;mAP@0.5:0.95能到0.6左右已经算正常水平,不用强求,因为0.5:0.95对框的精确贴合要求更高,监控视角的小目标天然吃亏。

如果loss曲线在20到30轮后基本平行、不再下降,说明学习率已经衰减到位,此时继续跑只是等mAP微调,可以考虑提前结束,不一定要跑满100轮。另外有个玄学但真实存在的情况:多卡训练时batch翻倍,初始学习率也要相应调大,否则收敛速度明显变慢。单卡训练不用管这个,只有--device 0,1这类多卡启动时才需要手动改--lr0。

4.4 断点续训与显存不足:两个必踩的启动问题

第一次跑训练,最容易撞上的是CUDA out of memory。很多情况下改参数重跑就行,没必要重新跑100轮。处理方法有两档:第一档是把batch降到8或4,代价是训练变慢;第二档是把--img从640降到480,输入图变小,显存占用大幅下降,代价是模型对小目标更不敏感。

如果训练已经跑到80轮崩了,用resume续训,不要从头再跑:

python train.py --data helmet.yaml \ --weights runs/helmet/baseline/weights/last.pt \ --resume --epochs 150

这段命令从last.pt断点继续训练,--epochs 150是最终总轮数而非新增轮数。YOLOv5的resume机制会读取上次训练的学习率调度状态,直白说就是除了训练数据和权重,优化器状态也续上了,比重新训练省时间,也是那段时间最常用的“后悔药”。

5. 常见问题与避坑记录:五个我踩过的坑

5.1 类别id从1开始:训练不报错,但目标检测不出来

现象:训练过程一切正常,loss正常下降,验证集mAP也不算低,但拿到现场测试时,预测结果里只有安全帽的框,人头一个都框不出来,或者安全帽和人头两个类别互相串。

原因:YOLO的类别索引从0开始,但不少人在自己标注数据或者整理数据集时习惯从1开始编号。如果标签文件里head写成1、helmet写成2,而data.yaml里nc还是2,模型就会把类别2当不存在,预测时所有目标都被当成背景或者被挤到类别0。

解决:先统计标签文件里的实际类别id分布,确认是不是从1开始:

cat helmet_dataset/labels/train/*.txt | awk '{print $1}' | sort | uniq -c

如果输出是1和2而没有0,说明id整体偏移了1位。用下面的脚本批量修正:

import glob shift = -1 # 把1->0, 2->1 for lab_path in glob.glob('helmet_dataset/labels/train/*.txt'): lines = [] with open(lab_path) as f: for line in f: parts = line.split() if parts: parts[0] = str(int(parts[0]) + shift) lines.append(' '.join(parts)) with open(lab_path, 'w') as f: f.write('\n'.join(lines) + '\n')

脚本逻辑是把每行第一个字段(类别id)整体减1。跑之前一定要先确认id分布,如果原本就是0开头,执行这个脚本反而会把标签全改成-1,那问题更大。

5.2 空标签文件:loss曲线不下降,mAP长期为0

现象:训练loss在前几个epoch有所下降后就不怎么动了,mAP@0.5在验证集上始终是0,或者训练跑到中途DataLoader直接报错中断。

原因:数据集整理时漏掉了部分标注,某些图片的txt文件是0字节。YOLOv5读取到空标签时,该图片算作负样本参与训练,如果这种图片占比偏高,模型会倾向于把所有目标都预测成背景。

解决:用第3.1节的体检脚本把空标签图片全部找出来,直接删除对应图片。删除比补标快得多——几百张图人工补标要半天,删掉可能只是损失一点样本多样性。复盘这个坑时我最大的体会是:训练之前先花两分钟跑一遍体检,远比赛后对着loss曲线猜原因省时间。我当时是在训练到第30轮时发现问题,退出来删图重跑,等于浪费了40分钟GPU时间。

5.3 图片和标签对不上:训练指标虚高的教科书翻车

现象:训练集和验证集指标都很好,mAP@0.5过了0.9,但把这个模型拿到新拍的工地照片上测试,框的位置明显错乱,有时把背景识别成安全帽。

原因:图片文件夹和标签文件夹来自不同的数据来源,文件名前缀相同但实际内容错位。比如某张图内容是一个没戴帽的工人,但对应的txt里写的却是一个戴帽工人头部的坐标,模型训练时一直在学错误的对应关系。这种情况在合并多个来源的图片时特别容易发生,尤其当两个来源都用了IMG_0001.jpg这类通用命名。

解决:写一个脚本核对图片和标签的一一对应关系,同时检查标注框是否超出图片边界:

import cv2, glob, os for img_path in glob.glob('helmet_dataset/images/train/*.jpg'): lab_path = img_path.replace('images/train', 'labels/train') \ .replace('.jpg', '.txt') if not os.path.exists(lab_path): print('missing:', lab_path) continue h, w = cv2.imread(img_path).shape[:2] with open(lab_path) as f: for line in f: cls_id, cx, cy, bw, bh = map(float, line.split()) if not (0 <= cx <= 1 and 0 <= cy <= 1 and 0 <= bw <= 1 and 0 <= bh <= 1): print(f'coord out of range: {lab_path}: {line}')

一旦出现大量越界坐标,优先怀疑是标注工具导出的坐标基准不一致,这通常意味着这批标签不可信,需要换数据源而不是硬着头皮训练。

5.4 Mosaic增强把远处人头搞没了:小目标检测的必要取舍

现象:训练时mAP@0.5轻松上0.9,但把模型部署到工地现场测试时,画面远处未戴帽的人几乎全漏,只有走近镜头的人才能被检测出来。

原因:这个数据集里监控视角的人头属于小目标,只有几十像素。YOLOv5在默认训练配置下开启了Mosaic增强,训练时会把四张图随机拼成一张再缩放,小目标在这个过程里被进一步压缩,模型学到的小目标特征非常有限。

解决:训练时换成关闭Mosaic的配置文件:

python train.py --data helmet.yaml --weights yolov5s.pt \ --img 640 --batch 16 --epochs 100 \ --hyp hyp.scratch-low.yaml

hyp.scratch-low.yaml里mosaic=0.0,同时把scale等增强强度也降了,整体来说更适配小目标场景。如果你不想完全关闭Mosaic,可以折中把输入尺寸从640提到960,保留Mosaic但给小目标更多像素空间。实测下来,在这个数据集上关闭Mosaic的收益比调高分辨率更直接,代价是收敛速度稍慢。这个取舍没有标准答案,只能基于你的实际部署场景试。

5.5 未戴帽样本太少:类别重采样补回来

现象:验证集整体mAP很高,但单独看head类别的recall远低于helmet。在工地新图上,没戴帽子的人被漏检的概率明显更大——对安全帽检测业务来说这是最致命的故障。

原因:数据集采集时以“检查大家是否戴帽”为目的,所以绝大部分图片里人人都戴着安全帽,未戴帽样本天然稀缺。模型在训练中见到的正负样本比例失衡,自然倾向把目标预测成戴帽。

解决:做类别重采样,把包含head类别的图片在每轮训练中多读几次。最简单的做法是在train.txt里把这类图片的路径重复写入:

import glob over_sampling = 2 # 重复倍数,从2倍开始试 imgs = glob.glob('helmet_dataset/images/train/*.jpg') selected = [] for img_path in imgs: lab_path = img_path.replace('images/train', 'labels/train') \ .replace('.jpg', '.txt') with open(lab_path) as f: cls_ids = [ln.split()[0] for ln in f if ln.strip()] if '0' in cls_ids: # 0是head类别 selected.append(img_path) with open('train.txt', 'w') as f: for img_path in imgs: f.write(img_path + '\n') for img_path in selected: for _ in range(over_sampling - 1): f.write(img_path + '\n')

然后训练时用--train-dir或直接把YOLOv5的数据加载指向这个train.txt。我一般从2倍开始试,观察head类别recall的变化再微调。如果数据集本身不到一千张,重采样容易造成过拟合,这时优先换数据源,而不是硬靠重采样撑。

6. 验证与落地:用mAP说话,导出成可部署的模型

6.1 用val.py出一份独立指标:盯着未戴帽的recall

训练结束后,不要直接看训练日志里的mAP,用独立的验证脚本再跑一遍。命令是:

python val.py --data helmet.yaml --weights runs/helmet/baseline/weights/best.pt \ --img 640 --conf-thres 0.25 --iou-thres 0.45

这个命令会输出每个类别单独的precision、recall和mAP@0.5。我每次先看head类别的recall,如果低于0.8,回到第5章的5.5做类别重采样,然后重新训练;如果head和helmet两个类别的recall都过了0.85,才考虑导出部署。--conf-thres 0.25是置信度阈值,这个值直接影响现场漏检率。头盔检测场景里漏检的代价远高于误检,现场使用时可以把阈值降到0.15到0.2,宁可多框几个错框,不能漏掉没戴帽的人。

6.2 导出ONNX做冒烟测试:模型文件能不能被外部加载

确定权重可用后,把它导出成ONNX格式,方便后续部署到服务端的onnxruntime或端侧推理框架:

python export.py --weights runs/helmet/baseline/weights/best.pt \ --include onnx --opset 12

导出成功后会生成best.onnx。做一次冒烟测试验证,用onnxruntime加载模型并对一张验证集图片做推理。这一步能提前暴露很多问题,比如opset版本不兼容、动态输入尺寸不支持、模型里的某些算子在目标部署环境不认。如果计划部署到Jetson或ncnn,opset选择12以下更安全,新版框架对更高opset的支持不一定跟得上。

导出之后,把数据集的图片尺寸、类别顺序、置信度阈值这三个信息一起交给部署端。项目收尾时最怕的就是模型文件给了,但对方不知道该用什么预处理、该用什么阈值。所以建议在部署包里带一个简短的README,写清楚输入尺寸是640还是960、类别顺序是head在前还是helmet在前,帮助后面对接的人少走弯路。

从那以后,我每次拿到头盔检测数据集,都强制把第3章那三件事走一遍:体检、配对、类别统计,训练完再单独看一次head类别的recall。这套流程谈不上高深,但它能拦住我重复踩同一批坑。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询