遗失物检测数据集解析:VOC与YOLO双格式标注实战
简介这是一套面向智能安防与客服场景的遗失物/遗落物检测数据集覆盖柜台、电梯、走廊等典型区域针对车钥匙、手提包、钱包、手机、笔记本、身份证、电脑等52类常见贵重物品提供目标检测与识别标注适合计算机视觉方向的研究者、算法工程师及相关竞赛团队用于模型训练和效果验证。数据集包含2173张图片的标注提供VOC格式XML与YOLO格式TXT两种标注形式便于接入常见检测框架压缩包整体约80.98MB共2000个文件其中1999个为XML标注文件另附1个使用前必读TXT说明文件结构清晰可直接筛选使用。目前已有556人学习下载可用于智慧楼宇、银行网点等场景的遗落物识别项目也能作为目标检测算法的实训数据快速上手或用于课程实验与算法对比评测。1. 遗失物检测为什么值得下这份数据集场景、格式与52类丢失物品这件事放到摄像头里做自动检测真能让人做到头秃。人容易锁一个小手机掉在深色椅子底下几乎和背景融为一体但安检口、高铁站、商场、图书馆这些场景又特别需要“遗落物提醒”。做这类视觉方案时缺的往往不是网络结构而是一份标注干净、类别够全、格式能直接开训的数据集。这份“遗失物遗落物检测数据集”打包了2173张实拍图、52个类别同时给了VOC和YOLO两种格式的标注解压就能开始跑 YOLOv8 训练。适合做室内外遗物识别、失物告警或者想拿现成数据把检测流程整个跑通的人。2. VOC和YOLO双格式拆解XML里的一件行李与txt里的一串数字拿到包后第一件事不是直接开训而是先弄清楚两份标注放在哪、怎么一一对应。常规的目录组织是JPEGImages 目录放原图Annotations 目录放 VOC 的 XML 标注另外还有一层 labels 目录放 YOLO 的 txt 标注。XML 和 jpg 同名txt 也和 jpg 同名这是两个格式能够互转的前提。2.1 VOC侧的XML标注怎么读VOC 格式的核心是“一个 XML 对应一张图图里有几个目标就写几个 object 节点”。我随意抽一个标注出来看它的结构一定是下面这个套路annotation folderJPEGImages/folder filenameIMG_20231218_103205.jpg/filename size width1920/width height1080/height depth3/depth /size object namebackpack/name truncated0/truncated difficult0/difficult bndbox xmin102/xmin ymin205/ymin xmax487/xmax ymax642/ymax /bndbox /object object nameumbrella/name bndbox xmin720/xmin ymin180/ymin xmax905/xmax ymax430/ymax /bndbox /object /annotation这里真正影响训练的就三块size 节点的宽高、object 里的 name、以及 bndbox 里的四个坐标。xmin、ymin、xmax、ymax 是像素坐标左上角是原点x 往右增大y 往下增大。truncated 和 difficult 在目标检测训练里一般用不上保留下来只是为了让标注格式完整。同一张图出现多个 object表示多个目标共存读取的时候要遍历全部 object不能只取第一个。我一贯的习惯是先写一小段解析脚本把每张的类别和坐标打出来确认标注不是空壳再谈训练。下面这段代码可以直接用来抽查任意一个 XMLimport xml.etree.ElementTree as ET def parse_voc(xml_path): tree ET.parse(xml_path) root tree.getroot() width int(root.find(size/width).text) height int(root.find(size/height).text) objects [] for obj in root.iter(object): name obj.find(name).text box obj.find(bndbox) xmin int(box.find(xmin).text) ymin int(box.find(ymin).text) xmax int(box.find(xmax).text) ymax int(box.find(ymax).text) objects.append({name: name, bbox: [xmin, ymin, xmax, ymax]}) print(f图片尺寸: {width}x{height}) for o in objects: print(o[name], o[bbox]) parse_voc(Annotations/IMG_20231218_103205.xml)这段代码里值得注意的一点宽高直接从 XML 的 size 节点拿不要自己去读图片。读图片又要解码又要占内存而且某些标注工具的 size 和实际图片尺寸不一致一旦不一致后面归一化坐标全错这种错是最难排查的。2.2 YOLO侧的txt标注怎么读YOLO 格式和 VOC 是平行的一个 txt 对应一张图一行对应一个目标。每行五个数字class_id、x_center、y_center、width、height其中框的中心点和宽高都除以了图片宽高做了归一化所以取值在 0 到 1 之间。一个典型的两目标 txt 长这样3 0.1534 0.3921 0.2005 0.4046 17 0.4231 0.2824 0.0964 0.2315第一行的 3 是类别索引对应类别清单里的第 4 个类。从 VOC 转成 YOLO 坐标公式是固定的x_center (xmin xmax) / 2 / widthy_center (ymin ymax) / 2 / heightw (xmax - xmin) / widthh (ymax - ymin) / height最容易搞反的不是中心点而是有人会把 x_center 当成左上角 x。这个错非常隐蔽因为训练不会报错只是框的位置整体偏移mAP 上不去还找不到原因。所以我把公式原样写在这里每次转格式都照着对一遍。如果哪天你想调整类别清单、合并类别或者拿这份 VOC 数据重新生成 YOLO 标注下面这段转换代码可以直接用def voc_to_yolo(xml_path, class_list, out_txt): root ET.parse(xml_path).getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_list: continue cls_id class_list.index(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) x_center (xmin xmax) / 2 / w y_center (ymin ymax) / 2 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cls_id} {x_center:.5f} {y_center:.5f} {bw:.5f} {bh:.5f}) with open(out_txt, w) as fp: fp.write(\n.join(lines))参数含义xml_path 是 VOC 标注文件class_list 是排好序的类别列表out_txt 是输出的 YOLO 标注路径。cls_id class_list.index(name)这行决定了类别索引如果你的类别列表顺序和原数据集不一致转出来的索引就是错的训练时会直接报索引越界。这段代码本质上是留作“后悔药”的包里的 txt 已经生成好但你要合并类别或删掉某个不想要的类时重转一遍比手改几百个 txt 靠谱得多。2.3 2173张52类的分布与类别命名规律52 个类别在遗失物场景里高频的一定是手机、钥匙、钱包、雨伞、证件卡片、帽子、衣物、水杯、背包、行李箱这些东西。具体到这份资源到底用了哪些类名我建议解压后第一件事就是翻一下数据包里的类别清单文件或者直接用脚本统计一遍千万别凭印象去猜。from collections import Counter import glob import xml.etree.ElementTree as ET counter Counter() xml_files glob.glob(Annotations/*.xml) for f in xml_files: root ET.parse(f).getroot() names [obj.find(name).text for obj in root.iter(object)] counter.update(names) print(标注XML文件数:, len(xml_files)) print(总标注目标数:, sum(counter.values())) for name, cnt in counter.most_common(): print(f{name}: {cnt})这段脚本没有按类别顺序排序的理由是作者整理类别时可能按“室内物件→随身物品→箱包”这种逻辑排而不是按字母。你直接用 counter.most_common() 看样本量能一眼看出哪些类是大头、哪些类是小尾巴。统计完你会得到一个很现实的结论样本量从几百张到几十张跨度非常大后面训练时类别不平衡是绕不开的坎。VOC 和 YOLO 两种格式的差异用一个对比表说清楚对比点VOC (XML)YOLO (txt)存储形式一个XML对应一张图一个txt对应一张图坐标表示像素绝对值 xmin/ymin/xmax/ymax归一化 x_center/y_center/w/h类别表示字符串类名整数索引配合names列表优点人类可读易编辑读取快训练框架直接消费主要缺点解析慢占用空间大索引一错全部错3. 7z解压和目录编排先把数据整理成YOLO能吃的结构数据包是 .7z 压缩格式这一步卡住过不少人尤其在 Linux 服务器上。Windows 用户如果直接用系统自带解压工具去解 .7z经常会失败或解出来乱码原因就是系统没有原生 7z 支持。3.1 一步到位解压7z包三个平台的操作与完整性校验Windows 上装 7-Zip 软件右键选择“7-Zip → 解压到当前文件夹”这个动作能覆盖 99% 场景。Linux 服务器上没有图形界面用命令行最靠谱sudo apt install -y p7zip-full 7z t LostAndFound_dataset.7z 7z x LostAndFound_dataset.7z -o./dataset7z t 是测试压缩包完整性这一步非常重要。很多人下载大文件时用网盘或 HTTP 断点续传很容易下到 90% 就停了但文件还在直接解压就会报“头部错误”“数据错误”。先跑 t 能确认压缩包本身没坏再执行解压。参数 -o 后面跟输出目录注意 -o 和目录之间没有空格这是 7z 命令的老毛病写成-o ./dataset反而会报错。macOS 用户如果没有 p7zip可以用 Homebrew 装brew install p7zip命令和 Linux 完全一致。解压前记住一个硬指标2173 张图加标注解压后目录要预留 3GB 以上空间不然解到一半磁盘满了包没坏反而是你本地磁盘坏给出一个“密码正确却被报错”的假象。提示解压时尽量把压缩包放在纯英文路径里。老版本 7-Zip 对中文路径支持不完善个别文件会解压失败。3.2 按YOLOv8要求重排目录images与labels的train/val拆分YOLOv8 默认的数据组织方式不是把图都堆在一个目录里而是要求 train 和 val 分开图和对应标注分目录存放。标准结构如下dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml常见的拆分比例是 8:2也就是 2173 张里约 1738 张训练、435 张验证。切分时必须保持同一张图的 jpg 和 txt 在同一个子集里否则训练时会出现“图有标注但读取不到”的警告。我个人建议用固定随机种子来切保证每次复现结果一致。mkdir -p dataset/images/train dataset/images/val dataset/labels/train dataset/labels/val python - EOF import os, random, shutil random.seed(42) jpg_files [f for f in os.listdir(JPEGImages) if f.endswith(.jpg)] random.shuffle(jpg_files) val_count int(len(jpg_files) * 0.2) for idx, f in enumerate(jpg_files): img_src os.path.join(JPEGImages, f) lbl_src os.path.join(labels, f.replace(.jpg, .txt)) split val if idx val_count else train shutil.copy(img_src, fdataset/images/{split}/{f}) shutil.copy(lbl_src, fdataset/labels/{split}/{f.replace(.jpg, .txt)}) EOF注意脚本里 img_src 的路径是可调整的如果你解压后 labels 目录不在顶层而是叫 Annotations 且全是 XML那得先把 XML 转成 txt 再执行这个拆分的动作。脚本的 random.seed(42) 是固定随机种子42 换成任何数字都可以但换一次就得到一次不同的划分不同实验之间的对比参考价值就降低了。3.3 写data.yaml类名、路径与索引对齐YOLOv8 的配置入口是 data.yaml路径写错或类别列表顺序不对训练会以各种诡异姿势翻车。一个可用的 data.yaml 长这样path: /home/user/dataset train: images/train val: images/val names: 0: phone 1: key 2: wallet 3: umbrella 4: backpack 5: glasses # ... 一直写到 51关键点有两个第一path 推荐写绝对路径写相对路径时 YOLOv8 会基于当前工作目录去拼一旦你在别的目录下执行训练脚本就找不到数据集第二names 的键必须从 0 开始连续不能跳号索引和 YOLO txt 里的第一列必须完全对应。比如 txt 里第一列是 17那 names 里就要有 17 号键否则训练时读取标注会报索引越界。4. 从数据集到模型超参数选择与损失曲线观察4.1 训练命令与显存/批次选择数据整理好之后训练这一步反而最标准化。Ultralytics 的 YOLOv8 安装和训练命令如下pip install ultralytics yolo detect train data/home/user/dataset/data.yaml modelyolov8n.pt epochs100 imgsz640 batch16 patience20model 参数决定你从哪个预训练权重开始。yolov8n.pt 是最轻量的版本在 52 类这种中等复杂度的任务上n 级模型通常已经够用显存占用也低。batch16 对应至少 8GB 显存实测如果只有 6GB 显存把 batch 降到 8或者 imgsz 降到 480。最容易翻车的点是 batch 值超过显存训练到一半爆显存中断前面的进度全丢。第一次拿到这种数据集我不建议一上来就改模型结构、加注意力机制。先用默认配置跑 100 个 epoch看 mAP 能不能到 0.85 以上。能到说明数据和标注质量是过关的后面所有优化才有意义。预训练权重第一次运行会自动下载如果服务器没外网提前把 .pt 文件放到项目根目录即可具体文件名保持一致就行。4.2 超参数调整针对小物件的增强开关遗失物检测有个特殊难点小目标特别多。手机、钥匙、证件这类物体在整张监控画面里占比很小默认的数据增强反而会伤害小目标。YOLOv8 默认开启 mosaic 增强它把四张图拼一起训练对提升泛化能力很有帮助但小目标在拼接后会被进一步缩小容易学不到。我一般会这样调mosaic 保持 1.0 让前 30 个 epoch 用后面如果发现小目标类别的召回率上不去就把 mosaic 降到 0.5同时打开 copy_paste。还有一项值得调的是 hsv_h、hsv_s、hsv_v 三个颜色增强参数它们控制色相、饱和度、明度的扰动范围光照条件差的监控场景适当调大一点能提升鲁棒性。训练时的增强参数不一定要写进命令行可以在训练脚本里通过 yolo 的配置文件改也可以在命令行直接传yolo detect train datadata.yaml modelyolov8n.pt epochs100 imgsz640 batch16 mosaic0.8 hsv_h0.015 hsv_s0.7 hsv_v0.4如果你要硬把它作为参数验证请记住它会影响小目标检测效果而不是无脑调大。4.3 从损失曲线判断欠拟合和过拟合训练结束后Ultralytics 会在 runs/detect/train 目录下生成 results.csv里面记录了每个 epoch 的 train/box_loss、train/cls_loss、train/dfl_loss 以及对应的 val 损失。这三条曲线的含义一定要分清损失项衡量内容常见异常box_loss预测框和真实框的位置偏差曲线平坦不降anchors 或学习率有问题cls_loss分类错误程度上升说明类别混淆变严重dfl_loss框的分布损失影响框精度波动大是正常但整体应下降判断过拟合最直接的方法train 损失不断下降val 损失却开始反弹这就是模型记住了训练集、泛化能力不足的信号。处理办法不是简单加数据增强而是先检查是不是训练集里某些类图片太少导致模型在少样本类上死记硬背。另一种情况是 val 损失始终下不去、和 train 损失差一大截这多半是 train 和 val 划分不合理比如同一场景不同角度的图被分到了两边造成数据泄漏。5. 避坑指南资源解压到训练的五处常见翻车5.1 7z解压中途报错或“密码正确仍报错”现象解压到 80% 弹窗提示“数据错误”或“无法作为 7z 分卷解压”又或者你确认密码是对的但 7-Zip 一直报“密码错误”。原因第一种是压缩包下载不完整网盘或浏览器断点续传导致文件尾部缺失7z 格式对完整性要求极高任何一个分块坏了都解压失败。第二种是磁盘空间不够解压工具先写入临时文件空间不足时报错内容完全不是“磁盘满”而是“头部错误”。第三种是路径里有中文或空格部分老版本 7-Zip 处理这种路径会解压失败报的却是密码错误非常迷惑。解决先执行7z t 文件.7z验证压缩包完整性输出全绿再解压把文件挪到纯英文短路径下比如 D:\dataset.7z检查磁盘剩余空间确保不低于 4GB。如果 t 测试报某个文件 CRC 错误说明压缩包损坏只能重新下载源文件。5.2 YOLO标注的class index越界现象训练刚开始几秒就抛IndexError: index out of range或者报 key error报错信息指向某个 label 文件。原因txt 里的 class_id 大于 data.yaml 里 names 列表的长度。常见于你重跑了一次 VOC 转 YOLO 脚本但 class_list 顺序或长度和原来不一致也可能某个 XML 里有个类没有在 names 里登记转换时被默认分配了一个越界数字。解决写一行命令扫描所有标注的最大类别编号对照 names 列表长度awk {print $1} labels/train/*.txt | sort -n | tail -1如果最大值是 51那 names 必须包含 52 个类名如果实际只有 50 个类名那一定有索引越界。定位到具体是哪个文件就用对应的 XML 重新生成这个 txt不要手改坐标。5.3 归一化坐标出现异常现象训练出来的模型在验证集上框总是偏移或者 val 时大量目标被低置信度过滤打开某个 txt 一看第三列第五列数字大于 1 甚至是负的。原因坐标归一化做了两遍。最常见的是你先把 VOC 转成 YOLO然后又用 YOLO 格式做了第二次归一化导致中心点和宽高全体放缩。另一种是转换脚本里宽高除反了把图片的高当成了宽。解决用 awk 一次性扫描所有异常值awk NF!5 || $10 || $20 || $21 || $30 || $31 || $40 || $41 || $50 || $51 {print FILENAME: $0} labels/train/*.txtNF!5 检查每行是不是 5 列$2、$3、$4、$5 检查坐标和宽高是否落在 0 到 1 之间。任何一个条件命中就把对应文件挑出来回到 VOC 的 XML 重新转换不要在这条异常数据上打补丁。5.4 类别样本悬殊导致mAP虚低现象整体 mAP50 只有 0.7 左右看类别明细发现手机、钱包这类大样本类 AP 很高而某些小众物品 AP 不到 0.2拉低了整体指标。原因52 个类里样本分布极不均衡。头部类几百张尾部类可能只有几十张模型把学习能力几乎都花在了头部类上尾部类学不出有效的特征。解决不要急着换大模型换大模型对小样本类的提升微乎其微。先看每个类别的 AP 明细确认尾类是谁然后对尾类做复制粘贴增强或者直接减少尾部类参与训练时的类别权重惩罚。如果某一类样本实在少到没法学考虑把它并入语义相近的大类比如把不同类型的证件卡片合并成一类。5.5 图和标注对不上现象训练日志里刷出一行 WARNING提示某张图没有标签文件或标签文件为空或者 val 的 mAP 高得离谱train 的 mAP 却很低。原因移动文件的时候只迁了图片没迁同名标注或者文件名大小写不一致Windows 下不区分大小写但 Linux 区分jpg 和 JPG 在迁移后对不上。标注为空的那个文件可能是转换脚本没匹配到对象名生成了一个空 txt。解决训练前跑一个同名配对脚本先把缺配对的找出来for f in images/train/*.jpg; do base$(basename $f .jpg) if [ ! -f labels/train/${base}.txt ]; then echo MISSING LABEL: $f fi done检查出的缺标注文件如果是标注工具遗漏就补标如果是该图确实无目标保留空的 txt 是允许的但要把 yaml 配置里allow_empty相关选项留意一下。6. 验证方法与进阶技巧混淆矩阵和低样本类别补救6.1 用混淆矩阵定位漏检类别训练完不要只看 mAPYOLOv8 在 val 结束后会自动生成混淆矩阵这是最值得看的一张图。矩阵对角线越亮说明正确分类越多某一行上非对角位置明显偏亮说明这个类经常被错判成另一个类。比如墨镜被错判成眼镜、手机被错判成遥控器这类混淆靠调参基本解决不了更有效的方式是把语义过于相近的两个类合并或者删掉样本最少的那一类让模型把精力匀出来。每次训练完我会强制把混淆矩阵打印出来逐类看一遍这个习惯比调学习率管用得多。6.2 低样本类别的一个补救技巧对样本量只有几十张的小类最直接有效的做法是复制粘贴增强把小目标的贴图从原图按 bbox 裁出来经过随机亮度扰动、轻微旋转、高斯模糊后粘贴到没有该类目标的背景图上同时生成新标注。下面这段代码是骨架参考# 简述把少类小目标贴到负样本图上生成合成训练样本 # 需要准备patch_list裁好的少类小目标图、base_list不含该类的背景图 for i in range(200): bg base_list[i % len(base_list)].copy() patch patch_list[i % len(patch_list)] h, w patch.shape[:2] x np.random.randint(0, bg.shape[1] - w) y np.random.randint(0, bg.shape[0] - h) bg[y:yh, x:xw] patch # 写YOLO标注class_id, x_center, y_center, w, h 均需除以bg宽高归一化 # yolo_txt.append(f{cls_id} {((xw/2)/bg_w):.5f} ...)注意粘贴时要保证小目标尺度接近真实场景不要每张都贴得很大否则模型只学到“大手机”实际监控里的远处小手机反而漏检。从那以后我每次拿到新数据集开始训练前都强制自己先跑一遍格式扫描脚本、看一眼类别分布直方图、再对着混淆矩阵过一遍所有类这三步走完才敢把数据喂进训练器。数值上可能没有立竿见影但排查问题是真省时间。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →