☰
525张鸡目标检测数据集:VOC与YOLO格式解析及训练实践
2026/10/10 11:29:16 网站建设 项目流程

简介:一份面向目标检测与计算机视觉学习者的鸡只图像标注数据集,包含525张真实场景JPEG图片及其对应的Pascal VOC和YOLO格式标注文件,类别涵盖Chicken,共607个矩形标注框。数据集使用LabelImg工具以统一规则绘制,适合用于目标检测模型训练、农业智能化识别等场景,尤其适合需要快速获取标准格式训练数据的开发者和研究人员。压缩包共1577个文件,包括525个jpg图片、525个xml标注文件、525个txt标注文件及2个辅助txt文件,整体约162.28MB,解压后即可直接用于YOLO或VOC系列模型的训练与验证。目前已有183人学习下载,可直接省去人工采集与标注环节,并同时获得两种常见格式、无需自行转换,帮助使用者专注于模型搭建与参数调优,是入门和进阶目标检测项目可选的实用数据集。

1. 拿到一份“鸡数据集”先别急着训练:525张图能做什么、不能做什么

做目标检测的同行应该都遇到过这种场景:项目里临时要加一个“鸡只识别”的需求,或者是养殖场景的巡检、或者是实验动物的行为分析,又或者是学生毕设想找一个冷门但能跑通的小数据集。标题里这份“鸡数据集VOC格式+yolo格式525张1类别.7z”就是一个典型的落地资源——它不是代码库,不是论文复现包,而是一份已经标注好的图像数据集,而且贴心地同时给了VOC和YOLO两种格式。这意味着你拿到手之后,既可以用传统Detection框架读取,也能直接丢给YOLO系列训练,省掉了最耗时间的标注环节。

525张、1个类别,这个规模在深度学习里属于“能跑通但别指望精度爆炸”的档位。适合做迁移学习、跑通训练流程、做毕业设计、验证检测pipeline,也适合给刚入门YOLO的人练手;但如果你想做养殖场级别的精准计数或者复杂光照下的鲁棒检测,525张是远远不够的。这篇文章就围绕这份数据集,把“这是什么、怎么解压、怎么校验、怎么转格式、怎么训练、坑在哪”整个链条拆开讲清楚,让你拿到手后不用再到处翻教程。

2. 先搞懂VOC和YOLO两种标注格式:为什么同一份数据要出两个版本

2.1 Pascal VOC格式:XML里藏着一整套“框”的语言

VOC格式源自Pascal VOC挑战赛,是目标检测领域最老牌的标注格式之一。它的核心是一个XML文件,和每张图片一一对应。XML里记录了图片尺寸、路径,以及每个目标的类别和边界框坐标。坐标系是左上角原点,x_min、y_min、x_max、y_max分别代表框的左右上下边界,单位是像素。

这份鸡数据集既然标了VOC格式,那文件夹结构就应该是常规的VOC布局。常见做法是:

dataset_root/ ├── Annotations/ # 存放所有XML标注 ├── JPEGImages/ # 存放所有JPG图片 ├── ImageSets/ │ └── Main/ # train.txt, val.txt, test.txt └── labels/ # 有些版本会转好的YOLO格式

注意一个细节:如果数据集的Annotations和JPEGImages是散的,没有ImageSets/Main里的train.txt、val.txt划分文件,那你自己要先切一份。525张图如果按8:1:1切,大概就是420张训练、52张验证、53张测试,这个比例在少样本场景下算合理。

2.2 YOLO格式:归一化坐标和一文件一框的约定

YOLO格式是Darknet/YOLO系列使用的txt标注。每个目标占一行,格式是:

class_id x_center y_center width height

这里的x_center、y_center、width、height全部是归一化值,即除以图片宽高后的比例,取值在0到1之间。一个txt文件对应一张图片,文件名要严格和图片同名(比如IMG_001.jpg对应IMG_001.txt)。如果没有目标,txt文件就是空的。

YOLO坐标换算的坑是新手最容易翻车的地方。从VOC的像素坐标转YOLO的归一化坐标,公式是:

x_center = (x_min + x_max) / 2 / image_width y_center = (y_min + y_max) / 2 / image_height width = (x_max - x_min) / image_width height = (y_max - y_min) / image_height

这个转换用代码跑一遍其实很快。如果这个数据集自带的YOLO格式不是转换好的,那就需要你自己动手,下面是转换脚本。

