☰
1876张VOC+YOLO双格式鼠标数据集:从标注校验到YOLO训练全流程
2026/10/1 14:51:05 网站建设 项目流程

简介:这是一份面向计算机视觉初学者与目标检测开发者的鼠标目标检测数据集,可用于训练和验证鼠标识别模型,适用于课堂实验、课程设计及算法入门练习。资源采用Pascal VOC与YOLO双格式标注,jpg图片与对应的xml、txt标注文件一一对应,无需额外转换即可直接投入主流检测框架训练。压缩包共2000个文件,其中1876个xml标注文件、124个txt文件,整体约219.91MB,标注工具为labelImg,采用矩形框方式对mouse单一类别进行标注,共2261个标注框,样本数量较为充足。目前已有296人学习下载。数据集仅提供准确合理的标注内容,不对训练所得模型或权重文件的精度作任何保证,读者可据此快速搭建数据加载流程、验证标注质量并开展模型训练与效果对比。

1. 鼠标数据集落地:1876 张 VOC+YOLO 双格式到底能跑出什么

做桌面行为识别、外设交互分析或者自动化测试的朋友,大概率都遇到过同一个尴尬:想训一个鼠标检测模型,翻遍公开数据集,要么是 COCO 里mouse和computer mouse混着标,要么干脆只有几十张图,训出来的权重一换分辨率就崩。这份 1876 张的鼠标数据集,走的是 Pascal VOC 加 YOLO 双格式路线,单类别mouse,总框数 2261,标注工具是 labelImg,矩形框规则。它解决的不是"有没有数据"的问题,而是"能不能直接喂进 YOLO 训练脚本、不用再写转换代码"的问题。适合谁?适合手头有 YOLO 训练流程、想快速验证鼠标检测这条链路的人,也适合拿它当小样本微调的起点。不适合指望靠它直接产出工业级高精度权重的人——单类别、场景集中,泛化边界得自己心里有数。

2. VOC 与 YOLO 双格式拆解:1876 张图背后的目录结构与标注逻辑

2.1 为什么同一批图要同时给 xml 和 txt

Pascal VOC 用 XML 存标注,结构是<annotation>下面挂<object>,每个 object 里有<name>、<bndbox>四个坐标,坐标是绝对像素值,原点在左上角。YOLO 用 txt,每行class_id cx cy w h,全部归一化到 0~1,class_id 从 0 开始。这份数据集两个都给了,意味着你既可以用torchvision.datasets.VOCDetection直接读,也可以直接丢给 ultralytics 的 YOLO 训练入口,省掉自己写转换脚本那一步。

从项目正文的文件列表能看出命名规律:mouse_xyxr_748.txt、mouse_xyxr_1209.txt这种,前缀统一,后缀是编号。jpg、xml、txt 三者的编号是对齐的,也就是说mouse_xyxr_748.jpg对应mouse_xyxr_748.xml和mouse_xyxr_748.txt。这个对齐关系是后面所有校验的基础,一旦错位,训练时就会出现"图里有鼠标但标签是空的"这种玄学现象。

2.2 目录该怎么摆才不翻车

数据集拿到手,第一件事不是急着训,是把目录摆成框架认的样子。VOC 标准结构是:

# VOC 标准目录结构,Annotations 放 xml,JPEGImages 放 jpg mouse_voc/ ├── Annotations/ # 1876 个 xml ├── JPEGImages/ # 1876 个 jpg └── ImageSets/ └── Main/ # 后面生成的 train.txt / val.txt

YOLO 这边常见两种摆法,一种是 images 和 labels 平级:

# YOLO 常用结构,images 和 labels 分开,文件名一一对应 mouse_yolo/ ├── images/ │ ├── train/ # 训练图 │ └── val/ # 验证图 └── labels/ ├── train/ # 对应 txt └── val/

另一种是 images 和 labels 同目录、只靠扩展名区分。ultralytics 两种都认,但第一种更清晰,我一般用第一种。注意 labels 目录里不能混进classes.txt之外的杂物,否则训练时会报"找不到标签"或者更隐蔽的"标签数量对不上"。

