ARTICLE DETAIL

资讯详情

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

猫狗检测数据集VOC+YOLO格式8291张2类别:从校验到YOLOv8训练全流程

猫狗检测数据集VOC+YOLO格式8291张2类别:从校验到YOLOv8训练全流程 简介本资源为猫狗检测数据集采用Pascal VOC与YOLO双格式标注面向目标检测初学者、算法工程师及需要快速验证模型的研究人员可直接用于训练与评估猫狗两类目标检测模型。压缩包共2000个文件以1999个xml标注文件和1个txt说明文件为主xml对应VOC格式的矩形框标注txt为使用前必读说明包体约472.28MB另含8291张jpg图片及等量yolo格式txt标注图片与标注一一对应。数据集共标注cat与dog两个类别cat框数4766、dog框数5500总框数10266均使用labelImg工具按矩形框规则完成标注。目前已有625人学习下载适合作为课程实验、模型对比或数据增强练习的现成素材省去自行采集与标注成本便于快速搭建检测流程并验证算法效果。1. 猫狗检测数据集 VOCYOLO 格式 8291 张 2 类别拿到压缩包之后先想清楚的三件事你从标题里能读到的信息其实很密8291 张图、2 个类别猫和狗、同时提供 VOC 和 YOLO 两套标注格式。但真正决定这个数据集能不能用、好不好用的不是这几个数字而是标注质量、类别平衡度和格式转换的细节。我见过太多人拿到数据集直接unzip然后train.py一把梭跑完发现 mAP 卡在 0.6 上不去回头查才发现是某些图片的标注框越界了或者猫狗比例严重失衡导致模型偏向某一类。猫狗检测本身是目标检测里最经典的入门场景之一VOC 格式是 Pascal VOC 那套 XML 标注体系YOLO 格式则是每张图对应一个.txt文件、每行class_id x_center y_center width height的归一化坐标。这个数据集同时给了两套格式省掉了你自己写转换脚本的功夫但也意味着你需要先验证两套标注是否一致。这篇文章会从数据集的目录结构讲起一路走到用 YOLOv8 跑通训练、调参、排查标注问题适合刚接触目标检测的新手也适合想拿一个干净数据集做 baseline 的老手。2. 拆开压缩包VOC 与 YOLO 双格式的目录结构与校验方法2.1 两种格式到底差在哪为什么这个数据集要同时给VOC 格式的核心是每张图片对应一个 XML 文件里面用object标签记录每个目标的类别名和边界框的左上角、右下角坐标xmin, ymin, xmax, ymax坐标是绝对像素值。YOLO 格式则把每个目标压缩成一行文本五个字段类别索引、框中心 x 坐标、框中心 y 坐标、框宽、框高后四个都是相对于图片宽高的归一化值0 到 1 之间。两种格式各有各的用处VOC 的 XML 可读性强方便人工检查和用 LabelImg 继续编辑YOLO 的 txt 直接被 Ultralytics 系列的训练框架读取不需要额外解析。这个数据集同时提供两套常见做法是拿 YOLO 格式直接训练用 VOC 格式做交叉校验——如果两套标注对不上说明转换过程中出过问题。一个典型的目录结构大概长这样cat_dog_dataset/ ├── VOC/ │ ├── JPEGImages/ # 8291 张 jpg 图片 │ ├── Annotations/ # 8291 个 xml 标注文件 │ └── ImageSets/ │ └── Main/ # train.txt / val.txt 划分文件 ├── YOLO/ │ ├── images/ │ │ ├── train/ # 训练集图片 │ │ └── val/ # 验证集图片 │ ├── labels/ │ │ ├── train/ # 训练集标注 txt │ │ └── val/ # 验证集标注 txt │ └── dataset.yaml # YOLO 训练配置文件 └── README.md拿到之后第一件事不是急着训练而是做三项校验图片和标注数量是否一一对应、类别索引是否从 0 开始连续、标注框有没有越界或宽高为 0 的情况。下面这段脚本可以一次性跑完这三项检查。import os import xml.etree.ElementTree as ET from pathlib import Path from collections import Counter VOC_IMG_DIR cat_dog_dataset/VOC/JPEGImages VOC_ANN_DIR cat_dog_dataset/VOC/Annotations YOLO_LABEL_DIR cat_dog_dataset/YOLO/labels/train # 1. 检查图片与标注数量是否一致 imgs set(p.stem for p in Path(VOC_IMG_DIR).glob(*.jpg)) xmls set(p.stem for p in Path(VOC_ANN_DIR).glob(*.xml)) print(f图片数: {len(imgs)}, 标注数: {len(xmls)}) print(f有图无标注: {imgs - xmls}) print(f有标注无图: {xmls - imgs}) # 2. 统计类别分布 class_counter Counter() for xml_file in Path(VOC_ANN_DIR).glob(*.xml): tree ET.parse(xml_file) for obj in tree.getroot().findall(object): class_counter[obj.find(name).text] 1 print(f类别分布: {dict(class_counter)}) # 3. 检查 YOLO 标注是否有越界或零面积框 bad_boxes [] for txt_file in Path(YOLO_LABEL_DIR).glob(*.txt): with open(txt_file) as f: for line_num, line in enumerate(f, 1): parts line.strip().split() if len(parts) ! 5: bad_boxes.append((txt_file.name, line_num, 字段数不对)) continue cls, x, y, w, h map(float, parts) if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): bad_boxes.append((txt_file.name, line_num, f越界: {line.strip()})) print(f异常标注框数量: {len(bad_boxes)}) for item in bad_boxes[:10]: print(item)这段脚本的逻辑很直白第一步用集合差集找出图片和标注不匹配的文件第二步遍历所有 XML 统计猫和狗各有多少个标注框第三步逐行读 YOLO 的 txt 检查归一化坐标是否在合法范围内。参数方面VOC_IMG_DIR这些路径按你实际解压后的位置改就行。跑完之后重点看两个数类别分布里猫和狗的比例如果超过 3:1训练时就要考虑用类别权重或者过采样来平衡异常标注框如果超过总数的 1%建议直接把这些文件剔掉或者手动修正否则训练时 loss 会异常震荡。2.2 用 Python 把 VOC 转成 YOLO 的完整脚本与四个边界坑虽然这个数据集已经给了 YOLO 格式但实际工作中你经常需要自己转——比如拿到一个新数据集只有 VOC 标注或者你想重新划分训练验证集。转换的核心逻辑就三步读 XML 拿到图片宽高和每个框的绝对坐标、把绝对坐标转成归一化的中心点加宽高、按类别名映射到类别索引写入 txt。听起来简单但下面这四个坑我几乎每次都能碰到一两个。第一个坑是图片宽高要从 XML 的size标签里读而不是用 PIL 去打开图片。有些数据集的 XML 里写的宽高和实际图片尺寸不一致这时候要以 XML 为准还是以实际图片为准我的经验是以实际图片为准因为训练时加载的是真实图片如果标注按 XML 的尺寸归一化但图片实际尺寸不同框就会偏。第二个坑是类别索引的映射必须固定不能每次按字母序排否则两次转换出来的索引可能不一样。第三个坑是坐标越界XML 里 xmax 可能超过图片宽度归一化之后 x_center 加 w/2 会大于 1需要裁剪到 [0, 1]。第四个坑是有些 XML 里会有difficult标记为 1 的目标这些在 VOC 评估里是忽略的但转 YOLO 时要不要保留我的做法是保留但在文件名里做个标记训练时可以选择性过滤。import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image # 固定的类别映射不要用动态排序 CLASS_MAP {cat: 0, dog: 1} def voc_to_yolo(xml_path, img_dir, output_dir): tree ET.parse(xml_path) root tree.getroot() # 用实际图片尺寸不用 XML 里的 size img_file img_dir / (xml_path.stem .jpg) with Image.open(img_file) as im: img_w, img_h im.size lines [] for obj in root.findall(object): cls_name obj.find(name).text.strip().lower() if cls_name not in CLASS_MAP: continue cls_id CLASS_MAP[cls_name] 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) # 裁剪到图片范围内 xmin max(0, min(xmin, img_w)) xmax max(0, min(xmax, img_w)) ymin max(0, min(ymin, img_h)) ymax max(0, min(ymax, img_h)) # 转归一化中心点加宽高 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 跳过零面积框 if w 0 or h 0: continue lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) out_file output_dir / (xml_path.stem .txt) out_file.write_text(\n.join(lines)) # 批量转换 xml_dir Path(cat_dog_dataset/VOC/Annotations) img_dir Path(cat_dog_dataset/VOC/JPEGImages) out_dir Path(cat_dog_dataset/YOLO/labels/all) out_dir.mkdir(parentsTrue, exist_okTrue) for xml_path in xml_dir.glob(*.xml): voc_to_yolo(xml_path, img_dir, out_dir) print(f转换完成共 {len(list(out_dir.glob(*.txt)))} 个文件)这段脚本的关键点在于CLASS_MAP写死映射关系保证索引一致用Image.open读实际尺寸而不是信 XML坐标裁剪到图片边界防止越界零面积框直接跳过。参数上x_center这些保留 6 位小数足够了YOLO 训练时精度不会因为小数位少而出问题。转换完之后建议再跑一遍 2.1 里的校验脚本确认新生成的 txt 没有异常。3. 用 YOLOv8 在 Anaconda 环境里跑通猫狗检测训练3.1 环境配置从 conda 创建到 ultralytics 安装的完整命令YOLOv8 的环境配置说简单也简单说坑多也坑多。最常见的翻车场景是直接用系统 Pythonpip install ultralytics结果和已有的 torch 版本冲突训练时 CUDA 报错。我一般会单独建一个 conda 环境把 Python 版本锁在 3.9 或 3.10这两个版本和 PyTorch 的兼容性最稳。下面是从零开始的完整命令。# 创建 conda 环境Python 3.10 conda create -n catdog_yolo python3.10 -y conda activate catdog_yolo # 安装 PyTorch根据你的 CUDA 版本选对应的命令 # CUDA 11.8 的情况 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 如果只有 CPU用这个 # pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 安装 ultralytics pip install ultralytics # 验证安装 python -c import torch; print(torch.__version__, torch.cuda.is_available()) python -c from ultralytics import YOLO; print(ultralytics ok)这里有几个参数需要根据你的机器改CUDA 版本决定了--index-url后面跟哪个地址用nvidia-smi看右上角的 CUDA Version。如果你没有独立显卡就用 CPU 版本的 torch训练会慢很多但能跑通。torch.cuda.is_available()返回True才说明 GPU 可用。ultralytics 安装时会自动拉取一些依赖如果网络慢可以加-i换源。装完之后建议再跑一次yolo checks命令它会打印出环境里各个组件的版本和状态有问题的项会标红。3.2 准备 dataset.yaml 与划分训练验证集YOLOv8 训练需要一个 yaml 配置文件告诉它去哪里找图片和标注。这个数据集如果已经分好了 train 和 val直接写路径就行如果没有分需要自己按 8:2 或 7:3 划分。我一般会写一个划分脚本保证每次划分结果可复现。import random from pathlib import Path import shutil random.seed(42) # 固定随机种子保证可复现 img_dir Path(cat_dog_dataset/YOLO/images/all) label_dir Path(cat_dog_dataset/YOLO/labels/all) out_base Path(cat_dog_dataset/YOLO) # 收集所有图片文件名不含扩展名 stems [p.stem for p in img_dir.glob(*.jpg)] random.shuffle(stems) split_idx int(len(stems) * 0.8) train_stems stems[:split_idx] val_stems stems[split_idx:] for split_name, split_stems in [(train, train_stems), (val, val_stems)]: img_out out_base / images / split_name lbl_out out_base / labels / split_name img_out.mkdir(parentsTrue, exist_okTrue) lbl_out.mkdir(parentsTrue, exist_okTrue) for stem in split_stems: shutil.copy(img_dir / f{stem}.jpg, img_out / f{stem}.jpg) src_lbl label_dir / f{stem}.txt if src_lbl.exists(): shutil.copy(src_lbl, lbl_out / f{stem}.txt) print(f训练集: {len(train_stems)}, 验证集: {len(val_stems)})划分完之后写dataset.yamlpath: /absolute/path/to/cat_dog_dataset/YOLO train: images/train val: images/val nc: 2 names: 0: cat 1: dogpath必须写绝对路径这是 YOLOv8 的一个硬性要求写相对路径经常找不到文件。nc是类别数这里是 2。names的索引必须和标注文件里的 class_id 对应如果标注里猫是 0 狗是 1这里就不能写反。划分比例方面8291 张图按 8:2 分训练集大概 6632 张验证集 1659 张这个量级对于猫狗两个类别来说足够了。如果显存小可以适当减少训练集图片数量或者降低输入分辨率。3.3 启动训练命令行参数逐项拆解环境好了、数据齐了训练命令本身不复杂但每个参数都影响最终效果。下面这条命令是我在 8GB 显存上跑猫狗检测的常用配置。yolo detect train \ datacat_dog_dataset/YOLO/dataset.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ device0 \ projectruns/catdog \ nameexp1 \ pretrainedTrue \ optimizerSGD \ cos_lrTrue \ close_mosaic10逐项说modelyolov8n.pt用的是 nano 版本参数量最小、训练最快适合先跑通流程如果追求精度可以换yolov8s.pt或yolov8m.pt但显存占用会翻倍。epochs100对猫狗这种简单场景通常够了配合patience20表示 20 轮没有提升就早停。imgsz640是输入分辨率猫狗目标通常比较大640 足够如果图片里猫狗很小可以提到 1280 但显存要跟上。batch16在 8GB 显存上跑 640 分辨率比较稳显存不够就降到 8。lr00.01是初始学习率用 SGD 优化器时这个值比较合适如果用 Adam 可以降到 0.001。cos_lrTrue开启余弦退火训练后期学习率自动降低有助于收敛。close_mosaic10表示最后 10 轮关闭 mosaic 数据增强让模型在真实分布上微调。device0指定第一块 GPUCPU 训练改成devicecpu。训练启动后终端会打印每一轮的 loss、mAP50、mAP50-95 等指标。重点盯两个box_loss和cls_loss是否稳定下降mAP50是否在 50 轮之后超过 0.8。如果 loss 震荡剧烈大概率是学习率太高或者 batch 太小如果 mAP 一直不涨检查标注是否有问题。训练完成后权重保存在runs/catdog/exp1/weights/best.pt这个文件后面做推理和部署都用它。4. 训练完之后怎么验证模型真的学到了猫和狗4.1 用验证集跑指标与混淆矩阵训练结束不代表模型就能用得看它在没见过的数据上表现如何。YOLOv8 提供了val模式直接加载 best.pt 在验证集上跑一遍。yolo detect val \ modelruns/catdog/exp1/weights/best.pt \ datacat_dog_dataset/YOLO/dataset.yaml \ imgsz640 \ batch16 \ conf0.25 \ iou0.5 \ plotsTrueconf0.25是置信度阈值低于这个值的检测框会被过滤掉iou0.5是 NMS 的 IoU 阈值控制重叠框的合并。plotsTrue会生成混淆矩阵、PR 曲线等图保存在runs/catdog/val/下面。混淆矩阵是重点看的对角线上的数值越大越好如果猫被大量误判成狗或者反过来说明两类在特征上区分度不够可能需要更多数据或者更深的模型。正常情况下猫狗检测的 mAP50 应该能到 0.9 以上如果只有 0.7 左右八成是标注有问题。4.2 单张图片推理与置信度门限调整验证集指标好看不代表实际用起来没问题拿几张真实图片跑一下推理更直观。from ultralytics import YOLO model YOLO(runs/catdog/exp1/weights/best.pt) # 单张图片推理 results model.predict( sourcetest_images/, conf0.4, # 置信度门限低于此值的框不输出 iou0.45, # NMS IoU 阈值 imgsz640, saveTrue, # 保存带框的图片 save_txtTrue, # 保存检测结果为 txt projectruns/predict, nametest ) # 打印每张图的检测结果 for r in results: print(f图片: {r.path}) for box in r.boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) xyxy box.xyxy[0].tolist() print(f 类别: {model.names[cls_id]}, 置信度: {conf:.3f}, 框: {xyxy})conf这个参数是实际部署时最需要调的。调高了漏检多调低了误检多。猫狗检测场景下我一般从 0.4 开始试如果发现有些猫只露出半个身子被漏掉就降到 0.3如果背景里经常出现误检的框就提到 0.5。iou0.45是 NMS 的默认值如果两只猫挨得很近导致其中一个被抑制掉可以适当提高到 0.5 或 0.6。save_txtTrue会把检测结果按 YOLO 格式存下来方便后续做统计或者二次处理。5. 避坑与排查猫狗检测数据集训练中最容易翻车的五个地方5.1 标注框越界导致 loss 变成 NaN现象训练前几个 epoch 正常突然 loss 变成nan终端刷屏警告。原因YOLO 格式的标注里 x_center 或 y_center 加上宽高的一半超过了 1计算 loss 时出现非法值。解决跑一遍 2.1 里的校验脚本把所有越界的标注找出来。如果是少量文件手动修正或删除如果大量越界说明转换脚本有问题检查是否用了 XML 里的尺寸而不是实际图片尺寸。5.2 类别索引写反导致猫狗互换现象训练指标正常但推理时猫的框标成了 dog狗的框标成了 cat。原因dataset.yaml里的names顺序和标注文件里的 class_id 不对应。比如标注里 cat 是 0、dog 是 1但 yaml 里写成了0: dog, 1: cat。解决打开任意一个标注 txt看第一列的数字然后对照 yaml 里的 names 确认。改 yaml 之后重新训练不需要改标注文件。5.3 图片和标注文件名不匹配导致部分数据被静默跳过现象训练日志里显示的图片数量和实际数据集数量对不上比如 8291 张只加载了 7000 多张。原因YOLOv8 按文件名匹配图片和标注如果某张图叫cat_001.jpg但标注叫cat_001.jpeg.txt或者cat_001.xml就匹配不上这张图会被跳过。解决确保 images 和 labels 目录下的文件名不含扩展名完全一致。用脚本批量检查差集把不匹配的文件找出来重命名。5.4 显存不足导致 batch 自动缩小但没注意到现象设置了batch16但训练速度异常慢nvidia-smi 看显存占用很低。原因YOLOv8 在显存不够时会自动把 batch 降到能跑的值但终端可能只在一开始提示一次后面就刷过去了。解决训练启动后看第一行日志里的batch实际值。如果被降到了 4 或 8要么换更小的模型yolov8n要么降低 imgsz要么用梯度累积模拟大 batch。5.5 验证集 mAP 高但实际推理效果差现象验证集 mAP50 有 0.92但拿手机拍的猫狗照片去推理漏检和误检都很严重。原因训练集和验证集来自同一批数据分布一致但实际场景的图片在光照、角度、背景上和数据集差异大。这是典型的过拟合到数据集分布。解决在训练时开启更强的数据增强比如mosaic1.0、hsv_h0.015、hsv_s0.7、hsv_v0.4、fliplr0.5。另外可以收集一些实际场景的图片做微调哪怕只有几十张也能明显改善。6. 把 8291 张猫狗数据用到位从 baseline 到可部署模型的三个进阶技巧第一个技巧是分层采样划分验证集。如果你的数据集里猫和狗的比例不是 1:1随机划分可能导致验证集里某一类特别少mAP 波动大。我一般会按类别分层采样保证训练集和验证集里猫狗比例一致。具体做法是先按类别把图片分组每组内部分别按 8:2 划分再合并。这样验证集的指标更稳定不会因为某次随机划分恰好把大部分猫分到训练集而虚高。第二个技巧是用预训练模型做特征提取然后冻结主干。YOLOv8 的yolov8n.pt是在 COCO 上预训练的COCO 里本身就有猫和狗这两个类别。如果你自己的数据集量不大比如只有一两千张可以冻结 backbone 只训练检测头这样收敛快且不容易过拟合。在命令行里加freeze10表示冻结前 10 层具体冻多少层可以试。我试过在 2000 张猫狗图上冻结 10 层20 个 epoch 就能到 mAP50 0.85比从头训练快很多。第三个技巧是导出 ONNX 做推理加速。训练完的.pt文件用 PyTorch 加载推理速度一般如果要做部署可以导出成 ONNX 格式再用 onnxruntime 跑CPU 上也能有不错的帧率。# 导出 ONNX yolo export modelruns/catdog/exp1/weights/best.pt formatonnx imgsz640 simplifyTrue # 用 onnxruntime 推理 python -c import onnxruntime as ort import numpy as np sess ort.InferenceSession(runs/catdog/exp1/weights/best.onnx) input_name sess.get_inputs()[0].name # 构造一个假输入测试 dummy np.random.randn(1, 3, 640, 640).astype(np.float32) outputs sess.run(None, {input_name: dummy}) print(ONNX 推理成功输出形状:, [o.shape for o in outputs]) simplifyTrue会对计算图做简化去掉冗余算子推理速度能提升 10% 到 20%。导出后的 ONNX 模型可以用在 C、Java 或者 Web 端不依赖 PyTorch 环境。注意导出时的imgsz要和训练时一致否则检测框坐标会偏。最后说一个我自己的习惯每次拿到新数据集先花半小时跑校验脚本和可视化把有问题的标注挑出来看一眼。这半小时能省掉后面几小时的无效训练。猫狗检测这个任务本身不难难的是数据干净、参数合理、验证到位。希望帮到你。本文还有配套的精品资源点击获取
返回列表