简介:面向计算机视觉目标检测与鸡只识别场景的PASCAL VOC、YOLO双格式数据集,包含525张真实场景jpg图片及一一对应的标注文件,覆盖不同光照、角度与背景下的鸡只个体。资源已使用labelImg完成矩形框标注,类别仅Chicken一种,总标注框数607个,既可直接用于训练YOLO系列、SSD、Faster R-CNN等目标检测模型,也可作为数据格式转换、标注规范学习的练习样本。压缩包内共1577个文件,包括jpg图片、VOC格式xml标注与YOLO格式txt标注,其中jpg图片525个、xml标注525个、txt文件527个(含类别名称等辅助信息),整体体积162.28MB,格式清晰、即下即用。在养殖监测、行为分析、自动计数等场景中,可作为预训练微调或算法验证的基础数据。目前已有183人浏览学习,适合目标检测初学者、农业视觉研究者以及需要快速获取带标注数据集进行模型验证的开发者使用。
1. 一个鸡舍项目让我重新理解了VOC与YOLO双格式数据集
做养鸡场巡检模型时,我拿到一份“鸡数据集VOC格式+yolo格式525张1类别.7z”,解压后是525张jpg、525份Pascal VOC格式xml和525份YOLO格式txt,单一Chicken类别,总框数607。这套数据用labelImg画矩形框,最大价值在于双格式对齐——同一标注同时给出绝对坐标和归一化坐标,做格式切换时不用自己写转换器。对准备跑YOLOv8训练但还没搭好数据管线的团队来说,这份数据能直接进训练流程,省掉标注格式翻车的时间。下文从格式解析、数据校验到训练参数逐层拆完。
2. 坐标体系差异:VOC XML与YOLO TXT的换算逻辑
2.1 labelImg同时输出两种格式时的文件命名规则
labelImg在“PascalVOC”和“YOLO”两种保存模式下分别生成xml和txt,文件名与图片完全同名,例如firc_Chicken_486.jpg对应firc_Chicken_486.xml和firc_Chicken_486.txt。图片、xml、txt三件套放在同一级目录或按images/labels分层都行,关键在于文件名主干的强一致性,训练框架就是靠这个前缀把图像与标签关联起来的。
这套数据里525张图与525份xml、525份txt一一对应,一个文件缺失或改名都会在训练时报“no labels found”。处理这类资源包时我习惯先跑一段文件名比对脚本,一次性找出缺jpg、缺xml或缺txt的样本,而不是等训练中断了再回来查。
2.2 XML与TXT字段的结构对照
VOC格式的核心是<object>块里的<bndbox>,保存的是矩形框左上角和右下角的绝对像素坐标:
<annotation> <size> <width>640</width> <height>480</height> <depth>3</depth> </size> <object> <name>Chicken</name> <bndbox> <xmin>120</xmin> <ymin>80</ymin> <xmax>300</xmax> <ymax>240</ymax> </bndbox> </object> </annotation>而YOLO格式的txt每一行是class_id x_center y_center width height,所有数值都归一化到0~1之间。坐标换算关系如下:
x_center = (xmin + xmax) / 2 / width y_center = (ymin + ymax) / 2 / height w = (xmax - xmin) / width h = (ymax - ymin) / height同样是xmin=120, ymin=80, xmax=300, ymax=240,在640×480图上算出来是0.328125 0.333333 0.28125 0.333333。关键区别在于VOC是像素绝对值、依赖图像尺寸,YOLO是相对值、不依赖尺寸;因此YOLO格式对缩放和旋转后的训练集更稳定,也正因为这个特性,数据增强管线里如果做随机裁剪或resize,必须同步按同比例变换框坐标,否则框会飘。
| 维度 | VOC XML | YOLO TXT |
|---|---|---|
| 坐标类型 | 绝对像素(xmin, ymin, xmax, ymax) | 归一化浮点(xc, yc, w, h) |
| 依赖图像尺寸 | 是 | 否 |
| 类别表示 | 字符串标签名 | 整数类别id |
| 典型应用 | 检测/分割通用标注 | YOLO系列训练 |
| 人工可读性 | 好 | 差 |
这份数据集里只有一个Chicken类别,所以txt中class_id固定为0。如果后续添加第二个类别,新类别id是1,不能重排已有id,否则旧标注全部错位。
2.3 用Python脚本验证两种格式坐标是否自洽
拿到双格式数据,第一件事不是直接训练,而是随机抽几张,用脚本把XML的绝对坐标和TXT的归一化坐标互相换算,核对偏差是否在浮点误差范围内:
import xml.etree.ElementTree as ET import numpy as np def xml_to_norm(xml_path): tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) boxes = [] for obj in root.findall("object"): bb = obj.find("bndbox") xmin = float(bb.find("xmin").text) ymin = float(bb.find("ymin").text) xmax = float(bb.find("xmax").text) ymax = float(bb.find("ymax").text) boxes.append([(xmin + xmax) / 2 / img_w, (ymin + ymax) / 2 / img_h, (xmax - xmin) / img_w, (ymax - ymin) / img_h]) return np.array(boxes) def txt_to_norm(txt_path): boxes = [] with open(txt_path, "r") as f: for line in f: parts = line.strip().split() # 跳过class_id,取后四个归一化坐标 boxes.append([float(x) for x in parts[1:]]) return np.array(boxes) # 抽样验证前10个文件 import glob xml_files = sorted(glob.glob("labels/*.xml"))[:10] for xml_file in xml_files: txt_file = xml_file.replace(".xml", ".txt") a = xml_to_norm(xml_file) b = txt_to_norm(txt_file) diff = np.abs(a - b).max() print(f"{xml_file}: max_diff={diff:.6f}")这段脚本的核心价值在于把labelImg导出的两种格式当作双重校验源:如果某个标注在XML里是有效框、但对应TXT里数值越界(比如框中心或长宽出现负数、超过1),说明导出时图像尺寸读取异常或人为改动过标注,这类脏数据进训练集会在损失函数里制造异常梯度。实际操作中,我会让diff阈值卡在1e-4,超过就标记复查,600多个框跑下来耗时不过几十秒。
3. 训练前的数据清洗:完整性校验与标注可视化
3.1 三件套完整性检查
网上很多开源数据集只给图片和其中一种标注格式,这份同时给xml和txt,省了转换时间,但多文件结构更容易出现文件名错位。最稳妥的校验方式是遍历全部文件主干名,检查jpg/xml/txt是否同时存在:
from pathlib import Path base = Path("firc_chicken_dataset") images = {p.stem: p for p in base.glob("*.jpg")} xmls = {p.stem: p for p in base.glob("*.xml")} txts = {p.stem: p for p in base.glob("*.txt")} all_stems = set(images) | set(xmls) | set(txts) for stem in sorted(all_stems): parts = [] if stem not in images: parts.append("jpg") if stem not in xmls: parts.append("xml") if stem not in txts: parts.append("txt") if parts: print(f"{stem}: missing {'+'.join(parts)}")如果最后输出为空,说明三件套完整,可以进入下一步。这里要强调一个容易忽略的坑:有些数据集用.jpeg或.png存储图片,而xml里的filename字段写的却是.jpg,在windows下不敏感的文件系统不发散,但Linux上训练时会因为读不到图片静默跳过。这个数据集统一为jpg格式,不必处理,但校验脚本里最好加上后缀比对。
3.2 标注框越界与文件损坏检查
labelImg正常导出的框不会越界,但如果有人手动改过xml、或者图片在标注后被压缩过,坐标就可能超出图像宽高。这类问题在数据量大时很难肉眼发现,需要一个自动检查脚本:
import xml.etree.ElementTree as ET from pathlib import Path def check_xml_bounds(xml_path): tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) for obj in root.findall("object"): bb = obj.find("bndbox") xmin = float(bb.find("xmin").text) ymin = float(bb.find("ymin").text) xmax = float(bb.find("xmax").text) ymax = float(bb.find("ymax").text) if xmin < 0 or ymin < 0 or xmax > img_w or ymax > img_h: print(f"{xml_path}: box out of bounds " f"({xmin:.1f},{ymin:.1f})-({xmax:.1f},{ymax:.1f}) " f"img={img_w}x{img_h}") if xmax <= xmin or ymax <= ymin: print(f"{xml_path}: degenerate box")越界和退化框(宽或高为0)会导致YOLO训练时计算损失出现NaN,或mAP评估时被直接剔除。实际操作中数据量不大的情况下,越界框也可以直接钳制到图像边界内,但前提是确认标注意图没有被破坏——如果框大部分在画面内,直接clip问题不大;如果框只有1%在画面内,可能是标注错误,建议删掉这帧而不是硬修。
3.3 用OpenCV可视化抽查标注质量
机器检查解决规则问题,但规则之外的语义错误(框偏了一半、框住了两只鸡、背景被框进了一半)只能靠人眼。批量把标注画到图片上,输出成网格拼图:
import cv2 import xml.etree.ElementTree as ET from pathlib import Path def draw_xml_box(img_path, xml_path, out_path): img = cv2.imread(str(img_path)) h, w = img.shape[:2] # 实际图像尺寸,不再信任xml里的size字段 tree = ET.parse(xml_path) root = tree.getroot() for obj in root.findall("object"): bb = obj.find("bndbox") xmin = int(float(bb.find("xmin").text)) ymin = int(float(bb.find("ymin").text)) xmax = int(float(bb.find("xmax").text)) ymax = int(float(bb.find("ymax").text)) # 越界钳制后再绘制 xmin, ymin = max(0, xmin), max(0, ymin) xmax, ymax = min(w, xmax), min(h, ymax) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 0, 255), 2) cv2.putText(img, "Chicken", (xmin, max(0, ymin - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite(str(out_path), img) # 抽样取30张,拼成5x6网格便于快速浏览 import math sample_stems = sorted(Path("firc_chicken_dataset").glob("*.jpg"))[:30] thumbnails = [] for i, img_path in enumerate(sample_stems): xml_path = img_path.with_suffix(".xml") tmp_path = Path(f"/tmp/draw_{i}.jpg") draw_xml_box(img_path, xml_path, tmp_path) thumb = cv2.imread(str(tmp_path)) thumbnails.append(cv2.resize(thumb, (320, 240))) grid = [] for row in range(5): grid.append(cv2.hconcat(thumbnails[row*6:(row+1)*6])) cv2.imwrite("grid_check.jpg", cv2.vconcat(grid))这里有个细节值得专门指出:脚本中img.shape[:2]取的是真实图像尺寸,而不是XML里<size>字段的声明值。因为图片压缩、截断、重编码后实际宽高可能和标注时不一致,以实际图像为准画框才能暴露问题。如果批量画完发现大量框系统性偏移,说明图像被resize过但标注没同步更新,这时整个数据集都需要重新生成标签。
4. YOLOv8训练单类别鸡检测数据集的完整流程
4.1 数据集目录与data.yaml配置
YOLOv8训练时要求数据集按特定目录结构组织。常见做法是建立images和labels两个兄弟目录,每个目录再分train和val子集:
chicken_dataset/ ├── images/ │ ├── train/ │ │ ├── firc_Chicken_001.jpg │ │ └── ... │ └── val/ │ ├── firc_Chicken_002.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── firc_Chicken_001.txt │ │ └── ... │ └── val/ │ ├── firc_Chicken_002.txt │ └── ... ├── data.yaml └── chicken_525.7z # 原压缩包保留备份划分时用随机种子固定随机性,保证可复现。525张单类别数据集不算大,建议按8:2划分,即420张训练、105张验证。不建议单独划分测试集——样本量不充裕,测试集会进一步压缩训练数据;更好的做法是用验证集做模型筛选,后续补数据后再单独留测试集。
data.yaml内容如下:
path: ./chicken_dataset train: images/train val: images/val nc: 1 names: 0: Chickenpath是数据集根目录的绝对或相对路径,train和val相对于该路径。nc是类别总数,这里只有1个类,所以为1。如果写成0或漏掉,YOLOv8会在训练时报shape mismatch错误。
4.2 训练命令与关键参数对照
目录和data.yaml准备好之后,直接跑:
yolo detect train \ model=yolov8s.pt \ data=chicken_dataset/data.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ project=runs/chicken \ name=exp1训练完成后,模型权重保存在runs/chicken/exp1/weights/best.pt和last.pt。best.pt是验证集mAP最高的权重,last.pt是最后一个epoch的权重;如果训练后期过拟合,last.pt反而可能掉点。
| 参数 | 推荐值 | 说明 |
|---|---|---|
| model | yolov8s.pt | 小模型,525张图足矣;数据量翻倍再换m |
| epochs | 100 | 单类别简单任务,60轮后基本收敛 |
| imgsz | 640 | 默认值;图片原尺寸如非正方形,会做letterbox拉伸 |
| batch | 16 | 显存不够降到8,同时考虑加梯度累积 |
| lr0 | 0.01 | YOLOv8默认值,SGD下0.01常见 |
| optimizer | auto | 自动选择SGD或AdamW |
| patience | 50 | 早停,防止无效训练浪费时间 |
| val | True | 每轮结束后跑验证集,计算mAP |
需要特别注意imgsz和原图尺寸的关系。这份数据集的图片是firc_Chicken_*.jpg,从XML的<size>字段可以读出来原始分辨率。如果原始分辨率与640差距过大,建议加上rect=True参数,让训练时每批图片保持宽高比,减少letterbox引起的背景失真。
4.3 训练流程中容易踩的坑:类别id与缓存报错
单类别数据集一个常见问题是误把txt里的0当成无效样本。YOLO格式中class_id从0开始计数,Chicken是第一个也是唯一一个类别,所以txt里每行第一列都是0,这没问题。但如果某个txt文件是空的(0字节),YOLO会在训练时报“WARNING: ignoring empty label file”,直接跳过该图片,导致实际参与训练的图片少于525张。可以用find . -name "*.txt" -size 0一次性查出来。
另一个坑是YOLOv8会在第一次加载数据时生成labels.cache缓存文件。如果训练过程中手动修改了txt或图片,缓存不会自动刷新,需要用cache=False参数启动训练,或删掉cache文件重新生成。实战中我遇到过改了标签但验证集结果不变的情况,最后发现是缓存没更新。
4.4 验证集评估命令
训练结束后,用验证集跑一次评估,输出每个类别的precision、recall和mAP50:
yolo detect val \ model=runs/chicken/exp1/weights/best.pt \ data=chicken_dataset/data.yaml \ split=val输出结果中重点关注三列:precision(精确率)、recall(召回率)和mAP50-95。单类别简单任务、数据质量没大问题的情况下,mAP50达到0.9以上说明模型学到了鸡的基本视觉特征;如果mAP50在0.7以下,优先怀疑标注错误或数据量不足,而不是模型网络结构的问题。
5. 单类别数据集的迭代策略:607框的边界在哪里
525张图607个框,平均每张图约1.16个目标。这类数据分布有两个显著特征,影响后续策略。
第一,大部分图片是单目标,多目标样本极少。检测模型对“多个目标相互遮挡”的场景会明显退化。如果实际部署的鸡舍画面里经常有3只以上鸡挤在一起,建议专门补充这类密集样本,而不是简单复制已有图片做数据增强。几何变换(旋转、翻转、缩放)虽然能增加图像数量,但不会产生新的遮挡关系,对这类短板帮助有限。
检查数据分布可以统计每张图的框数:
for f in labels/train/*.txt; do wc -l < "$f" done | sort -n | uniq -c如果2和3的计数很少而1占绝大多数,就能确认分布偏向单目标。另一个指标是小目标占比:计算所有框相对图像面积的比例,如果大量框面积小于图像面积的5%,说明小目标检测压力大,训练时应调小imgsz到416~512,降低下采样倍率,避免小目标特征被卷积层抹掉。
第二,单类别二分类(有鸡/无鸡)问题比多类别简单,但丢失了负样本约束。模型可能把背景中的圆形物体(如饲料盆、暗色石头)误判为鸡。缓解手段是保留并加入无目标的负样本图片,txt文件为空即可。YOLOv8原生支持空标签,负样本参与训练会让背景分支的损失下降更充分。
最后从607框往里继续扩数据时,不要只加图片而忽略标注质量一致性。大多数开源数据集的分歧不在标注存在性,而在边界框的贴合程度——同样一只鸡,标注者习惯收紧到羽毛边缘还是留出背景余量,会直接影响回归分支的收敛目标。接入这批运功之前,先用自己的标注风格核对200个框,偏差大的样本统一修正后再进训练管线。
本文还有配套的精品资源,点击获取