2.3 用脚本做一次三文件对齐校验

在训之前,强制跑一遍对齐校验,这是血泪经验。下面这段脚本检查 jpg、xml、txt 三者编号是否一一对应,顺便统计框数是否等于 2261:

import os img_dir = "mouse_voc/JPEGImages" xml_dir = "mouse_voc/Annotations" txt_dir = "mouse_yolo/labels/train" # 按你实际路径改 # 取编号集合,假设文件名格式为 mouse_xyxr_编号.扩展名 def get_ids(d, ext): ids = set() for f in os.listdir(d): if f.endswith(ext): ids.add(os.path.splitext(f)[0]) return ids img_ids = get_ids(img_dir, ".jpg") xml_ids = get_ids(xml_dir, ".xml") txt_ids = get_ids(txt_dir, ".txt") print("jpg:", len(img_ids), "xml:", len(xml_ids), "txt:", len(txt_ids)) print("jpg-xml 差集:", img_ids - xml_ids) print("jpg-txt 差集:", img_ids - txt_ids) # 统计 txt 里的总框数,验证是否等于 2261 total_boxes = 0 for f in os.listdir(txt_dir): if f.endswith(".txt"): with open(os.path.join(txt_dir, f)) as fp: total_boxes += sum(1 for line in fp if line.strip()) print("txt 总框数:", total_boxes)

逻辑说明:get_ids把扩展名剥掉只留编号,三个集合一对比,差集非空就说明有文件缺失或命名不一致。框数统计直接读 txt 行数,因为 YOLO 格式一行一个框。参数上,txt_dir要按你实际放 labels 的路径改,如果你还没划分 train/val,就先指向 labels 根目录。跑出来txt 总框数应该是 2261,对不上就说明有 txt 被截断或者多了空行。

提示:空行在 YOLO txt 里是合法的(表示该图无目标),但这份数据集每张图都有鼠标,理论上不该出现空 txt。如果校验发现空文件,先怀疑是不是转换时把某张图的框丢了。

3. 从 VOC 到 YOLO 训练:类别映射、数据划分与训练参数怎么设

3.1 单类别 class_id 映射别想当然

这份数据集只有mouse一个类别,YOLO 的 class_id 就是 0。但很多人从 VOC 转 YOLO 时会踩一个坑:VOC 的<name>是字符串,转的时候如果按字母序排类别,mouse排第一也是 0,看起来没问题;可一旦你后面往数据集里加了第二个类别,字母序一变,class_id 就全乱了。所以转换脚本里类别映射要写死,不要依赖排序:

# VOC xml 转 YOLO txt,类别映射写死,避免排序带来的 id 漂移 import xml.etree.ElementTree as ET import os classes = ["mouse"] # 顺序固定,class_id 就是索引 cls2id = {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 cls2id: continue bnd = obj.find("bndbox") x1 = float(bnd.find("xmin").text) y1 = float(bnd.find("ymin").text) x2 = float(bnd.find("xmax").text) y2 = float(bnd.find("ymax").text) # 归一化,注意 YOLO 用中心点 + 宽高 cx = (x1 + x2) / 2.0 / img_w cy = (y1 + y2) / 2.0 / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h lines.append(f"{cls2id[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") with open(out_path, "w") as f: f.write("\n".join(lines))

逻辑说明:cls2id是显式映射,mouse永远是 0。归一化时除以的是图片真实宽高,img_w、img_h要从 jpg 读,不能硬编码。坐标格式是中心点加宽高,不是左上右下,这是 YOLO 和 VOC 最大的格式差异,写反了训练 loss 会一直不降。参数上,:.6f保留六位小数够用,YOLO 官方也是这个精度。

3.2 训练集验证集划分:别用随机种子糊弄

1876 张图,常见做法是 8:2 或者 9:1。但鼠标数据集有个特点:如果图片是按场景连续采集的,随机划分会导致训练集和验证集里出现几乎一样的图,验证指标虚高。我一般会先按文件名编号排序,再按固定间隔抽验证集,保证验证集覆盖整个采集区间:

