☰
YOLOv3安全帽检测实战:从数据集准备到模型训练与部署
2026/10/2 4:01:43 网站建设 项目流程

简介:YOLOv3安全帽检测资源包专为工业安全场景中的目标检测需求设计,适合计算机视觉学习者、安防系统开发者及需要快速部署安全帽识别功能的技术人员。资源基于几千张真实场景数据训练150轮,得到mAP与召回率均超90%的权重文件,并保留完整的PR曲线、结果图表等评估记录。压缩包共166个文件、约546.6MB,其中包含Python源码、YOLO系列配置(yaml/yml)、训练权重(pt)、标注文件(xml)、测试图片与可视化图表等,PyTorch环境按YOLOv5要求配置后即可直接调用。现有642人学习使用,资源还提供了原始数据集与检测结果的博客链接,便于对照复现与实际部署参考,可帮助使用者快速搭建安全帽佩戴检测流程,省去从零训练的时间和算力成本。

1. YOLOv3 安全帽检测:直接可用的权重、训练代码与数据集

YOLOv3 安全帽检测在今天依然是一个性价比很高的落地项目,说反直觉一点:你不需要从零标注上万张图,也不需要把 YOLOv3 的原理全部啃完才能出结果。这份资源把三样东西放在了一起:训练好的模型权重、可以直接复现的训练代码、整理并校验过的安全帽数据集。它适合毕设阶段需要快速出效果的同学,也适合工地监控、工厂巡检这类场景固定、需要自建检测服务的小项目。拿到的第一件事不是急着跑训练,而是把数据集过一遍,确认类别定义、标签格式和训练验证划分的逻辑。模型是现成的,但在“直接拿来用”之前,你必须先验证它认识的是你的目标类别。

2. 先把数据集拆明白:标注格式审查、YOLO 标签转换与训练/验证划分

2.1 拿到数据集先看三样东西:标注格式、类别定义和图片尺寸

安全帽检测数据集在网上流传的版本不少,但组织形式差别很大。有的用 VOC 的 XML,每张图配一个同名 xml;有的直接给 YOLO 的 txt,每行一条目标;还有的干脆把标注放在 json 里。拿到手先别急着训练,先把标注格式看清楚。这份资源里的数据集按 YOLO 格式组织,但我仍然建议做一次人工抽查,因为标注错位的后果比模型不收敛更难察觉。

先说类别定义。安全帽检测最常见的分法有两类:0 代表戴帽(helmet 或 head_with_helmet),1 代表未戴帽(head 或 person)。也有的数据集拆成三类:head、helmet、person,把“戴帽的人头”“未戴帽的人头”“未戴帽的完整人”分开。类别数直接决定后面 cfg 里 classes 和 filters 的取值,分法不同,模型输出头的维度就不同。打开 obj.names 确认类别顺序,再拿一张标注图对应验证,这一步花不了五分钟,但能省掉后面一整天的排查时间。

图片尺寸也要看。YOLOv3 训练时会把输入 resize 到固定分辨率,常见是 416x416 或 608x608,但原图可能是 1920x1080 的监控截图。如果画面里有大量小目标,推理时要把结果还原到原图坐标,就得在预处理阶段记录好原始尺寸换算;这一步漏了,后面画框会全部错位。我的习惯是先把文件夹里所有图片的宽高批量统计出来,看是否存在尺度差异很大的图片,有的话在划分数据时单独处理。

统计类别分布可以用一个简短的 Python 脚本,直接能跑:

import os label_dir = "labels" # YOLO txt 标签目录 class_count = {} for fname in os.listdir(label_dir): if not fname.endswith(".txt"): continue with open(os.path.join(label_dir, fname), "r") as f: for line in f: parts = line.strip().split() if len(parts) < 5: continue cls = int(parts[0]) class_count[cls] = class_count.get(cls, 0) + 1 print("类别统计:", class_count)

这个脚本会打印每个类别出现的目标总数。如果戴帽和未戴帽的数量差距超过 5:1,训练时模型会对多数类过拟合,常见做法是在采样时对少数类图片做重复加入,或者针对少数类单独做数据增强。darknet 本身不内置加权 loss,所以这类不均衡问题通常靠调整训练图片的重复次数来解决,后面 3.4 节会展开。

2.2 VOC 格式转 YOLO 格式:一份可改的转换脚本

