公共场所危险物品检测数据集:VOC+YOLO双格式1431张6类别实战指南

发布时间:2026/10/5 0:34:07
公共场所危险物品检测数据集:VOC+YOLO双格式1431张6类别实战指南 简介本资源为公共场所危险物品检测数据集面向从事目标检测算法训练与安防场景研究的高校学生、算法工程师及竞赛选手可用于危险物品识别模型的训练、验证与迁移学习实验。数据集同时提供Pascal VOC与YOLO两种标注格式包含jpg图片及对应的xml、txt标注文件无需额外转换即可直接接入主流检测框架。压缩包共2000个文件以1431个xml标注文件和569个txt标注文件为主整体约126.77MB采用7z格式打包。标注共覆盖6个类别包括billete、knife、monedero、pistol、smartphone、tarjeta总标注框数1497个其中knife类别框数最多达1035个其余类别分布相对稀疏使用labelImg以矩形框方式完成标注。目前已有330人学习下载适合需要快速获取真实场景标注数据、验证检测模型效果或开展小样本类别研究的读者参考使用。1. 公共场所危险物品检测数据集1431 张 VOCYOLO 双格式到底能干什么地铁安检口、商场入口、车站候车厅这类公共场所摄像头每天产生的画面里真正需要报警的危险物品占比极低但漏检一次代价极高。想做一个能跑起来的危险物品检测模型第一步不是调网络结构而是先有一份标注规范、类别清晰、格式能直接喂给 YOLO 的数据集。这份公共场所危险物品检测数据集 VOCYOLO 格式 1431 张 6 类别就是干这个的1431 张图像同时提供 VOC 的 XML 标注和 YOLO 的 txt 标注覆盖 6 个危险物品类别拿来就能训练 yolov8 训练自己的数据集这类任务。它适合三类人一是刚入门目标检测、想找一个真实场景数据集练手的新手二是做安防、安检方向、需要快速验证算法可行性的工程师三是想拿现成标注做迁移学习、再往自己场景微调的人。1431 张不算大但胜在双格式齐全、类别聚焦省掉了最耗时的标注环节。下面从数据本身讲到训练、评估、踩坑把这条路走通。2. 拆开这份数据集VOC 与 YOLO 双格式的目录结构和类别定义拿到一个压缩包先别急着解压就往模型里塞。花十分钟把目录结构、标注格式、类别分布摸清楚能省掉后面几个小时的报错排查。这一章讲清楚这份数据集长什么样以及 VOC 和 YOLO 两种格式到底差在哪。2.1 解压后应该看到的目录长什么样这类双格式数据集常见做法是根目录下并列两套标注图像共享或各存一份。典型结构如下danger_dataset/ ├── JPEGImages/ # 所有图像1431 张通常是 .jpg ├── Annotations/ # VOC 格式 XML 标注与图像同名 ├── labels/ # YOLO 格式 txt 标注与图像同名 ├── ImageSets/ │ └── Main/ │ ├── train.txt # 训练集图像名列表不含扩展名 │ ├── val.txt # 验证集图像名列表 │ └── trainval.txt # 训练验证 └── classes.txt # 6 个类别名称一行一个先跑一条命令确认图像数量和标注数量是否对得上这是最容易被忽略的一步# 统计图像数量 ls danger_dataset/JPEGImages/ | wc -l # 统计 VOC 标注数量 ls danger_dataset/Annotations/ | wc -l # 统计 YOLO 标注数量 ls danger_dataset/labels/ | wc -l三个数字理论上应该都是 1431。如果 labels 数量偏少说明有图像没有对应标注可能是负样本也可能是漏标如果 XML 数量偏少同理。数量对不上不一定是错但必须知道原因否则训练时模型会学到错误的背景概念。2.2 VOC 的 XML 和 YOLO 的 txt 到底差在哪VOC 格式用 XML 描述每个目标核心是绝对像素坐标YOLO 格式用 txt核心是归一化后的中心点加宽高。理解这个差异是后面写转换脚本、排查坐标错位的基础。维度VOC (XML)YOLO (txt)坐标形式左上角 xmin,ymin 右下角 xmax,ymax中心点 cx,cy 宽 w 高 h数值范围绝对像素值归一化到 0~1类别表示标签名文本如nameknife/name类别索引从 0 开始一行/一目标每个目标一个object块每个目标一行依赖文件无需要 classes.txt 定义索引顺序一个 VOC 的 XML 片段长这样annotation filename0001.jpg/filename size width640/width height480/height /size object nameknife/name bndbox xmin120/xminymin80/ymin xmax300/xmaxymax260/ymax /bndbox /object /annotation对应的 YOLO txt 一行是0 0.328125 0.354167 0.281250 0.375000这里的 0 是类别索引后面四个数分别是中心点 x、中心点 y、宽、高全部除以图像宽高归一化。换算关系是cx(xminxmax)/2/widthw(xmax-xmin)/widthy 方向同理。记住这个公式后面转换和排查都靠它。2.3 6 个类别怎么确认类别顺序为什么不能乱类别顺序是 YOLO 训练里最隐蔽的坑之一。classes.txt 里第一行对应索引 0第二行对应索引 1一旦训练时的类别顺序和标注生成时不一致模型会把刀识别成枪而且 loss 还会正常下降属于典型的玄学翻车。# 查看类别定义 cat danger_dataset/classes.txt # 统计每个类别在 YOLO 标注里出现的次数 awk {print $1} danger_dataset/labels/*.txt | sort | uniq -c | sort -rn第二条命令输出的是每个类别索引的目标框数量。如果某个类别数量极少比如个位数训练时基本学不出来要么补充数据要么在评估时单独说明。类别分布严重不均衡时别指望默认配置能有好效果后面第 4 章会讲怎么处理。提示动手训练前先把 classes.txt 备份一份训练配置里的names必须和它逐行一致顺序都不能换。3. 从 VOC 到 YOLO转换脚本、划分数据集和训练配置双格式数据集虽然省了标注但真正训练时通常还是要统一到一种格式并且自己重新划分训练验证集。这一章给出可直接抄的转换脚本、数据集划分方法和 yolov8 训练配置把拿到数据到跑起训练这段路铺平。3.1 写一个 VOC 转 YOLO 的转换脚本即使数据集已经带了 YOLO 格式也建议自己跑一遍转换一来验证标注一致性二来熟悉坐标换算。下面这个脚本把 XML 转成 YOLO txtimport os import xml.etree.ElementTree as ET # 类别名到索引的映射顺序必须和 classes.txt 一致 classes [class0, class1, class2, class3, class4, class5] class_to_id {name: i for i, name in enumerate(classes)} def convert(xml_path, out_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_to_id: continue # 跳过未定义类别避免索引错乱 cls_id class_to_id[name] 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.0 / w cy (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(out_path, w) as f: f.write(\n.join(lines)) if __name__ __main__: xml_dir danger_dataset/Annotations out_dir danger_dataset/labels_new os.makedirs(out_dir, exist_okTrue) for fn in os.listdir(xml_dir): if fn.endswith(.xml): convert(os.path.join(xml_dir, fn), os.path.join(out_dir, fn.replace(.xml, .txt)))逻辑说明脚本遍历每个 XML读取图像宽高对每个 object 做坐标归一化。关键参数是classes列表必须和数据集 classes.txt 完全一致否则索引全错。:.6f保留六位小数是 YOLO 官方推荐的精度太少会丢坐标精度太多没必要。跳过未定义类别那行很重要遇到拼写不一致的标签名时不会静默生成错误索引。3.2 划分训练集和验证集别用随机划分糊弄1431 张的规模常见做法是 8:2 或 9:1 划分。但危险物品检测有个特殊性同一场景、同一物品的连续帧如果被随机分到训练和验证两侧验证指标会虚高。更稳的做法是按场景或按视频来源分组划分。import os import random img_dir danger_dataset/JPEGImages names [f[:-4] for f in os.listdir(img_dir) if f.endswith(.jpg)] random.seed(42) # 固定种子保证可复现 random.shuffle(names) split int(len(names) * 0.9) train, val names[:split], names[split:] with open(train.txt, w) as f: f.write(\n.join(train)) with open(val.txt, w) as f: f.write(\n.join(val)) print(ftrain{len(train)}, val{len(val)})参数说明random.seed(42)固定随机种子保证每次划分一致方便对比实验。0.9是训练集比例数据量小的时候验证集留 10% 就够留太多训练数据不够。如果数据来自多个视频源把random.shuffle换成按源分组能显著降低验证指标虚高的问题。3.3 yolov8 训练配置从 data.yaml 到启动命令YOLO 格式准备好后写一个 data.yaml 指向数据路径path: /abs/path/danger_dataset train: train.txt val: val.txt nc: 6 names: [class0, class1, class2, class3, class4, class5]nc是类别数必须和 names 长度一致。path用绝对路径最稳相对路径在不同工作目录下容易找不到文件。然后启动训练yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20参数逐个说modelyolov8n.pt用 nano 版本1431 张数据量小大模型容易过拟合imgsz640是通用输入尺寸如果原图分辨率远大于 640小目标会被缩没需要权衡batch16在 8G 显存上基本能跑显存不够就降到 8lr00.01是初始学习率小数据集可以降到 0.001 更稳patience20表示 20 轮没提升就早停省时间。注意训练前先用yolo detect train ... epochs1跑一轮确认数据能正常加载、类别数对得上再开长训练。直接上 100 轮配置错了就是白等几小时。4. 训练危险物品检测模型时最容易翻车的几个地方数据格式对了、脚本跑通了不代表训练就顺。这一章记录几个我在小规模危险物品数据集上反复踩过的坑每条按现象、原因、解决写能帮你少走弯路。4.1 现象loss 正常下降但验证集 mAP 一直是 0原因通常有两个。一是验证集的 txt 路径没配对YOLO 找不到标注把所有验证图当成了纯背景自然算不出 mAP。二是类别索引错位预测框和标注框类别对不上IoU 再高也不算正样本。解决先跑一条命令确认验证标注能被读到ls danger_dataset/labels/ | head # 确认 val.txt 里的名字在 labels 目录下都有对应 txt while read n; do [ -f danger_dataset/labels/$n.txt ] || echo missing: $n; done val.txt如果输出一堆 missing就是路径或命名问题。命名要保证图像、VOC、YOLO 三者同名只差扩展名。4.2 现象模型把背景当危险物品误检特别多原因1431 张里负样本没有危险物品的图比例如果太低模型没见过什么都没有的画面就会强行在背景里找目标。危险物品检测场景里绝大多数画面本来就是安全的负样本反而应该占相当比例。解决往训练集里补一批纯背景图标注文件留空空 txt。YOLO 支持空标注文件表示这张图没有目标。空标注比例控制在 10%~20% 比较合适。注意空 txt 文件必须存在不能只有图像没有 txt否则会被当成漏标。4.3 现象小目标远处的刀具、小包裹几乎检不到原因imgsz640对原图做了下采样远处的小目标可能只剩几个像素特征还没提取就没了。这是分辨率和小目标之间的根本矛盾。解决三个方向。一是提高输入尺寸到 960 或 1280代价是显存和速度二是用切片推理把大图切成小块分别检测再合并三是数据层面对小目标做复制粘贴增强增加其在训练集中的出现频率。先试提高 imgsz最直接。4.4 现象训练到后期 mAP 突然掉下去原因小数据集上学习率没降下来模型在最优解附近震荡甚至跑飞。1431 张的规模默认学习率衰减策略往往不够。解决加余弦退火或降低初始学习率。命令行加cos_lrTrue或者把lr0从 0.01 降到 0.001。同时开patience20早停别让它在坏区间里继续跑。判断依据是看验证 loss如果验证 loss 开始上升而训练 loss 还在降就是过拟合或震荡的信号。4.5 现象换一台机器训练结果完全不一样原因随机种子、CUDA 版本、甚至数据加载顺序都会影响结果。目标检测训练本身就有随机性小数据集上波动更明显。解决固定seed参数记录完整的训练命令和环境版本。对比实验时只改一个变量其他全部锁死。别指望两次训练 mAP 完全一致差 1~2 个点是正常的差 10 个点以上就要查配置。5. 用这份数据集做迁移学习和效果验证的进阶技巧数据量只有 1431 张从零训练很难达到生产可用水平但作为迁移学习的起点、或者验证一个新想法的快速实验平台它很合适。这一章讲两个具体技巧怎么用预训练权重快速起量以及怎么设计一套靠谱的验证流程避免被虚高的指标骗了。5.1 冻结主干 分层学习率小数据集的标准打法直接拿 COCO 预训练的 yolov8n.pt 微调比从零训练收敛快得多。更进一步的做法是前若干轮冻结主干网络只训练检测头让模型先适应你的 6 个类别再解冻全网络做精细调整。# 第一阶段冻结主干只训检测头 yolo detect train datadata.yaml modelyolov8n.pt \ epochs30 freeze10 lr00.001 batch16 imgsz640 # 第二阶段解冻全部小学习率精调 yolo detect train datadata.yaml modelruns/detect/train/weights/best.pt \ epochs70 lr00.0001 batch16 imgsz640freeze10表示冻结前 10 层具体层数可以按模型结构调整。第一阶段学习率可以稍大因为只更新检测头第二阶段必须降下来否则会破坏预训练学到的特征。这种两段式训练在小数据集上通常比一次性训练高几个点 mAP。5.2 验证不能只看 mAP要分场景看召回危险物品检测的核心指标是召回率不是 mAP。漏检一个危险物品的代价远大于误检一个。所以验证时要单独看每个类别的召回尤其是数量少的类别。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) metrics model.val(datadata.yaml, imgsz640, conf0.25) # 逐类别看指标 for i, name in enumerate(metrics.names.values()): p metrics.box.p[i] # 精确率 r metrics.box.r[i] # 召回率 ap metrics.box.ap50[i] print(f{name}: P{p:.3f} R{r:.3f} AP50{ap:.3f})参数说明conf0.25是置信度阈值验证时用默认值即可实际部署时这个阈值要按场景调。危险物品场景宁可调低阈值提高召回误检可以靠人工复核兜底。看指标时重点关注召回低的类别那才是真正会出事的短板。5.3 一个我常用的习惯先做 50 张的过拟合测试每次拿到新数据集我不会直接开长训练而是先拿 50 张图跑 200 轮看模型能不能把这 50 张几乎完美拟合。如果连 50 张都过拟合不了说明数据管道、标注格式或类别定义有问题这时候开 1000 轮也是浪费时间。这个习惯帮我省下的时间比任何调参技巧都多。具体做法从 train.txt 里抽 50 个名字单独建一个 mini.yamlepochs200训练完看训练集 mAP 能不能到 0.95 以上。到不了就回去查标注别怀疑模型。这个方案值不值得做取决于你愿不愿意在动手前花这半小时——我的答案是每次都值得。希望帮到你。本文还有配套的精品资源点击获取