ARTICLE DETAIL

资讯详情

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

YOLO网球检测实战:1956张图像小数据集训练与部署指南

YOLO网球检测实战:1956张图像小数据集训练与部署指南 简介这是一份面向目标检测学习者的网球场景数据集围绕网球场与运动员两类目标构建可直接用于YOLO系列算法的训练与验证。数据集共1956张图像已按训练与验证需求划分完毕并附带data.yaml配置文件兼容yolov5、yolov8、yolov9、yolov7、yolov10及yolo11等主流版本适合课程设计、算法对比实验与模型微调等场景。压缩包内共2000个文件以1707个xml标注文件和293个txt标注文件为主分别对应VOC与YOLO两种标注格式其中YOLO格式采用类别索引加归一化中心点与宽高的五元组表示便于直接读取训练。资源包整体约87.8MB目录结构清晰标签文件与图像一一对应省去自行标注与格式转换的环节。目前已有131人学习下载读者可快速获得一份开箱即用的网球目标检测数据用于验证模型精度、调试训练流程或开展迁移学习实验。1. 网球数据集到手先别急着训练1956 张图像带标签的 YOLO 落地判断拿到一个名为「yolo算法-网球数据集-1956张图像带标签-网-运动员.zip」的压缩包第一反应不该是解压后直接yolo train而是先判断这批数据到底能不能撑起一个可用的检测模型。1956 张图像在目标检测里属于小体量但如果标注质量高、场景聚焦网球、球网、运动员三类目标它完全够跑通一个 YOLO 网球检测的完整链路甚至能做出可演示的 demo。问题在于很多人卡在第一步不知道标签是什么格式、类别怎么映射、训练集验证集怎么切、预训练权重选哪个。这篇笔记就按一线实操的顺序把从解压到推理的每一步拆开讲包括我踩过的坑和参数怎么调。适合手里已经有类似数据集、想快速跑通 YOLO 训练并部署的工程师也适合想理解小数据集训练边界的同学。2. 拆包先看结构1956 张图像和标签到底长什么样2.1 解压后的目录结构与文件格式判断拿到压缩包后先别用图形界面双击用命令行解压并统计文件类型这样能快速判断数据集的组织方式。常见做法是unzip yolo算法-网球数据集-1956张图像带标签-网-运动员.zip -d tennis_dataset cd tennis_dataset find . -maxdepth 2 -type d | head -20 find . -type f | sed s/.*\.// | sort | uniq -c | sort -rn第一行解压到指定目录第二行进入目录第三行列出前两层目录结构第四行统计所有文件的扩展名分布。如果输出里.jpg或.png约 1956 个.txt数量接近甚至等于图像数那基本可以确定是 YOLO 格式的标注每张图对应一个同名 txt。如果出现.xml那就是 VOC 格式需要转换。如果只有图像没有 txt那这个「带标签」可能指的是分类标签或分割掩码需要进一步确认。我一般还会抽查几个 txt 内容head -5 $(find . -name *.txt | head -1)YOLO 格式每行是class_id x_center y_center width height数值都是归一化到 0~1 的浮点数。如果看到坐标是整数且范围在图像宽高内那可能是 VOC 或 COCO 转过来的中间态需要写脚本归一化。2.2 类别映射与标签一致性检查标题里提到「网-运动员」说明至少有三类网球、球网、运动员。但实际标签里的 class_id 可能从 0 开始也可能从 1 开始甚至有的数据集把「网球」和「球网」合并成一类。必须先把类别名和 id 的对应关系确定下来否则训练出来的模型会把球网识别成网球。常见做法是写一个统计脚本import os from collections import Counter label_dir tennis_dataset/labels class_counter Counter() for txt_file in os.listdir(label_dir): if not txt_file.endswith(.txt): continue with open(os.path.join(label_dir, txt_file), r) as f: for line in f: parts line.strip().split() if len(parts) 5: class_counter[int(parts[0])] 1 print(类别分布, class_counter)这段代码遍历所有标签文件统计每个 class_id 出现的次数。如果某个 id 的样本数极少比如少于 50那这个类别在 1956 张里可能只有几十个实例训练时容易欠拟合。参数说明label_dir要换成你实际的标签目录通常是labels/或与images/平级。如果统计结果里只有两个 id那说明「网球」和「球网」可能被合并了需要根据实际业务决定是否拆分。提示如果类别数超过 3 个但标题只提了三个目标检查是否有「运动员」被细分为「发球方」「接球方」等子类这种细粒度在小数据集上通常训不好建议合并。2.3 图像尺寸与标注框的合理性抽检1956 张图像可能来自不同来源尺寸不一致。YOLO 训练时默认会 resize 到统一尺寸如 640×640但如果原图长宽比差异太大resize 后目标会变形。先统计图像尺寸分布python -c from PIL import Image import os sizes {} for img in os.listdir(tennis_dataset/images): if img.endswith((.jpg,.png)): with Image.open(os.path.join(tennis_dataset/images, img)) as im: sizes[im.size] sizes.get(im.size, 0) 1 for k,v in sorted(sizes.items(), keylambda x:-x[1])[:10]: print(k, v) 如果前几种尺寸占比超过 80%说明数据集相对统一可以直接用默认的 letterbox 策略。如果尺寸非常分散建议在训练配置里开启rectTrue做矩形训练减少 padding 带来的无效计算。另外抽几张图可视化标注框确认没有框错、漏标。我习惯用下面这段脚本快速画框import cv2 import os img_path tennis_dataset/images/000001.jpg label_path tennis_dataset/labels/000001.txt img cv2.imread(img_path) h, w img.shape[:2] with open(label_path) as f: for line in f: cls, x, y, bw, bh map(float, line.split()) x1 int((x - bw/2) * w) y1 int((y - bh/2) * h) x2 int((x bw/2) * w) y2 int((y bh/2) * h) cv2.rectangle(img, (x1,y1), (x2,y2), (0,255,0), 2) cv2.imwrite(check.jpg, img)跑完打开check.jpg如果框的位置明显偏移说明标签坐标系和图像坐标系不一致可能是归一化时用错了宽高顺序。这个坑很常见血泪经验是YOLO 的x_center是相对宽度归一化y_center是相对高度归一化千万别搞反。3. 用 YOLOv8 跑通训练从环境到第一个 baseline3.1 环境安装与预训练权重选择YOLOv8 是目前小数据集上最容易出效果的版本之一安装直接用 pippip install ultralytics如果要用 GPU 训练确认 CUDA 版本和 PyTorch 匹配。我一般会先跑一行命令验证环境yolo checks输出里会显示 CUDA 是否可用、版本号等信息。预训练权重选yolov8n.pt还是yolov8s.pt1956 张图属于小数据n 版本参数少、收敛快适合先跑 baselines 版本精度略高但容易过拟合。我的习惯是先用 n 跑 50 epoch 看 mAP 趋势如果验证集 mAP 还在涨再换 s 微调。权重文件不需要手动下载ultralytics会在第一次训练时自动拉取。但要注意如果网络环境不稳定可能会卡在下载环节可以提前把yolov8n.pt放到项目根目录训练时指定本地路径。3.2 数据集 YAML 配置与路径陷阱YOLOv8 要求一个 YAML 文件描述数据集路径和类别名。常见写法path: /home/user/tennis_dataset train: images/train val: images/val names: 0: tennis_ball 1: net 2: player这里有几个容易翻车的点。第一path必须是绝对路径或者相对于运行训练命令时的当前目录。我遇到过在runs/目录下启动训练结果path解析错误报「No images found」。第二train和val是相对于path的子路径不是绝对路径。第三names的 id 必须和标签里的 class_id 完全一致顺序不能错。如果数据集还没有划分 train/val需要先切分。1956 张图按 8:2 切验证集约 390 张。切分脚本import os, random, shutil random.seed(42) img_dir tennis_dataset/images label_dir tennis_dataset/labels train_img tennis_dataset/images/train val_img tennis_dataset/images/val train_lbl tennis_dataset/labels/train val_lbl tennis_dataset/labels/val for d in [train_img, val_img, train_lbl, val_lbl]: os.makedirs(d, exist_okTrue) imgs [f for f in os.listdir(img_dir) if f.endswith((.jpg,.png))] random.shuffle(imgs) split int(len(imgs) * 0.8) for i, img in enumerate(imgs): src_img os.path.join(img_dir, img) src_lbl os.path.join(label_dir, img.rsplit(.,1)[0] .txt) if i split: shutil.copy(src_img, os.path.join(train_img, img)) if os.path.exists(src_lbl): shutil.copy(src_lbl, os.path.join(train_lbl, os.path.basename(src_lbl))) else: shutil.copy(src_img, os.path.join(val_img, img)) if os.path.exists(src_lbl): shutil.copy(src_lbl, os.path.join(val_lbl, os.path.basename(src_lbl)))参数说明random.seed(42)保证每次切分结果一致方便复现。split控制训练集比例小数据集可以调到 0.85 增加训练样本。注意标签文件要和图像同名否则训练时会被当成无标签样本忽略。3.3 训练命令与关键参数设置启动训练yolo detect train \ datatennis.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projecttennis_runs \ namebaseline逐项说明data指向 YAML 文件epochs100对小数据集足够配合patience20早停防止过拟合imgsz640是默认输入尺寸如果原图分辨率普遍低于 640可以降到 416 加速训练batch16根据显存调整8G 显存跑 n 版本可以到 32lr00.01是初始学习率小数据集建议比默认的 0.01 再小一点比如 0.005避免震荡。训练过程中重点看mAP50和mAP50-95两个指标。如果mAP50在 30 epoch 后就平了说明模型容量不够或数据太简单如果mAP50波动很大检查 batch size 是否太小或学习率太高。我一般会在训练结束后用 TensorBoard 看 loss 曲线tensorboard --logdir tennis_runs如果分类 loss 下降但定位 loss 不降可能是标注框质量有问题回到 2.3 节重新抽检。4. 训练完别只看 mAP推理、导出与踩坑排查4.1 用验证集跑推理并可视化结果训练结束后权重保存在tennis_runs/baseline/weights/best.pt。先用验证集跑一遍推理看实际效果yolo detect predict \ modeltennis_runs/baseline/weights/best.pt \ sourcetennis_dataset/images/val \ saveTrue \ conf0.25 \ iou0.45conf0.25是置信度阈值低于这个值的框会被过滤iou0.45是 NMS 的 IoU 阈值控制重叠框的合并。如果发现漏检多把conf降到 0.1 试试如果误检多提高到 0.4。结果图默认保存在runs/detect/predict/下打开几张看看网球是否被框住、球网是否被误判成背景。我还会用 Python 脚本批量统计 TP/FP/FN更客观地评估from ultralytics import YOLO model YOLO(tennis_runs/baseline/weights/best.pt) metrics model.val(datatennis.yaml, splitval) print(metrics.box.map) # mAP50-95 print(metrics.box.map50) # mAP50 print(metrics.box.mp) # 平均精度 print(metrics.box.mr) # 平均召回如果mr明显低于mp说明漏检比误检严重优先检查小目标网球的标注是否完整。4.2 导出 ONNX 与部署前的尺寸对齐如果要把模型部署到边缘设备或 C 推理通常导出 ONNXyolo export modeltennis_runs/baseline/weights/best.pt formatonnx imgsz640导出后得到一个.onnx文件。注意导出时的imgsz必须和训练时一致否则推理结果会偏移。我遇到过训练用 640、导出用 416结果框全乱的情况。另外ONNX 推理时预处理要自己做 letterbox保持和 YOLO 训练时相同的缩放和 padding 逻辑否则精度掉点。4.3 小数据集训练的四个典型翻车现场现象一训练 loss 正常下降但验证集 mAP 始终为 0。原因验证集路径配置错误或者验证集标签文件缺失。YOLO 在验证时如果找不到标签会跳过该图导致 mAP 计算为空。 解决检查val路径下是否有对应的labels目录且每个图像都有同名 txt。用find val_images -type f | wc -l和find val_labels -type f | wc -l对比数量。现象二网球检测效果差球网和运动员正常。原因网球在图像中占比小属于小目标YOLOv8n 的 P3 特征图感受野有限。 解决换用yolov8s.pt或更大模型或者在训练时开启mosaic1.0增强小目标样本。也可以把imgsz提高到 1280但显存消耗会翻倍。现象三训练到 50 epoch 后 mAP 突然掉点。原因过拟合。1956 张图对 n 版本来说仍然偏少模型开始记住训练集噪声。 解决加数据增强hsv_h0.015, hsv_s0.7, hsv_v0.4, degrees10, translate0.1或者用早停patience15。我一般还会冻结 backbone 前 10 层跑 20 epoch再解冻全量微调。现象四推理时框的位置整体偏移。原因标签归一化时用错了图像宽高或者推理预处理没有做 letterbox。 解决回到 2.3 节的可视化脚本确认训练标签框位置正确。如果训练时正确、推理时偏移检查推理代码的 resize 逻辑确保和ultralytics的LetterBox一致。注意如果数据集里混入了非网球场景的负样本比如空场地不要直接删掉保留 5% 作为背景负样本能降低误检率。5. 把 1956 张图用到极致增强策略与迭代习惯小数据集训练 YOLO核心思路不是换更大的模型而是让每一张图产生更多有效梯度。我习惯在 baseline 跑通后做三件事第一用albumentations做离线增强把训练集扩到 5000 张左右重点加随机裁剪、旋转和色彩抖动模拟不同光照和视角第二用训练好的模型在验证集上跑推理把置信度在 0.1~0.3 之间的框人工复核确认是漏标还是误检漏标的补上标签再训一轮第三尝试yolov8s和yolov8n的模型融合用 WBF加权框融合代替 NMS通常能涨 1~2 个点 mAP。具体增强脚本可以这样写import albumentations as A import cv2, os transform A.Compose([ A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(p0.3), A.Rotate(limit15, p0.3), A.RandomCrop(width512, height512, p0.3), ], bbox_paramsA.BboxParams(formatyolo, label_fields[class_labels])) img cv2.imread(tennis_dataset/images/train/000001.jpg) with open(tennis_dataset/labels/train/000001.txt) as f: bboxes [] class_labels [] for line in f: cls, x, y, w, h map(float, line.split()) bboxes.append([x, y, w, h]) class_labels.append(int(cls)) aug transform(imageimg, bboxesbboxes, class_labelsclass_labels) cv2.imwrite(aug_000001.jpg, aug[image]) with open(aug_000001.txt, w) as f: for bbox, cls in zip(aug[bboxes], aug[class_labels]): f.write(f{cls} { .join(map(str, bbox))}\n)参数说明HorizontalFlip对网球场景安全因为左右翻转不改变语义Rotate限制在 15 度以内避免球网倾斜过度RandomCrop可能裁掉小目标建议配合min_visibility0.3过滤掉裁后面积过小的框。增强后的标签要重新归一化albumentations的yolo格式会自动处理。最后说一个我自己的习惯每次训练完不管 mAP 多高都会把best.pt在验证集上跑一遍挑出置信度最低的 10 张图逐张看标注。十次里有八次能发现漏标或错标补完再训一轮mAP 通常还能再涨。小数据集没有捷径标注质量就是上限。希望帮到你。本文还有配套的精品资源点击获取
返回列表