简介面向目标检测算法训练与模型评估的猪只图像数据集同时提供VOC与YOLO两种主流标注格式适合计算机视觉初学者、算法工程师以及养殖智能化项目开发者使用可直接用于猪只检测、数量统计等场景的数据支撑。压缩包内共1903个文件包含634张jpg原图、634个xml标注文件和635个txt标注文件整体大小约229.59MB图片为jpg格式单张大小在1KB至500KB之间标注类别统一为pig采用labelImg工具人工标注完成。标注过程遵循严格规范尽可能准确框选目标边界减少位置误差尽量覆盖图像中的所有猪只目标避免漏标同时进行一致性检查以保证标注结果的正确性与复用性。数据按图片、xml、txt分类存放解压后即可便捷查看校验。目前已有110人浏览学习适合需要快速获取带标注猪只数据集的实践项目使用。1. 634张猪数据集VOC和yolo双格式为什么是目标检测的“最佳起步块”634张猪的标注图说多不多说少不少恰好卡在一个敏感的位置拿它训练YOLO系列模型完全跑得动新手能练全流程闭环但也就是这规模最容易让刚入手的人踩翻车点——类别不平衡、过拟合、训练验证集划分泄漏、xml和txt标签错位全在这几百张图里等着你。VOC和yolo双格式都给你备好本质上是把“照猫画虎”的成本砍掉了labelimg 打标完 yolo 格式的标多数人能直接进训练不用再连夜整数据转换。这篇文章我按自己接手小样本检测数据集的习惯来写先讲清楚两种格式的字段差别和坐标基准再给VOC转yolo的脚本和边界处理接着落到yolov5/v8训练的最小命令和参数最后把最容易出问题的5个坑和一套验证方法摆出来。适合正在做猪只检测、畜牧智能监测、或者第一次碰自建小数据集的同学照着复现。2. 先把两种格式的底账算清楚VOC是给软件看的yolo是给模型吃的2.1 labelimg 和小样本标注绕不开的几个工具选项目标检测常用标注工具里labelimg 是大家上手最快的一个——Windows和Ubuntu都能跑装好就是图形界面框完直接存xml几乎没学习成本。它默认存的VOC格式也就是Pascal VOC的xml标注一张图配一个同名xml文件。YOLO训练不认xml认txt所以你的数据集标题里“VOC和yolo格式”两套都带本质上就是同一批框用两种方式存了两遍。常见做法其实还有另外几条路labelme 偏向多边形分割猪这种轮廓不是特别规整的目标也能用但标注出来的点集要再转检测框反而多一道工序roboflow 之类的在线工具带格式互转直接把voc换成yolo不过数据要上传服务器做畜牧场景的本地数据往往会踩隐私这道坎。x-anylabeling 支持yolo格式存盘但导出目录结构和官方yolov5要求的有点区别新手容易在目录上栽跟头。综合下来labelimg 存VOC、写脚本转yolo是这套634张数据最稳的路线。2.2 一张图对照VOC和yolo字段映射与坐标基准VOC的xml里存的是绝对坐标xmin、ymin是框左上角的像素坐标xmax、ymax是右下角像素坐标。yolo的txt存的是归一化相对坐标中心点x、中心点y、框宽w、框高h四个数全部除以图片宽高范围在0到1之间。核心换算公式也就一条x_center (xmin xmax) / 2 / img_width y_center (ymin ymax) / 2 / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_height理解透这条算式后面所有转换都不玄学。VOC坐标系的单位是像素yolo坐标系的单位是“占图片宽/高的比例”所以任何一张不同分辨率的猪图只要按这个比例归一化模型读到的框就是尺度无关的。这也是为什么yolo训练时imgsz640能把各种尺寸的图统一缩放标注框不会跟着变形。xml和txt还有个关键差别类别名的存法。VOC里写的是字符串比如“pig”yolo的txt里只写类别编号第一列就是class id。你说labelimg打标完yolo格式的标其实它存的是“VOC的xml内容 yolo的txt内容”类别映射那份文件得自己维护——一般来说新建一个classes.txt每行一个类名行号就是id0对应第一行。猪数据集如果单类别那就是0看起来简单但后面章节我会提到就是这个“看起来简单”最骗人。2.3 目录组织与训练/验证集划分634张该切几刀拿到双格式数据集第一件事不是急着转格式而是先把目录重新收拾一遍。yolov5和yolov8官方训练要求的目录结构都是images和labels分家训练集、验证集各占一个子目录。我一般按9:1切分634张大约是570张训练、64张验证。这个比例对小样本比8:2舒服因为验证集只用于观察趋势不需要太大而每一张训练图在几百张规模下都很值钱。再讲究一点可以先按猪栏做分组——如果634张里有多张连续帧来自同一摄像头直接把连续帧随机打散进训练和验证模型会靠记忆场景而不是记忆猪验证得分虚高。切分脚本一般长这样import os, random, shutil img_files [f for f in os.listdir(VOC/images) if f.endswith(.jpg)] random.seed(42) random.shuffle(img_files) split int(len(img_files) * 0.9) os.makedirs(yolo/images/train, exist_okTrue) os.makedirs(yolo/images/val, exist_okTrue) os.makedirs(yolo/labels/train, exist_okTrue) os.makedirs(yolo/labels/val, exist_okTrue) for name in img_files[:split]: shutil.copy(fVOC/images/{name}, yolo/images/train/) for name in img_files[split:]: shutil.copy(fVOC/images/{name}, yolo/images/val/)这段是只拷贝图片到train和val目录标签文件得保证和图片同名——因为labelimg生成的xml是“同名不同后缀”转成yolo的txt后同样要保证“同名不同后缀”一个jpg对应一个txt。shutil.copy只治标最稳妥的方式是先建一个id列表再用两层循环分别copy图片和对应的txttxt不存在就报错退出防止后面训练时出现“有图无标签”的静默问题。3. 把VOC转成yolo格式转换脚本与四个边界坑3.1 转换前先检查这4个字段少一个都别运行VOC转yolo脚本本身十几行就能写完但真正考验人的是xml上身那些脏数据。我处理猪数据集时踩过的字段坑基本是四类一是xml里没有filename或者filename后缀名和实际图片不一致后期找图对不上二是size节点缺失脚本读不到图片宽高归一化直接除零三是坐标超出图片边界比如xmax比img_width还大这在半遮挡猪只拖框时经常发生四是类别名带空格或中文转成txt之后标签文件和训练配置对不上。所以凡是接手的标注集我都先跑一遍预检脚本把四类问题一次性扫出来。预检不通过不转换这是第一个要立的规矩。import xml.etree.ElementTree as ET import os def check_xml(xml_path, img_dir): tree ET.parse(xml_path) root tree.getroot() name root.find(filename).text size root.find(size) w int(size.find(width).text) h int(size.find(height).text) img_file os.path.join(img_dir, name) if not os.path.exists(img_file): return f图片缺失: {name} for obj in root.iter(object): 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) if xmax w or ymax h or xmin 0 or ymin 0: return f坐标越界: {name} ({xmin},{ymin},{xmax},{ymax}) if xmax xmin or ymax ymin: return f宽高为0或负: {name} return None这段不是转换是转换前的哨兵。坐标越界和宽高为0这两类yolo训练时常常不直接报错而是loss曲线乱跳、目标漏检排查半天才发现是标签数据本身有问题。预检脚本跑完确认634张里没有脏数据再进转换环节。3.2 转换脚本voc2yolo.py 的完整写法主转换脚本我一般拆成两个函数一个解析单个xml一个批量遍历目录。import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_file, class_list): tree ET.parse(xml_file) root tree.getroot() img_w int(root.find(size).find(width).text) img_h int(root.find(size).find(height).text) lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in class_list: continue cls_id class_list.index(cls_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) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) width min(max(width, 0.0), 1.0) height min(max(height, 0.0), 1.0) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) return lines class_list [pig] xml_dir VOC/Annotations out_dir yolo/labels for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue lines voc_to_yolo(os.path.join(xml_dir, xml_file), class_list) txt_name xml_file.replace(.xml, .txt) with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines))坐标算完之后再夹一遍min/max裁剪不是多此一举。前面预检挡住了大部分越界但小数点进位或标签框本来就沾边时归一化后可能出现1.000001这种值yolo的anchor匹配阶段会用网格坐标做索引超出1.0会直接索引越界崩掉训练。裁剪到0到1区间等于给转换上了道保险。3.3 执行与校验两个快速验证手段脚本跑完不等于转对了得让数据自己说话。第一个验证是比对统计量把转换前的xml坐标反算一遍和txt里的数值对一对数量级先统计所有xml的平均框宽高像素值再统计txt里的平均归一化宽高乘上对应图片的宽高两者应当误差在1像素以内。第二个验证更直观写个可视化脚本把txt框画回原图import cv2 img cv2.imread(yolo/images/train/000001.jpg) h, w img.shape[:2] with open(yolo/labels/train/000001.txt) as f: for line in f: parts line.strip().split() cls_id, xc, yc, bw, bh int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) 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.imwrite(check_000001.jpg, img)画完随机挑二十张看一眼框不下沉、不漂移、不残缺才算转换结束。这一步是肉眼质检yolo训练是黑匣子输入数据不干净后面所有性能分析都是盲人摸象。4. 用yolo格式直接开训最小命令与必调参数4.1 准备data.yaml634张小数据集的路径与类别配置转换完格式接下来是把数据集喂给yolov5或yolov8。两者都吃同一种yaml配置文件需要写清楚训练和验证图片的绝对路径或相对路径、类别数nc、类别名names。path: /home/user/pig_dataset train: images/train val: images/val nc: 1 names: [pig]这里最容易翻的点有两个一是train和val写的是“相对images目录的路径”还是“相对数据集的路径”不同版本yolo要求不太一样yolov5用的是相对数据集根目录的路径yolov8也兼容二是names的顺序必须和刚才转换txt时class_list下标完全一致一旦names里的顺序换了比如写成“pig”在前面而类名本来就只有猪且id全是0倒不会错位但如果有两个类顺序错一个就全完。还要检查图片后缀是不是jpg、png混着。roboflow导出的数据集经常png和jpg混用yolo对后缀不敏感但你的txt文件名是从xml的filename字段来的xml里写jpg实际存png时不统一训练会报“label not found”。634张不算大直接全部统一成jpg省心用Python的cv2.imwrite批量转一遍把后缀统一再同步改xml或txt文件名。4.2 训练命令与batch、imgsz、epoch三个参数怎么定yolov5和yolov8二选一即可。我的习惯是给小样本直接用yolov5s或yolov8s因为是猪这种中等尺寸的目标s足够m和l只会增大过拟合风险。训练命令最简版python train.py --data pig.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 150 --patience 30这个命令一条条拆开说。--img 640是训练分辨率猪只目标不算极小640够用强行用1280对634张训练图没有正向收益反而让显存压力和过拟合同步上涨。--batch 16看显存8G显卡跑yolov5s加640输入batch16绰绰有余batch太小会让BatchNorm的统计量抖动激烈小数据集上尤其明显所以哪怕epoch多一点batch也别低于8。--epochs 150配patience 30意思是如果验证集mAP连续30个epoch没提升自动停这招对小数据集非常关键——过拟合之后验证分数掉头往下早停就是后悔药。yolov8的命令结构略有差异但参数语义几乎一样yolo detect train datapig.yaml modelyolov8s.pt imgsz640 batch16 epochs150 patience30yolov8的模型会自动继承yolov8s.pt的结构和预训练权重迁移学习对634张的小数据是刚需不要从空白权重开始训。预训练权重让模型已经会看边缘、纹理和基础形状你的猪数据集只是让它把“目标”这个抽象概念微调成“猪”这个概念。4.3 小样本要不要关mosaic增强这是634张规模下最值得讨论的一个参数。yolov5和yolov8默认开启mosaic增强把四张图拼成一张训练好处是目标尺度和上下文变化大坏处是小数据集上目标分布被mosaic改变得过于剧烈而且mosaic样本里一个框被裁掉一半、目标只露个头的场景太多模型学到的是“半截猪也能算猪”推理时反而误检。我一般在小样本上会关掉mosaic或降到很低。yolov5的做法是训练参数加--mosaic 0或者在hyp.yaml里把mosaic的启用概率设成0yolov8可以在配置里写mosaic: 0.0。保留fliplr水平翻转和hsv增强就够了这两种增强不会扭曲目标结构对小样本来说更安全。实际效果是Loss曲线更平滑mAP0.5的起点可能低一点点但上限更稳。这一点也是被问得最多的“为什么别人不开mosaic分数更高”。因为别人可能手里有几千张图mosaic相当于把数据集翻几倍而634张开满mosaic相当于给模型上课时一半时间在看不完整的目标损失函数在“补全目标”和“识别目标”之间来回拉扯训出来的权重当然飘。5. 避坑634张小样本最常见的5个翻车现场5.1 现象训练loss降不下去loss曲线水平震荡原因多半是类别id错位。xml里的类名“pig”转txt时class_list里只要多写或少写一个类名下标就整体偏移。比如class_list写成[pig, boar]但数据里根本没有boarpig的id仍然没问题但如果你后来删了某个类或者用了网上给的转换脚本没注意names顺序id就会错。解决训练前跑一遍“标签自检”统计每个txt文件里第一列的最大值必须等于nc-1如果出现大于等于nc的值说明标签里有类别id越界排查class_list顺序。5.2 现象训练mAP高达0.95但实际测视频一堆漏检这是典型的验证集划分泄漏。634张图如果来自连续视频帧frames之间极度相似随机切分时同一头猪的相邻帧分别进了train和val模型等于开卷考试记忆的是具体画面不是泛化能力。解决按视频片段或时间戳分组。如果你不知道哪些帧属于同一条视频流就看文件名前缀一般摄像头采集的帧都有规律比如cam1_0001.jpg这种。切分时按前缀group做分组整组进train或整组进val不要逐文件随机切。5.3 现象训练集loss持续下降验证集loss从第40个epoch开始掉头向上这是教科书级别的过拟合。634张图对yolov5s来说参数量远大于数据量模型有足够容量把训练图背下来。解决三个方向并行——提高增强力度轻度旋转±10度、亮度饱和抖动加权重衰减yolov5默认weight_decay是0.0005可以试0.001以及换小模型yolov5n。记住早停patience要留让过拟合发生之后还能自动回到最佳权重。5.4 现象训练到一半报错“shape mismatch”或“index out of range”常见原因是txt里有大于1的归一化坐标或负坐标anchor匹配时网格索引出界。大多数转换脚本不处理越界labelimg拖框拖得急框边缘碰上图片边界输出天然就是xmax等于宽转出来是1.0但浮点计算可能给1.0000001。解决转换脚本里做clip就像前面写的min(max(……))那样确保所有值落在0到1区间。同时扔掉宽或高小于3像素的极端小框这种框在训练时对loss贡献是纯噪声。5.5 现象训练一切正常但推理结果类别名是“0”而不是“pig”这是detect脚本里没加载names映射。yolo的模型输出本质是class id展示名字靠数据集yaml里的names字段。训练时data.yaml要是没写names或者推理时用了默认coco.yaml的names就会把猪识别成别的类名。解决推理脚本里显式指定--data pig.yaml而不是只给权重路径。网上教程只说--weights最佳权重路径漏了--data这一步漏掉就会看到“0”这种数字标签。这也是为什么我强调转格式时class_list和names的一致性要当作头等大事。6. 从“能跑”到“能交差”小样本效果验证与增强技巧6.1 三张图判断模型是不是真学到了猪训练完别急着看mAP先跑三张图的推理做肉眼检查。第一张是验证集里中规中矩的图一头或几头猪完整站立第二张是遮挡场景猪半截在栏舍遮挡物后面第三张是多目标密集场景五六头猪挤在一起。yolov8推理命令yolo predict modelruns/detect/train/weights/best.pt sourcetest_images/ saveTrue conf0.25重点看遮挡场景的框质量和密集场景的漏检数。634张数据训练出的模型通常对标准场景很好对遮挡和密集场景是最大的泛化短板。如果第二张图漏检基本说明数据里遮挡样本不够后续补数据的方向就朝这里加。6.2 增强做加法还是做减法小样本调优最迷惑人的地方就是增强强度。常规目标检测数据集增强拉满没有问题但634张图首要矛盾是过拟合增强可以对抗过拟合但过度增强又会把猪的纹理、颜色特征搅得不像猪。我的经验是做减法优于做加法默认开着mosaic、mixup、copy_paste的yolo配置在小样本上把mosaic和mixup关掉保留翻转和颜色抖动效果往往比开满增强高2到3个点mAP0.5。区分两类增强几何增强翻转、旋转不改变目标本身的视觉语义适合小样本混合增强mosaic、mixup创建的是虚拟样本其分布和真实场景有偏移适合大数据集不适合634张这种规模。如果你试完发现增强增强之后验证集P曲线锐利但R曲线明显偏低说明增强让模型“变保守了”该往回减。6.3 PR曲线和置信度阈值的配合yolov5在val阶段会输出PR_curve.png和F1_curve.pngyolov8同样有这些图。不要只看mAP0.5一个数要看PR曲线的形态。猪只检测的实际场景通常对漏检要更敏感因为你不想有一头猪没被识别到这时候把conf_thres调低到0.15左右能用更多误检换更少漏检反过来如果应用场景是计数误检会导致数目虚高conf可以提到0.4。实际调阈值时我一般用val目录单独测一遍不同conf下的F1值取F1最高点对应的conf做推理参数。634张规模下模型的置信度分布往往比较集中F1曲线会有一个明显的单峰那个峰就是当前模型最合理的“工作点”。这一步做完你的模型才不是实验室数字而是能落到现场的东西。我自己做小样本项目走到这里最大的习惯是每隔20个epoch就把验证集抽几张出来人工看一次框而不是只看指标。指标告诉你数值图片告诉你模型在犯什么错。634张的猪数据集训练起来也就一二十分钟的事多跑几轮、多对比几次增强配置比盲目找网络结构有意义得多。这套双格式数据集的价值在于把路上最容易卡人的格式清洗问题替你解决了你值得把省下来的时间花在验证和调阈值上。希望帮到你。本文还有配套的精品资源点击获取