如果你拿到的是 VOC 的 XML,第一步就是转成 YOLO 的 txt。转换核心是坐标换算:XML 里给的是左上角和右下角的绝对像素值 xmin、ymin、xmax、ymax,YOLO 要的是归一化之后的中心点坐标和宽高:

x_center = (xmin + xmax) / 2 / 图片宽度

y_center = (ymin + ymax) / 2 / 图片高度

box_width = (xmax - xmin) / 图片宽度

box_height = (ymax - ymin) / 图片高度

这里最容易踩的点是除数选择:x 方向一律除以图片宽度,y 方向一律除以图片高度,不要图省事统一除以某一项。下面是一份我常用的转换脚本,按自己的目录改一下路径就能跑:

import os import xml.etree.ElementTree as ET import cv2 xml_dir = "Annotations" # VOC XML 目录 img_dir = "JPEGImages" # 原图目录 out_dir = "labels" # 输出 YOLO txt 目录 os.makedirs(out_dir, exist_ok=True) # 类别映射,顺序必须与 obj.names 保持一致 class_map = {"helmet": 0, "head": 1} for xml_name in os.listdir(xml_dir): if not xml_name.endswith(".xml"): continue xml_path = os.path.join(xml_dir, xml_name) tree = ET.parse(xml_path) root = tree.getroot() img_path = os.path.join(img_dir, xml_name[:-4] + ".jpg") img = cv2.imread(img_path) if img is None: print("跳过无法读取的图片:", img_path) continue h, w = img.shape[:2] out_lines = [] for obj in root.findall("object"): cls = obj.find("name").text if cls not in class_map: continue box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) # 修正越界坐标 xmin = max(0, min(xmin, w)) xmax = max(0, min(xmax, w)) ymin = max(0, min(ymin, h)) ymax = max(0, min(ymax, h)) if xmax <= xmin or ymax <= ymin: continue cx = (xmin + xmax) / 2.0 / w cy = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h out_lines.append(f"{class_map[cls]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") if out_lines: with open(os.path.join(out_dir, xml_name[:-4] + ".txt"), "w") as f: f.write("\n".join(out_lines))

几个值得注意的参数。class_map 的顺序必须和后面的 obj.names 顺序完全一致,否则训练时类别标签错位;越界坐标要 clamp 到图片尺寸内,XML 里偶尔有标注框超出图片边界的情况,不处理会导致训练时 loss 异常;跳过错图分支也很重要,一张损坏的 jpg 会让整个训练集加载失败。运行完建议随机抽 20 张图,用 OpenCV 把转换后的框画回去人工看一眼,比任何脚本检查都直观。

2.3 训练集/验证集划分与路径清单

标注文件整理好之后,还需要生成 darknet 训练用的路径清单 train.txt 和 valid.txt。darknet 不直接读图片目录,它按 txt 文件里逐行给出的图片路径去加载数据。划分比例我一般用 8:2,数据量低于 3000 张时可以放宽到 9:1,但验证集至少要留 200 张以上,否则算出来的 mAP 抖动很大,没法判断模型好坏。

生成清单的脚本很简单:

find "$PWD/images" -name "*.jpg" | shuf > all_images.txt head -n 6000 all_images.txt > train.txt tail -n 1500 all_images.txt > valid.txt

这里以 7500 张图片、分 6000/1500 为例,可以按实际数据量修改。shuf 先打乱再用 head/tail 切分,能保证随机性;如果你希望多次训练用同一组划分,就在 shuf 前固定随机种子。注意一个坑:train.txt 和 valid.txt 的图片数量比例不等于标签实例数量比例,因为每张图的目标数不一样。按 8:2 切了图片,不代表戴帽/未戴帽的实例数也是 8:2,最终要看 2.1 的类别统计结果。

目录结构也应该在训练前定死。我习惯这样组织:

dataset/ |-- images/ # 所有 jpg |-- labels/ # 与图片同名的 txt |-- train.txt |-- valid.txt |-- obj.names `-- obj.data

obj.data 是 darknet 的训练入口配置,把上面的路径都指过去:

classes = 2 train = dataset/train.txt valid = dataset/valid.txt names = dataset/obj.names backup = backup/

backup 目录要先建好,darknet 不会自动创建,训练过程中每迭代一定次数会在里面落一个权重。这个文件的路径写相对还是绝对要看工作目录,我建议全写绝对路径,因为 darknet 对相对路径的拼接很敏感,少一个斜杠就报找不到文件。