import os import xml.etree.ElementTree as ET from PIL import Image def voc_to_yolo(xml_path, target_dir, image_dir): """ 把单个VOC XML转成YOLO txt :param xml_path: XML文件路径 :param target_dir: txt输出目录 :param image_dir: 图片目录,用于读取宽高 """ tree = ET.parse(xml_path) root = tree.getroot() # 从XML的size节点里取宽高,不要直接读图片,避免IO开销 size = root.find('size') img_w = int(size.find('width').text) img_h = int(size.find('height').text) filename = root.find('filename').text base_name = os.path.splitext(filename)[0] out_path = os.path.join(target_dir, base_name + '.txt') lines = [] for obj in root.findall('object'): cls_name = obj.find('name').text # 类别名转成ID,这里只有一类,直接写0 cls_id = 0 box = obj.find('bndbox') x_min = float(box.find('xmin').text) y_min = float(box.find('ymin').text) x_max = float(box.find('xmax').text) y_max = float(box.find('ymax').text) # 计算归一化中心点、宽高 x_center = (x_min + x_max) / 2.0 / img_w y_center = (y_min + y_max) / 2.0 / img_h w = (x_max - x_min) / img_w h = (y_max - y_min) / img_h # 防止越界,YOLO训练时如果坐标超出[0,1]会报错或裁掉 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) w = min(max(w, 0.0), 1.0) h = min(max(h, 0.0), 1.0) lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(out_path, 'w') as f: f.write('\n'.join(lines))

这段脚本的逻辑关键点在于:宽高优先从XML的size节点读取,而不是用PIL重新打开图片。因为525张图如果每一张都用PIL读一遍,转换耗时会长很多。另一个细节是坐标做了越界保护(clamp到0-1),这个处理在VOC转YOLO时很有必要——有些标注工具会把框稍微标出图片边界,不处理的话Ultralytics YOLO的训练日志里会频繁报警告。

2.3 为什么同一个数据集要给两种格式

你可能好奇:既然YOLO格式更方便,为什么不只给一种?原因有两层。第一层是历史兼容性——很多老项目、论文复现仓库、MMDetection系工具链原生吃VOC格式,如果你只拿到YOLO的txt,反而要用反向脚本转回去。第二层是校验价值——两种格式同时存在时可以互相交叉验证标注数据是否损坏。比如我做数据清洗时,会用脚本把两种格式读出来,画在同一张图上对比,如果框的位置明显对不上,那肯定是某一侧转换出了问题。

3. 解压与目录结构校验:7z包到手后的第一步

3.1 在Windows和Linux上解压7z文件

标题后缀是.7z,这本身就是一种高压缩比的归档格式,压缩率通常比zip高10%到30%,尤其对图片这种大文件效果明显。但它不像zip那样操作系统原生支持,Windows上需要装7-Zip,Linux上需要p7zip工具包。

Windows上装完7-Zip后,右键->7-Zip->Extract到这里即可。但不建议用系统自带的其他解压器去解.7z,容易解出文件缺失或者中文乱码。命令行方式更快:

# Windows,前提是7z.exe在PATH里 7z x chicken_dataset.7z -oD:\datasets\chicken -y
# Linux / Ubuntu,先装p7zip sudo apt install p7zip-full 7z x chicken_dataset.7z -o/path/to/chicken -y

这里-o参数指定输出目录,注意-o后面不要加空格,这是7z命令一个容易翻车的地方。-y表示全自动确认覆盖,避免解压中途卡在交互提示上。

解压完先别急着打开图片,先确认文件树有没有按预期展开。经验做法是直接数文件数量:

# 查看图片数量 ls JPEGImages/ | wc -l # 查看标注数量 ls Annotations/ | wc -l

如果JPEGImages里是525张,Annotations里也应该有525个XML,数量对不上就说明归档不完整或者有坏文件。

3.2 验证标注文件可读性:跑一个快速检查脚本

解压完只是第一步,标注文件能不能被正确解析是另一回事。很多数据集的XML里会有空节点、缺少size字段、或者filename和实际图片名大小写不一致。我一般会拿到手就跑一个快速校验脚本,3分钟把整个数据集的健康度摸一遍:

import os import xml.etree.ElementTree as ET from PIL import Image from pathlib import Path image_dir = Path('JPEGImages') anno_dir = Path('Annotations') errors = [] no_anno = [] for img_path in sorted(image_dir.iterdir()): if img_path.suffix.lower() not in ['.jpg', '.jpeg', '.png']: continue # 找对应XML xml_path = anno_dir / (img_path.stem + '.xml') if not xml_path.exists(): no_anno.append(img_path.name) continue try: tree = ET.parse(xml_path) root = tree.getroot() # 读取图片真实宽高 with Image.open(img_path) as im: real_w, real_h = im.size # 读取XML里声明的宽高 size = root.find('size') xml_w = int(size.find('width').text) xml_h = int(size.find('height').text) if real_w != xml_w or real_h != xml_h: errors.append(f"{img_path.name}: XML size {xml_w}x{xml_h} != image {real_w}x{real_h}") # 检查bndbox是否超出图片范围 for obj in root.findall('object'): box = obj.find('bndbox') x_min = float(box.find('xmin').text) y_min = float(box.find('ymin').text) x_max = float(box.find('xmax').text) y_max = float(box.find('ymax').text) if x_max > real_w + 5 or y_max > real_h + 5: errors.append(f"{img_path.name}: bndbox out of range") except Exception as e: errors.append(f"{img_path.name}: {e}") print(f"无标注文件: {len(no_anno)}") for f in no_anno[:10]: print(f" {f}") print(f"错误数: {len(errors)}") for e in errors[:20]: print(f" {e}")

这个脚本没跑YOLO格式的校验,但VOC侧能通过的话,YOLO侧只要是自己转出来的就基本信得过。要注意的是XML的size和图片实际尺寸不一致这个坑——有的数据集是从视频抽帧后没更新标注里的尺寸,这种如果直接转YOLO格式,归一化坐标全部是错的,训练出来框的位置会整体漂移。

3.3 抽几张小图可视化标注:别全信文件名

校验脚本跑完没报错,只能说明格式上没问题,不代表标注内容是对的。我会再抽10张图把框画出来肉眼看一遍,这一步特别关键——标注内容是不是这个类别、框是不是框住了目标的完整身体、有没有把鸡冠子切掉半个,肉眼一扫就全知道了。

画框常用的方式是直接用OpenCV把XML的坐标画回图片上:

import cv2 import xml.etree.ElementTree as ET import random img_path = 'JPEGImages/IMG_001.jpg' xml_path = 'Annotations/IMG_001.xml' img = cv2.imread(img_path) tree = ET.parse(xml_path) for obj in tree.getroot().findall('object'): box = obj.find('bndbox') x_min = int(float(box.find('xmin').text)) y_min = int(float(box.find('ymin').text)) x_max = int(float(box.find('xmax').text)) y_max = int(float(box.find('ymax').text)) color = (0, 255, 0) cv2.rectangle(img, (x_min, y_min), (x_max, y_max), color, 2) cv2.putText(img, 'chicken', (x_min, y_min - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1) cv2.imwrite('check_output.jpg', img)

如果发现某张图的框明显只框了半只鸡,那说明原标注质量一般,后续训练时可以考虑把这类样本清洗掉。525张里面如果有5%到10%的标注不准确,属于正常范围,不必恐慌;但如果超过20%,就要怀疑数据集本身是自动标注出来的,训练效果会打折扣。

4. 用这个数据集跑通YOLO训练:从源码包到第一条val曲线

4.1 环境准备:yolo训练自己的数据集,pycharm里就能干

这里以Ultralytics YOLOv8为例讲训练流程,因为这是目前最主流的YOLO入门方案,pip安装就能用,不需要编译源码。标题里的数据集给的是YOLO格式,正好直接吃。

# 创建虚拟环境,避免污染系统Python python -m venv yolo_env source yolo_env/bin/activate # Windows下是 yolo_env\Scripts\activate pip install ultralytics

注意ultralytics会自动拉取torch和torchvision,如果机器上有NVIDIA GPU建议先装对应版本的torch cu118或cu121再装ultralytics,否则默认装CPU版,训练速度慢到怀疑人生。安装完后命令行直接敲yolo就能看到帮助信息。

使用pycharm的话,在Settings -> Project -> Python Interpreter里选到刚才创建的虚拟环境,然后在Terminal里执行yolo命令或在Python里调用model.train()都可以。很多新手在这卡住是因为pycharm右下角没切解释器,跑出来的还是全局Python环境,import ultralytics直接报ModuleNotFoundError。

4.2 数据YAML怎么写:chicken.yaml是训练入口

YOLO训练前要准备一个数据集描述文件,也就是data.yaml。它的内容是把图片目录和类别列表告诉训练器。525张图规模不大,建议把train和val明确指到对应的图片目录,不要用相对路径,避免yolo命令的执行位置不同导致路径解析失败。

# chicken.yaml path: D:/datasets/chicken # 数据集根目录 train: images/train # 训练图片目录 val: images/val # 验证图片目录 nc: 1 # 类别数 names: ['chicken'] # 类别名,顺序要和标注里的class_id对应

如果原数据集没有按train/val分开目录,你需要先把JPEGImages按比例切分成两个目录,同时把对应的txt标注一起挪进去。这一步可以用脚本完成,核心逻辑是读图片文件列表,打乱后按比例切分,再构建新目录结构:

# 直接用python脚本切分 python -c " import os, random, shutil from pathlib import Path src_img = Path('JPEGImages') src_lbl = Path('labels') out_root = Path('chicken_dataset') random.seed(42) imgs = list(src_img.glob('*.jpg')) random.shuffle(imgs) val_cnt = int(len(imgs) * 0.2) for split, subset in [('train', imgs[val_cnt:]), ('val', imgs[:val_cnt])]: for img_path in subset: out_img = out_root / 'images' / split / img_path.name out_lbl = out_root / 'labels' / split / (img_path.stem + '.txt') out_img.parent.mkdir(parents=True, exist_ok=True) out_lbl.parent.mkdir(parents=True, exist_ok=True) shutil.copy(img_path, out_img) src_lbl_path = src_lbl / (img_path.stem + '.txt') if src_lbl_path.exists(): shutil.copy(src_lbl_path, out_lbl) "

这个脚本把525张图按8:2切成train和val,随机种子固定为42保证每次划分一致。切分时用的是shutil.copy而不是move,保留原始目录不动,这样算错了还有后悔药。

4.3 最小训练命令:别一上来就调一堆参数

第一次跑通流程,最重要的事情是确保全链路通。我会用最小的模型nano,训练100轮,batch size按显存来。命令行如下:

yolo train model=yolov8n.pt data=chicken.yaml epochs=100 imgsz=640 batch=8 device=0

device=0表示用第一张GPU;如果是纯CPU机器就device=cpu但epochs建议降到50,时间会很长。这个命令会先去下载yolov8n.pt预训练权重(如果本地没有),这个下载可能需要几分钟到十几分钟不等,取决于网络环境,建议提前手动下好放到当前目录。

训练开始后,每个epoch结束会输出一个指标表格,重点关注这几个数字:

  • box_loss:边界框回归损失,整体趋势应该下降
  • cls_loss:分类损失,一开始偏高,随后下降
  • mAP50:IoU阈值0.5下的平均精度,训练结束前如果能到0.9以上就很理想

要注意的是525张图对nano模型来说可能2到3个小时就能训完(取决于GPU型号,如果你用的是rtx 3060级别的话,一个epoch大概十几秒)。如果100轮结束mAP50还在0.5以下,不用急着加数据,先回头看标注质量——框是不是有大量漏标、错标。

4.4 推理验证:测试一下模型在未见过图片上的表现

训练结束会在runs/detect/train目录下生成best.pt和last.pt。best.pt是按验证集指标选出来的最优权重,last.pt是最后一个epoch的权重。推理验证用best.pt:

yolo predict model=runs/detect/train/weights/best.pt source=test_images/ imgsz=640 conf=0.25

source可以是图片目录也可以是单张图,输出结果会保存到runs/detect/predict目录。这一步的意义是看模型在训练时没见过的图上表现如何,不是看val指标——因为val的指标已经有日志了,推理是看实际效果。重点观察漏检,特别是远距离的小鸡、遮挡的鸡、色调和背景接近的鸡。

5. 千万别踩的五个坑:数据集+VOC转YOLO训练的血泪经验

5.1 坑一:YOLO格式里类别ID从1开始,然后模型训练直接报错

现象:训练开始后第一秒就报AssertionError: class 1 is not in class list或类似的越界错。

原因:有些数据集的txt里类别ID写的是1(因为标注工具默认第一个类从1编号),但YOLO要求类别ID从0开始。这份数据集标题说了是1类别,正常应该都是0,但保不齐有漏网的。

解决:写两行快速检测脚本,扫描所有txt里的第一个数字,看看是否只出现过0。

from pathlib import Path labels_dir = Path('labels') cls_set = set() for txt in labels_dir.glob('*.txt'): for line in txt.read_text().strip().splitlines(): cls_set.add(int(line.split()[0])) print(f"出现过的类别ID: {cls_set}")

如果是{0},没问题;如果是{1},把每个txt的第一个数字减1就完事。这个脚本同样适用于多类别数据集的ID校验。

5.2 坑二:图片是PNG但标注名是JPG导入后找不到文件

现象:训练跑到一半报Image not found: XXX.jpg,停在那里。

原因:解压出来的图片扩展名可能是.jpg,但标注XML的<filename>节点里写的是.png,或者相反。文件名对不上导致图片读取失败。这类问题在校验脚本阶段其实就能发现,但如果你跳过了3.2的脚本直接训练,就会在训练中才爆出来。

解决:别一个个改名,写个循环读XML里的实际文件名,把标注引用改成磁盘里真实存在的文件名。一般来说重命名图片比改XML快,因为525张图后缀可能混着.jpg、.JPG、.png。

5.3 坑三:7z解压出来的路径里带中文或者空格,yaml路径解析全乱

现象:FileNotFoundError: [Errno 2] No such file or directory: 'D:/chicken 数据集/train',看着像路径不对。

原因:Windows上很多解压软件默认把压缩包内嵌套的文件夹结构解出来,如果根目录名字带中文或空格,YAML里的路径解析时容易出问题,尤其空格在命令行里会被切分成多个参数。

解决:解压后的数据集目录统一改成纯英文小写无空格路径,比如D:/chicken525。这看起来是小事,但十个刚跑YOLO的人至少有两个人栽在这上面。

5.4 坑四:小数据集上绕不开的类别不平衡和过拟合

现象:训练了50轮之后发现train loss还在往下掉,但val loss开始回升,mAP50在验证集上波动不涨。

原因:525张图是典型的小数据集,模型很容易把训练集里的背景、光线特征背下来,而不是学到“鸡”的通用语义。这是数据规模决定的,不是参数问题。

解决:常见的做法是在YOLO的标准训练参数上开数据增强,比如在data.yaml里手动配增强策略。但Ultralytics默认就有增强,对小白来说可以先从两个参数入手:把epochs调低到70到80,把batch稍微调大,减少过拟合机会;或者直接用小的模型nano/s而不是m,参数少更不容易过拟合。想认真做的话,用预训练权重做finetune是必须的,yolov8n.pt就是干这个的。

5.5 坑五:YOLO格式里框坐标出现负数或大于1的数值

现象:训练时日志里不断刷WARNING: Some labels are out of bounds,虽然不报错,但最终指标很差。

原因:如果原数据集的YOLO格式是手工整理的,可能有个别框越界,或者从VOC转YOLO时没有做归一化保护。这类问题肉眼看不到,因为它不影响运行,只是YOLO训练时会把这些越界的像素裁掉,导致框变小、标注偏移。

解决:跑一个全局坐标范围校验脚本,把每个txt里的5个数值都检查一遍,任何超出0到1范围的行都打印出来,然后决定是删掉这一行还是把坐标clamp回来。我的建议是直接删掉异常行,因为clamp回来得到的也是错误标注。

6. 从“能跑通”到“能交付”:小训练集的验证技巧与最优实践

既然只有525张图,训练的目标就应该从“刷榜”调整为“交付一个可靠基线”。我的习惯是这样:第一步固定随机种子重新划分一次数据集,把你的训练集、验证集、测试集比例调成7:2:1,也就是367训练、105验证、53测试。测试集从头到尾不参与训练和验证,这是检测项目交付的底线逻辑。很多人在小数据集上偷懒只用train和val,最后在真正的新环境里跌得很难看。

第二步是给训练加一个经验性的early stopping判断。Ultralytics的YOLO默认会在patience参数达到时自动停止,但525张图的情况下我建议把epochs固定为100、patience设为20,不要依赖默认的50,因为小数据集过拟合来得早,等50轮没提升才停,最优权重通常早就在20轮前取到了。训练完成后,用best.pt在测试集上做一次推理,统计一个表格:正确检测数、漏检数、误检数。这个表格就是你对项目方或导师交付的结果,而不是一个干巴巴的mAP数字。

最后一条建议是用这525张图做一个“半自动标注”的种子,而不是终点。我的习惯是先训一个nano模型,在真实场景拍回来的新图片上跑推理,把置信度高于0.6的检测结果转成标注初稿,人工修正后再扩充进训练集。这才是小数据集的正确使用方式——它不是最终资产,而是一台播种机。我也踩过不少坑,最深的教训就是拿到数据集后永远不要直接开训,先花一小时做格式校验和可视化抽查,这能帮你省下后面几天的返工时间。希望帮到你。

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

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

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

立即咨询