
矿下调度室里几十路监控画面挂在墙上真正能同时盯住两路的人都没有。重点巷道里有人跌倒如果几十秒内没被发现后果可以想见。所以现在很多智慧矿井项目都在做人员行为状态检测——用视觉模型实时判别人员的爬、走、靠、站、跌倒等状态而这件事的根基就是一份标注可靠的数据集。这篇就聊一套典型的配置智慧矿井人员行为状态检测数据集VOCYOLO双格式4847张图、8个类别。我接触过不少做矿山安监视觉的团队大家前期最头疼的往往不是模型选型而是数据从哪来、标注怎么用、训练怎么排。这套数据集的标题信息很有代表性值得掰开揉碎讲清楚。文章后面会拆解矿井场景的难点、八类标签的边界定义、双格式的转换逻辑、训练策略以及从检测框到现场报警之间的工程问题。如果你正准备拿这类数据集跑实验或者想评估数据质量这篇应该能帮你省不少时间。1. 矿井环境下的行为检测难点远比想象中多1.1 光照、粉尘、遮挡矿井图像的三座山先说一个容易被低估的事实矿井监控画面和普通园区监控完全不是一个难度等级。井下照明通常是矿灯和巷道顶灯混着来画面里经常出现一个强光点加一圈深阴影的极端动态范围。矿工头顶那盏矿灯一旦正对摄像头整个人脸和上半身直接过曝成一片亮斑算法拿到这个区域时基本都是废信息。再加上井下粉尘镜头前会有一层持续的雾感图像对比度整体被压低边缘变糊。很多通用数据集上表现良好的检测器到了这种画面上mAP掉十几个点是常有的事。遮挡问题也很突出。井下人员习惯穿宽松工作服轮廓本来就不紧凑。安全帽、自救器、矿灯、工具包这些东西把人体各个部位盖得严严实实。人与人之间还会互相遮挡遇到抢险或交接班场景几个人凑在一起互相重叠的框很难分清边界。摄像头安装角度也不统一有些是巷道顶部俯视有些是侧壁平视同一个“走”的动作在两个视角下形态完全不同。这套数据集如果采集自真实井下或模拟巷道环境那标注难度就天然比普通行人检测高一个量级。标框的人不但要看清人还要判断“靠墙算不算靠”“蹲下和跌倒怎么划界”。我见过不少标注了上万张的数据集真正能直接拿去训模型的比例并不高问题大多出在边界定义模糊。1.2 八类状态各自绑定的业务价值行为状态检测不是单纯为了把“人”框出来而是要把“人在干什么”变成可报警、可统计的语义信息。我们看这8个类别每一个背后都有对应的管理诉求。跌倒是价值最高的类别直接关联安全应急。井下湿滑、台阶多跌倒可能意味着摔伤、晕厥早发现几秒钟差别很大。爬这个动作也很特殊正常人在井下巷道里趴着移动多半是发生事故后的自救或设备检修属于需要关注的非正常状态。靠和站偏静态更多用来判断人员是否在指定区域停留、是否疲劳蹭墙休息。走和跑是动态通行的基本状态跑动往往关联到紧急撤离或违规追赶。蹲和坐则通常对应作业或休息场景既能用来统计工时也能辅助判断人员是否长时间滞留某处。所以这8类如果标注准确下游能干的事非常多跌倒即时报警、区域人员密度统计、违规滞留提醒、巡检路径分析。这也是为什么智慧矿井项目愿意花成本去做行为识别而不是只做普通的人体检测。1.3 单帧检测能做多少不能做多少有一点必须提前说清楚这类数据集给到你的通常是单帧标注也就是每张图片里每个人的类别标签和包围框。单帧模型能告诉你“这一帧里这个人大概率在蹲着”但它不知道“这个人是刚刚从站立状态突然倒下去的”。真正的行为识别分成两层。第一层是空间层用目标检测模型对每一帧做类别预测输出8个状态的置信度。第二层是时间层把连续帧的预测结果串起来判断状态变化是否符合某种模式。比如跌倒的典型特征是短时间内人体框的中心点快速下移、宽高比剧烈变化、随后低置信度地躺倒或蹲坐。这些信息在单帧数据里是不完整的。所以拿到这套数据时心态摆正它的核心价值是帮你训练好第一层空间检测器。“跌倒”能不能从检测框上升到报警还要靠工程侧的时序逻辑去补。这个问题我在第5章展开聊。2. 八类标签拆解类别边界比模型结构更能决定效果2.1 类别清单与ID映射标题里明确提到的状态词有5个爬、走、靠、站、跌。8个类别里还剩3个从矿井行为检测数据集的常见配置推测多半是蹲、坐、跑这类高频姿态。注意这只是一种合理推测真正以数据包里的标注类别清单为准。如果你拿到的是YOLO格式直接看类别配置文件如果是VOC格式把全部XML里的name字段去重看一眼就清楚了。下面给一张常见的类别映射参考表实际顺序可能不同但结构类似ID英文标签中文含义说明0crawl爬低姿移动常见于事故自救或设备检修1walk走正常步行矿下通行主状态2lean靠身体倚靠墙体或设备重心有支撑3stand站直立且不依赖外部支撑4fall跌突发的倒地或即将倒地的状态5squat蹲小腿承重、臀部不上座面的低姿状态6sit坐臀部接触地面、台阶或设备上的状态7run跑步频明显高于走、前倾明显的快速移动这个ID顺序在训练时就是YOLO标签txt文件里每一行开头的那个数字。你在自己的data.yaml里写类别名顺序必须和数据包里的txt标注顺序完全一致差一位整个模型就废了。我见过最典型的翻车现场就是训练数据里ID 2是lean配置文件里ID 2却写成了stand模型训完所有的站都被判成靠墙。这种错不会报任何异常最后看混淆矩阵才反应过来。2.2 相邻类别怎么定义边界8个类别里最难的是那些视觉上互相靠近的“邻居”站和靠的区分点在于“重心是否依赖外部支撑”。站立时人体重心垂直落在双脚上躯干基本直立靠墙时后背或肩膀明显贴住墙面身体有倾斜角度脚和墙之间常呈三角支撑。但在俯视摄像头下这个角度看不太清楚。标注规范里通常要求只有明显接触墙面且躯干倾斜超过某个角度才算靠否则一律标站。宁可少标靠不要用模棱两可的框污染模型。蹲和坐经常被搞混。区分点有两个一是臀部有没有接触面蹲的臀部悬空坐的臀部压在台阶或坐面上二是重心高度蹲的重心在小腿以上比较高坐的重心直接落在支撑面。侧面视角好判断俯视视角就得靠腿部折叠程度去猜。这部分是最消耗标注时间的。蹲和跌倒也有交叠。一个人跌倒后如果本能地蜷缩起来视觉上和蹲姿非常接近。这种情况标注规范里通常规定前一帧还有剧烈位移、当前帧呈低姿蜷缩的连续多帧都应标fall如果一开始就是静态低姿则标squat。没有这个规则标注员会按自己的主观感觉来同类样本内部都会打架。走和跑相对好分看步幅和步频。但低帧率摄像头下运动模糊严重跑动的人腿部拖影框容易画大画歪。建议标注时不要只看单帧有条件就看着相邻几帧一起标。你会发现类别边界定义这件事直接决定了模型训练时的“标准答案”质量。边界定义清楚了哪怕换个基础模型效果也稳边界一塌糊涂结构再新的网络也救不回来。2.3 从4847张图推算类别分布与长尾风险4847张图听起来不算少但要除以8个类别。如果类别分布均衡每个类别约600张但真实情况下走的样本可能占了大头爬和跌倒这种非常规状态可能只有两三百张甚至更少。这属于典型的长尾分布。类别不均衡带来的直接问题是头部类别把损失函数主导了尾部类别的召回率上不去。尤其是跌倒这种安全关键类别漏报后果严重宁可误报也不能漏报所以训练时要额外给它权重。拿到数据先做一次类别统计。YOLO格式的话遍历所有txt文件统计每行第一个数字的频次顺带检查有没有出现超出0-7的编号。这一步抛出一个简单的统计脚本后面可以直接用import os from collections import Counter label_dir path/to/labels counter Counter() for f in os.listdir(label_dir): if not f.endswith(.txt): continue with open(os.path.join(label_dir, f), r) as fh: for line in fh: parts line.split() if parts: counter[int(parts[0])] 1 for cls_id, count in sorted(counter.items()): print(fclass {cls_id}: {count})这个脚本输出的分布基本决定了你后面要不要做采样均衡或损失加权。如果发现爬、坐、跑各自只有200张上下那训练时就得格外小心过拟合增强策略也要往这些类别倾斜。3. VOC与YOLO双格式转换逻辑、校验脚本与常见坑3.1 为什么经常同时给VOC和YOLO两套格式VOC格式的XML可读性好一个框就是一组嵌套标签人能直接打开看标注工具大多原生支持。YOLO格式则更像为训练准备的“极简指令”每个目标就是一行数字模型读取效率高、不依赖额外解析库。两块场景其实不一样VOC适合人看、适合转成其他格式YOLO适合直接喂给训练脚本。所以你会发现很多数据集顺手给了两套本质上是同一份标注的两种表达。XML里写的是类别名和绝对坐标xmin、ymin、xmax、ymaxtxt里写的是类别ID和归一化后的中心点坐标加宽高。两边信息一致只是换算方式不同。3.2 从xml到txt的坐标转换与脚本如果只有VOC格式或者想自己验证双格式是否一致转换公式很简单cx (xmin xmax) / 2 / 图片宽度cy (ymin ymax) / 2 / 图片高度w (xmax - xmin) / 图片宽度h (ymax - ymin) / 图片高度注意所有数值最后都要除以图片真实宽高不要用XML里记录的size字段以外的任何猜测值。写个通用脚本如下import xml.etree.ElementTree as ET import os CLASS_MAP {crawl: 0, walk: 1, lean: 2, stand: 3, fall: 4, squat: 5, sit: 6, run: 7} def convert_voc_to_yolo(xml_path, txt_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASS_MAP: print(funknown class: {name} in {xml_path}) 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) cx ((xmin xmax) / 2) / img_w cy ((ymin ymax) / 2) / img_h bw (xmax - xmin) / img_w bh (ymax - ymin) / img_h lines.append(f{CLASS_MAP[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(txt_path, w) as f: f.write(\n.join(lines))这段代码足够应对绝大多数情况。如果你的数据集里出现某个类别名不在映射表里脚本会打印出来这就是你发现标注问题的第一道关口。3.3 拿到数据先做三件事对应、越界、可视化第一件事是确认图片和标注文件一一对应。常见错误是XML或TXT比图片多几个、少几个训练时图片找不到标签会跳过但标签找不到图片会导致你误以为某些类别根本不存在。先做一次文件名差集检查image_names {os.path.splitext(f)[0] for f in os.listdir(img_dir)} label_names {os.path.splitext(f)[0] for f in os.listdir(label_dir)} print(labels without images:, label_names - image_names) print(images without labels:, image_names - label_names)第二件事是检查坐标越界。YOLO格式的cx、cy、w、h理论上应该在0到1之间偶尔会有小数点误差但不会大于1.01。一旦出现负数或者明显的1.2、1.5这种值要么是转换脚本写错要么是原始标注框出界。跑一个遍历脚本把越界的文件名和坐标打出来def check_labels(label_dir): bad [] for f in os.listdir(label_dir): if not f.endswith(.txt): continue with open(os.path.join(label_dir, f), r) as fh: for line in fh: parts line.strip().split() if len(parts) ! 5: bad.append((f, len, parts)) continue cls_id, cx, cy, bw, bh int(parts[0]), *map(float, parts[1:]) if not (0 cls_id 8): bad.append((f, class, cls_id)) if not (0 cx 1 and 0 cy 1): bad.append((f, center, cx, cy)) if not (0 bw 1 and 0 bh 1): bad.append((f, size, bw, bh)) return bad第三件事是可视化抽检。写个脚本把框画回原图随机抽100张人工过一遍。这一步看起来土但比任何自动化检查都耐磨。自动化只能抓格式错误画框能看出人的类型标没标对、框有没有紧贴人体、跌倒样本的框是否包含了完全躺平的肢体。我自己的经验是这个抽检至少做两遍第一遍扫全量格式第二遍专门盯着跌倒、爬、蹲、坐这些难类看边界。还有人会问VOC和YOLO两份标注会不会内容不一致存在这种可能。有些数据集是分头生成的XML和TXT版本没有同步。校验方式很简单用上面的转换脚本把XML重新转成txt再和自带的txt比对内容任何不一致都能自动发现。这个环节别省转换脚本算坐标时浮点精度足够两个文件本不该有差异。4. 4847张图的训练安排数据划分、参数设置与评估视角4.1 验证集划分别用纯随机按场景分4847张图像如果来自连续监控视频那相邻帧之间的画面高度相似。直接全局随机划分train和val同一段视频的相近画面会同时出现在两边验证集指标会虚高得离谱——模型其实是“记住了”训练集里出现过的画面而不是泛化到了新场景。正确做法是按场景切片划分。如果数据包含不同拍摄点位的图片按点位分如果数据记录了时间段按时间片分如果连这个都没有就按文件名前缀的统计特征去分。原则是同一段连续画面里的帧必须全部进训练集或全部进验证集不能拆开。我见过有些人8:2随机分完训练集里某条巷子的人站着验证集里同样的巷子几乎同样的站位也站着模型啥都没学到验证mAP已经0.9了一到现场立刻现原形。建议训练集和验证集按8:2或9:1划分。数据量只有4847张的话我倾向9:1因为验证集只是评估模型效果最终还要靠现场的独立测试来验证。如果觉得9:1验证集的指标波动大可以多做几次不同随机种子划分看稳定性而不是盲目牺牲训练数据量。4.2 一套稳妥的初始训练配置模型选型上先跑一个轻量的YOLOv8n把数据质量测出来再升级到YOLOv8m或YOLOv8l冲精度。用5000张量级的数据直接上大模型不是不行但出了问题你分不清是数据问题还是模型问题调试成本很高。初始训练参数给一个可以直接抄的表参数建议值说明modelyolov8n第一轮跑通验证数据可用性imgsz640通用稳妥矿下小人目标多可调832epochs150配合早停别一上来300轮batch16或32按显存调训练不稳定就先降batchearly_stoppatience 20验证集连续20轮不涨就停optimizerAdamW或SGD数据量小AdamW收敛更快训练数据增强方面YOLO自带的Mosaic、随机翻转和色彩抖动已经够用。矿下场景建议额外加两类一是随机亮度对比度扰动模拟矿灯过曝和阴影二是轻微高斯模糊或运动模糊模拟粉尘和摄像头抖动。这些增强对跌倒、跑这类动作的鲁棒性帮助很大。到了第二轮可以换YOLOv8m输入尺寸提到768或832epochs加到200把mAP50-95往上推。你会发现大输入尺寸对矿下密集小目标的作用比换大模型更明显因为人员框在整个画面里占比普遍偏小。4.3 评估别只盯整体mAP跑完训练第一件事不是看那张漂亮的PR曲线而是拆到每个类别去看指标。统一叫mAP的数值会骗人如果walk和stand样本多整体mAP被它们拉高fall和crawl即便做得稀烂整体数字依然能看。要看的关键指标是三个每类的AP、混淆矩阵、以及F1曲线下的最佳阈值。混淆矩阵最有用它直接告诉你哪两个类别互相打架。前面聊过站和靠最容易混蹲和跌也常有交叠。如果混淆矩阵里fall被大量预测成squat你在推理阶段加时序逻辑时就得格外注意降低误报和漏报的策略也要据此设计。阈值选择也很讲究。默认置信度阈值0.25或0.5不是为矿井场景调的。跌倒类可以单独把置信度阈值调低换取更高召回率靠和站这种非关键类则可以把阈值调高减少无意义的报警噪音。多类别检测的好处就是可以对不同类别设置不同阈值很多框架支持别浪费这个自由度。5. 从检测框到现场可用时序判定、部署约束和增强建议5.1 单帧检测会误报跌倒报警要加时序逻辑模型训完在矿下现场最容易翻车的不是精度数字而是“动不动乱报”。没有时序逻辑时弯腰捡扳手、蹲下系鞋带、快速钻进矮巷道都可能被单帧模型给出fall的高置信度。然后调度室里的报警器隔几秒响一次用不了半小时现场负责人就会把整个系统关掉。解决方案很简单把单帧预测串成状态序列只有满足时序条件才触发报警。伪逻辑大致是这样对每个跟踪目标维护最近T帧的类别序列 如果连续T帧类别为fall且目标中心点在最近T帧内发生明显下移 则触发跌倒报警 否则不触发只记录状态T取3到5帧比较合适帧率25fps的情况下对应约0.2秒确认窗口既有实时性又能滤掉大部分抖动。中心点位移阈值要根据摄像头高度和视场角调俯视摄像头下跌倒的位移幅度和平视差异很大没有通用值只能标定的时候在真实现场测几组数据。如果数据集中fall样本本身就不多模型对跌倒的置信度普遍偏低时序逻辑里还要加入“类别概率平滑”。就是把单帧置信度做指数平均而不是直接用每一帧的原始置信度能大幅减少单帧误判。5.2 边缘端部署量化、帧率和视角差异矿井现场部署通常不是一台大GPU服务器怼在调度室那么简单。摄像头到调度室之间的带宽和稳定性不确定很多方案要求在矿下边缘盒子或防爆计算机上跑推理算力有限。YOLOv8n用TensorRT量化到FP16甚至INT8推理延迟能压到每帧10毫秒左右基本满足多路视频并发。代价是量化后mAP会掉一点尤其对跌倒这类尾部类别掉得更明显。所以量化策略上不要无脑全INT8。先把模型部署到目标设备实测各类别AP下降幅度如果fall的召回率掉得太多就保留FP16或者针对跌倒场景单独用一个轻量分类器做二次初筛。工程上常常不是优雅解决而是这种“笨但有效”的层层兜底。视角差异是另一个现场问题。训练数据大多是侧视或斜俯视现场有些摄像头装在正上方看到的主要是安全帽和肩膀人体宽高比完全不同。遇到这种情况最好的办法是收集一点目标点位的历史画面做增量微调哪怕只标几百张也比纯靠原数据集硬扛效果好得多。5.3 数据增强和边界情况处理回到数据本身4847张图用完一遍就没了为了让模型更耐打增强策略要针对矿下特征来亮度对比度随机扰动覆盖矿灯过曝和暗光阴影高斯模糊和运动模糊模拟粉尘、低帧率拖影随机遮挡模拟人为加一些黑色块模拟矿车或管道遮挡多尺度训练输入尺寸在一定范围内随机缩放适配不同安装高度下的目标尺度差异别忽略一个反直觉的增强水平翻转。矿下巷道是双向通行的人在画面里从左往右和从右往左的行为完全对称翻转几乎是纯收益。除非你的俯视数据里有明显的方向性标注比如矿车方向否则这类增强放心加。边界情况里最麻烦的是多人高度重叠。检测器输出的人体框互相交叠、类别各自不同跟踪算法又没接数据包里的跨帧关联信息这个数据集标注的是单帧框和类别不具备按目标ID追踪的条件所以多目标重叠时行为识别容易乱。工程上通常加一个规则当多个头部框交叠严重且类别冲突时优先采纳更高置信度的状态其他框进入待定下一帧再确认。这个规则虽然糙但在现场实测里比纯模型输出稳很多。如果让我给这套数据的使用排一个优先级我会把类别边界定义放在第一位先用我上面说的脚本和可视化方式把数据筛干净再谈训练。数据没问题后面所有事都顺数据有暗病模型再调也白搭。最后再分享一个我吃过亏后的习惯拿到VOCYOLO双格式数据集永远先用转换脚本把XML重新转出一份txt和自带的txt逐行比对确认完全一致后再划分数据集。这一遍15分钟能搞定却能从源头排除“两边标注不同步”这种最难排查的暗坑。希望这篇能让你少走几步弯路。