3. 训练自己的安全帽模型:锚框重聚类与低显存调优

3.1 用预训练权重起步,别从零训练

YOLOv3 的 backbone 是 Darknet-53,在 ImageNet 上预训练过。即使安全帽数据集和 ImageNet 内容差得很远,用 darknet53.conv.74 这个预训练文件做迁移学习,收敛速度和最终精度都明显优于从零开始。原因很简单:浅层网络学到的是边缘、纹理、颜色块这些通用特征,在任何视觉任务里都有用,只有深层输出头需要针对安全帽重新学。

常见的误区是直接拿网上的 yolov3.weights 来继续训练,这个权重包含完整的 80 类 COCO 输出头,输出维度跟你的 2 类配置不匹配。darknet53.conv.74 只包含前 74 层卷积的权重,不含最后的检测头,结构上和你改好的 cfg 完全兼容,这也是官方推荐的基础权重。它的大小在 150MB 左右,加载速度很快,训练命令在 3.4 节给出。

这里有一个很多人没注意到的点:迁移学习时,backbone 的浅层权重基本不需要大改,所以训练初期可以把学习率设得稍高,让检测头快速收敛;到了后面,整个网络一起微调时再把学习率降下来。换句话说,训练的前几千次迭代你是在“教”输出头认识安全帽,后几千次是在“打磨”整体特征,两者节奏不一样。

3.2 修改 cfg 文件:classes、filters、max_batches 与学习率

开始训练之前要改 cfg。你需要找到文件里三处出现的 classes 行,把值改成你的类别数。每个 classes 的上一层都有一个卷积层,它的 filters 值必须等于 3 * (classes + 5),这个 5 是坐标的 4 个值加 1 个目标置信度。如果是 2 类,filters = 3 * 7 = 21;3 类就是 24。这个公式是整份代码里最容易翻车的地方:classes 忘了改,或者 filters 没跟着改,训练时 loss 看起来正常,但推理结果完全错乱。

max_batches 的取值经验是“类别数 × 2000”,但下限不要低于 6000。2 类就取 6000;数据量大、目标变化多,可以开到 8000 到 10000。steps 一般设成 max_batches 的 80% 和 90%,比如 max_batches=6000 时 steps=4800,5400。学习率在 steps 处乘以 0.1,分段降下来,避免后期在最优解附近来回震荡。

学习率的初始值,YOLOv3 官方用的是 0.001。如果你的数据集很小,比如只有两三千张,建议直接降到 0.0005 起步。数据量越小,模型越容易过拟合,初始学习率太高会看到 loss 在前几百次迭代里反复跳变,后面再降也不好救。

3.3 锚框重聚类:别直接用 COCO 那组

YOLOv3 默认锚框是在 COCO 80 类数据集上聚类出来的,对安全帽这种小目标密集的场景明显偏大。安全帽在监控画面里通常只占几十个像素,COCO 那组里最大的锚框有 373x326,训练时检测头要花很大力气去学习“把大锚框收缩成小目标”,这个浪费可以提前用重聚类规避。

darknet 提供了 calc_anchors 命令,直接基于你的标注数据重新聚类:

./darknet detector calc_anchors dataset/obj.data -num_of_clusters 9 -width 416 -height 416 -show

这个命令会读 obj.data 里指向的训练图片,把每张图里目标的宽高统计出来,聚成 9 个锚框,同时生成 anchors.txt。你需要把输出值粘贴回 cfg 文件 [yolo] 层前面的 anchors = 那一行。注意 -num_of_clusters 要跟 cfg 里的尺度数量匹配:标准 YOLOv3 是三个尺度、每个尺度 3 个锚,一共 9 个;如果用 yolov3-tiny,只有两个尺度,聚类数要改成 6。

聚类结果出来后,和默认锚框对比一下能看出不少信息。如果数据集里小目标占比高,聚类出的最大锚框会明显小于 COCO 那组;如果两类目标的长宽比差异很大,聚类出来的锚框也会自然分开。锚框是训练时先验框的初始值,换掉之后收敛速度快很多,这是整个训练过程里性价比最高的一步,不要跳过。

3.4 低显存训练:batch、subdivisions 与分辨率怎么调

