简介:这是一份面向目标检测任务整理的鼠类数据集,适合计算机视觉初学者、算法工程师及需要扩充训练样本的开发者。资源围绕“rat”这一类别进行标注,共提供约587张jpg原图,并同时给出VOC格式的xml标注与YOLO格式的txt标注,两种格式一一对应,便于直接接入常见检测框架。压缩包内含1762个文件,主体为588个txt、587个xml、587个jpg,图片大小在1-500KB之间,整包约149.16MB,解压后按图片、xml、txt三个文件夹分类存放,目录结构清晰,可快速定位到所需文件。标注环节使用labelImg完成,并遵循准确框选边界、尽量覆盖全部目标、交叉一致性检查等规范,降低了数据清洗门槛。目前已有146人学习下载,适合用于模型训练、格式对比或标注流程参考,是一份轻量且可直接落地的数据集。
1. 鼠数据集 VOC 和 YOLO 格式:587 张标注图能支撑一个检测项目吗
从仓储防鼠、实验室动物行为分析到家庭宠物识别,鼠类检测是目标检测里典型的小样本垂直场景:很难找到公开标好的鼠图像集,通常只能自己拍自己标。标题里这套鼠数据集,用约 587 张真实图像同时提供 VOC 和 YOLO 两套目标标注,等于提前把最耗时的一步做完了。它能解决的问题很直接:当你准备用 YOLOv8 或 YOLO11 做鼠检测时,数据准备环节被大幅压缩,直接进入训练和调参。适合三类人:刚入门目标检测、想拿小数据集跑通全流程的新手;做鼠类预警或实验室监测的开发者;想彻底搞懂 VOC 与 YOLO 标注映射关系的进阶者。接下来我按数据结构、训练复现、格式转换、避坑和验证的顺序拆开讲。
2. 数据集结构拆解:VOC 的 XML 和 YOLO 的 txt 分别在记什么
同一批图像配两套标注文件,刚接手的人常以为是冗余,其实是两套完全不同的坐标系:VOC 用绝对像素坐标,给人看和改;YOLO 用归一化比例坐标,给训练框架直接吃。手里同时有两种格式时,工作流会灵活很多——写增强脚本时用 XML 好调试,直接训练时用 txt 少一步换算。
2.1 VOC 标注长什么样:目标框落在 XML 的哪个节点里
VOC 格式每个图像对应一个同名 XML 文件,目录结构一般长这样:
mouse_dataset_voc/ ├── JPEGImages/ │ ├── mouse_001.jpg │ ├── mouse_002.jpg │ └── ... ├── Annotations/ │ ├── mouse_001.xml │ ├── mouse_002.xml │ └── ... └── ImageSets/ └── Main/ ├── train.txt └── val.txtJPEGImages放原图,Annotations放标注 XML,ImageSets/Main里是划分好的训练和验证图像名列表。打开任一 XML 会有这些关键标签:
<annotation> <folder>JPEGImages</folder> <filename>mouse_001.jpg</filename> <size> <width>1280</width> <height>720</height> <depth>3</depth> </size> <!-- 目标一 --> <object> <name>mouse</name> <bndbox> <xmin>80</xmin> <ymin>120</ymin> <xmax>310</xmax> <ymax>240</ymax> </bndbox> </object> <!-- 目标二 --> <object> <name>mouse</name> <bndbox> <xmin>420</xmin> <ymin>90</ymin> <xmax>551</xmax> <ymax>180</ymax> </bndbox> </object> </annotation>逻辑说明:<size>里的宽高决定后面所有坐标的解释方式,它必须跟实际图片尺寸一致,否则后续归一化全部偏移。<object>可以出现多次,每个 object 代表一个鼠目标;name是类别名,单类别数据集里一般全是mouse。bndbox下四个值分别是左上角 x、左上角 y、右下角 x、右下角 y,单位是像素,并且是整数。这个顺序不是xmin, ymin, width, height,写成四元组是[xmin, ymin, xmax, ymax],比 COCO 的[xmin, ymin, width, height]多一步换算。
这个格式的好处是表意清晰,任何目标检测常用标注工具打开都能直接改;缺点是训练时还得再转一次坐标。因此装机量更大的反而是下一节这种写法。
2.2 YOLO 标注长什么样:一行一个归一化目标框
YOLO 格式的文件组织要简单得多:每个标注文件是跟图像同名的 txt,放在 labels 目录里,内容是一行一个目标:
0 0.152344 0.250000 0.179688 0.166667 0 0.378906 0.187500 0.102344 0.125000第一列是类别编号,从 0 开始。后面四列分别是目标框中心点 x、中心点 y、框宽、框高,全部除以图像宽高做了归一化,因此取值范围约定在 0 到 1 之间。拿上一节那张 1280×720 的图举例,第一只鼠的边框坐标是 xmin=80, ymin=120, xmax=310, ymax=240,换算成中心点就是 (80+310)/2=195, (120+240)/2=180,再除以宽高得到 x_center=195/1280≈0.152344, y_center=180/720=0.25;框宽 230/1280≈0.179688,框高 120/720≈0.166667。这样坐标值跟图像分辨率解耦,模型在任意输入尺寸下都能复用。
需要留个心眼的是,YOLO txt 文件里没有图像元信息。如果标注文件里出现负数或大于 1 的值,说明原框越界或标注工具记录错误,训练时虽然不会崩,但会让 anchor 匹配乱套。这也是后面专门要做越界检查的原因。
2.3 两种格式的思路对照:人读的坐标系和机器读的坐标系
把两套格式铺开对比,核心差异有三处。第一,VOC 表达的是绝对像素位置,YOLO 表达的是相对图像比例;第二,VOC 给出的是左上和右下两个对角点,YOLO 给出的是中心点和宽高;第三,VOC 里类别是字符串 name,YOLO 里类别是整数索引,索引对应的类别全表在 data.yaml 的 names 字段里另行定义。
换算公式其实就四个:
- x_center = (xmin + xmax) / 2 / image_width
- y_center = (ymin + ymax) / 2 / image_height
- width = (xmax - xmin) / image_width
- height = (ymax - ymin) / image_height
很多标注工具导出 VOC 是默认项,导出 YOLO 却要手动勾选。如果你手头只有 VOC 标注,又急着跑 YOLOv8,就需要自己写或找现成的转换脚本。转换这件事看着简单,实际有四个高频坑:图像尺寸对不上、类别名大小写混用、文件名后缀不一致、越界框没处理。这些我在第 4 章单独展开。
如果你用过 COCO 数据集,会发现它的 json 里坐标是[xmin, ymin, width, height],和 VOC 的[xmin, ymin, xmax, ymax]只差一个减法。这也引出一个常见误操作:把 VOC 转 YOLO 的脚本直接套在 COCO json 上,出来的框宽高永远翻倍。换数据集格式之前,先把源格式的坐标系定义查清楚再动手,比试错更快。
提示:用标注工具导出时,先确认“导出格式”里选的是 Pascal VOC 还是 YOLO,两者保存目录和扩展名完全不同,不要拿 txt 格式的 YOLO 标注去套 VOC 的目录结构。
3. 用 YOLOv8 训练自己的鼠检测数据集:目录、配置和最小命令
数据集本身是双格式,落地时最通用的一条路是直接用 YOLO 格式训练。下面这套流程是我在小样本数据集上跑过多次的标准做法,从目录摆放到看到验证指标,半小时内能走完。
3.1 按框架约定摆目录:images 与 labels 必须同名对应
YOLOv8 的约定是图像目录与标注目录结构对称,图片和 txt 文件名一一对应。如果你直接把 VOC 那套 JPEGImages/Annotations 目录拿给 YOLO 读,一定会读到“No labels found”。正确组织方式如下:
mouse_dataset/ ├── data.yaml ├── images/ │ ├── train/ │ │ ├── mouse_001.jpg │ │ ├── mouse_002.jpg │ │ └── ... │ ├── val/ │ │ ├── mouse_401.jpg │ │ └── ... │ └── test/ │ ├── mouse_541.jpg │ └── ... └── labels/ ├── train/ │ ├── mouse_001.txt │ └── ... ├── val/ │ ├── mouse_401.txt │ └── ... └── test/ └── ...我从 587 张里取 80% 做 train,10% 做 val,10% 做 test。划分时按文件名随机抽样,再同时移动到 images 与 labels 两个目录,不要只复制图片不复制标注。划分配对建议写成脚本,别手动拖文件,手一抖图片和 txt 对不上,训练日志的 val 指标会非常难看。下面是带固定种子的划分脚本:
import random import shutil from pathlib import Path random.seed(42) src_img = Path('voc/JPEGImages') src_lab = Path('yolo/labels') base = Path('mouse_dataset') for split, ratio in [('train', 0.8), ('val', 0.1), ('test', 0.1)]: imgs = sorted(src_img.glob('*.jpg')) random.shuffle(imgs) count = int(len(imgs) * ratio) for img in imgs[:count]: lab = src_lab / (img.stem + '.txt') if not lab.exists(): continue dst_img = base / 'images' / split / img.name dst_img.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(img, dst_img) dst_lab = base / 'labels' / split / (img.stem + '.txt') dst_lab.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(lab, dst_lab)这段逻辑有两个关键设计:一是用同一种子 shuffle,保证重跑结果一致;二是只挑标注存在的图像进训练集,避免空标注图片把 val 指标带偏。参数 ratio 不用追求精确,训练集足够大时划分误差对模型影响很小。
3.2 写 data.yaml 并跑通训练命令:参数不玄学,看环境说话
data.yaml 是 YOLO 读取数据集的入口,路径字段写绝对路径,避免框架拼接时产生歧义:
# data.yaml path: /home/user/mouse_dataset train: images/train val: images/val test: images/test names: 0: mouse然后执行训练:
cd /home/user yolo detect train data=mouse_dataset/data.yaml \ model=yolov8n.pt epochs=80 imgsz=640 \ batch=16 device=0 workers=4参数说明:model=yolov8n.pt是 nano 规模预训练权重,本地没有时会自动下载 YOLOv8 在 COCO 上的预训练模型,用它做起点比从零初始化收敛快得多,这一点在小数据集上几乎是必须的。imgsz=640控制训练输入分辨率,想检测更小号的鼠可以升到 1024,但显存和训练时间会同步上涨。batch=16在 16G 显存上能跑,显存小就降到 8 或 4。workers=4是读图线程数,Windows 上建议设 0,否则容易报 DataLoader worker 错误。device=0指第一块 GPU,纯 CPU 环境改成device=cpu,能出结果但慢一些。
如果你不想手动调,YOLOv8 自带默认值也够:epochs 默认 100,imgsz 默认 640,batch 默认 16。我第一次跑这种小数据集选过 yolov8s.pt,精度提升有限,训练时间却翻了快一倍,后来固定在 nano 起步、不行再上 small。
3.3 训练日志怎么读:从预热到收敛,哪些指标值得盯
训练跑起来后终端刷新的日志分两部分。前半段是 loss 曲线,后半段是每个 epoch 结束后的 box loss、cls loss、dfl loss。重点看的不是 loss 单点值,而是 train 与 val 的 loss 有没有拉开过大的剪刀差。出现这种情况说明过拟合,587 张的小数据集尤其敏感,改进方法是补增强或减少 epochs。
最终表格会输出类似这样:
| Class | Images | Instances | Box(P) | R | mAP50 | mAP50-95 |
|---|---|---|---|---|---|---|
| all | 59 | 102 | 0.941 | 0.887 | 0.912 | 0.761 |
这里 Images=59 是 val 图像数,Instances=102 是这 59 张图里所有真实目标的总个数。Box(P) 是精确率,R 是召回率,mAP50 是 IoU 阈值 0.5 下的均值平均精度,mAP50-95 是阈值从 0.5 到 0.95 取平均。前者衡量“框大体位置对不对”,后者衡量“框贴得准不准”。小目标场景下常见 mAP50 高、mAP50-95 低,这不用慌,说明边界框误差主要来自精修而不是整体漏检。如果是部署做预警类需求,我更看重 mAP50 和 R,因为漏报警比框不准更致命。
4. 从 VOC 转 YOLO 标注:一个转换脚本和四个边界坑
很多真实项目里,别人给的标注文件可能只有 VOC 一种格式。能把 XML 稳定转成 YOLO txt,以后处理数据集用于 YOLOv8 训练时都能直接用。下面是我一直保留的转换脚本,以及四个反复踩到的边界问题。
4.1 转换脚本:把 XML 的绝对像素换算成归一化比例
import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path: Path, class_names: list, out_dir: Path) -> None: 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.iter('object'): name = obj.find('name').text if name not in class_names: continue cls_id = class_names.index(name) b = obj.find('bndbox') xmin = float(b.find('xmin').text) ymin = float(b.find('ymin').text) xmax = float(b.find('xmax').text) ymax = float(b.find('ymax').text) # 中心点与宽高,全部归一化到 [0,1] x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h # 越界保护 x_center = max(0.000001, min(0.999999, x_center)) y_center = max(0.000001, min(0.999999, y_center)) width = max(0.000001, min(0.999999, width)) height = max(0.000001, min(0.999999, height)) lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") if lines: (out_dir / (xml_path.stem + '.txt')).write_text('\n'.join(lines) + '\n') if __name__ == '__main__': class_names = ['mouse'] xml_root = Path('Annotations') out_dir = Path('labels') out_dir.mkdir(exist_ok=True) for xml_file in xml_root.glob('*.xml'): voc_to_yolo(xml_file, class_names, out_dir)逻辑与参数:class_names 是类别列表,顺序必须和训练 data.yaml 里的 names 完全一致,这个列表的索引就是 YOLO 标注里的第一列。归一化时我保留 6 位小数,足够单精度训练使用;越界保护用 0.000001 而不是 0,是因为 0 表示边框刚好贴边,检测时会产生极端 anchor,容易加剧训练发散。脚本只处理*.xml,如果标注是*.XML大小写差异,把 glob 写成glob('*.xml')的兄弟glob('*.[xX][mM][lL]'),或者先统一后缀。
转换完成后,肉眼验证一个样本:对照原图,把 txt 里的数值反算回像素坐标,看框是否和老鼠对齐。这一步我每次换新数据集都会做,能挡掉大部分标注工具版本差异造成的坐标漂移。
4.2 坑一:size 与实际图宽高不一致,框整体漂移
现象:转换出的 txt 数值看起来正常,训练也能跑,但验证集里边界框普遍偏左上或偏右下,尤其图像边缘的老鼠框错位明显。
原因:VOC XML 里的<size>是标注工具写图片时的快照。如果图片后来被外部脚本改过分辨率,或原图是带 EXIF 旋转的手机照片,工具记录的宽高就没跟上实际像素。
解决:转换前用 PIL 或 OpenCV 读取真实宽高,不一致就按真实值覆盖:
from PIL import Image img_w, img_h = Image.open(img_path).size # 实际尺寸优先如果你的图片本身带旋转信息,建议统一转换成无 EXIF 的正向图再训练,不然即使宽高对了,目标框整体也会跟着转 90 度。
4.3 坑二:类别名大小写混用,脚本直接索引越界
现象:脚本运行到一半抛ValueError: 'Mouse' is not in list,或者更隐蔽的,class_names.index(name)返回错误索引,导致一张图上有的鼠框类别是 0,有的类别是 1。
原因:标注人员在不同批次里录入的类别名不一致,比如mouse、Mouse、mice、鼠混在一批 XML 里。
解决:转换前先做一个统计,把所有出现过的 name 值打出来:
names = set() for xml_file in xml_root.glob('*.xml'): root = ET.parse(xml_file).getroot() for obj in root.iter('object'): names.add(obj.find('name').text) print(names)看到结果后再写一个别名映射,统一归到 class_names 里的标准名。千万不要在转换脚本里临时改名,那只会让映射越来越乱。
4.4 坑三:XML 与图片不同名,转换结果静默丢失一半
现象:转换脚本无报错,但 labels 目录里只有 200 个 txt,而 Annotations 目录里有 300 个 XML。
原因:VOC 文件命名时不只有mouse_001,还有一批是Mouse-001或batch2_house.jpg,XML 的 stem 和图片对不上。YOLO 训练时按图片文件名找标注,找不到就跳过,图照常训练但被当成背景。
解决:转换前做一个配对检查:
xml_stems = {p.stem for p in xml_root.glob('*.xml')} img_stems = {p.stem for p in img_root.glob('*.jpg')} print(f"XML 有 {len(xml_stems)} 个,图片有 {len(img_stems)} 个,缺失 {len(xml_stems - img_stems)} 个")把不匹配的挑出来人工处理,别让脚本替你做决定。静默丢弃的标注是数据集里最贵的沉淀,丢了想补标得重新翻图。
4.5 坑四:越界框直接写进 txt,训练时警告刷屏
现象:训练每轮都刷标签越界警告,启动阶段还会在 dataset 检查里看到红字提示。
原因:老鼠身体有一部分在图像边缘,标注工具没截断,导致 xmax 大于图宽,归一化后 x_center 超过 1,甚至出现负值。
解决:既要写到脚本的越界保护里,也要在数据检查阶段揪出来。我一般在转换完写一个校验命令,把值在 [0,1] 之外的行单独列出来:
awk '$2<0 || $2>1 || $3<0 || $3>1 {print FILENAME, $0}' labels/*.txt这一条命令能快速定位所有越界标注。配合脚本内的截断逻辑,训练时才能完全不报警。注意这里我选择的策略是截断而不是丢弃整条边界框,因为一只鼠贴着图像边缘时,框的大部分仍有效,直接丢掉会让模型学不到边缘目标的形态。
5. 避坑排查:鼠检测训练里五个高频翻车现场
拿 587 张这个量级的数据集训练,框架层面的坑比算法层面的更磨人。下面五个问题在社区里几乎每天都能看到,按现象、原因、解决三个步骤写清,方便你直接对照排查。
5.1 训练可以跑,但 val mAP 一直是 0,连 0.01 都上不去
现象:训练 loss 正常下降,每个 epoch 结束后的验证表格却清一色是 0,Box(P)、mAP50 全部为零。
原因:最常见的是 labels 目录里的 txt 文件名和 images 目录里的图片名没有一一对应。比如图片叫mouse_001.jpg,标注却叫M001.txt,YOLO 逐个匹配时找不到标注,就把这张图当背景样本用。验证集里没有真值目标,mAP 自然恒为 0。
解决:先停训练,检查配对数量和文件名大小写。运行 4.4 里的配对脚本,把不匹配名单清出来;如果确认文件名一致,再打开 labels 下的 txt,确认第一行的类别编号是否落在 data.yaml 的 names 长度范围内。这个翻车现场最常见的诱因,就是先前划分数据集时只复制了图片、没复制 txt。
5.2 启动就报 No labels found,但目录里明明有文件
现象:训练命令刚执行,日志里出现WARNING No labels found in /path/to/train/labels,随后训练自动终止,或全程不更新。
原因:要么是目录路径写错,要么是标注文件内容为空。我见过最多的一种翻车,是 data.yaml 里train: images/train写成相对路径,却忘了写最上层的path:字段,框架用默认路径拼接,自然找不到 labels。还有一个隐蔽原因是 txt 文件存在但 0 字节,YOLO 一律认为它是空标注,直接忽略。
解决:把path字段改成绝对路径,并确保 images 和 labels 下同名的子目录名完全一致。空标注文件用find labels -name "*.txt" -empty找出来,要么回 VOC 重转,要么删掉对应的图片,不要留隐患。
5.3 训练到一半 loss 变成 NaN,BN 层开始崩溃
现象:训练到十几个 epoch,loss 从 0.02 突然跳到 NaN,后续不再恢复,验证集指标也全部变 NaN。
原因:小数据集上,学习率过高或增强过度是主力。YOLOv8 默认训练 100 epoch 时,学习率会做 warmup 爬升,如果你改了lr0=0.02这类激进参数,很容易直接把 BN 层统计数据冲爆;另外 mosaic 和 mixup 同时把增强拉满,在小样本上也可能出现数值发散。这个现象网上常叫“yolo训练中bn崩溃”,本质上不是网络结构坏,是参数和数据的配比失衡。
解决:把lr0调回默认的 0.01,或干脆不开任何自定义学习率参数;mosaic如果已经设成 1.0,降到 0.5 试一轮。更稳妥的做法是回归最简单的配置跑 20 个 epoch,确认不 NaN 后再逐步加增强。
5.4 验证 mAP50 到 0.9,实际推理却漏检测
现象:val 曲线好看,模型一上真实监控画面,帧率正常但鼠目标大量漏掉,尤其目标小到十几个像素的时候。
原因:小目标占比高时,mAP50 会被大量中等目标拉高,看不出小目标上的准确率。模型在 640 分辨率下,一个 15×15 像素的老鼠只占不到 1% 面积,特征图下采样后基本没信息。这是小数据集场景最容易出现的指标乐观偏差,说玄学也好运气也罢,本质是评测样本和部署样本的分布不一致。
解决:训练时把imgsz调到 768 或 1024,推理时也要用相同分辨率,否则训练与部署口径不一致,指标会平白掉一截。如果你确定部署端是固定视频流,可以按裁剪区域训练,把画面切成多块,让鼠目标在 640 里占据更大比例。
5.5 导出 ONNX 后推理结果对不上,预处理细节被忽略
现象:PyTorch 里模型检测框正常,用 ONNX Runtime 跑同样的图,结果全乱,框的位置和置信度都偏差很大。
原因:ONNX 导出后,YOLOv8 的推理逻辑是解耦的,常见坑有三个:没有做 letterbox 缩放、没有把 BGR 转 RGB、没有按训练时的预处理做归一化。这三步差一步,结果就可能从“检测到”变成“一个都没有”。
解决:写成标准推理函数,先 letterbox 到训练 imgsz,再[..., ::-1]转 RGB,最后除以 255.0,不要假设 ONNX 会自动处理。验证时用yolo export model=best.pt format=onnx导出的文件,配合yolo predict model=best.onnx source=test.jpg对比效果,能省下不少自制推理脚本的调试时间。
6. 模型怎么验证才放心:PR 曲线、盲测和连续帧检测
模型训完,除了看一眼 val 指标,我还会做三件事。
第一,导出 PR 曲线找 confidence 阈值。YOLOv8 训练结束会保存PR_curve.png,看曲线的肘部位置。低于肘部,阈值太低会有大量误检,高于肘部,漏检率上升。部署阶段我用肘部对应的值,而不是默认的 0.25。命令先跑一次:
yolo detect predict model=best.pt source=val_images/ conf=0.35 iou=0.5 save=True这里的conf控制置信度门槛,iou控制 NMS 去重时的重叠阈值。鼠检测场景里,如果多只老鼠挤在一起,iou可以降到 0.4,否则相邻框会被 NMS 误杀。
第二,用 test 目录做盲测,且 test 的划分要仿真实分布。不要从同一个视频抽帧里既取 train 又取 test,那样模型等于“见过”测试帧,指标虚高。正确做法是按采集时间段或场景分组:比如白天建的 train,晚上拍的做 test,这样测出来才是真实泛化水平。
第三,连续帧验证。静态图片通过不代表视频流能用。我习惯拿一段 10 秒的监控视频,每 5 帧抽 1 帧预测,检查有没有出现单帧闪断:上一帧检出,下一帧什么都检不出,后面又恢复。这类闪断常来自运动模糊和遮挡,直接表现是 Recall 波动。处理办法不是调模型,而是加一个轻量跟踪器,靠前后帧关联把断检补上。
我自己在鼠检测这类小样本项目上,最大的习惯是先把数据隐患清干净再谈模型。587 张不算多,但足够暴露你在标注转换、目录划分和指标解读上的所有粗糙习惯;你把这三关都打通了,换到任何大一点的数据集只会更顺。希望帮到你。
本文还有配套的精品资源,点击获取