ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

公共场所危险物品检测数据集:VOC与YOLO双格式实战解析

公共场所危险物品检测数据集:VOC与YOLO双格式实战解析 简介本数据集面向公共场所安全监控、危险物品识别等计算机视觉任务适合目标检测模型训练与算法验证尤其适用于安防场景下的多类别物品检测研究。资源采用Pascal VOC与YOLO双格式标注包含1431张jpg图片及对应的xml、txt标注文件标注工具为labelImg以矩形框方式完成六类目标标注billete、knife、monedero、pistol、smartphone、tarjeta总框数1497个其中knife类别框数最多达1035个。压缩包为7z格式共2000个文件含1431个xml与569个txt整体约126.77MB目录结构清晰便于直接接入主流检测框架进行训练与评估。目前已有330人学习下载可为安防检测、危险品识别等课题提供现成的标注数据基础帮助读者省去数据采集与标注环节快速开展模型实验与效果对比。1. 1431 张图、6 个类别这份公共场所危险物品检测数据集到底能干什么如果你正在找一份能直接跑通 YOLO 训练、又带 VOC 双格式标注的小规模危险物品数据集这份 1431 张图片、6 个类别、1497 个标注框的资源值得先看一眼。它覆盖了 billete纸币、knife刀具、monedero零钱包、pistol手枪、smartphone手机、tarjeta卡片六类目标全部用 labelImg 画矩形框同时提供 Pascal VOC 的 xml 和 YOLO 的 txt 两套标注。换句话说你不需要自己写格式转换脚本拿到就能分别喂给 YOLOv5/v8 或者任何吃 VOC 的老框架。它的定位很明确不是那种几十万张的工业级数据集而是一个能让你在单卡上几小时内跑完一轮实验的轻量包。适合做公共场所安检场景的原型验证、课堂演示、或者作为你自建数据集前的格式参考。1431 张的体量意味着你用它来验证数据增强策略、损失函数改动、或者 efficient head 这类结构微调时迭代成本很低。但也要提前说清楚类别分布并不均衡knife 有 1035 个框monedero 只有 18 个这种长尾分布会直接影响训练时的类别权重设置后面会专门讲怎么处理。2. 拆开压缩包VOC 与 YOLO 双格式的目录结构与字段含义2.1 文件清单与命名规则解压后你会看到 jpg 图片、对应的 xml 文件、以及同名的 txt 文件三者数量都是 1431。另外还有一批 firc_ 开头的 txt 和一个「使用前必读.txt」。firc_ 系列通常是图片的分组或索引文件具体用途以「使用前必读.txt」里的说明为准不要想当然地当成标注文件去解析。图片命名没有统一前缀所以划分训练集和验证集时不能靠文件名排序得用脚本随机切分或者按 firc 分组切分。VOC 格式的 xml 里每个目标对应一个object节点包含name、bndbox下的 xmin/ymin/xmax/ymax。YOLO 格式的 txt 每行是class_id x_center y_center width height全部归一化到 0~1。两套标注的类别顺序必须一致否则你混用时会出现类别错位。常见做法是先用脚本统计一遍两边的类别名和数量确认对齐后再开始训练。2.2 类别分布与长尾问题类别名标注框数占比billete24916.6%knife103569.1%monedero181.2%pistol734.9%smartphone946.3%tarjeta281.9%knife 一类占了将近七成monedero 和 tarjeta 加起来不到 50 个框。这种分布下如果你直接用默认的类别权重训练模型会强烈偏向 knife小类别几乎学不到。我一般会先算一遍每个类别的框数然后要么在 loss 里加类别权重要么对小类别做过采样。YOLOv8 的cls损失本身不带自动加权需要你自己在 dataset 的get_labels或者 sampler 里处理。2.3 用脚本核对标注一致性在训练之前先跑一段校验脚本确认每张图都有对应的 xml 和 txt且类别 id 映射正确。下面这段代码遍历目录输出缺失文件和类别统计import os from collections import Counter from xml.etree import ElementTree as ET img_dir images xml_dir annotations_xml txt_dir labels_yolo imgs {os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith(.jpg)} xmls {os.path.splitext(f)[0] for f in os.listdir(xml_dir) if f.endswith(.xml)} txts {os.path.splitext(f)[0] for f in os.listdir(txt_dir) if f.endswith(.txt)} print(缺失 xml:, imgs - xmls) print(缺失 txt:, imgs - txts) cls_counter Counter() for f in os.listdir(xml_dir): if not f.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, f)) for obj in tree.findall(object): cls_counter[obj.find(name).text] 1 print(VOC 类别统计:, cls_counter)这段脚本做了三件事用集合差集找出缺标注的图片、遍历 xml 统计每个类别的框数、把结果打印出来。参数上只需要改三个目录路径。如果你发现某个类别在 VOC 和 YOLO 里的数量对不上说明转换时出过错得回去检查类别映射表。常见做法是把类别名按字母序排好生成一个classes.txt两边都用这个顺序。3. 从 VOC 到 YOLO转换脚本、类别映射与训练配置3.1 自己写一遍转换脚本的必要性虽然数据集已经提供了 YOLO 格式的 txt但你还是应该自己写一遍转换脚本。原因有两个一是你要确认类别 id 的映射顺序和你的data.yaml一致二是你可能会做数据增强后再生成新的标注那时候需要批量转换。下面是一个标准的 VOC 转 YOLO 脚本import os import xml.etree.ElementTree as ET classes [billete, knife, monedero, pistol, smartphone, tarjeta] cls_to_id {c: i for i, c in enumerate(classes)} def convert(xml_path, out_path, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text if name not in cls_to_id: continue bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) xc (xmin xmax) / 2.0 / img_w yc (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_to_id[name]} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines))关键参数是classes列表的顺序它决定了 txt 里每行第一个数字代表哪个类别。img_w和img_h必须从对应 jpg 读取不能硬编码因为数据集里图片尺寸可能不统一。转换完成后随便抽几张图用可视化脚本画框确认坐标没有偏移。3.2 data.yaml 与训练超参设置YOLOv8 的data.yaml写法如下path: ./danger_dataset train: images/train val: images/val nc: 6 names: [billete, knife, monedero, pistol, smartphone, tarjeta]nc必须等于 6names的顺序和转换脚本里的classes完全一致。训练时针对长尾分布我一般会把lr0设到 0.001 而不是默认的 0.01因为小数据集上大学习率容易让模型只记住 knife。batch设 16 或 32取决于你的显存。epochs可以先跑 100 轮看曲线如果 val 的 mAP 在 50 轮后还在涨再加到 200。还有一个容易忽略的点close_mosaic参数。默认是最后 10 轮关闭 mosaic 增强但小数据集上 mosaic 会进一步加剧类别不平衡因为 knife 的图片被拼来拼去小类别更没机会。我通常会把close_mosaic设成 20 甚至 30让模型在后半程看到更多原始分布。3.3 训练命令与日志观察yolo detect train datadata.yaml modelyolov8n.pt epochs150 imgsz640 batch16 lr00.001 close_mosaic20跑起来之后重点看三个指标metrics/mAP50、metrics/mAP50-95、以及每个类别的metrics/mAP50分项。如果 knife 的 mAP 很高但 monedero 一直是 0说明小类别完全没学到需要回去加类别权重或者做过采样。日志里还会输出instances数量确认每轮的训练实例数和你的标注总数对得上。4. 避坑与排查标注、类别与训练中的五个血泪教训4.1 现象训练时 loss 突然变 NaN原因学习率过大或者某张图的标注框宽高为 0。这个数据集里 monedero 只有 18 个框如果其中某个框的 xmax 等于 xmin归一化后宽度为 0YOLO 在计算 loss 时会出问题。解决在转换脚本里加一行判断if xmax xmin or ymax ymin: continue把无效框过滤掉。同时把lr0降到 0.001 以下再试。4.2 现象验证集 mAP 很高但实际推理时框全偏了原因训练时用了 letterbox 填充但推理时没有保持同样的预处理。YOLO 默认会做 letterbox但如果你自己写推理脚本忘了把坐标映射回原图尺寸框就会整体偏移。解决直接用 ultralytics 的model.predict它内部处理了坐标还原。如果必须自己写记住 letterbox 的缩放比例和 padding 值反算回去。4.3 现象类别 id 对不上knife 被识别成 pistol原因VOC 的 xml 里类别名是字符串YOLO 的 txt 里是数字 id。如果你手动改过classes.txt的顺序但没重新生成 txt就会错位。解决永远用同一个classes列表生成 txt 和data.yaml不要中途改顺序。校验脚本里加一步随机抽 10 张图把 xml 的类别名和 txt 的 id 映射回去对比。4.4 现象小类别 monedero 和 tarjeta 的召回率始终为 0原因框数太少模型在训练中几乎看不到正样本。加上 mosaic 增强后小类别被进一步稀释。解决对小类别做过采样把包含 monedero 和 tarjeta 的图片复制多份或者在 dataloader 里给它们更高的采样权重。另一个办法是冻结 backbone只训练 head减少模型对小类别的遗忘。4.5 现象训练到一半显存爆了原因batch设太大或者imgsz设成了 1280。1431 张图里有些图片分辨率较高放大到 1280 后显存占用翻倍。解决先用imgsz640跑通确认流程没问题再尝试更大尺寸。如果显存不够把batch降到 8或者用yolov8n而不是yolov8m。5. 进阶技巧用分层采样和类别权重把长尾拉平5.1 分层采样让每个 batch 都见到小类别YOLO 默认的 dataloader 是随机打乱小类别可能连续几个 batch 都不出现。我一般会写一个自定义 sampler保证每个 batch 里至少包含一张含 monedero 或 tarjeta 的图片。实现方式是按图片的类别标签分组然后从每组轮流采样。下面是一个简化版import random from torch.utils.data import Sampler class BalancedSampler(Sampler): def __init__(self, labels, batch_size16): self.batch_size batch_size self.groups {} for idx, lbls in enumerate(labels): key tuple(sorted(set(lbls))) self.groups.setdefault(key, []).append(idx) self.keys list(self.groups.keys()) def __iter__(self): while True: batch [] for key in self.keys: if self.groups[key]: batch.append(random.choice(self.groups[key])) while len(batch) self.batch_size: key random.choice(self.keys) batch.append(random.choice(self.groups[key])) yield from batch[:self.batch_size] def __len__(self): return sum(len(v) for v in self.groups.values())这段代码按类别组合分组每个 batch 先从每组抽一张不够的再随机补。参数batch_size要和训练时的 batch 一致。注意这个 sampler 是无限迭代的需要配合max_steps或者epochs控制训练长度。5.2 类别权重在 loss 里给小类别加杠杆YOLOv8 的v8DetectionLoss里cls分支用的是 BCEWithLogitsLoss。你可以手动传入pos_weight让 monedero 和 tarjeta 的损失权重更高。常见做法是按框数的倒数开根号来设权重类别框数权重倒数开根号归一化billete2490.63knife10350.31monedero182.35pistol731.17smartphone941.03tarjeta281.88把权重传给 loss 函数时注意顺序要和data.yaml的names一致。如果不想改源码也可以在数据集层面做重采样效果类似但更简单。5.3 验证方法用混淆矩阵和 PR 曲线确认长尾是否改善训练完之后跑一遍yolo detect val然后看混淆矩阵。如果 monedero 的列里大部分预测都落到 knife 或背景上说明模型还是没学会区分。这时候可以进一步加大权重或者把 monedero 的图片单独拿出来做一轮微调。PR 曲线里小类别的曲线如果贴近坐标轴说明召回率极低需要继续调整采样策略。从那以后我每次拿到这种长尾数据集都会先跑一遍类别统计把权重和采样策略定下来再开训不然跑完 150 轮才发现小类别全灭那才是真的后悔药都没得吃。希望这份拆解能帮到你拿到数据后先校验、再转换、最后调采样三步走完再开训能省下不少返工时间。本文还有配套的精品资源点击获取
返回列表