显存不够是训练安全帽模型最常见的硬件瓶颈。darknet 的 batch 是每次迭代的样本总数,subdivisions 把一个大 batch 拆成若干小份依次送进显存,所以 subdivisions 越大、显存占用越低、训练速度越慢。下面这组参数是 8GB 显存跑 416x416 的典型配置:

| 参数 | 默认参考 | 8GB 显存建议 | 说明 | | batch | 64 | 64 | 单次迭代样本总数 | | subdivisions | 16 | 32 或 64 | 越大越省显存 | | width/height | 416 | 416 | 降到 320 可再省约 40% 计算量 | | learning_rate | 0.001 | 0.001 | 不收敛时降到 0.0001 |

如果你的显卡只有 4GB,我一般会先把 width 和 height 改成 320 或 352,再配合 subdivisions=64。分辨率下降对小目标检测的影响会在 mAP 上明确体现,所以这是显存不足时的后备选项,不是首选。如果数据集里安全帽本来就小,优先保分辨率,用 subdivisions 扛显存。

训练命令本身不复杂,关键是路径和文件别写错:

./darknet detector train dataset/obj.data cfg/yolov3_helmet.cfg darknet53.conv.74 -map

结尾的 -map 让 darknet 每 100 次迭代计算一次验证集 mAP 并写进日志。如果不加,训练日志里只有 loss,很难判断模型是不是真的在变好。训练日志主要看两个值:avg loss 总体趋势,以及 Region avg IoU。avg loss 从几万降到个位数是正常的;Region avg IoU 如果一直低于 0.5,说明锚框和真实目标形状匹配得不好,回头重做 3.3 的聚类。loss 这东西有时候带点玄学,偶发跳高不用慌,连续 2000 次迭代不降才有问题。

如果你训练到一半中断了,可以在命令末尾加上 -clear 重新开始,或者不加 -clear 从 backup 里最新的 weights 继续训练。继续训练时不需要再传 darknet53.conv.74,而是传 backup 里的中间权重。恢复训练前建议把学习率临时调低一点,避免在中断点附近产生过大的更新步长。

4. 模型推理落地:OpenCV DNN 示例代码与置信度调参

4.1 先用 darknet 命令行验证权重

训练完之后,backup 目录里会有多个权重文件。先不用急着写 Python,先用 darknet 自带的命令验证一遍,快速确认权重没有在保存环节出问题。验证单张图的命令是:

./darknet detector test dataset/obj.data cfg/yolov3_helmet.cfg backup/yolov3_helmet_final.weights demo_img.jpg -thresh 0.5

-thresh 是置信度阈值,低于它的框不输出。如果戴安全帽的人被正确框出来,说明训练过程没有硬伤;如果完全没框或框得乱七八糟,先回第 3 章检查 classes 和 filters,而不是调阈值。我一般会挑戴帽、未戴帽、远处小目标三张图各测一遍,覆盖主要场景。命令行验证只是第一步,实际部署时的输入输出路径不一定一致,后面还是要落到代码里。

4.2 用 OpenCV DNN 跑 Python 推理:示例代码与参数解读

darknet 的 C++ 版本要编译 CUDA、OpenCV,换环境容易踩版本坑。实际项目里我更推荐用 OpenCV Python 的 DNN 模块直接加载 .cfg 和 .weights,推理时不依赖 darknet 编译产物,只要 OpenCV 版本大于 4.1 就能跑。下面这段是完整的单图检测代码:

import cv2 import numpy as np # 加载模型 net = cv2.dnn.readNetFromDarknet("cfg/yolov3_helmet.cfg", "backup/yolov3_helmet_final.weights") layer_names = net.getLayerNames() out_layers = [layer_names[i - 1] for i in net.getUnconnectedOutLayers()] # 读取并预处理 img = cv2.imread("demo_img.jpg") h, w = img.shape[:2] blob = cv2.dnn.blobFromImage(img, 1/255.0, (416, 416), swapRB=True, crop=False) net.setInput(blob) outs = net.forward(out_layers) # 解析三个尺度的输出 boxes, confs, cls_ids = [], [], [] for out in outs: for det in out: scores = det[5:] cls_id = np.argmax(scores) conf = float(scores[cls_id]) if conf < 0.5: continue cx, cy, bw, bh = det[0:4] * (w, h, w, h) x1 = int(cx - bw/2) y1 = int(cy - bh/2) boxes.append([x1, y1, int(bw), int(bh)]) confs.append(conf) cls_ids.append(cls_id) # NMS 去重 idx = cv2.dnn.NMSBoxes(boxes, confs, 0.5, 0.4) for i in idx: x1, y1, bw, bh = boxes[i] label = f"{confs[i]:.2f}" cv2.rectangle(img, (x1, y1), (x1+bw, y1+bh), (0, 0, 255), 2) cv2.putText(img, label, (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite("result.jpg", img)

