资讯动态

煤矿井下安全帽数据集实战:从标注到部署的漏检优化指南

发布时间:2026/10/13 4:15:25 来源:尧图企业网站定制
简介煤矿井下安全帽数据集是一套面向计算机视觉目标检测任务的已标注数据集专为煤矿作业场景下的安全帽佩戴识别而构建可用于监督学习、实时安全监控、异常检测及行为分析等方向。包内共436个文件包含218张真实井下场景的JPG图像与218个配套XML标注文件标注以边界框形式精确标出安全帽位置压缩包整体约179.65MB结构清晰便于直接用于模型训练与评估。目前已有816人学习下载适合目标检测初学者及工业安全智能化研究者作为训练基准或算法评测数据。借助该数据集可训练出自动检测安全帽佩戴情况的识别模型辅助煤矿智能监控系统及时提醒未戴帽违规行为提升井下作业安全水平同时也可作为基准数据集比较不同检测算法的性能为后续研究与工程落地提供扎实数据基础。1. 煤矿井下安全帽数据集解决现场误检问题的关键资源某次做矿下安监视觉检测我用的是通用大规模安全帽数据集训练出来mAP 0.9结果拿到井下现场漏检率直接让人怀疑人生。后来换用煤矿井下安全帽数据集才把漏检压下来。这份数据集的核心价值在于它采集于真实的井下环境光照杂乱、粉尘大、视角刁钻标注覆盖了戴帽与未戴帽两类场景专门喂给目标检测模型做安全帽佩戴识别。适合正在做矿山智慧安监、人员违章识别、以及安全帽检测算法研究和边缘部署的开发者。数据集本身不是玄学但用不好就会变成黑匣子所以这篇笔记想把结构和坑一次讲清。2. 数据集结构先看清目录、标注与划分方式2.1 目录结构与文件分布常见做法是这份标注好的安全帽数据集遵循 Pascal VOC 的组织方式顶层目录会有这几个文件夹目录/文件作用JPEGImages/原始井下图片JPG格式命名一般是 CAM_日期_时间 或按井下位置编号Annotations/标注文件VOC XML 格式每个图和 JPEGImages 一一对应ImageSets/Main/按 train/val/test 划分的 txt 文件label.txt / classes.txt类别清单下载下来先不要急着训练先摸清布局。我一般会这样看tree --maxdepth 2 -d . find . -name *.xml | wc -l find . -name *.jpg | wc -l head -n 20 ImageSets/Main/train.txt第一行看目录树第二三行确认图片与标注数量是否一致最后看一下划分文件格式。如果数量对不上说明原数据里可能有部分图像没有标注或者标注文件是别的格式。很多朋友拿到数据就直接解压开跑结果后期发现标注和图片张冠李戴所以先做这一步能省一天排错时间。还有一种情况是 JPEGImages 里混入了验证时生成的缩略图数量也对不上这时候按扩展名和大小筛一遍把小于 30KB 的图先放到一边检查。2.2 标注格式VOC XML 与 YOLO TXT 的差异我常见到的这种专业数据集里面用的多是 VOC XML 标注一个文件对应一张图。XML 里几何信息在bndbox节点下类别名通常在object里的name。以煤矿安全帽场景为例类别一般有三种定义方式组合含义helmet / head戴帽是 helmet不戴帽是 headhelmet / personperson 框整个人helmet 框帽子safety_helmet / head / person语义更细但训练时 person 权重会不同注意不戴帽子和戴帽子的边界有时很难定义。有人会把“帽子拿在手上”算不戴帽有人会算成戴帽。训练前先把类别口径统一不然评估时 mAP 虚高现场又是另一回事。我遇到过标注员把远处正在弯腰的人漏标只标了身上的反光背心这种脏样本看着无害却会让模型学到“有反光背心就默认戴帽”的坏习惯。下面用 Python 读取一个 XML 示例确认标注内容import xml.etree.ElementTree as ET def parse_voc(xml_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w, h int(size.find(width).text), int(size.find(height).text) boxes [] for obj in root.iter(object): name obj.find(name).text bndbox obj.find(bndbox) x1 int(float(bndbox.find(xmin).text)) y1 int(float(bndbox.find(ymin).text)) x2 int(float(bndbox.find(xmax).text)) y2 int(float(bndbox.find(ymax).text)) boxes.append((name, x1, y1, x2, y2)) return w, h, boxes w, h, boxes parse_voc(Annotations/000001.xml) print(w, h, len(boxes), boxes)xml.etree.ElementTree是 Python 自带的解析库不需要额外装。这里把 xml 里的尺寸和所有目标框打印出来目的不是马上训练而是快速检查有没有坐标为 0 的框、有没有负宽度、有没有类别名拼错。如果 xml 里 name 写成了helmett后面做格式转换时会直接漏掉这个类别训练时这张图就变成空标注相当于喂了一个负样本。检查完最好再随机挑 5 张图用矩形框可视化直接人工核对框是否贴住目标边缘这一步能发现比例失调的框。2.3 数据划分不要随机切分对井下场景如果随机划分 train/val很容易出现同一个摄像头画面被同时放进训练集和验证集验证指标虚高到了新巷道就现原形。建议按“采集位置”或“时间桶”分组划分。参考脚本import os, random from collections import defaultdict random.seed(42) images [f for f in os.listdir(JPEGImages) if f.endswith(.jpg)] grouped defaultdict(list) for img in images: cam_id img.split(_)[0] grouped[cam_id].append(img) train, val [], [] for cam_id, imgs in grouped.items(): random.shuffle(imgs) n_val max(1, int(len(imgs) * 0.1)) val.extend(imgs[:n_val]) train.extend(imgs[n_val:]) print(len(train), len(val))参数说明分组时以图片文件名的第一个字段比如摄像头编号作为键确保同一个摄像头的视频帧全部进同一个集合避免帧与帧之间高度相关导致数据泄漏。如果你的文件名是时间戳可以用日期作为分组键也能达到类似效果。实际项目里还要考虑一种情况不同摄像头可能装在同一个工作面画面内容高度重叠单纯按摄像头分并不能完全隔离。保守一点的做法是把同一个工作面的摄像头归成一组再按组划分。划分结果最好保存成 json之后每次跑实验都复用不要重新随机否则对比实验的公平性就没有保障。3. 训练前准备格式转换与关键训练参数3.1 从VOC XML到YOLO TXT的转换当前主流检测框架比如 YOLO 系列接收的标注是 TXT 格式每行一个目标class_index x_center y_center width height四个坐标全部归一化。如果你拿到的是 VOC XML就需要转换。我一般会写一个转换脚本同时把异常框过滤掉import os import xml.etree.ElementTree as ET CLASS_MAP {helmet: 0, head: 1, person: 2} def convert_xyxy_to_yolo(px, py, pw, ph, w_img, h_img): x_c (px pw / 2) / w_img y_c (py ph / 2) / h_img w_n pw / w_img h_n ph / h_img return x_c, y_c, w_n, h_n def xml_to_txt(xml_path, txt_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w_img int(size.find(width).text) h_img int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASS_MAP: print(funknown class: {name}, skip) continue bnd obj.find(bndbox) x1 float(bnd.find(xmin).text) y1 float(bnd.find(ymin).text) x2 float(bnd.find(xmax).text) y2 float(bnd.find(ymax).text) if x2 x1 or y2 y1: continue w_box x2 - x1 h_box y2 - y1 x_c, y_c, w_n, h_n convert_xyxy_to_yolo( x1, y1, w_box, h_box, w_img, h_img ) if w_n 0 or h_n 0 or w_n 1 or h_n 1: continue lines.append(f{CLASS_MAP[name]} {x_c:.6f} {y_c:.6f} {w_n:.6f} {h_n:.6f}) with open(txt_path, w) as f: f.write(\n.join(lines))转换逻辑里做了三次防护不认识的类别直接跳过避免脏标签混入x2 x1说明框退化丢掉归一化后宽高不在(0,1]内也丢掉。参数上CLASS_MAP按你的实际类别顺序写YOLO 数据集的data.yaml里class顺序必须和这里一一对应否则模型学到的是错位标签。转换完以后记得到 labels 目录里抽查几个 txt看第一个字段是不是 0/1/2而不是 1/2/3。我踩过这坑某次因为 class 从 1 开始训练时给了 3 个类别结果 head 和 person 全部预测错位现场一塌糊涂。3.2 图像尺寸、Batch Size与Anchor井下安全帽目标通常很小人距离摄像头十几米在 1080P 图上帽子可能只有几十个像素。训练时输入尺寸如果直接套默认 640小目标信息就容易丢。我一般会用 1280前提是你的 GPU 显存足够否则就保持 640 但开启多尺度训练。Batch size 方面显存受限时 batch 从 8 降到 4模型更容易在低照度数据上收敛。anchor 一般让框架自动计算但如果你发现数据里小目标特别多可以在训练前用脚本统计所有标注框的宽高分布决定是否手动设定 anchor 大小。# 统计标注框宽高画散点图前先看输出 awk {print $4, $5} labels/*.txt | awk {print $1*1280, $2*1280} | sort -n | head -20这个命令把每个 TXT 里归一化的宽高乘回 1280输出前 20 个最小的目标尺寸让你心里有数最小的框是 10 像素还是 30 像素。如果最小框小于 15 像素无论怎么调 anchor 都很难检出更有效的做法是提高输入分辨率或者用 SAHI 分割对齐。这里提醒一句训练尺寸和推理尺寸必须保持一致。有人训练用 1280部署时为了快改成 640结果小目标全丢这不叫优化叫自欺欺人。3.3 针对井下低照度与粉尘的数据增强井下图像最典型的问题就是暗、灰、亮斑强。数据集本身会有一定覆盖但训练时还是推荐针对性增强。以某个 YOLO 项目的增强 yaml 为例# augment.yaml mosaic: 0.8 mixup: 0.2 hsv_h: 0.015 hsv_s: 0.4 hsv_v: 0.2 random_perspective: 0.0hsv_v设成 0.2 是让亮度有机会抖动但注意不要设太大否则暗图会变伪影。粉尘造成的模糊一般用轻微高斯模糊或 GaussianNoise 来模拟。但增强比例要控制Mosaic 开太大会让目标被切碎Mixup 开太大头盔边缘和背景混淆。我个人的习惯是 mosaic 0.5 到 0.8mixup 0.1 到 0.2先跑一版再决定。另外别忘了添加 RGB 噪声模拟井下粉尘的颗粒感代码上可以用np.random.normal生成噪声叠加到输入图但推理阶段不要做同样的处理否则现场数据分布又变了。4. 训练与评估不要被mAP骗了4.1 用PR曲线和F1选阈值而不是只看mAP很多项目汇报里写 mAP 0.92听着很牛但那是不同置信度下 PR 曲线的平均积分。实际部署时你是固定一个置信度阈值工作的站在 0.5 和 0.8 时的表现可能天差地别。井下安全帽检测更看重对“未戴帽”的召回因为它直接关联隐患排查。如果有未戴帽样本漏检整套系统的价值就打了折扣。评估时打印出 PR 曲线数据找到召回率达到 90% 对应的置信度阈值。常见做法是用训练完毕的模型对验证集做一次批量预测# 以某个YOLO仓库的val.py为例 python val.py --data data.yaml --weights best.pt --conf-thres 0.05 --iou-thres 0.5这里把 conf-thres 低到 0.05是为了拿到完整的 PR 曲线最终表里能看到每个置信度下的 precision 和 recall。然后你根据现场诉求选择阈值如果现场有审核人员复核可以把阈值放低一点多给一点误报保漏报如果想让系统自动联动报警则宁可阈值抬高因为误报频繁会让人取消报警系统。我一般会在测试集上同时打印 F1-score 随置信度的变化取 F1 最高的点做基准值再根据现场偏向微调。4.2 现场翻车案例灯光检出成安全帽某次在一条巷道测试模型把矿灯和刮板运输机上的反光贴大面积误报成安全帽。验证集里这事并没有暴露因为我当时的验证集图片大多是工作面上正常采光没有模拟夜里或逆光场景。根因是黄色安全帽和黄褐色矿灯在颜色空间上高度重合单靠颜色区分不可靠。后来做的两步修正一是推理前用 CLAHE限制对比度自适应直方图均衡化做预处理把暗部细节拉出来帮助模型辨认形状二是在训练集里混入一部分带强反光样本让模型学会把反光当背景。效果立竿见影误报数降到原来的三分之一。import cv2 def clahe_preprocess(img): lab cv2.cvtColor(img, cv2.COLOR_BGR2LAB) l, a, b cv2.split(lab) clahe cv2.createCLAHE(clipLimit3.0, tileGridSize(8, 8)) l clahe.apply(l) lab cv2.merge([l, a, b]) return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)clipLimit是 3.0取值越高对比度越强但如果太高会把噪声也放大尤其在粉尘颗粒多的地方反而容易产生假边缘。tileGridSize决定局部对比度增强的窗口大小8×8 在 1080P 下效果适中换成 16×16 会保留更多暗部整体亮度但计算量稍大。这个预处理只加在推理端训练端不要做否则模型输入分布不一致效果会反弹。4.3 置信度阈值与NMS参数的调整建议推理时除了预处理conf-thres 和 NMS 的 iou-thres 也要跟着场景走。我用 YOLO 做推理时常用参数如下参数建议值说明conf-thres0.35-0.5现场要求低漏报建议 0.35 以下要求低误报建议 0.5 以上iou-thres0.45-0.6重叠目标比较多时调大支持更多的框合并max-det200-300单张图目标数量受限防止巷道多人连排时漏检如果现场摄像头比较远安全帽在画面中只占二三十像素我会把 conf-thres 降到 0.3 以下同时用推理时图像分辨率至少 1280。不要在训练时用 640推理时却缩到 416那样现场效果会很惨。NMS 的 iou-thres 也要注意井下人多时相邻安全帽很容易被 NMS 合并成一个框导致人数统计少一个。遇到这类情况把 iou-thres 调到 0.6 试试但也要警惕误检框变多。5. 避坑煤矿井下安全帽数据集使用中的五个常见问题5.1 训练阶段loss震荡不收敛mAP波动大现象训练时 loss 曲线上下抖动前 100 轮 mAP 始终在 0.3 徘徊甚至出现训练集 mAP 高、验证集 mAP 低的过拟合。原因常见的有三类。第一数据集里标签有脏数据比如框坐标为 0、类别为空格。第二数据集中小目标占比大默认 anchor 不匹配。第三井下图像普遍较暗增强参数开得过大导致模型在增强图上学不到稳定特征。解决先跑一个标签质量检查脚本find labels -name *.txt | xargs -I{} sh -c awk -F {if($13) print FILENAME, $0} {}这个命令检查第一列类别索引是否超出 class 数比如超过 3 就是异常。接着在训练命令里开启 autoanchor对应 YOLOv5 的--autoanchor它会自动计算适合数据集的锚框。训练命令改成python train.py --data data.yaml --cfg yolov5s.yaml --autoanchor --epochs 150 --batch 8 --img 1280参数说明--autoanchor是让算法重新计算锚框分布--img 1280是为小目标考虑batch 8 是兼顾显存和 BN 稳定性。如果 loss 仍然震荡考虑把学习率从 0.01 降到 0.005或者用 warm-up 加长到 5 个 epoch让模型先稳定下来。5.2 数据划分训练和验证重叠线上指标虚高现象验证集 mAP 0.95但是模型部署到新的一段井下巷道漏检严重。原因数据集里的图片很多来自同一摄像头同一时段按文件随机划分时高度相似的帧会同时出现在训练集和验证集。模型相当于“见过”验证集。解决按照摄像头 ID 或日期严格分组。前面 2.3 节已经写过分组脚本重点是要保存分组结果不要每次重新随机。如果你拿到的数据集没有明显摄像头 ID可以按图像的亮度直方图聚类把相似图片分到同一组再进行划分这是比较激进的做法。我见过更严苛的做法把同一个巷道内连续 5 分钟内的视频帧全部归到同一组这样能最大程度避免帧连续性泄漏。5.3 目标识别矿灯、安全帽反光条造成大量误检现象推理输出里安全帽框打到矿灯上、打到头盔上的反光条上甚至打到电缆挂钩的反光贴。原因井下补光区域亮度高反光条的材质特征和黄色安全帽很像模型抓到了颜色和亮度信息但没有学到形状和上下文。如果训练集里反光样本不足就会这样。解决收集现场误检截图做成难负样本。把所有误检区域裁剪出来保存成背景图加进训练数据标注为空让模型学会把反光条当负样本。训练时把 hsv_v 从 0.2 降低到 0.1避免增强阶段把反光效果放得过大。推理时用 CLAHE 预处理见 4.2也能降低反光对比度。另外可以给这些误检区域打上 ignore 标签有些 YOLO 版本支持difficult字段调参时能直接忽略难负样本。5.4 颜色分布黑色和深灰色安全帽漏检率偏高现象训练集里黄色安全帽多现场黑色安全帽漏检高模型输出一张图里只框住黄色安全帽黑灰色的没反应。原因不同矿区安全帽颜色政策不同有些采煤区用黑色但数据集采集多来自黄色安全帽区域导致训练数据颜色分布倾斜。模型学会的是“黄色帽形”的组合。解决先统计训练数据标注框的 HSV 直方图看看是不是黄色系占了 80% 以上。然后在数据增强阶段用一个接近黑色的减光变换专门增广少量样本。最重要的还是调换一部分远端样本补采黑色安全帽的实际图像重新标注。单靠颜色抖动拉不上来因为黑色和黄色在亮度通道上差了三个量级。5.5 部署阶段模型帧率不达标无法在边缘盒子上实时跑现象训练好的 YOLOv5s 模型在边缘设备上只有 7FPS现场客户要求不下 15FPS。原因模型在训练机用了大分辨率如 1280和高帧率衡量但边缘设备的 CPU/NPU 算力低而且依赖库加载时间没优化。解决部署前不要直接用原来的 pt 模型做推理。先用 INT8 量化的 ONNX 或 TensorRT 加速输入分辨率从 1280 降到 960 或 800推理帧率可以翻倍。如果帧率仍然不够换更轻的骨干对应 YOLOv5n 或 YOLOv8n并删掉大目标输出层只保留适合目标尺寸的特征尺度。提供一下 ONNX 导出的注意项python export.py --weights best.pt --include onnx --imgsz 960 --half参数含义--imgsz是导出时的推理尺寸必须和部署尺寸一致--half代表半精度适合支持 FP16 的显卡或 NPU。在纯 CPU 设备上half 不一定更快需要做 AB 测试确认。这里还有个性别不是玄学的坑量化后精度通常下降 1-2 个点如果本来就贴着接受线量化后必然不及格所以要在数据集上留有余量。6. 进阶拿难例挖掘把精度再拉三到五个点第一种策略是先用第一轮训练好的模型对未标注的井下视频帧做预测把“预测为 0 或低置信度但实际是安全帽”的框抽出来人工确认后加入训练集。第二种是把“预测置信度很高但实际是背景”的误检框抽出来作为负样本。这两种合在一起就是难例挖掘我每次一轮训练后都会跑一遍。这里贴一段半自动抽难例的 Python 脚本import cv2 from pathlib import Path def mine_annotated(model, img_dir, save_dir): save_dir Path(save_dir) save_dir.mkdir(exist_okTrue) for img_path in Path(img_dir).glob(*.jpg): img cv2.imread(str(img_path)) preds model.predict(srcimg, conf0.25, iou0.6) if not preds: continue boxes preds[0].boxes.xyxy.cpu().numpy().astype(int) cf preds[0].boxes.conf.cpu().numpy() # 把置信度在0.3到0.5之间的框挑出来这些往往是困难样本 for box, score in zip(boxes, cf): if 0.3 score 0.5: x1, y1, x2, y2 box crop img[y1:y2, x1:x2] cv2.imwrite(str(save_dir / f{img_path.stem}_mined.jpg), crop)参数解释conf0.25是推理时的最低置信度这里故意设得低一点模型更容易输出不确定的框0.3 到 0.5 的置信度区间就是难例候选区。把这些裁剪图收集起来先筛掉误检再把确实为安全帽的图标注好插入训练集。持续两轮迭代后我自己一般能把 F1 提高 3 到 5 个点尤其是黑色安全帽和反光场景。要注意难例挖掘很容易把同一段视频里的相似帧重复挖进来导致数据集膨胀但多样性不见涨。所以挖掘前先做帧去重按帧间像素差过滤一遍。从那以后我每次训练完第一轮都强制走一遍难例挖掘再汇报指标希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