猪场监控数据集解析:VOC与YOLO格式转换及YOLOv8训练实践
发布时间:2026/9/15 0:57:52 作者:尧图编辑部 阅读量:1,286

简介源于养猪场监控实拍的猪只识别检测数据集面向计算机视觉开发者、算法研究者及智慧养殖项目团队可高效解决猪只检测、数量统计、行为分析与状态监测等实际需求。资源包共2000个文件包含1001个txt标签YOLO格式与999个xml标签VOC格式整体约911MB压缩包解压后可直接接入主流检测框架训练。数据集中猪只目标超过3万全部采用labelImg纯手工标注无漏标图片背景来自真实监控环境光照、角度和猪只密度差异明显体态丰富适合模型泛化训练与二次关键点标注。场景覆盖不同栏舍与活动状态可用于YOLO系列、SSD、YOLOX、CenterNet、Faster R-CNN等常见算法的训练与效果对比也适合课程设计、毕业设计或实际项目预研。目前已有368人学习/下载既能省去自行采集与标注数据的时间又能为猪只识别相关研究提供可靠基础值得直接使用。1. 猪场监控画面里的猪只检测不是那种公开数据集能随便替代的监控摄像头下的猪圈场景光线不均、栏杆遮挡、猪只互相堆叠常见的 COCO 或公开动物数据集根本覆盖不住这种密集目标分布。这个猪场监控实拍数据集单帧画面里可能同时出现十几头到几十头猪目标之间高度重叠且轮廓相似人工标注 3 万多个目标本身就是个体力活而且标注人员得能分清哪头猪的边界在哪里。数据集里同时给了 VOC 和 YOLO 两种格式的标签意味着拿到手不用做格式转换就能直接喂给检测框架省掉的是从零标注和格式迁移的硬成本。适合做畜牧场景目标检测落地、算法对比实验的人也适合想用现成高质量人工标注来训练猪只行为分析模型的团队。2. 目录结构与双标签格式解析VOC 和 YOLO 标签分别记录了什么这份数据集的核心价值在于同一份标注同时兼容两种格式而这两种格式的坐标系和存储方式完全不同。用 labelImg 标注时默认产出的是 VOC 格式 XMLYOLO 标签则是从 XML 转换出来的。理解这个转换关系后续做数据增广、格式扩展或者二次标注时就不容易改坏坐标。2.1 数据集的目录划分与文件名清单规则常见做法是数据集根目录下分三个子目录JPEGImages 存放原始监控帧图片Annotations 存放 VOC XML 标注文件labels 存放 YOLO txt 标注文件。项目正文里出现的 pig_41_2.txt、pig_34_2.txt 这类文件属于数据划分清单每行是一个图片名不带扩展名分别对应不同的训练集或验证集拆分。比如你可以把前 8 个文件作为训练集图片列表后 2 个作为验证集也可以按 9:1 重新随机划分。这种按文件名清单管理数据的方式避免了把几百 GB 的图片直接复制来复制去。使用时只需要读取这些 txt 文件生成对应的图片路径列表再交给训练脚本即可。下面是一段读取划分清单并校验文件是否存在的 Python 脚本import os def load_split(txt_path, image_dir): with open(txt_path, r, encodingutf-8) as f: names [line.strip() for line in f if line.strip()] missing [] for n in names: img os.path.join(image_dir, n .jpg) if not os.path.exists(img): missing.append(img) if missing: print(f缺失 {len(missing)} 张图片例如: {missing[:5]}) return names这里 load_split 函数把划分文件里每一行当作图片名拼上 jpg 扩展名去 JPEGImages 目录里检查是否存在。如果监控帧图片是 png 格式需要把.jpg换成.png或者先用 os.listdir 拿到真实扩展名再拼接。这一步能在训练前提前暴露图片缺失问题省得训练中途因为文件找不到报错。2.2 VOC 格式 XML 里有用的字段不只是 bndbox打开一份猪只标注的 XML结构基本是标准的 Pascal VOC 格式。每个 object 节点对应一头猪bndbox 子节点存的是绝对像素坐标 xmin、ymin、xmax、ymax。除了框坐标之外filename 字段记录图片名size 里记录原图宽高。真正容易忽略的是 object 节点里可能会有 pose、truncated、difficult 这类附加字段。labelImg 默认会给 truncated 和 difficult 写 0但如果你后续要用这些字段筛选困难样本就得自己在代码里解析。读取 XML 并统计每个目标的宽高可以用下面代码import xml.etree.ElementTree as ET def parse_voc_xml(xml_path): tree ET.parse(xml_path) root tree.getroot() filename root.find(filename).text size root.find(size) width int(size.find(width).text) height int(size.find(height).text) objects [] for obj in root.iter(object): name obj.find(name).text box obj.find(bndbox) xmin int(box.find(xmin).text) ymin int(box.find(ymin).text) xmax int(box.find(xmax).text) ymax int(box.find(ymax).text) objects.append((name, xmin, ymin, xmax, ymax)) return filename, width, height, objects这里 xmin 到 xmax 的范围是 [xmin, xmax]注意坐标是从 0 开始还是从 1 开始。labelImg 默认从左上角坐标 1,1 开始记录转换成 YOLO 格式时如果直接除宽高会出现极小的归一化偏移。常规做法是坐标减 0.5 后再归一化但更稳妥的方式是在转换时统一做 clamp 到 [0,1] 区间。2.3 YOLO 标签的归一化坐标与 VOC 转 YOLO 的公式YOLO 格式每行对应一个目标格式为 class_id x_center y_center width height前四个值是相对图片宽高的比例取值在 0 到 1 之间。class_id 从 0 开始当前数据集只有猪这一类所以理论上 class_id 恒为 0。如果后续要扩展猪的品种分类就得修改类别文件否则训练时类别数不匹配会直接报错。手工从 VOC 转 YOLO核心公式是x_center (xmin xmax) / 2 / widthy_center (ymin ymax) / 2 / heightw (xmax - xmin) / widthh (ymax - ymin) / height下面是一个完整的转换脚本读取 Annotations 里的全部 XML输出到 labels 目录import os import xml.etree.ElementTree as ET voc_dir Annotations yolo_dir labels os.makedirs(yolo_dir, exist_okTrue) class_names [pig] # 单类别 for xml_file in os.listdir(voc_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(voc_dir, xml_file)) root tree.getroot() width int(root.find(size).find(width).text) height int(root.find(size).find(height).text) txt_name xml_file.replace(.xml, .txt) with open(os.path.join(yolo_dir, txt_name), w) as f: for obj in root.iter(object): name obj.find(name).text if name not in class_names: class_id 0 else: class_id class_names.index(name) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) x_center (xmin xmax) / 2 / width y_center (ymin ymax) / 2 / height w (xmax - xmin) / width h (ymax - ymin) / height f.write(f{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n)这段代码存在一个常见陷阱如果图片里有类名不在 class_names 里会被强制映射到 0。对单类别数据集没影响但如果你后续把数据集扩展成多类别应该对未知类别做跳过或报错处理而不是静默归零。另外检查生成的 txt 文件行数等于 XML 里 object 的数量没有损失目标则转换正常。下面用表格总结两种格式在训练框架中的使用差异对比项VOC 格式YOLO 格式存储形式XML 文件每目标一个 object 节点txt 文件每行一个目标坐标基准绝对像素左上角为原点相对图片宽高的归一化值标注类别顺序任意训练时需要显式指定类别映射第 0 列必须是整数类别 ID框架兼容Faster R-CNN、SSD 读取YOLO 系列直接读取预处理成本需要解析 XML 并做缓存读取简单但坐标不能越界3. 训练前必须完成的校验数据划分、标注完整性与目标尺寸统计直接拿数据集跑训练不是不行但很容易在训练到一半卡在坐标越界或类别不匹配这种低级错误上。我的习惯是在训练脚本启动前先用少量代码把数据集从标注格式到训练配置整个校验一遍。这个数据集有 3 万多个目标数量大手动检查不现实写脚本批量检查才是正规做法。3.1 按图片名清单生成 train.txt 和 val.txtYOLOv8 自带的 YAML 配置里 train 和 val 可以指向包含图片路径的 txt 文件也可以指向文件夹。如果你手头是多个 pig_*.txt 划分清单更稳妥的方式是把它合并成一个 train.txt 和 val.txt。下面这个命令可以快速把多个清单合并去重cat pig_41_2.txt pig_34_2.txt pig_28_2.txt pig_138_1.txt pig_208_2.txt train_list.txt cat pig_194_2.txt pig_209_2.txt pig_40_2.txt pig_99_1.txt pig_123_1.txt val_list.txt sort train_list.txt -u -o train_list.txt这里的 cat 命令把前 5 个文件名清单拼接到 train_list.txt后 5 个拼到 val_list.txt。实际划分比例要根据图片总量调整不一定机械地按数量均分。如果你希望训练集和验证集在时间上不重叠最好按监控摄像头编号来切而不是随机切。这个数据集的文件名里没有明显的摄像头编号只能按现有清单划分也可自行随机打乱。生成路径清单后还要确认每个图片名都能在 JPEGImages 里找到。常见做法用 find 命令生成一份全量图片名再和 txt 清单做 diffls JPEGImages | sed s/\.jpg// | sort all_images.txt diff train_list.txt all_images.txt | head运行 diff 后如果没有任何输出说明 train_list.txt 里的名字都在图片目录里。diff 有输出则说明存在缺失或文件名不一致常见原因是扩展名不是 jpg 而是 png或者清单里混入了空行。3.2 检查 YOLO 标签是否越界、漏标和类别 ID 异常YOLO 训练时会忽略坐标越界的目标检测时往往表现为部分猪只漏检。检查越界的脚本非常简单遍历所有标签文件把每个数字拿出来判断是否在 0 到 1 之间。需要注意归一化之后 x_center width / 2 可能大于 1肉眼看着没问题但严格来说目标中心点必须在 0 到 1 之间而宽度可以超出边界一部分。纯手工标注如果用的是矩形框跨出图片就会发生这种情况。我用过一段很轻量的校验脚本import os label_dir labels violations 0 empty_files 0 for txt_file in os.listdir(label_dir): path os.path.join(label_dir, txt_file) if os.path.getsize(path) 0: empty_files 1 print(f空标签: {txt_file}) continue with open(path, r) as f: lines f.readlines() for line in lines: parts line.strip().split() class_id int(parts[0]) coords [float(x) for x in parts[1:]] if class_id ! 0: print(f类别异常: {txt_file}, {class_id}) if any(c 0 or c 1 for c in coords): violations 1 print(f坐标越界: {txt_file}, {line.strip()}) print(f越界目标数: {violations}, 空标签文件数: {empty_files})对于越界目标常见的处理策略是 clamp 到 0 到 1 之间例如宽度为 0.6 的框x_center 为 0.8那么右边界是 1.1可以强行裁成 1.0。但这种裁剪会让框面积变小对标注精度有轻微影响。更好的选择是回看对应图片确认目标是否真的被截断了如果确实是截图边缘的猪只保留也行YOLO 训练时对边缘目标本身就不太友好。空标签文件意味着某张图片没有任何猪只这类图片可以保留作为负样本但训练时要注意取样比例。3.3 统计目标宽高分布为锚框设置提供依据YOLOv8 和 YOLOv5 都支持自动锚框但自动锚框在目标尺寸分布极端时会偏向多数的目标尺寸。猪场监控里的猪只远小近大而且可能存在大量目标集中在中间尺寸这就导致小目标漏检严重。我习惯先统计所有标签的宽高生成分布数据再判断是否需要关闭自动锚框并手工按分布设置。用下面的脚本提取所有标签的宽高并做分桶import os import numpy as np label_dir labels widths [] heights [] for txt_file in os.listdir(label_dir): path os.path.join(label_dir, txt_file) with open(path, r) as f: for line in f: parts line.strip().split() w float(parts[3]) h float(parts[4]) widths.append(w) heights.append(h) widths np.array(widths) heights np.array(heights) print(f目标总数: {len(widths)}) print(f宽度分布: 10%{np.percentile(widths, 10):.3f}, 50%{np.median(widths):.3f}, 90%{np.percentile(widths, 90):.3f}) print(f高度分布: 10%{np.percentile(heights, 10):.3f}, 50%{np.median(heights):.3f}, 90%{np.percentile(heights, 90):.3f})这段代码输出目标宽高在归一化坐标系里的分位数。以我的经验如果 90% 目标的宽度小于 0.1说明监控距离远导致目标整体偏小训练时输入尺寸要调大比如 imgsz 参数从默认的 640 调到 960 或 1280。如果 10% 目标宽度都大于 0.4说明画面里猪只数量少且特写多锚框设计要倾向大尺寸。这个统计结果在后续配置 YOLOv8 的 model 参数时直接有用。4. 用 YOLOv8 在 3 万目标数据集上训练猪只检测模型这个数据集标签格式和主流 YOLOv8 完全兼容数据准备妥当后训练本身并不复杂。关键在于怎么组织目录以及怎么根据上一轮的统计结果调参数。YOLOv8 目前仍是应用最广的检测框架训练脚本、权重导出、部署转换都成熟适合作为这款数据集的首跑选择。4.1 将数据集组织成 YOLOv8 标准布局并编写 data.yamlYOLOv8 官方推荐的数据集结构通常不是用 JPEGImages 和 Annotations而是 images 和 labels 两个平行目录每个目录下再分 train 和 val 子目录。但也可以让 data.yaml 里的 train 和 val 直接指向带路径列表的 txt 文件。这个数据集本身是监控实拍产物图片文件名跨 txt 分布并不连续我更推荐使用路径清单方式避免复制数万张图片。data.yaml 内容如下train: /data/pig_dataset/train_list.txt val: /data/pig_dataset/val_list.txt nc: 1 names: [pig]写完后用下面的命令快速验证路径配置正确python -c from ultralytics.utils import DATASETS_DIR; print(DATASETS_DIR) yolo detect train modelyolov8s.pt data/data/pig_dataset/data.yaml epochs1第一次跑可以用 epochs1 做 smoke test如果数据路径或标注格式有问题会在几十秒内暴露。注意 YOLOv8 读取路径清单时会自动将相对路径解析到清单文件所在目录所以要保证 train_list.txt 里要么写绝对路径要么写相对于清单文件的相对路径。4.2 训练命令与关键参数选择正式训练我一般用以下命令并根据显存大小调整 batchyolo detect train \ modelyolov8s.pt \ data/data/pig_dataset/data.yaml \ epochs100 \ imgsz960 \ batch16 \ device0,1 \ optimizerSGD \ lr00.01 \ lrf0.1 \ mosaic1.0 \ cacheTrue \ workers8这里每个参数都有实际依据。imgsz 是输入分辨率上面统计的猪只目标如果偏小960 比默认 640 有明显 mAP 提升但训练和推理时间增加约 1.5 倍。batch 根据显卡显存调整16GB 显存跑 s 模型 960 输入用 batch16 是比较稳的。optimizer 我选了 SGD因为数据量 3 万目标不算大SGD 相对 AdamW 不容易过拟合配合 mosaic 数据增强效果更好。cacheTrue 把图片预加载进内存能大幅减少磁盘 IO猪场监控图片数量多但单张不大内存 32GB 以上可以开。下表是不同增广参数在密集小目标场景下的建议参数效果密集猪只建议mosaic拼接 4 图增加小目标训练样本1.0 保持最后 10 epoch 可关掉hsv_h/hsv_s/hsv_v色彩抖动猪场监控画面整体偏灰保留默认即可fliplr水平翻转监控左右对称保持 0.5 有用scale随机缩放0.5 即可避免猪只过小失真4.3 训练日志怎么看重点关注 Recall 和 mAP50训练日志里除了 loss要重点看 Box_P、Box_R 和 mAP50。猪只检测这种单类别密集目标场景允许模型产生较多误检但漏检影响更大所以 Recall 比 Precision 更有参考价值。如果训练到第 60 个 epoch 时 Recall 还在持续上升而 Precision 已经不再变化可以继续训练不需要提前停止。YOLOv8 训练结束后会保存 weights/best.pt 和 weights/last.pt用 best.pt 跑验证集得到最准确的指标yolo detect val modelruns/detect/train/weights/best.pt data/data/pig_dataset/data.yaml imgsz960验证输出里 mAP50-95 在密集猪只数据集上一般比常规目标低 10 到 20 个点原因是猪只互相遮挡导致 IoU 阈值较高时匹配失败。如果你的 mAP50-95 长期低于 0.3需要回到数据校验环节检查是否有标签混错而不是急着改模型结构。4.4 从数据集自身特点出发的调优方向猪只目标密集而且大量排列在水平方向上非极大值抑制时对同类目标的抑制阈值要保守。YOLOv8 默认 NMS IoU 阈值是 0.5在猪只互相重叠的场景下两个实例的 IoU 可能超过 0.7这会导致其中一个被抑制掉。建议验证时手动调低 conf_thres 并观察可视化结果yolo detect predict modelruns/detect/train/weights/best.pt source/data/pig_dataset/JPEGImages img_path.jpg conf0.1 iou0.3iou 参数调低到 0.3两个高度重叠的猪只才可能同时保留。如果测试发现大量猪只被漏检优先检查 conf 是否过高。3 万多个目标的标注量足够训练一个比较稳的检测模型不需要额外依赖预训练权重之外的其他迁移资源。5. 二次标注扩展在猪只检测框上快速生成关键点初标数据集摘要里提到可以二次标注关键点检测用于猪的行为分析。现代猪只行为分析不只依赖检测框还依赖关键点来判断站立、躺卧、进食姿态。把现有检测框作为初始位置用半自动方式生成关键点可以显著减少标注成本。如果你熟悉 YOLOv8-pose可以把它的关键点从默认的 17 改成 4 个对应嘴巴、两个耳朵基部和背部尾根。第一步是在 yolo 检测结果上裁剪出单头猪的局部图像具体做法是用 best.pt 推理得到检测框然后把框的区域切出来保存yolo detect predict modelruns/detect/train/weights/best.pt source/data/pig_dataset/JPEGImages/pig_41_2.jpg save_cropTrueYOLOv8 的 save_crop 参数会为每个检测框生成一张裁剪后的图片保存在 runs/detect/predict/crops/pig 目录下。接下来把这些裁剪图交给标注员在每张裁剪图上画 4 个关键点即可最后再映射回原图坐标。映射时需要注意裁剪图上的关键点坐标是相对裁剪图左上角的需要加上检测框的 xmin 和 ymin 才能还原到原图坐标系。更高效的一种方式是利用关键点模型进行 warm start。你在几百张图上手动标注完关键点后先训练一个低精度的 pose 模型再用该模型去预测剩余图片人工修正预测结果形成半自动标注循环。这套流程适合这个数据集的原因在于检测框质量很高3 万多目标都是纯手工标注没有明显漏标所以关键点映射的底座是准的不会因为检测漏检导致关键点也跟着丢。如果你只想做行为分析而不想训练关键点模型也可以使用检测框的几何特征来分类。例如计算检测框的宽高比猪躺卧时宽高比显著大于 1站立时宽高比接近 1采食时头部区域会在框的底部频繁移动。这类纯规则方法不用二次标注适用于快速验证行为分析算法可行性后续再决定是否增加关键点标注。本文还有配套的精品资源点击获取