几个关键参数要理解。blobFromImage 里的 (416, 416) 是网络输入分辨率,务必和训练时的 width/height 一致;scale 1/255.0 做像素归一化,darknet 训练时就是这么处理的,少了它置信度会整体偏低。det[0:4] 保存的是相对输入尺寸的比例坐标,所以乘上原图宽高才能还原成原图坐标;最后一维 scores 的类别顺序对应 obj.names 的顺序,names 第一行是 helmet,那 scores 索引 0 就是戴帽。

如果你的 OpenCV 版本较旧,net.getUnconnectedOutLayers() 返回的是输出层名字字符串而不是索引,那直接把返回结果赋给 out_layers 即可,不需要经过 layer_names 映射。NMSBoxes 的第三个参数是置信度门槛,第四个是重叠抑制门槛。安全帽目标密集,nms_threshold 设 0.4 比较合适;设太小会把两个相邻的戴帽人头合并成一个框,设太大会在同一顶帽子上出多个框。

4.3 视频流与实时检测:跳帧、分辨率与置信度

视频检测和单图本质上是同一套推理流程,只是多了读帧和帧率控制。darknet 命令行也能跑视频,但编译时要带 OPENCV=1,否则会报错。命令行跑视频的方式是:

./darknet detector demo dataset/obj.data cfg/yolov3_helmet.cfg backup/yolov3_helmet_final.weights test_video.mp4 -thresh 0.5

视频检测要关注的不只是准确率,还有帧率。YOLOv3 在 GPU 上推理 416x416 单张大约 30ms,但加解码、后处理,实际帧率会掉到 20 帧以下。对监控场景来说 20 帧已经够用,很多人在这里直接跳帧:每隔 2 到 3 帧检测一次,中间帧沿用上一帧结果画框。这个做法对固定机位的安全帽检测基本成立,因为相邻帧之间人的位置变化不大。

置信度阈值是视频场景最常用的调参手段。安全帽检测和通用目标检测不同,工地报警场景里如果把阈值降到 0.2,会出现大量重复报警;升到 0.6,漏检率又上去。我一般先跑一段 5 分钟的视频,人工统计漏检和误检,再决定阈值。这个数字在不同项目里差别很大,不存在通用的最佳值,只能按现场画面实测。

如果检测结果要对接告警平台,记得把框的坐标、置信度、类别和时间戳一起落成结构化数据。OpenCV 画框只是给人看的,机器告警需要的是 JSON 或 CSV。这一步不做,模型即使准了,也难接进真实业务。

5. 安全帽检测避坑:五个常见翻车点与排查办法

5.1 推理结果全部错乱:classes 改了,filters 没改

现象:命令行检测能跑,但输出框完全不在目标上,或者一张图里框了几十个乱七八糟的区域,置信度还很高。

原因:cfg 文件里最后一层卷积的 filters 没有按新类别数重算。比如原来 filters=255 对应 80 类,你把 classes 改成 2,但 filters 还是 255,检测头输出维度对不上,模型把大量噪声当成目标输出。

解决:搜索 cfg 文件里所有 classes 行,共 3 处,改成实际类别数,同时把每个 classes 上一层的卷积 filters 改成 3*(classes+5)。改完后从头重新训练,不要拿旧权重硬跑。血泪经验是改 cfg 时养成习惯:先改 classes,再改 filters,最后 grep 一遍确认两个数字匹配。

5.2 loss 长时间不降或者出现 nan

现象:训练开始后 avg loss 在同一个量级上徘徊几千次迭代不下降,或者某个时刻直接变成 nan,后面全部输出异常。

原因:最常见是学习率太大,或者锚框和数据集目标形状严重不匹配。其次是标签文件里有脏数据,比如归一化坐标大于 1、宽高为 0,这些值会在反向传播时产生异常梯度。