import os, shutil all_imgs = sorted([f for f in os.listdir("mouse_voc/JPEGImages") if f.endswith(".jpg")]) val_ratio = 0.1 val_imgs = all_imgs[::int(1/val_ratio)] # 每隔 10 张抽 1 张 train_imgs = [f for f in all_imgs if f not in val_imgs] for phase, files in [("train", train_imgs), ("val", val_imgs)]: os.makedirs(f"mouse_yolo/images/{phase}", exist_ok=True) os.makedirs(f"mouse_yolo/labels/{phase}", exist_ok=True) for f in files: stem = os.path.splitext(f)[0] shutil.copy(f"mouse_voc/JPEGImages/{f}", f"mouse_yolo/images/{phase}/{f}") shutil.copy(f"mouse_yolo/labels_all/{stem}.txt", f"mouse_yolo/labels/{phase}/{stem}.txt")

逻辑说明:all_imgs[::10]是等间隔抽样,比random.shuffle更能反映采集分布。labels_all是你转换后 txt 的临时目录,划分时同步复制。参数上val_ratio=0.1对应约 188 张验证图,够看指标趋势;如果数据场景特别单一,可以降到 0.05。

3.3 YOLO 训练参数:小数据集要压住学习率

1876 张图在 YOLO 里算小数据集,直接套 COCO 的默认参数容易过拟合。下面是一份我常用的配置,以 YOLOv8 为例:

# mouse.yaml,数据集配置文件 path: ./mouse_yolo train: images/train val: images/val nc: 1 names: ["mouse"]
# 训练命令,小数据集压学习率、加早停 yolo detect train \ data=mouse.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.001 \ patience=20 \ pretrained=True \ project=mouse_run \ name=exp1

逻辑说明:model=yolov8n.pt用预训练权重,小数据集从零训基本没戏。lr0=0.001比默认的 0.01 低一个量级,防止小数据上 loss 震荡。patience=20是早停,20 轮验证指标不升就停,省时间。imgsz=640是常规选择,如果你的鼠标在图中占比很小,可以提到 960 甚至 1280,但显存要跟上。batch=16在 8G 显存上跑 640 分辨率没问题,显存小就降到 8。

注意:如果你用的是 YOLOv5,配置项名字略有不同,lr0对应--lr0,patience对应--patience,逻辑一样。别把 v8 的 yaml 直接喂给 v5。

4. 训练过程排查:loss 不降、框数对不上、验证指标虚高怎么破

4.1 现象:loss 从第一轮就不降,甚至往上飘

原因通常有三个:一是坐标格式写反了,把cx cy w h写成了x1 y1 x2 y2,模型学的是错的监督信号;二是归一化除错了,比如用了 255 而不是图片宽高;三是 class_id 越界,nc=1但 txt 里出现了1或更大的 id。解决方式:先拿一张图的可视化脚本把框画出来,看框是不是套在鼠标上。如果框的位置完全不对,就是格式问题;如果框位置对但 loss 不降,检查nc和 txt 里的最大 id 是否一致。

4.2 现象:训练报"标签数量与图片数量不匹配"

原因多半是 images 和 labels 目录里文件名对不上,比如图片叫mouse_xyxr_748.jpg,标签叫mouse_xyxr_748.txt但被放到了错误的 phase 目录。ultralytics 会按文件名去 labels 里找同名 txt,找不到就报这个错。解决方式:跑一遍第 2 章那段对齐校验脚本,把差集打印出来,缺哪个补哪个。还有一种隐蔽情况:txt 文件存在但内容为空,框架会认为该图无目标,如果这张图实际有鼠标,就是转换时丢了框。

4.3 现象:验证集 mAP 高得离谱,实际推理一塌糊涂

