YOLO猫狗检测实战:三种标签格式与训练划分全解析

📅 发布时间:2026/10/10 14:44:13
YOLO猫狗检测实战:三种标签格式与训练划分全解析
简介这份资源面向目标检测入门与进阶学习者提供一套真实场景下的猫狗目标检测数据集可直接用于YOLO系列模型的训练与验证。数据经labelimg精细标注标注框质量较高并同时提供vocxml、cocojson与yolotxt三种格式标签分别存放于不同文件夹便于在不同框架间灵活切换。压缩包共约2000个文件以xml与txt标签文件为主另含少量html教程、py划分脚本与yaml配置文件整体约23.05MB体积轻便易于下载与迁移。资源还附赠YOLO环境搭建、训练案例教程以及训练集、验证集、测试集划分脚本读者可依据自身需求重新划分数据快速跑通从环境配置到模型训练再到数据划分的完整流程。目前已有1357人学习下载适合课程设计、毕业设计或算法入门阶段的目标检测实践者参考使用。1. 从一堆散装文件到能跑的训练管线这套猫狗检测资源到底省了哪几步如果你做过目标检测大概率经历过这个阶段模型代码跑通了配置文件也改好了结果卡在数据上——图片没有统一命名标签格式对不上训练集验证集还得手动分。真正耗时间的从来不是网络结构而是把一堆原始图片变成train.txt、val.txt和对应标签目录的过程。这套 YOLO 猫狗目标检测数据集核心价值就在于它把这段最枯燥的活提前干完了1000 张真实场景图片全部用 labelImg 标注同时提供 VOC 的 xml、COCO 的 json 和 YOLO 的 txt 三种标签格式外加三个划分脚本和 Windows/Linux 两套环境搭建与训练教程。适合刚入门 YOLO、想先跑通一条完整训练链路的人也适合需要快速验证某个改动对检测效果影响的老手。下面我按实际拆包顺序把这份资源怎么用、参数怎么改、哪里容易翻车讲清楚。2. 三种标签格式的对应关系VOC、COCO、YOLO 到底怎么选2.1 先搞清楚三种格式的坐标体系差异这份资源把同一批标注结果导出成了三种格式这不是为了凑数而是因为不同训练框架和评估工具吃不同的输入。VOC 格式是每张图一个 xml 文件里面用bndbox记录xmin、ymin、xmax、ymax坐标是绝对像素值原点在左上角。COCO 格式把所有图片的标注汇总到一个 json 里bbox字段是[x, y, width, height]同样是绝对像素但类别 id 从 1 开始且需要单独的 categories 列表。YOLO 格式最精简每张图对应一个 txt每行是class_id x_center y_center width height后四个值全部归一化到 0 到 1 之间。很多人第一次转换时栽在归一化上VOC 的绝对坐标直接除以图片宽高得到 YOLO 坐标但x_center是框中心点的 x 除以宽不是xmin除以宽。如果你拿xmin/width当中心点训练时框会整体偏移loss 降不下去还找不到原因。这份资源三种格式已经对齐好了你不需要自己转但理解这个差异能帮你在后续换数据集时少走弯路。2.2 按训练目标选格式别三种都往网络里塞选哪种格式取决于你用什么框架。YOLOv5、YOLOv8 以及 Ultralytics 系的新版本默认读 YOLO txt目录结构要求images/train和labels/train平行放置文件名一一对应。如果你要用 Detectron2 或者 MMDetectionCOCO json 更省事直接注册数据集就行。VOC xml 现在主要用在一些老代码或者需要 Pascal VOC 评估指标的场景。我一般会保留 YOLO 格式作为训练主格式因为 Ultralytics 的data.yaml配置最简单改个path和names就能跑。COCO json 留着做交叉验证比如用 pycocotools 算 mAP 时直接加载。VOC xml 作为原始标注存档万一后面要重新导出别的格式xml 是最容易解析的中间态。提示三种格式的类别顺序必须一致。这份资源里猫和狗的 class_id 是固定的如果你自己重新划分数据集别打乱类别映射否则训练出来的模型会把猫认成狗。2.3 用脚本验证三种格式是否对齐拿到数据后别急着训练先跑一段校验代码确认同一张图在三种格式下的框是同一个位置。下面这段脚本读取一张图的 VOC xml 和 YOLO txt把 YOLO 坐标反归一化后和 VOC 的绝对坐标对比误差超过 1 像素就说明转换有问题。import xml.etree.ElementTree as ET from PIL import Image # 读取 VOC xml tree ET.parse(annotations/000001.xml) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) voc_boxes [] for obj in root.findall(object): bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) voc_boxes.append((xmin, ymin, xmax, ymax)) # 读取 YOLO txt 并反归一化 yolo_boxes [] with open(labels/000001.txt, r) as f: for line in f: cls, xc, yc, w, h map(float, line.strip().split()) xmin (xc - w / 2) * img_w ymin (yc - h / 2) * img_h xmax (xc w / 2) * img_w ymax (yc h / 2) * img_h yolo_boxes.append((xmin, ymin, xmax, ymax)) # 逐框对比 for i, (vb, yb) in enumerate(zip(voc_boxes, yolo_boxes)): diff max(abs(vb[j] - yb[j]) for j in range(4)) print(fbox {i}: max diff {diff:.2f} px) if diff 1.0: print(f VOC: {vb}) print(f YOLO: {yb})这段代码的关键在反归一化公式xmin (xc - w/2) * img_w。如果你发现 diff 很大先检查图片宽高是不是读错了有些标注工具会把size里的宽高写反。另一个常见问题是 YOLO txt 里的类别顺序和 VOC xml 里的name字段顺序不一致这个脚本没校验类别实际使用时建议把类别名也打出来对一遍。3. 划分脚本怎么用从 1000 张图到 train/val/test 目录3.1 三个脚本的分工与调用顺序资源里带了三个 Python 脚本名字分别是「训练集、验证集、测试集划分脚本图片标签划分写入新文件夹.py」「训练集、验证集划分脚本图片标签划分写入新文件夹.py」和「split_train_val生成ImageSets下txt文件划分脚本.py」。前两个是直接把图片和标签复制到新文件夹适合 YOLO 这种按目录结构读取的训练框架第三个是生成ImageSets/Main下的 txt 文件适合 VOC 风格的 Dataset 类。我一般先用第一个脚本做三路划分得到images/train、images/val、images/test和对应的labels目录。如果后面要换框架再用第三个脚本生成 txt 索引。注意脚本里的路径参数是写死的还是通过命令行传入的打开脚本先看开头几行。# 典型划分脚本的核心逻辑根据资源内脚本整理 import os import random import shutil # 这几个参数需要按你的实际路径改 image_dir JPEGImages # 原始图片目录 label_dir labels_yolo # YOLO txt 标签目录 output_dir dataset_split # 输出根目录 train_ratio 0.7 val_ratio 0.2 test_ratio 0.1 random.seed(42) # 固定随机种子保证可复现 # 收集所有图片文件名不含扩展名 names [os.path.splitext(f)[0] for f in os.listdir(image_dir) if f.endswith(.jpg)] random.shuffle(names) n_total len(names) n_train int(n_total * train_ratio) n_val int(n_total * val_ratio) splits { train: names[:n_train], val: names[n_train:n_train n_val], test: names[n_train n_val:] } for split, split_names in splits.items(): img_out os.path.join(output_dir, images, split) lbl_out os.path.join(output_dir, labels, split) os.makedirs(img_out, exist_okTrue) os.makedirs(lbl_out, exist_okTrue) for name in split_names: shutil.copy(os.path.join(image_dir, name .jpg), os.path.join(img_out, name .jpg)) shutil.copy(os.path.join(label_dir, name .txt), os.path.join(lbl_out, name .txt)) print(f{split}: {len(split_names)} images)参数说明random.seed(42)这行很重要固定种子后每次划分结果一样方便你对比不同训练参数的效果。train_ratio和val_ratio按 7:2:1 设置如果数据量小可以调成 8:1:1验证集太少会导致评估指标波动大。shutil.copy是复制不是移动原始文件保留改错了可以重来。3.2 划分后必须检查的目录结构和文件对应脚本跑完后别直接开训。先确认三件事图片和标签文件名是否一一对应、每个 split 下的文件数量是否符合预期、有没有空标签文件。空标签文件在 YOLO 里表示负样本但如果你的数据集里所有图都有目标出现空文件就说明标注或复制过程出了问题。# 检查每个 split 下图片和标签数量是否一致 for split in train val test; do img_count$(ls dataset_split/images/$split | wc -l) lbl_count$(ls dataset_split/labels/$split | wc -l) echo $split: images$img_count labels$lbl_count done # 找出没有对应标签的图片 for img in dataset_split/images/train/*.jpg; do name$(basename $img .jpg) if [ ! -f dataset_split/labels/train/$name.txt ]; then echo missing label: $name fi done如果数量不一致最常见的原因是原始图片里有非 jpg 格式比如 png脚本只匹配了.jpg。另一个坑是文件名里有空格或中文shutil.copy不会报错但后续 DataLoader 读取时可能出问题。我一般会在划分前统一重命名成000001.jpg这种纯数字格式省得后面排查。3.3 生成 ImageSets txt 的脚本什么时候用第三个脚本生成的是ImageSets/Main/train.txt、val.txt这类索引文件每行一个文件名。这种结构在 Pascal VOC 的VOCDetection类里是标配如果你要用 py-faster-rcnn 或者一些老版本的 YOLO 代码就需要这个。生成逻辑很简单遍历划分好的目录把文件名写进 txt。# 生成 ImageSets 索引文件 import os splits [train, val, test] image_root dataset_split/images for split in splits: split_dir os.path.join(image_root, split) names [os.path.splitext(f)[0] for f in os.listdir(split_dir) if f.endswith(.jpg)] names.sort() out_path os.path.join(ImageSets, Main, f{split}.txt) os.makedirs(os.path.dirname(out_path), exist_okTrue) with open(out_path, w) as f: f.write(\n.join(names)) print(f{split}.txt: {len(names)} entries)这个脚本不复制文件只写索引所以跑得很快。注意names.sort()这行排序后索引顺序固定方便复现。如果你后面要改划分比例重新跑一遍这个脚本就行不用重新复制图片。4. 环境搭建与训练配置Windows 和 Linux 的实操差异4.1 Windows 下用 PyCharm 配 YOLO 环境的几个关键点资源里的 Windows 教程是基于 PyCharm 的这也是很多新手的起点。核心步骤是创建虚拟环境、装 PyTorch、装 Ultralytics。但 Windows 上最容易翻车的是 CUDA 版本和 PyTorch 版本不匹配。我一般会先确认显卡驱动支持的 CUDA 版本然后去 PyTorch 官网找对应的安装命令别直接用pip install torch那样装的是 CPU 版。# 先查 CUDA 版本需要装好显卡驱动 nvidia-smi # 根据 CUDA 版本选择 PyTorch 安装命令例如 CUDA 11.8 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 再装 Ultralytics pip install ultralytics装完后在 PyCharm 的 Python Console 里跑import torch; print(torch.cuda.is_available())返回 True 才算 GPU 可用。如果返回 False先检查是不是装成了 CPU 版用pip list | findstr torch看版本号里有没有cu后缀。另一个常见问题是 PyCharm 的项目解释器没选对虚拟环境代码跑起来用的是系统 Python装的包全在另一个环境里。4.2 Linux 下从零搭环境的命令序列Linux 教程覆盖了 Ubuntu 环境安装这部分对服务器部署更实用。我一般用 conda 建环境避免污染系统 Python。下面是一套我常用的命令序列和资源里的教程思路一致但补了一些依赖项。# 创建 conda 环境 conda create -n yolo python3.9 -y conda activate yolo # 安装 PyTorch根据 CUDA 版本调整 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装 Ultralytics 和其他依赖 pip install ultralytics opencv-python matplotlib tqdm # 验证 GPU python -c import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))Linux 上如果torch.cuda.is_available()返回 False先看nvidia-smi能不能正常输出。如果命令不存在说明显卡驱动没装好需要先装驱动再装 CUDA Toolkit。另一个坑是 conda 环境里装了 CPU 版 PyTorch原因是 pip 源默认拉的是 CPU 包必须加--index-url指定 CUDA 版本。4.3 把 data.yaml 改成你自己的路径Ultralytics 系训练的核心配置文件是data.yaml里面指定训练集、验证集路径和类别名。这份资源的目录结构如果直接拿来用需要把path改成你的实际路径。# data.yaml path: /home/user/dataset_split # 数据集根目录 train: images/train # 相对 path 的训练集路径 val: images/val test: images/test names: 0: cat 1: dognames里的顺序必须和 YOLO txt 里的 class_id 对应。如果你只有猫和狗两类就是 0 和 1。如果后面加了新类别在names里追加同时确保标注文件里的 class_id 也更新。改完 yaml 后用yolo checks命令可以快速验证配置有没有语法错误。4.4 启动训练与关键参数设置训练命令本身很简单但参数设置决定了你能不能复现出好结果。资源里的训练教程是基于案例修改的我补充几个实际调参时的经验值。# 基础训练命令 yolo detect train datadata.yaml modelyolov8n.pt epochs100 imgsz640 batch16 # 如果显存不够减小 batch 或 imgsz yolo detect train datadata.yaml modelyolov8n.pt epochs100 imgsz416 batch8 # 从上次中断的地方继续训练 yolo detect train datadata.yaml modelruns/detect/train/weights/last.pt epochs100 resumeTrueimgsz640是默认值如果图片里目标很小可以调到 1280但显存占用会翻倍。batch16在 8GB 显存上跑 640 尺寸基本够用不够就降到 8。epochs100对 1000 张图来说通常够收敛如果 loss 还在降可以加到 200。modelyolov8n.pt是 nano 版本速度快但精度低想提升精度换成yolov8s.pt或yolov8m.pt代价是训练时间变长。5. 避坑与排查标注、划分、训练三个环节的翻车记录5.1 标注框超出图片边界导致训练报错现象训练启动后报RuntimeError: box coordinate out of range或者 loss 直接变成 nan。原因labelImg 标注时如果框拖到了图片外面xml 里的xmax可能大于图片宽度转成 YOLO 格式后归一化坐标超过 1。解决写个脚本遍历所有 YOLO txt把坐标 clamp 到 0 到 1 之间同时检查 VOC xml 里的xmax width和ymax height。# 修复超出边界的 YOLO 坐标 import os label_dir labels_yolo fixed 0 for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue path os.path.join(label_dir, fname) lines [] with open(path, r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls, xc, yc, w, h parts xc, yc, w, h map(float, (xc, yc, w, h)) # clamp 到合法范围 xc min(max(xc, 0.0), 1.0) yc min(max(yc, 0.0), 1.0) w min(max(w, 0.0), 1.0) h min(max(h, 0.0), 1.0) lines.append(f{cls} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}) with open(path, w) as f: f.write(\n.join(lines)) fixed 1 print(fchecked {fixed} files)5.2 划分脚本随机种子不固定导致每次结果不同现象每次跑划分脚本train 和 val 里的图片都不一样之前训练好的模型换一次划分后评估指标波动很大。原因脚本里用了random.shuffle但没设种子或者设了种子但每次调用顺序不同。解决在脚本开头加random.seed(42)并且确保所有随机操作都在设种子之后。如果脚本里用了 numpy 的随机函数还要加np.random.seed(42)。5.3 图片和标签文件名大小写不一致现象Linux 下训练正常Windows 下报找不到标签文件。原因Windows 文件系统不区分大小写Linux 区分。如果图片叫Cat_001.jpg标签叫cat_001.txtLinux 上匹配不到。解决统一重命名成小写或者用脚本批量改。我一般会在划分前跑一遍重命名把所有文件名转成小写并去掉特殊字符。5.4 训练时 loss 不降反升现象前几个 epoch loss 正常下降后面突然飙升或者震荡。原因学习率太大、batch size 太小导致梯度噪声大、或者数据里有错误标注。解决先把学习率调小一个数量级比如从 0.01 降到 0.001。如果还不行检查有没有标注框宽高为 0 的情况这种框在计算 loss 时会产生异常值。另外1000 张图用 batch16 时一个 epoch 只有 60 多个 iteration学习率需要相应调小。5.5 验证集指标远低于训练集现象训练集 mAP 到 0.9验证集只有 0.5。原因过拟合、训练集和验证集分布不一致、或者验证集太小。解决先看验证集数量如果只有几十张指标波动大是正常的可以增大验证集比例。如果验证集数量够但指标差距大加数据增强augmentTrue或者换更小的模型。另一个容易被忽略的点是训练集和验证集里猫狗比例差异太大划分时最好做分层抽样。6. 用 ONNX 导出做一次跨平台推理验证训练完之后怎么确认模型是真的学到了东西而不是只记住了训练集的图片我一般会做一次 ONNX 导出然后用 OpenCV 的 DNN 模块加载在一张完全没参与训练的图片上跑推理。这一步能同时验证模型导出是否正常、预处理和后处理是否对齐、以及跨平台部署有没有坑。# 导出 ONNX 模型 from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) model.export(formatonnx, imgsz640, simplifyTrue) print(ONNX model exported)导出时simplifyTrue会调用 onnx-simplifier 优化计算图减少冗余节点。imgsz640必须和训练时一致否则推理时的特征图尺寸对不上。导出完成后会在best.pt同目录生成best.onnx。# 用 OpenCV DNN 加载 ONNX 并推理 import cv2 import numpy as np net cv2.dnn.readNetFromONNX(runs/detect/train/weights/best.onnx) img cv2.imread(test_image.jpg) h, w img.shape[:2] # 预处理resize 到 640x640归一化转 blob blob cv2.dnn.blobFromImage(img, 1/255.0, (640, 640), swapRBTrue, cropFalse) net.setInput(blob) outputs net.forward() # YOLOv8 输出形状是 [1, 4num_classes, 8400] outputs outputs[0].T # 转成 [8400, 4num_classes] boxes [] conf_thres 0.25 for row in outputs: scores row[4:] class_id np.argmax(scores) confidence scores[class_id] if confidence conf_thres: xc, yc, bw, bh row[:4] # 还原到原图坐标 x1 int((xc - bw / 2) / 640 * w) y1 int((yc - bh / 2) / 640 * h) x2 int((xc bw / 2) / 640 * w) y2 int((yc bh / 2) / 640 * h) boxes.append((class_id, confidence, x1, y1, x2, y2)) for cls, conf, x1, y1, x2, y2 in boxes: label f{cat if cls 0 else dog} {conf:.2f} cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imwrite(result.jpg, img) print(fdetected {len(boxes)} objects)这段代码里最容易出错的是输出形状的解析。YOLOv8 的 ONNX 输出是[1, 4nc, 8400]其中 8400 是所有 anchor 点的数量4nc是框坐标加类别分数。转置后每一行是一个候选框前四个值是xc, yc, w, h注意这里的w和h是相对于 640 输入尺寸的不是原图。还原到原图时要分别除以 640 再乘以原图宽高。如果你发现框的位置整体偏移先检查swapRBTrue有没有加OpenCV 读进来是 BGR模型训练时用的是 RGB。另一个验证点是类别顺序。ONNX 模型输出的类别索引和data.yaml里的names顺序一致如果你导出时用的best.pt是猫狗两类那 0 就是猫1 就是狗。如果推理结果把猫标成狗先确认names有没有写反。我现在的习惯是每次训练完先导出 ONNX用 OpenCV 跑一张训练集里没有的图确认框的位置和类别都对再去做部署。这一步花不了几分钟但能提前发现预处理不一致、坐标还原错误、类别映射错位这些后面很难排查的问题。从那以后我每次换数据集或者改模型结构都强制走一遍 ONNX 推理验证省得在部署环境里对着黑匣子猜。希望帮到你。本文还有配套的精品资源点击获取