解决:把 learning_rate 从 0.001 降到 0.0001 再跑;如果还不行,按 3.3 节重聚类锚框。训练前用脚本检查所有标签,过滤宽高小于 3 像素的框。darknet 对脏标签容忍度很低,一个坏标注就能把整个训练过程带崩,这个检查不能省。

5.3 小目标安全帽漏检严重

现象:远处画面里只占 20 个像素左右的安全帽检测不到,近处大目标都正常。

原因:YOLOv3 的三个输出尺度里,52x52 那层负责小目标,但它受限于输入分辨率。用 416x416 输入时,原图里 20x20 的安全帽被压缩后只剩几个像素,特征几乎丢失。另一个原因是锚框聚类结果里没有覆盖这么小的尺寸。

解决:训练和推理都改成 608x608;显存不够就降到 544 或 512。同时重聚类时保持 9 个锚框,但观察最小锚框是否小于 10 像素。如果小目标还是多,可以刻意把小目标占比高的图片在训练列表里多放几遍。

5.4 训练时图片加载失败,或者标签与图片错位

现象:darknet 启动训练后报 cannot load image,或者训练日志里每张图的标签数量比实际少很多,label count 异常低。

原因:路径清单里写的是相对路径,而启动训练时的工作目录不在数据集上一级;也可能是 images 和 labels 里有同名但不同后缀的图片,jpg 和 png 混用导致转换脚本漏掉部分标签。

解决:把 train.txt 里所有路径换成绝对路径;转换脚本里按 jpg、jpeg、png 三种后缀分别匹配。还有一个隐蔽点:图片和标签按文件名排序后必须一一对应,如果某张图的标签文件缺失,darknet 会当无目标图参与训练,不报错但拖慢收敛,所以转完格式后要对比文件名集合。

5.5 换机器部署时,darknet 权重加载报版本错误

现象:在训练机上跑得好好的 .weights,拷贝到另一台机器后用同一套 darknet 编译产物运行,报 CUDA、cuDNN 或 OpenCV 相关的版本错误。

原因:darknet 是在编译期绑定 CUDA、cuDNN、OpenCV 的,换机器后库版本不一致,二进制兼容性就出问题。这和 .weights 文件本身无关,权重是纯数据文件。

解决:两台机器的 darknet 源码分别重新编译;或者更省事的方式是像第 4 章那样直接用 OpenCV DNN 做推理,只依赖 opencv-python,不依赖 darknet 的编译产物。这也是我推荐的部署方式,因为部署环境通常没有完整 CUDA 工具链,OpenCV DNN 能少踩很多坑。

6. 最后一步:用 mAP 验证模型,再挑最终权重

训练日志里的 avg loss 只反映拟合程度,不能直接反映检测精度。判断一个权重能不能用,最终要看验证集上的 mAP。darknet 自带 map 命令,只需要一份没参与训练的 valid 清单:

./darknet detector map dataset/obj.data cfg/yolov3_helmet.cfg backup/yolov3_helmet_final.weights

输出会按类别分别给出 AP,最后汇总 mAP。对安全帽这种两类任务,mAP 在 0.75 以上算可用,0.8 以上算稳定,低于 0.7 基本不建议直接上线。这里有个经验:valid 切分要固定,同一份验证集才有纵向对比的意义。我一般会把训练时每 500 次迭代保存的权重批量跑一遍 mAP,挑最高的那个作为最终产物,而不是默认用最后一个权重文件:

for f in backup/*.weights; do echo "$f" >> map_log.txt ./darknet detector map dataset/obj.data cfg/yolov3_helmet.cfg "$f" >> map_log.txt 2>&1 done tail -n 5 map_log.txt

mAP 只看整体,看不出误检方向。安全帽检测最烦的是把反光、水渍、阴影当帽子。跑完 mAP 后我习惯再抽 100 张验证图,把预测框和标签框做一次匹配,统计“未戴帽被识别成戴帽”和“戴帽被漏检”的比例。前者在工地场景里是误报,后者是漏报,两者对业务的影响完全不同。匹配规则很简单:预测框和标签框的 IoU 大于 0.5 算命中,然后把结果分四类计数,这个统计脚本两小时就能写完。

从那以后,我每次训练完都会强制走一遍 mAP 加误检方向统计,确认权重没问题再进入部署,这个习惯帮我躲过了好几次把“看着还行”的模型直接丢到现场的风险。希望帮到你。

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

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

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

立即咨询