简介:面向计算机视觉目标检测任务,这份鸡只图像数据集整理为Pascal VOC与YOLO两种主流标注格式,适合深度学习初学者、算法研发人员以及农业智能化领域开发者,直接用于模型训练、验证与迁移学习,免去标注格式转换与二次整理的麻烦。资源共1577个文件,主体包括525张jpg原图、525个xml标注文件和525个txt标注文件,另含两个辅助txt文件,压缩包整体约162.28MB,目录按图片与标注类型区分,结构清晰,方便接入常见检测框架。所有图片均借助labelImg标注,目标类别为Chicken,共标注607个矩形框,标注信息完整规范,可被VOC系列和YOLO系列项目直接解析。当前已有183人学习下载,对需要高质量单类别检测数据的科研与工程实践,这份资源提供了即取即用的数据基础,能明显缩短数据准备周期。
1. 拿到鸡数据集VOC格式+yolo格式压缩包:先确认两件事再动手
一个“鸡数据集VOC格式+yolo格式525张1类别.7z”的压缩包,在目标检测项目里意味着什么?它给你准备好了同一批 525 张图片的两套标注:一套是 VOC 的 xml,一套是 yolo 的 txt。前者方便在 labelImg 这类工具里继续手工修框,后者可以直接丢给 yolov8 这类训练框架跑。对刚接触自定义数据集的人来说,这类双格式包最大的价值就是省去了“格式转换”这道最容易出错的工序。
但我拿到任何标注压缩包,第一件事从来不是解压后立刻开训,而是先确认两件事:图片和标注文件数量对不对得上,以及这些图的实际像素尺寸是不是和 xml 里 size 字段一致。这两个点前者决定训练时不报缺标签,后者决定目标框坐标换算后不会整批偏移。下文就按这个顺序把解压、校验、转换、训练和踩坑点完整走一遍。适合正在做鸡只检测、畜牧场景识别,或者想拿一个小型单类别数据集练手 yolov8 的读者。
2. VOC 和 yolo 的格式映射:同一批图片,两种坐标方言
“双格式数据集”听起来复杂,本质上只是同一批图片的两种标注表述。VOC 用 xml 保存人类可读的像素框,yolo 用 txt 保存训练器喜欢的归一化坐标。两者之间是简单的算术关系,但坐标系、类别表示和信息完整度都有差异,不搞清楚直接转,后面训练翻车的概率非常高。
2.1 VOC 的 xml 在描述什么:从 folder 到 bndbox
VOC 格式的标注文件是 xml,文件名和图片一一对应,扩展名从 .jpg 变成 .xml。一个典型的标注文件长这样:
<annotation> <folder>chicken_data</folder> <filename>IMG_0001.jpg</filename> <size> <width>1280</width> <height>720</height> <depth>3</depth> </size> <object> <name>chicken</name> <bndbox> <xmin>120</xmin> <ymin>80</ymin> <xmax>260</xmax> <ymax>200</ymax> </bndbox> </object> </annotation>需要的字段就四个:filename 定位到具体哪张图,size 记录原图宽高,object 下的 name 是类别,bndbox 里四个值给出目标的左上角和右下角。注意 bndbox 是绝对像素坐标,xmin 和 ymin 从图片左上角算起,单位是“像素”,不是百分比。这个格式最直接的好处是人工可读,labelImg 打开 xml 就能继续编辑,看到数字就能判断坐标是不是画错了。
和电力红外数据集 firc-dataset 这类项目采用的打包方式一样,很多双格式数据集会把 VOC 作为“源格式”保存。原因很简单:xml 信息完整,丢失风险低;yolo 的 txt 只是它的一个投影,随时可以重新生成。
2.2 yolo 的 txt 只有一行四列:归一化坐标的特性和换算公式
yolo 格式的标注文件除了类别编号,只有一行四个数字,例如:
0 0.1484 0.1944 0.1094 0.1667四个数字依次是:类别编号、目标中心点 x、目标中心点 y、目标宽度 w、目标高度 h。注意这里 x、y、w、h 全都是相对图片宽高的比例,取值在 0 到 1 之间。上面这行数字对应 2.1 里那个框,换算过程就是:
x_center = (120 + 260) / 2 / 1280 = 0.1484 y_center = (80 + 200) / 2 / 720 = 0.1944 w = (260 - 120) / 1280 = 0.1094 h = (200 - 80) / 720 = 0.1667中心点坐标必须除以图片宽和高,很多第一次转格式的人把宽高搞反,或者忘记除以 2,得到的就是整批偏移的框。yolo 格式的好处是脱离开具体分辨率,训练时无论输入图缩放到 640 还是 416,标注都能直接映射,这也是 yolo 系训练框架默认吃 txt 的原因。
下表把两者对照一下,方便排查问题时快速定位:
| 维度 | VOC 的 xml | yolo 的 txt |
|---|---|---|
| 坐标系 | 左上角 + 右下角,绝对像素 | 中心点 + 宽高,相对比例 |
| 类别表示 | name 字符串 | 类别编号 int |
| 信息完整度 | 含 folder、size、segmented 等 | 仅标注本体,需 data.yaml 补类别名 |
| 人工可读性 | 高,labelImg 可直接编辑 | 低,只有数字 |
2.3 两类格式互相转换的边界:哪些字段会丢,哪些必须手工补
从 VOC 转 yolo 会丢掉的东西不少:xml 里如果有多个 object,yolo 格式没有分隔符,只能一行一个目标;如果 XML 里有 truncated、difficult 这类难例标记,txt 格式也表达不了。反过来从 yolo 转 VOC 更麻烦,因为 yolo 只给你一个类别编号,必须额外维护一个类别映射表,才能把 0 还原成 chicken 这个名字。所以,“双格式数据集”的真正价值在于省去你维护映射表的功夫,而不是说两种格式内容完全等价。
我一般会保留一个 category_map.py 文件:
CATEGORY_MAP = { "chicken": 0, } ID_TO_NAME = {v: k for k, v in CATEGORY_MAP.items()}单类别数据集的危险性恰恰在这里:只有 0 一个编号,用class_names.index("chicken")也能跑通,但这会在以后混入第二类时直接错位。宁可先写好显式映射,也不要偷懒用 index。
3. 解压 7z 与数据校验:把压缩包变成能训练的干净数据
压缩包是 .7z 而不是常见的 .zip,第一步就是正确解压。解压之后不要着急训练,先做一轮配对校验和可视化抽查。很多标注数据集在流传过程中会出问题:文件缺失、重复命名、标注与图片错位,这些坑在校验阶段暴露出来,成本最低。
3.1 在 Windows 和 Linux 下解压 .7z:两条命令和两个注意点
拿到 .7z 后缀的文件,先检查环境里有没有 7z 命令。Windows 下安装了 WinRAR 也能解,但命令行还是装 7-Zip 最稳,安装后用7z x而不是7z e,差别在于 x 会保留压缩包内的目录结构,e 会把所有文件拍平到一个目录里,对于带 Annotations 和 images 两级目录的数据集来说,拍平等于把标注全弄混。
Linux 下先装 p7zip:
sudo apt install p7zip-full然后解压:
7z x "鸡数据集VOC格式+yolo格式525张1类别.7z" -o/home/user/datasets/ cd /home/user/datasets ls -lp7zip-full 安装后提供的是 7za 和 7z 两个命令,7z 是完整版,实际调用的压缩算法一样,日常用哪个都行。x 后面加引号是因为文件名里有中文,不加引号 shell 会把它看成多个参数,这一点在 Linux 下尤其容易被忽略。解开后如果看到 Annotations、images、labels 这种标准目录,说明包内结构正常。
注意点有两个。第一个是中文文件名在 Linux 下解压后容易乱码,这是因为打包方在 Windows 上按 GBK/CP936 写入文件名,而 Linux 默认按 UTF-8 解,处理办法见第 5 章。第二个是路径深度,解压前先把 -o 的目录设得浅一点,比如 /home/user/datasets,别解到 /home/user/Downloads/archive/old/ 这种长路径里,因为后面训练脚本拼接路径时,路径过长很容易触发各种读取异常。
3.2 配对校验脚本:找缺失图片、缺失标注、重复文件名
解压只是第一步。525 张图 525 个标注是理想情况,实际压缩包里常见多一张少一张、某张图只有 xml 没有 txt、同一个文件名出现两次,这些都要在校验环节暴露出来。写一个简单的 Python 脚本做配对检查:
from pathlib import Path from collections import Counter IMAGE_SUFFIX = {".jpg", ".jpeg", ".png", ".bmp"} image_dir = Path("images") # 图片所在目录 xml_dir = Path("Annotations") # VOC 标注所在目录 txt_dir = Path("labels") # yolo 标注所在目录 imgs = Counter(p.stem for p in image_dir.iterdir() if p.suffix.lower() in IMAGE_SUFFIX) xmls = Counter(p.stem for p in xml_dir.iterdir() if p.suffix.lower() == ".xml") txts = Counter(p.stem for p in txt_dir.iterdir() if p.suffix.lower() == ".txt") for stem in imgs: if stem not in xmls: print(f"缺 xml: {stem}") if stem not in txts: print(f"缺 txt: {stem}") for stem in xmls: if stem not in imgs: print(f"xml 无对应图: {stem}") duplicates = {k for k, v in imgs.items() if v > 1} print("重复图片:", duplicates)逻辑很简单:把三套文件的文件名(去掉扩展名)分别放进 Counter,再互相查。凡是交叉缺失的,打印出来。Counter 顺手保留了对重复文件的检测能力,同名文件一旦存在两个,就会出现在 duplicates 里。校验结果应该是三列文件数完全一致,任何缺口都要在训练前补上,否则训练器会静默跳过部分样本,你都不知道模型少看了哪几张图。
3.3 可视化抽查:把 VOC 框画到图上,确认标注没歪
配对校验通过,不代表标注内容正确。我习惯随机抽 20 张图把框画上去,肉眼扫一遍,这比任何指标都管用。下面这段脚本直接用 OpenCV 读 xml,把 bndbox 画出来:
import cv2 from pathlib import Path import random import xml.etree.ElementTree as ET def draw_voc_boxes(image_path, xml_path, out_path): img = cv2.imread(str(image_path)) tree = ET.parse(xml_path) root = tree.getroot() for obj in root.iter("object"): name = obj.find("name").text b = obj.find("bndbox") x1 = int(b.find("xmin").text) y1 = int(b.find("ymin").text) x2 = int(b.find("xmax").text) y2 = int(b.find("ymax").text) color = (0, 255, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, name, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2) cv2.imwrite(str(out_path), img) # 随机抽 20 张 image_dir = Path("images") xml_dir = Path("Annotations") out_dir = Path("check_output") out_dir.mkdir(exist_ok=True) all_images = list(image_dir.glob("*.jpg")) random.seed(0) for img_path in random.sample(all_images, min(20, len(all_images))): xml_path = xml_dir / (img_path.stem + ".xml") if xml_path.exists(): draw_voc_boxes(img_path, xml_path, out_dir / img_path.name)上面的函数假定 xml 和 jpg 在同名路径下,抽查时把参数换成实际目录即可。看到的问题一般是三类:框明显小于目标、框超出图片边界、目标本身写反了边(xmin 大于 xmax)。前两类需要回标注工具修 xml,第三类多半是标注时手抖,重新标注那一张就行。也可以把 yolo 的 txt 同样画出来,对比两个格式画框结果是否一致,这是验证“双格式是否真的对齐”最直接的办法。
4. 转成 yolov8 训练目录并跑通:data.yaml、命令与必调参数
很多人拿到这种 VOC+yolo 双格式包后直接开训,结果各种报错。原因往往是目录结构不是 yolov8 期望的样子,或者 data.yaml 里的类别数写错。这一章按 yolov8 训练自己的数据集的完整流程走一遍,从目录结构到命令参数一次说清。
4.1 目录整理成 yolov8 默认结构:images 与 labels 的 train/val 分工
yolov8 的 detect 任务里,默认读取结构是 images 和 labels 两个并列目录,各自下面再有 train、val 子目录。同名的图片和 txt 只要在对应子目录里,训练器自己会配对,不需要额外写映射文件。推荐结构:
chicken-data/ ├── data.yaml ├── images/ │ ├── train/ │ │ └── IMG_0001.jpg │ └── val/ │ └── IMG_0500.jpg └── labels/ ├── train/ │ └── IMG_0001.txt └── val/ └── IMG_0500.txt如果压缩包里的 yolo 标签本来就带 train/val 划分,直接把整个目录挪过来用。如果只给了全部 525 张没有划分,那就自己按 8:2 或 9:1 随机划。单类别小数据集建议比例 8:2,val 留 100 张左右,太少的话 mAP 波动会很大,看不出真实水平。
划分时注意两点:一是按文件名随机打乱,不是按目录顺序取前 80%;二是图片移动后同名 txt 必须跟着走,否则配对断掉。可以用下面这段脚本,基于第 3 章校验过的文件名列表做分配:
import random from pathlib import Path import shutil all_files = list(Path("images").glob("*.jpg")) random.seed(42) random.shuffle(all_files) split = int(len(all_files) * 0.8) train_files = all_files[:split] val_files = all_files[split:] for split_name, files in [("train", train_files), ("val", val_files)]: img_out = Path("chicken-data/images") / split_name lbl_out = Path("chicken-data/labels") / split_name img_out.mkdir(parents=True, exist_ok=True) lbl_out.mkdir(parents=True, exist_ok=True) for f in files: shutil.copy(f, img_out / f.name) txt = Path("labels") / (f.stem + ".txt") if txt.exists(): shutil.copy(txt, lbl_out / txt.name) else: print(f"警告: {f.name} 没有对应 txt")4.2 写 data.yaml:绝对路径、单类别、换行符三个细节
yolov8 用 data.yaml 统一描述数据集,内容很少,但三个细节容易出错:
path: /home/user/chicken-data # 改成自己的绝对路径 train: images/train val: images/val nc: 1 names: 0: chicken第一个细节是 path 必须是绝对路径,relative 写法有用例能跑,但 train 和 val 字段会基于当前工作目录解析,训练脚本换目录就全断。第二个细节是 nc 必须写 1。这个包只有 chicken 一个类别,如果误以为是“背景 + 鸡”写成 nc: 2,训练时类别编号 1 永远没有 gt,损失曲线会异常,mAP 列也读不出来。第三个细节是 names 的索引编号和 yolo 格式里第一个数字严格对应,这里 0 对应 chicken,就要求所有 txt 行首都是 0,校验脚本里顺手检查这一点,能提前暴露整批错位。
4.3 训练命令与关键参数:epochs、imgsz、batch 怎么定
目录就位、yaml 写好后,训练命令是:
yolo detect train \ data=/home/user/chicken-data/data.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ patience=30 \ project=/home/user/chicken-runs \ name=exp1参数说明:model 用 yolov8s.pt,不要贪大选 l 或 x,也不要为了省时间选 n。n 在小目标上精度掉得比想象中多,s 在 525 张单类别数据上是精度和速度的平衡点。epochs 设 100,配合 patience 30 做早停;如果 val loss 连续 30 个 epoch 没下降,训练提前终止,实际可能只跑到 60 到 80 轮。batch 看显存,v100 这类 16G 以上的卡可以开到 32 或者 64,显存小就 8 起步,出现 CUDA out of memory 时优先降 batch 而不是降 imgsz。
imgsz 保持 640,除非你的鸡在图中占比特别大或特别小。占比大可以降到 512 提速,占比小的话 640 都不够,建议先做切片或先放大目标区域,而不是盲目抬到 1280,因为小数据集上高分辨率很容易过拟合。
4.4 训练中看什么:loss 曲线、mAP50 和混淆矩阵的输出位置
训练跑起来后,别只盯着终端刷屏。yolov8 的损失函数由三块组成:box_loss 负责定位框,cls_loss 负责分类,dfl_loss 负责边框分布;单类别数据集的 cls_loss 通常最先收敛到很小,box_loss 才是决定 mAP 上限的部分。每轮结束后,results.png 会画三块 loss 和 mAP50、mAP50-95 两条曲线,重点关注 box_loss 有没有在最后 20 轮继续下降。
训练结束后,weights 目录下会有 best.pt 和 last.pt,best 是 val mAP 最高的权重,last 是最后一轮权重,后者在早停场景下通常略差。混淆矩阵生成在 confusion_matrix.png 里,这也是很多人会忽略的输出文件,第 6 章单独讲它为什么不是简单的百分比。
5. 这个标注集最容易踩的四个坑:现象、原因、解决
5.1 图片能打开,训练时却报图片尺寸错误:EXIF 旋转把宽高带偏了
现象:数据校验时一切正常,train 开始后报错,提示某张 jpg 的尺寸与 xml 记录不一致,甚至 cv2.imread 读出 1280x720,但训练增强里读出来的宽高完全相反。
原因:手机或部分相机拍摄的 jpg 会把旋转信息写进 EXIF 的 Orientation 字段。PIL 默认不应用 EXIF,OpenCV 也不应用,而很多标注工具在显示时按旋转后的方向展示,人工标注时看到的框和实际像素矩阵就差了 90 度。
解决:批量预处理一遍,把图按 EXIF 转正后另存为新文件,同时把 xml 里 size 的宽高改成转正后的值。用 Python 的 PIL 可以处理:
from PIL import Image, ImageOps def fix_exif(src, dst): img = Image.open(src) img = ImageOps.exif_transpose(img) img.save(dst, "JPEG", quality=95) # 对 images 目录下所有 jpg 执行,并同步修正对应 xml5.2 画框跑到图片外面去了:目标在边缘时的 bndbox 越界
现象:可视化抽查时发现有的 xml 里 xmax 大于图片宽度,或者 ymin 是负数。VOC 标注工具通常不拦截这种情况。
原因:标注目标贴近图片边缘时,鼠标很容易拖出画布,标注软件允许生成的 bndbox 越过边界。
解决:在格式转换脚本里做一次剪裁,把四个坐标统一 clamp 到图片范围内,并丢弃面积过小的框。参考 2.1 的换算公式,在除以宽度和高度之前,先用 min(max(...), 0) 处理一遍边缘值。数量多的话建议直接改 xml 而不是在转换时事后修补,因为 yolo 格式里越界数据同样会影响增强器的正常采样。
5.3 标签第一列出现不是 0 的编号:类别映射表错位
现象:解压后某些 txt 文件第一列是 1 或 2,而不是 0;训练时 loss 正常,但 val 预测结果全部预测成某一个类别。
原因:如果打包方在转换时用了class_names.index(name),当 xml 混入未声明类别时,新类别会拿到错误编号。尤其单类别数据集里混了一张其他目标的图,index 出来的编号就会整体错位。
解决:自己维护一份固定的 name 到 id 映射,转换时对不在映射表里的类别直接报错,而不是跳过或自增。同时在校验脚本里加一句检查:所有 txt 的首列必须属于 {0,..., nc-1},否则打印出具体文件名。
5.4 Linux 下解压出中文乱码:Windows 打包与 Linux 解压的编码差
现象:在 Windows 上用 7-Zip 正常解压的文件,到 Linux 上运行7z x后,目录名变成一串乱码,Python 脚本按正常路径找不到文件。
原因:Windows 的 7z 默认按 UTF-8 记录文件名时,若遇到含中文的命名,有时会按系统本地编码写入,而 Linux 解压时按 UTF-8 解读,两边文件名编码不一致。
解决:解压时显式指定代码页:
7z x "鸡数据集VOC格式+yolo格式525张1类别.7z" -mcp=936 -o/home/user/datasets如果已经解压乱了,先确认实际看到的文件名编码,再用 convmv 批量转码,或者干脆在 Windows 上先解压再传到 Linux。像我这种经常在双系统间搬数据的人,现在的习惯是拿到压缩包先解压看一次目录,再决定要不要继续往下做。
6. 验证模型的混淆矩阵:行和列加起来不唯一是为什么
训练结束后,yolov8 会在 runs/detect 目录下生成 confusion_matrix.png,很多第一次用的人发现它和想象中不一样:每一行归一化后,对角线是查准率还是查全率?行加起来是 1,列加起来不是,总和更不唯一。
先说结论:yolov8 的混淆矩阵每一行是真实类别,每个单元格表示“这个真实类别被预测成各类别/背景”的比例,按行归一化,所以行和等于 1;如果加上了 background 那一列,行和仍然等于 1。列方向是预测视角,列的归一化单位不同,求和自然不等于 1。“总合不唯一”是这个矩阵的正常性质,不是 bug。想得到更直观的单值指标,直接看 val 输出的 mAP50 和 mAP50-95。
验证一个训练好的模型,我一般再跑一次 val:
from ultralytics import YOLO model = YOLO("/home/user/chicken-runs/exp1/weights/best.pt") metrics = model.val(data="/home/user/chicken-data/data.yaml") print("mAP50:", metrics.box.map50) print("mAP50-95:", metrics.box.map)这两个数才是判断模型能不能用的核心。单类别数据集上 mAP50 低于 0.7,大概率是数据量不够或者标注质量有问题,先回头看第 5 章的坑;高于 0.9 基本可以进入部署测试。拿 best.pt 对几张 val 图做一次可视化预测,确认类别名和框位置没有系统性偏差,再考虑导出 onnx。
我的习惯是训练完先保存 confusion_matrix.png 和 results.png 到一个单独目录,再决定要不要重新调参——毕竟重训一次的成本不高,盲目改参数的重训才浪费时间。希望帮到你。
本文还有配套的精品资源,点击获取