ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

1100张老鼠图像如何训练YOLOv8?小目标检测调优实战

2026/9/15 3:27:33 拓冰建站 浏览量
1100张老鼠图像如何训练YOLOv8?小目标检测调优实战 简介这是一份面向目标检测任务的老鼠图像数据集共包含约1100张已标注图片采用YOLO标注格式类别仅“老鼠”一类适合需要训练老鼠检测模型、开展YOLO系列改进实验或进行迁移学习的研究者与开发者。资源包共2000个文件其中920张jpg图片与1078个xml标注文件构成数据主体另有1个可视化python脚本和1个json配置文件整体约171.6MB。数据已做好训练集与测试集划分使用者运行show脚本即可直观检查每张图片的标注框是否符合预期省去自行整理与格式转换的步骤。目前已有91人学习下载可直接用于目标检测入门实践、算法精度对比以及实验室监测、智慧仓储等需要自动识别老鼠的场景。若需做YOLOv5改进验证也可参考作者主页的配套实战内容进行延伸。1. 老鼠图像检测为什么说你的 1100 张标注图还差一道工序做动物行为分析、药物毒理实验或者仓储鼠害监测的团队大概率都卡在同一个环节数据已经标好了YOLO 格式的 txt 文件安安静静躺在 labels 目录里但训练出来的模型要么把老鼠和背景糊在一起要么出现了大量误检。拿到一批约 1100 张、已经标注好的老鼠图像数据集是不是直接扔进 YOLOv8 就能跑出理想效果答案是否定的。1100 张这个量级很微妙——比学术基准数据集小一个数量级但比纯手工采集强很多恰好是“迁移学习能救、硬train from scratch 必翻车”的分界线。我见过不少翻车案例有人拿 1100 张直接训 300 轮损失函数曲线看似收敛了实际验证集 mAP50 只有 0.6 出头还有人不做任何数据检查训练到一半才发现 label 里有空标签文件,损失直接 NaN。本文就顺着“YOLO 标注格式解析 → 数据验证与划分 → 训练参数调优 → 小目标检测优化”这条链路把 1100 张老鼠数据集从标注文件变成可交付的检测模型并重点处理老鼠这种“小目标背景杂乱形态多变”的特殊场景。2. YOLO 标注格式底层解析txt 文件里的五个数字怎么读很多人用 LabelImg 或者 X-AnyLabeling 标注完就完事了对 labels 目录下的 txt 文件没有完整认识。老鼠数据集既然声明是 YOLO 格式那必须先理解它的物理存储结构。2.1 标签文件的原子结构YOLO 格式的每个 txt 文件与一张图片一一对应文件名相同、扩展名不同。比如IMG_0231.jpg对应IMG_0231.txt内容长这样0 0.328125 0.53125 0.151367 0.328125 0 0.712891 0.223632 0.113281 0.189453每一行代表一个标注框共 5 个数字class_id x_center y_center width height注意这里的前 4 个几何值全部是相对原图宽高的归一化结果。比如第一行里的0.328125等于 bbox 中心的像素 x 坐标除以图像宽度不是像素值。类别 ID 从 0 开始计数如果 cats 目录下定义了names: [mouse]那么第 0 类就是老鼠如果数据集同时标了 rat 和小鼠两种[mouse, rat]中 0 是老鼠、1 是大鼠。动手解析这类文件我一般用 Python 的os和numpy直接扫描import os import numpy as np def parse_yolo_label(txt_path, img_width, img_height): boxes [] with open(txt_path, r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: print(f跳过异常行: {txt_path} - {line.strip()}) continue cls_id int(parts[0]) x_center, y_center, w, h map(float, parts[1:]) # 还原像素坐标 x1 (x_center - w / 2) * img_width y1 (y_center - h / 2) * img_height x2 (x_center w / 2) * img_width y2 (y_center h / 2) * img_height boxes.append((cls_id, x1, y1, x2, y2)) return np.array(boxes, dtypeobject)逻辑说明上面的代码先逐行拆分出 5 个字段然后做一步“归一化转像素坐标”的换算。为什么要还原像素坐标因为训练时的损失计算用的是归一化坐标但画框、评估、计算 IoU 时需要像素级坐标这步换算不能省。第一行字段0代表类别索引后面四个浮点数为归一化中心点和宽高。如果某行不足 5 个字段说明标注软件导出时截断了这类脏数据应在训练前剔除。2.2 反常识YOLO 坐标只能用归一化值吗严格来说YOLOv5 之后官方推荐归一化坐标但不是因为归一化本身有多高级而是为了适配多分辨率输入。如果你的老鼠图像尺寸不一致——比如一部分是 640x480 监控截图另一部分是 1920x1080 科研相机图像——归一化标签可以无缝执行 resize 而无需重新标注。反常识的点在于很多人以为 YOLO 训练器会自动做 resize所以标注尺寸无所谓但训练器改的是输入张量你的归一化标签乘以新尺寸后框的比例关系必须与真实目标一致如果原图被不等比例压缩那只归一化坐标也无法拯救形变。1100 张老鼠数据里经常混着不同来源的图像我处理这种数据集时第一个动作是统计尺寸分布from PIL import Image import glob shape_counter {} for img_path in glob.glob(images/*.jpg): shape Image.open(img_path).size shape_counter[shape] shape_counter.get(shape, 0) 1 for shape, cnt in sorted(shape_counter.items(), keylambda x: -x[1]): print(shape, cnt)逻辑说明统计每一种(width, height)出现了多少次一眼就能看出数据是否存在多分辨率混用。如果主要尺寸集中在一种训练时统一 resize 到 640x640几何失真可以忽略如果尺寸分布散最好按最长边等比缩放加填充或者用 YOLOv8 的rect模式保持宽高比。参数说明rectTrue会让训练器按照批次自动选择最接近的宽高比减少图像形变带来的 bbox 漂移。2.3 标注格式匹配数据集的真伪校验拿到“约 1100 张数据”不要轻信手机号。/images 目录有 1100 张 jpg/labels 目录是不是也有整整 1100 个 txt缺失一个标签文件就意味着这张图在训练时被静默跳过或报错。先跑一次硬核对img_files set(os.path.splitext(os.path.basename(p))[0] for p in glob.glob(images/*.jpg)) label_files set(os.path.splitext(os.path.basename(p))[0] for p in glob.glob(labels/*.txt)) print(f无标签文件 {len(img_files - label_files)} 张, img_files - label_files) print(f无对应图片 {len(label_files - img_files)} 个, label_files - img_files)逻辑说明用集合做差集找出只有图没有标签、或只有标签没有图的文件名。另外还要检查 txt 是否为空文件空文件在 YOLO 训练中的表现是“这张图不参与正样本匹配”相当于背景样本多了会让模型偏向于输出低置信度。检查脚本for lp in label_files: if os.path.getsize(os.path.join(labels, lp .txt)) 0: print(lp)。一套流程走完你才真正拿到了“可被 YOLO 消费”的数据。接下来才是划分、训练和调优。3. 1100 张老鼠数据集的 train/val 划分与样本验证上一章解决了“标签对不对、能不能读”的问题。这一章解决“怎么划分、划完要不要动”的问题。3.1 手动 stratify 划分按目标数量分桶采样1100 张图如果用train_test_split(test_size0.2)纯随机切极有可能把包含大量密集老鼠的样本都砸进训练集验证集全是没有老鼠的空场景——然后 val mAP 虚高。更可靠的做法是按图像中标注框数量分桶再做分层采样from sklearn.model_selection import train_test_split import numpy as np import glob, os img_paths sorted(glob.glob(images/*.jpg)) density_bins [] for p in img_paths: label_path p.replace(images, labels).replace(.jpg, .txt) bbox_count 0 if os.path.exists(label_path): with open(label_path) as f: bbox_count sum(1 for line in f if len(line.strip().split()) 5) if bbox_count 1: density_bins.append(single) elif bbox_count 4: density_bins.append(sparse) elif bbox_count 10: density_bins.append(dense) else: density_bins.append(crowd) train_idx, val_idx train_test_split( np.arange(len(img_paths)), test_size0.2, random_state42, stratifydensity_bins ) print(ftrain: {len(train_idx)}, val: {len(val_idx)})逻辑说明stratifydensity_bins让训练集和验证集里“单目标图、稀疏图、密集图”的占比与全数据集保持一致。对于老鼠检测这种目标尺度变化大的任务validation 必须包含拥挤场景否则模型在小目标上的表现没有参考意义。random_state 固定为 42保证每次划分结果一致便于后续对比实验。划分完成后写一个data.yamlpath: /absolute/path/to/mouse_dataset train: images/train val: images/val names: 0: mouse参数说明path建议写绝对路径规避 YOLO 在不同运行目录下相对路径解析失败的问题。如果数据集里只有一类老鼠names段只留0: mouse如果有大鼠小鼠区分顺序与训练时 txt 标签里的 class_id 严格对应。3.2 划分后的框尺寸分布审计划分完不等于就绪。老鼠目标在全图占比通常很小10 米开外的监控画面里一只老鼠可能只有 32x32 像素。这种小目标在 YOLO 的 640x640 输入下对应到下采样后的特征图上可能只覆盖 1~2 个网格极易漏检。我一般用脚本统计 bbox 尺寸分布from PIL import Image import pandas as pd widths, heights, areas [], [], [] for img_path in train_img_paths: img Image.open(img_path) w, h img.size label_path img_path.replace(images/train, labels).replace(.jpg, .txt) with open(label_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue _, xc, yc, bw, bh map(float, parts) widths.append(bw * w) heights.append(bh * h) areas.append(bw * bh * w * h) df pd.DataFrame({w: widths, h: heights, area: areas}) print(df.describe(percentiles[.25, .5, .75, .9, .95])) print(f面积小于 32x32 的框占比: {(df.area 1024).mean():.2%})逻辑说明如果(df.area 1024).mean()超过 20%你的数据集中小目标占比很高后面章节中的优化手段就不是可选项而是必选项。这一步的产出决定训练参数——小目标为主的数据集imgsz不建议用 640 以下anchor相关改进也要提前规划。3.3 训练前可视化抽检一副图 34 张画布跑训练之前把划分后的训练集随机抽 20 张画上标注框人眼过一遍。这个步骤能发现大多数自动化检查漏不掉的问题标注框偏大/偏小、类别串了、旋转目标没包紧、漏标严重等等。用 OpenCV 画框import cv2 import random random.seed(7) sample random.sample(train_img_paths, 20) for idx, path in enumerate(sample): img cv2.imread(path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) label_path path.replace(images/train, labels).replace(.jpg, .txt) h, w img.shape[:2] with open(label_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id, xc, yc, bw, bh map(float, parts) x1 int((xc - bw/2) * w) y1 int((yc - bh/2) * h) x2 int((xc bw/2) * w) y2 int((yc bh/2) * h) color (255, 0, 0) if cls_id 0 else (0, 255, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.imwrite(fviz/{idx:03d}.jpg, img[:, :, ::-1])逻辑说明这里用imwrite前把 RGB 又转回 BGR避免保存后颜色通道错乱。验证目的不是欣赏标注质量而是找“框不在目标上”“框住了两只老鼠”“某些目标明显漏标”的问题。这一步最多花 20 分钟能把你从“模型训练完才发现数据有问题”的坑里拽出来——500 轮训完再去重新标注数据付出的是天级时间成本。4. YOLO 训练环境配置与关键参数调试从模型结构到损失函数数据准备好了轮到 YOLO 本身。当前 yolo 版本已经迭代到了 v11主流稳定版是 yolov8 和 yolov11。很多初学者选 YOLO 版本只看“最新”但实际项目选型要看硬件条件、显存和检测精度需求。4.1 环境安装与硬件选型安装 YOLO 训练环境最顺滑的方式还是 Ultralytics 包pip install ultralytics -i https://pypi.tuna.tsinghua.edu.cn/source如果机器上有 NVIDIA 显卡安装后执行python -c import torch; print(torch.cuda.is_available())输出 True 即可。如果没有独显或者 AMD 显卡用户跑 yolo只能走 CPU 推理或训练路线——不是不能用1100 张图在 CPU 上训练 300 轮yolov8n 大约十几个小时yolov8m 可能要三十小时往上。AMD 显卡跑 yolo 社区有 ROCm 兼容方案但坑非常多新人不建议在环境上耗时间老老实实用 CPU 配小模型先把流程跑通。版本选择上如果你的显卡显存 6GB 以下用yolov8n或yolov11n8~12GB 显存可以上yolov8s或yolov8m超过 16GB 再考虑yolov8l。1100 张训练集撑不起 yolo-l 以上的模型强行上大模型只会过拟合。4.2 训练命令与参数逐项解析我基于 1100 张老鼠数据集跑过的稳定训练命令是yolo detect train modelyolov8n.pt data/path/to/mouse_dataset/data.yaml \ imgsz640 epochs150 batch16 device0 \ optimizerAdamW lr00.001 weight_decay0.0005 \ patience30 cacheram workers4 \ pretrainedTrue close_mosaic10 seed42逻辑说明modelyolov8n.pt表示加载 COCO 预训练权重这一步至关重要。1100 张数据从零训练无法收敛到可用精度预训练权重提供了底层特征提取能力——边缘、纹理、颜色分布等低级特征在 COCO 上已经学好了你的数据只需要教会模型“老鼠长什么样”。pretrainedTrue与下载的 .pt 文件配合使用。imgsz640是输入分辨率老鼠属于小目标不建议低于 640epochs150不是越多越好配合patience30在验证集指标连续 30 轮不上升时自动早停batch16是单卡显存决定的显存不够时报 CUDA OOM 就减半。close_mosaic10表示训练最后 10 轮关闭 mosaic 增强——mosaic 是把四张图拼成一张虽然能增强泛化性但会改变目标尺度分布直接导致小目标性能退化最后几轮关掉让模型适应真实分布。优化器选择AdamW而不是默认的 SGD是因为 AdamW 在中小数据集上收敛更稳不容易出现 loss 震荡对学习率的敏感度也低一些。4.3 loss 曲线怎么读训练跑起来后不要傻等。观察三个指标第一个是train/box_loss如果它持续下降但val/box_loss在某个点反弹说明过拟合开始patience会自动停第二个是metrics/mAP50(B)这个值最终应该超过 0.8 才算数据可用第三个是metrics/mAP50-95(B)它比 mAP50 更严格要求预测框与真实框 IoU 从 0.5 到 0.95 平均——小目标数据集里 mAP50-95 通常只有 mAP50 的六到七成这是正常的。如果训练结束 mAP50 不升反降常见原因是数据划分不均匀或者标签坐标本身有系统性偏移回第 3 章重新可视化。4.4 预测阶段的后处理参数训练完预测时后处理参数直接影响检出率from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict( sourcetest_images/, conf0.25, iou0.45, imgsz640, max_det300 )# 解析结果 for r in results: boxes r.boxes.xyxy.cpu().numpy() scores r.boxes.conf.cpu().numpy() cls_ids r.boxes.cls.cpu().numpy() for box, score, cls_id in zip(boxes, scores, cls_ids): print(fclass{int(cls_id)} conf{score:.2f} box{box.astype(int)})逻辑说明conf0.25是置信度阈值低于这个分数直接丢弃。老鼠场景误检一般发生在阴影或杂物背景上如果发现误检过多把 conf 抬到 0.35~0.4如果漏检多降到 0.15 再配合后续 NMS。iou0.45是 NMS 的 IoU 阈值鼠群密集场景建议降到 0.3否则两个贴近的老鼠框会被合并成一个。5. 小目标老鼠检测的进阶优化红外小目标思路与 mAP50-95 提升老鼠数据是典型的小目标主导场景监控画面里目标可能只占整体面积的 1% 以下。这一部分把小目标优化的常用手段整合成一套实战方案按优先级排。5.1 P2 检测层给模型加一层高分辨率特征图YOLOv8 的默认检测头从 P3 开始8 倍下采样对于 16x16 像素以下的目标这个层级的特征图只有 2x2 的响应区域信息量严重不足。常见做法是自定义 yaml 文件增加 P2 层4 倍下采样# my_yolov8s_p2.yaml nc: 1 depth_multiple: 0.33 width_multiple: 0.50 backbone: - [-1, 1, Conv, [64, 3, 2]] - [-1, 1, Conv, [128, 3, 2]] - [-1, 3, C2f, [128, True]] - [-1, 1, Conv, [256, 3, 2]] - [-1, 6, C2f, [256, True]] - [-1, 1, Conv, [512, 3, 2]] - [-1, 6, C2f, [512, True]] - [-1, 1, Conv, [1024, 3, 2]] - [-1, 3, C2f, [1024, True]] - [-1, 1, SPPF, [1024, 5]] head: - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 6], 1, Concat, [1]] - [-1, 3, C2f, [512]] - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 4], 1, Concat, [1]] - [-1, 3, C2f, [256]] - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 2], 1, Concat, [1]] - [-1, 3, C2f, [128]] - [ -1, 1, Detect, [nc, [128, 256, 512, 1024]]]配置导出后训练yolo detect train model/path/to/my_yolov8s_p2.yaml \ pretrainedyolov8s.pt data/path/to/mouse_dataset/data.yaml \ imgsz640 epochs150 batch16 device0逻辑说明在原有 P3/P4/P5 基础上增加了 P2 层Detect 头的输出变成 4 个尺度。代价是计算量增加约 20%推理变慢一些但 mAP50-95 通常能提升 2~5 个点小目标收益尤其明显。注意nc: 1要与你的类别数严格一致。5.2 判断小目标检测模型的评价参数小目标任务的评价不能只看 mAP50要看 mAP50-95 和不同尺寸目标的分离指标。Ultralytics 训练结果里没有直接按尺寸拆分 mAP可以自己评估from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) metrics model.val(datadata.yaml, splitval) map50 metrics.box.map50 map50_95 metrics.box.map print(fmAP50: {map50:.3f}, mAP50-95: {map50_95:.3f}) # 分尺寸统计 for i, metric in enumerate(metrics.box.maps_per_class): print(fclass {i}: {metric:.3f})逻辑说明训练日志里通常只输出 mAP50-95 的两种口径但实际部署时更关心 32x32 以下的小目标到底检出多少。5.3 数据增强策略调整翻转、马赛克与尺度抖动前述close_mosaic10之外YOLO 的增强策略通过degrees, translate, scale, fliplr等参数控制。老鼠检测场景下我建议参数设置如下degrees0.0 translate0.1 scale0.5 fliplr0.5 mosaic1.0 mixup0.0逻辑说明老鼠没有方向性旋转问题degrees0避免模型学会识别倒着的老鼠浪费学习能力。scale0.5提供 0.5~1.5 倍的尺度抖动对老鼠远近不同带来的尺寸变化有帮助。mixup默认关闭这类数据增强在两个类别以上时效果更好单类目标场景开 mixup 反而会制造模糊标签。如果发现光照变化大导致误检可以去掉颜色增强相关的hsv_h/hsv_s/hsv_v默认值改为hsv_h0.015, hsv_s0.5, hsv_v0.3让模型对颜色变化更鲁棒。5.4 推理端切片检测放大后用小图扫一遍全图监控摄像头拍到的画面是 1920x1080整图缩到 640 输入一只 40x40 像素的老鼠在输入里只剩 13x13 像素。一个替代方案是切片推理把大图切成 640x640 的 tile每块独立预测最后合并结果。import cv2 import numpy as np from ultralytics import YOLO model YOLO(best.pt) def detect_tiled(img_path, tile_size640, overlap0.2): img cv2.imread(img_path) h, w img.shape[:2] step int(tile_size * (1 - overlap)) all_boxes [] for y in range(0, h, step): for x in range(0, w, step): tile img[y:ytile_size, x:xtile_size] pad_h tile_size - tile.shape[0] pad_w tile_size - tile.shape[1] if pad_h 0 or pad_w 0: tile cv2.copyMakeBorder(tile, 0, pad_h, 0, pad_w, cv2.BORDER_CONSTANT) result model.predict(tile, conf0.25, imgsz640, verboseFalse)[0] box result.boxes.xyxy.cpu().numpy() for b in box: b[0] x; b[1] y; b[2] x; b[3] y all_boxes.append(b) return np.array(all_boxes) if all_boxes else np.empty((0, 4))逻辑说明overlap0.2防止目标正好被切片线切碎因为切碎的目标在单个 tile 里只有一半置信度很低。切片后每个 tile 独立推理再把坐标加回原图偏移量。这种方案的检测速度约为整图推理的 4~6 倍但小目标召回率通常明显提升适合离线分析监控录像。参数调整方向如果目标更小把tile_size降到 512如果目标较大升到 768 以减少重复计算。5.5 单类模型的置信度阈值策略只有老鼠一个目标类别时误检主要来自背景假阳性漏检主要来自低置信度的小目标。这里有个平衡技巧把 conf 设低到 0.1 推理随后按面积过滤——小于某个面积阈值的检测框大概率是背景噪声直接丢弃大于阈值的保留。面积阈值可以用训练集 bbox 面积分布的 5% 分位数作为参考值。具体数值我在项目中常用 900 像素30x30作为下限根据相机距离做调整。配合前面章节的划分、训练参数这套流程能从 1100 张老鼠数据直接产出一个可交付的小目标检测模型。本文还有配套的精品资源点击获取