这是小数据集最典型的翻车场景。原因就是第 3 章说的,训练集和验证集里出现了高度相似的图,模型等于在"背题"。解决方式:改成等间隔抽样划分,或者按场景聚类后再划分。另一个原因是验证集太小,188 张里如果只有几十个框,mAP 波动会很大。可以适当提高验证集比例到 0.15,或者用 K 折交叉验证看稳定性。

4.4 现象:训练到一半 BN 层崩溃,loss 变 NaN

小 batch 加上高学习率时,BatchNorm 的统计量会不稳定,尤其 batch 小于 8 的时候。解决方式:把batch提到 16 以上,或者改用lr0=0.0005再降一档。如果显存实在不够,可以在模型配置里把 BN 换成 GroupNorm,但这会改动网络结构,预训练权重就不能直接用了,得权衡。

4.5 现象:混淆矩阵总合不唯一,数字对不上

YOLO 训练完输出的混淆矩阵,有时候行和列加起来不等于总框数。这通常是因为置信度阈值和 NMS 的 IoU 阈值设置导致的:低于置信度阈值的预测框不计入矩阵,被 NMS 合并掉的框也只算一个。这不是 bug,是评估逻辑。想看清楚,把conf阈值调低到 0.001 再看矩阵,数字会接近总框数。但别用这个阈值去部署,误检会爆炸。

5. 进阶玩法:用这 2261 个框做小样本微调与推理验证

数据集训完一轮,别急着收工。1876 张图、2261 个框,这个量级最适合做两件事:一是当预训练起点,往你自己的场景数据上微调;二是拿来做推理链路的端到端验证,确认从图片输入到框输出的整条路是通的。

先说微调。如果你手头有几百张自己场景的鼠标图,别从 COCO 权重直接训,先用这份数据集训一个mouse专用权重,再拿它当预训练模型去微调你的数据。命令上就是把model=yolov8n.pt换成你上一轮产出的best.pt:

# 用鼠标数据集产出的权重做起点,微调到自有场景 yolo detect train \ data=my_mouse.yaml \ model=mouse_run/exp1/weights/best.pt \ epochs=50 \ imgsz=640 \ lr0=0.0005 \ freeze=10

逻辑说明:freeze=10冻结 backbone 前 10 层,只训 head 和部分 neck,小数据上能防过拟合。lr0=0.0005比第一轮再低一半,因为权重已经比较接近目标域了。如果你的场景和数据集差异大(比如从办公桌换到工业面板),freeze可以去掉,让全网微调,但学习率别超过 0.001。

再说推理验证。训完一定要拿验证集之外的图跑一遍,看框的贴合度。下面这段脚本批量推理并保存可视化结果:

from ultralytics import YOLO import cv2, os model = YOLO("mouse_run/exp1/weights/best.pt") test_dir = "test_imgs" os.makedirs("test_out", exist_ok=True) for f in os.listdir(test_dir): if not f.lower().endswith((".jpg", ".png")): continue img = cv2.imread(os.path.join(test_dir, f)) results = model(img, conf=0.25, iou=0.45)[0] for box in results.boxes: x1, y1, x2, y2 = map(int, box.xyxy[0].tolist()) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, f"mouse {box.conf[0]:.2f}", (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imwrite(os.path.join("test_out", f), img)

逻辑说明:conf=0.25是推理常用阈值,iou=0.45控制 NMS 合并力度。box.xyxy拿的是左上右下绝对坐标,直接画框。参数上,如果你的场景鼠标很小,conf可以降到 0.15 看召回,但部署时再调回去。跑完打开test_out里的图,重点看三种情况:鼠标被遮挡时框还在不在、多个鼠标同框时有没有漏检、鼠标和键盘挨着时有没有误检。

最后说一个我自己的习惯。每次拿到新数据集,不管标注说明写得多好,我都会先抽 20 张图把 xml 和 txt 的框分别画出来叠在一起看。VOC 的框和 YOLO 的框如果完全重合,说明转换没问题;如果偏了,就是归一化或者坐标格式的锅。这个动作花不了十分钟,但能省掉后面几小时的 loss 排查。从那以后我每次训新数据集都强制走一遍这个叠框校验,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询