简介:猪只目标检测数据集面向计算机视觉学习者与目标检测算法开发者,适用于目标检测、目标识别等模型的训练、验证与效果评估。数据包含634张左右jpg格式猪只图片,单张体积约1-500KB,画面覆盖多种姿态与场景,数据多样。每张图片均配套VOC格式xml标注与YOLO格式txt标注,类别统一为pig,基于labelImg标注完成,解压后无需格式转换即可接入主流检测框架。
资源压缩包总大小约229.59MB,共1903个文件,含634张jpg图片、634份xml标注、634份txt标注及少量说明文件,目录按图片、xml与txt分三类存放,取用对照方便。已有111人学习下载,适合需要现成猪只数据集入门目标检测、进行模型微调或评估精度的开发者,可显著节省数据采集与标注时间成本。
1. 猪目标检测数据集:634张VOC+YOLO双格式标注能解决什么问题
养猪场智能化改造里,猪只检测算法是所有上层应用的底座。存栏盘点要数猪、健康监测要定位猪体、分娩看护要跟踪母猪位置,这些都依赖一个先决条件:检测模型能从圈舍背景里把猪找出来。模型从哪里来?从有标注的数据来。这份约634张的猪数据集同时提供VOC和YOLO两种格式的目标标注,XML适合人工查看修改,txt适合直接喂给yolov8训练。
634张这个规模,不够工业级几万张的量,但足够跑通yolov8训练自己的数据集的完整流程,也足够做方案验证、课程设计和PoC。对新手,双格式标注省掉格式迁移的理解成本;对熟手,小数据集的敏感性能逼你把划分、标注校验这些基本功做扎实。后面的每一章,都围绕怎么把这份数据用出最大价值展开。
2. 看懂两种目标标注格式:VOC的XML与YOLO的txt在定义上的四个不同
处理634张猪数据集的第一步,不是急着训模型,而是先把VOC标注和YOLO标注在结构上的差异搞清楚。两者描述的是同一个目标框,但存储方式、坐标基准、类别编码和读取逻辑完全不同。这章把四个差异逐一看透,后续转换脚本才不会写错。
2.1 VOC的XML:object节点、bndbox与difficult标记
VOC格式以XML文件存储标注信息。解开一个标注文件,结构大致是这样的:
<annotation> <filename>pig_001.jpg</filename> <size> <width>1280</width> <height>720</height> <depth>3</depth> </size> <object> <name>pig</name> <difficult>0</difficult> <bndbox> <xmin>256</xmin> <ymin>144</ymin> <xmax>1024</xmax> <ymax>576</ymax> </bndbox> </object> </annotation>filename、size、object、bndbox这四个层级最关键。size下的width和height是后续坐标归一化的分母,一旦和实际图片尺寸不一致,转出来的YOLO标注就会系统性出错。object节点可以出现多次,每头猪对应一个object,bndbox里的xmin、ymin、xmax、ymax是绝对像素坐标,数值范围直接受图片分辨率影响。
difficult标记在VOC竞赛里表示目标是否难以识别,取值0或1。训练时通常会把difficult等于1的框过滤掉,因为难样本会影响初版模型的收敛。这份数据里的difficult多为0,但转换脚本里保留读取逻辑是更稳的做法,万一遇到手工标注时误标成1的框,还能在转换阶段决定要不要丢弃。
2.2 YOLO的txt:归一化坐标与class_id从0开始的约定
YOLO格式的标注是纯文本,不需要首行说明,每行直接对应一个目标框,规范格式是五个数值,用空格分隔:
class_id x_center y_center width heightclass_id从0开始。数据集中如果只有pig一个类别,那它就该是0;如果还分small_pig和big_pig,那么classes列表里排第一的类别为0,第二个为1。这个顺序跟XML里写的类别名没有必然关系,完全取决于你在转换脚本里给classes列表排的顺序。yolov8训练时data.yaml的names顺序必须和这里一致,class id才会对应上。
四个坐标全部是归一化浮点数。归一化的分母是图片的宽度和高度,不是某个固定的目标尺寸。中心点坐标的计算方式是先算像素中心再除以图片尺寸。这样做的好处是标注与图片分辨率解耦,换用不同输入尺寸训练时不用重新标注。
2.3 从像素坐标到归一化坐标:为什么除法顺序很重要
一次完整的坐标换算涉及三个步骤:算中心点、算宽高、除以图片尺寸。顺序反掉的结果是数值完全不同。正确的递推式是先加后除。
拿一个具体例子。1280x720的图片里,某头猪的框是xmin=320,ymin=180,xmax=960,ymax=540。中心点x=(320+960)/2=640,y=(180+540)/2=360;框宽=960-320=640,框高=540-180=360。除以图片宽度1280,x_center归一化为0.5;除以图片高度720,y_center归一化为0.5。宽度、高度也都是0.5。
这里翻车的点有三个。第一,有人误把x_center直接写成(xmax-xmin)/2再除以width,那算出来的是半宽不是中心点。第二,width和height在归一化时分母弄反,把框的横纵比例搞畸形。第三,转换脚本读取img_w和img_h时从size节点取值,但xml里size记录的尺寸与实际图片不符时,分母错误会扩散到这张图片的所有标注。
转换前后的数值边界也值得留心。一个完全落在图片内的框,其x_center、y_center、width、height的取值都在0到1之间。如果标注时手滑把框拖出图片边缘,换算出的值会超过1,训练时yolo损失函数会把这个框当异常样本处理,造成loss波动。第5章会专门讲如何把这些脏框提前过滤掉。
3. 动手转换:把VOC标注改成YOLO格式的脚本与目录对齐
了解了格式差异之后,进入实际动手环节。转换脚本是这份数据集从VOC走向yolov8的关键桥梁。这章给出一个可以直接运行的python脚本,同时说明目录结构怎么布局、转换完如何确认结果正确。
3.1 转换脚本:解析XML并写出归一化txt
常见做法是写一个独立的python脚本放在数据集根目录下运行。脚本核心逻辑分五步:读XML、取图片尺寸、遍历object、换算坐标、写txt。以下是可用版本:
import xml.etree.ElementTree as ET import os CLASSES = ['pig'] def voc_to_yolo(xml_path, out_dir): tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) if img_w <= 0 or img_h <= 0: raise ValueError(f'bad image size in {xml_path}') lines = [] for obj in root.findall('object'): name = obj.find('name').text if name not in CLASSES: continue cls_id = CLASSES.index(name) bndbox = obj.find('bndbox') xmin = float(bndbox.find('xmin').text) ymin = float(bndbox.find('ymin').text) xmax = float(bndbox.find('xmax').text) ymax = float(bndbox.find('ymax').text) # clamp避免框超出图片边界 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)) # 过滤宽高为0或反向的无效框 if xmax <= xmin or ymax <= ymin: print(f'[skip invalid box] {xml_path} {name}') continue x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h box_w = (xmax - xmin) / img_w box_h = (ymax - ymin) / img_h x_center = round(x_center, 6) y_center = round(y_center, 6) box_w = round(box_w, 6) box_h = round(box_h, 6) lines.append(f'{cls_id} {x_center} {y_center} {box_w} {box_h}') if not lines: print(f'[no valid objects] {xml_path}') return os.makedirs(out_dir, exist_ok=True) base = os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(out_dir, base + '.txt'), 'w') as f: f.write('\n'.join(lines) + '\n')代码里CLASSES列表是最重要的开关,顺序就是类别ID。当前只有pig一类,所以所有框的class_id都是0。后续如果要把猪分成大猪小猪,直接改CLASSES列表,并保证data.yaml里的names顺序完全一致。
坐标精度保留6位小数,对yolov8训练来说足够。yolov8默认把输入缩放到640x640,6位小数对应的误差远小于一个像素,再多保留几位也没有实际意义。clamp这一步不能省略,它负责兜住标注拖拽出界的脏框,避免脏数据一路流到训练阶段。
3.2 目录结构:images和labels必须保持同名前缀
yolov8训练时读取数据集的默认方式不是靠配置文件里的索引,而是靠目录结构。常见做法是这样布局:
datasets/ images/ train/ train_001.jpg val/ val_001.jpg labels/ train/ train_001.txt val/ val_001.txtimages和labels是两个根目录,下面的train和val子目录名称必须完全一致。图片文件和标注文件只靠文件名关联——train_001.jpg对应train_001.txt,中间没有任何辅助索引。这就是该数据存储格式的约定,简单但容错率低。
从平铺的Annotations目录转成上面的两层结构,需要做目录移动和划分。保守做法是让转换脚本输出到一个raw_labels目录,完成校验后再按后续的划分结果分别移动到labels/train和labels/val下。移动之前一定先跑一遍校验脚本,确认每个txt都存在且内容合法,避免把脏数据挪进正式目录。
3.3 转换结果抽样校验:画框检查最直观
脚本跑完之后不要急着删VOC标注。先抽三五个txt,对照原图画框检查。画框用OpenCV几行代码就能完成:
import cv2 def draw_yolo_boxes(image_path, label_path, class_names): img = cv2.imread(image_path) h, w = img.shape[:2] with open(label_path) as f: for line in f: parts = line.split() cls_id = int(parts[0]) xc, yc, bw, bh = map(float, parts[1:]) x1 = int((xc - bw / 2) * w) y1 = int((yc - bh / 2) * h) x2 = int((xc + bw / 2) * w) y2 = int((yc + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[cls_id], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) cv2.imshow('check', img) cv2.waitKey(0)抽样逻辑建议覆盖三类图片:单猪图、多猪密集图、暗光环境下拍摄的图。三类都画框正常,说明转换脚本在这个数据集的代表性场景上没有系统性偏差。如果只在密集图中发现框错位,多半是某个XML里多个object的顺序或者坐标字段读反了。
用OpenCV画框只是为了校验,不要求跟标注工具长得一样,重点看框与猪身体的贴合度。框紧贴身体、不出图片边界、类别名正确,这条VOC到YOLO的链路就可以放心交给训练脚本了。
4. 634张猪数据的体检与划分:类别统计、分布分析和训练验证分裂
转换完成后会得到一份干净的、yolov8能直接消费的数据集。但634张这个量级还存在两个隐患:类别样本不平衡、划分不当导致验证结果失真。这章先做统计体检,再讲划分策略,最后给出增强参数的取舍。
4.1 用脚本统计框数:了解每张图的平均猪头数
先别急着训练。用脚本扫一遍所有YOLO格式的txt,统计每张图的框数和总框数。这一步虽然简单,却能回答一个重要问题:634张图片里到底有多少个猪目标,这直接决定模型容量和预期精度。
import os import glob def stat_labels(label_dir): total_boxes = 0 empty_files = 0 file_count = 0 per_class = {} for path in glob.glob(os.path.join(label_dir, '*.txt')): file_count += 1 with open(path) as f: lines = [ln for ln in f if ln.strip()] if not lines: empty_files += 1 total_boxes += len(lines) for ln in lines: cls_id = int(ln.split()[0]) per_class[cls_id] = per_class.get(cls_id, 0) + 1 print(f'files: {file_count}') print(f'total boxes: {total_boxes}') print(f'empty files: {empty_files}') print(f'per class: {per_class}')如果总框数在3000到4000之间,说明平均每张图有5到6头猪,属于密度适中的圈舍场景。如果总框数不足1000,模型在密集场景下漏检会比较严重,评估时就该把漏检单独统计,而不是只盯mAP。如果文件数接近634但empty_files很多,说明部分XML里object节点为空,或在转换时被过滤掉了,需要回到VOC原标注确认这些空图要不要保留。
4.2 训练/验证/测试划分:按场景分组避免数据泄漏
634张图片直接用random split会带来数据泄漏问题。猪舍监控视频抽帧数据里,相邻帧的背景几乎相同,猪的姿态变化也小。如果同一视频的帧同时进入训练集和验证集,模型相当于开卷考试,验证mAP会虚高到不真实。
常见做法是按拍摄批次或视频来源分组。假设这批图片来自若干个不同的视频文件或拍摄时间片断,先按来源分组再决定归属。如果来源信息不完整,退一步用文件名前缀分组。原则是:同一场景的图片宁可全进训练集,也不要被拆散到两边。
划分比例上,634张我会按75%/15%/10%来切,约475张训练、95张验证、64张测试。验证集不需要太大,但每一类都要有代表性样本。划分脚本里加一个按class_id的统计检查,确保验证集里每个类至少出现5个框,否则这一类的验证loss会成为一个无法解释的黑匣子,你不知道是模型没学会还是压根没样本。
4.3 数据增强:mosaic、HSV抖动和letterbox的取舍
634张原图训练yolov8必须开增强,但增强参数要跟着场景调整。猪舍场景中猪只常有局部遮挡,yolov8默认开启的mosaic增强把四张图拼成一张,能模拟更多遮挡排列,对小数据集很友好。但mosaic也会让小目标变得更小,如果数据集中存在较多远距离小猪,建议把mosaic比例从默认的1.0降到0.5左右。
颜色增强方面建议保守。hsv_h、hsv_s、hsv_v在yolov8的默认值分别是0.015、0.7、0.4。猪舍图片本身就是暖色调灯光,hsv_v加得太大容易让颜色失真,模型学到的颜色特征偏离真实场景。可以先只把hsv_v降到0.2,观察两三个epoch的验证loss趋势再决定要不要继续调。
letterbox处理在训练和推理时都要留意。yolov8默认把输入缩放到640x640,并保持原始宽高比,多余部分填充灰边。如果猪圈图片多为1280x720宽幅,缩放后猪在整体画面中的占比较小,框的相对尺寸也被压缩。此时可以尝试imgsz=960,把小猪目标放大后再训。显存占用会上升,但漏检率往往能明显下降,这笔交换在少样本场景下通常是划算的。
5. 常见问题排查:标注校验与yolov8训练踩坑记录
这一章专门记录我在使用634张左右规模的猪数据集时实际遇见的坑。它们不会在训练启动时报错,但会以各种隐蔽的方式降低模型性能。每条都按现象、原因、解决来写,你可以直接对照排查。
5.1 现象:xml和jpg数量对不上
转换前做统计发现Annotations里有621个xml,JPEGImages里有634张jpg。差的13个是数据采集过程中的历史遗留:某几张jpg因文件损坏被删除,但xml没有同步清理;或者另一些图片拍糊了被标注人员跳过,没生成对应的VOC文件。
解决:用脚本以文件名为基准做集合差异运算,找出多出来的XML和缺标注的JPG,分别处理。
ls JPEGImages | sed 's/.jpg$//' | sort > imgs.txt ls Annotations | sed 's/.xml$//' | sort > anns.txt echo "missing xml:"; comm -23 imgs.txt anns.txt echo "missing jpg:"; comm -13 imgs.txt anns.txt两个comm命令分别得到两类差异。缺xml的jpg从数据集里移出;缺jpg的xml也删掉,因为训练时没有图片可配。更稳妥的做法是在转换脚本里增加一个数量校验阶段,发现不一致就直接报错停住,不要带病继续。
5.2 现象:坐标越界与宽高为0的bbox
yolov8训练过程中loss出现异常尖峰,或者某个epoch的验证mAP突然掉到接近0。用脚本扫描labels目录,发现某些txt里出现了大于1或小于0的归一化坐标,甚至width或height为0。这些脏框来自标注工具拖拽出界的误操作,或者图片在预处理时被裁剪但标注没同步更新。
解决:在转换脚本里加clamp和有效性判断,坐标值被限制在图片尺寸范围内,xmax不大于xmin、ymax不大于ymin的框直接跳过。如果训练前才发现脏框,写一个过滤脚本扫描并删除非法行:
def clean_label(path): valid = [] with open(path) as f: for line in f: parts = line.split() if len(parts) != 5: continue vals = [float(v) for v in parts[1:]] if all(0 <= v <= 1 for v in vals): valid.append(line.strip()) with open(path, 'w') as f: f.write('\n'.join(valid))5.3 现象:类别ID错位导致预测全错
训练结束后验证,发现框的位置都正确,但每头猪都被预测成同一个错误类别。原因十有八九是转换脚本里的CLASSES顺序和data.yaml的names顺序不一致。比如classes列表里pig排第0,而data.yaml里写的是names: ['background', 'pig'],于是id 0被模型当成了背景类。
解决:统一classes的排列,并在训练前打印yaml内容逐一核对。yolov8训练日志每个epoch会输出每个类别的AP,如果某个类的AP一直为0且其他类数值异常,优先怀疑ID错位而不是模型结构。
5.4 现象:文件名大小写不一致导致图片不加载
在Windows上用LabelImg标注时文件名是Pig_001.JPG,放到Linux服务器上训练,脚本按jpg后缀去索引,Pig_001.JPG被漏掉。或者txt文件名是pig_001.txt,图片是Pig_001.jpg,大小写不一致导致yolov8读不到对应的标注文件,训练样本数悄悄减少。
解决:转换前把所有文件名统一为小写,bash一行完成:
for f in images/* labels/*; do mv "$f" "$(echo "$f" | tr 'A-Z' 'a-z')"; done统一后再跑一次文件名校验脚本,确保同名前缀的数量与图片数一致。这个坑在Windows标注、Linux训练的工作流里出现频率极高,值得养成习惯。
5.5 现象:灰度图像混入RGB训练集
部分监控设备在夜间输出灰度视频,抽帧后混进了数据集。yolov8的输入层是3通道,训练时遇到单通道图片虽然不一定报错,但模型特征提取会丢失颜色信息,导致白天数据的泛化明显变差。
解决:在数据准备阶段扫描所有图片的通道数,把灰度图转换为三通道RGB后再加入训练集。也可以直接丢弃灰度图,前提是丢弃的数量不多且不影响类别分布。转换脚本可以这样写:
from PIL import Image import os def convert_gray_to_rgb(img_dir): for f in os.listdir(img_dir): path = os.path.join(img_dir, f) img = Image.open(path) if img.mode != 'RGB': img.convert('RGB').save(path)5.6 现象:yolo损失函数波动剧烈但mAP停滞
小数据集训练常见的另一个现象是:训练loss在下降,val loss却上下波动,mAP50也没明显增长。这往往是增强参数过强、batch size过小、初始学习率偏高三者叠加的结果,不一定是指标体系出了问题。
解决:先把hsv增强参数调低,再看batch size是否小于8,最后检查初始学习率是否超过0.01。逐一排查之后通常能稳住训练曲线。小数据集的训练问题,大多出在标注和增强上,而不是模型结构本身玄学。
6. 用634张跑通yolov8训练全流程:命令、参数与最终核验
数据干净、划分完成后,接下来就是用yolov8把模型跑出来。634张这个规模撑不起大模型,所以优先用yolov8n,跑通后再决定要不要升级到yolov8s。
6.1 初版训练命令与参数设定
yolov8训练参数要围绕小数据、少epoch、重验证来设。以下是我在这个数据集上的开局命令:
yolo detect train \ data=datasets/pig.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ patience=20 \ save_period=10 \ device=0 \ project=runs/pig_exppatience=20表示20个epoch内val指标没有进步就提前停止,避免小数据集过拟合后继续空转。save_period=10定期保存中间权重,防止某个epoch崩溃丢失进度。batch在显存允许范围内尽量取大,batch=16时BN层的统计更稳定,loss曲线更平滑。
6.2 可视化核验与阈值调节
训练结束后用best.pt做预测,保存带框图片。我习惯把conf从0.25降到0.1再看一遍,区分哪些漏检是阈值过滤掉的、哪些是模型真正找不到的。如果降阈值后能找回大量正确框,说明模型本身有检出能力,只是默认阈值偏严,部署时把conf调到0.15即可。
如果降阈值后找回的框大多为重复框或误检,说明模型存在过拟合,需要从增强幅度和正则化方面往回调。634张规模训出的模型在真实猪舍里跑,漏检比误检更值得关注——漏掉的猪影响存栏数统计,而误检通常能在后处理里滤掉。
6.3 进阶做法:预训练权重finetune与部署验证
634张数据从头训不是最优选择。yolov8n.pt自带的COCO预训练权重已经包含通用物体特征,在猪数据上finetune比随机初始化收敛快得多,同样epoch数下mAP50能高出5到8个百分点。训练完需要部署时,用yolo export导出onnx格式,再结合推理引擎测单张耗时,就能评估整条链路的实时性。
这套从格式转换、数据划分、训练调参到可视化核验的流程,是我做过几轮猪数据项目后固定下来的工作流。先把标注格式吃透、划分做干净,再用小模型跑通全流程,最后才考虑模型规模和部署优化。634张不算大,但这条链路踩过的坑,在几万张数据上还会以相似的方式再来一遍。希望帮到你。
本文还有配套的精品资源,点击获取