资讯详情

目标检测小样本实战:猪数据集VOC转YOLO全流程指南

发布时间:2026/10/1 13:50:48

500+
企业客户服务经验
120+
行业领域内容覆盖
3000+
原创页面设计沉淀
98%
客户满意度

目标检测小样本实战:猪数据集VOC转YOLO全流程指南

简介猪只目标检测数据集面向计算机视觉学习者、算法工程师及智慧养殖领域研究者聚焦猪只个体识别、群体计数与行为分析场景提供约634张猪图片及人工标注标注类别统一为pig可直接用于YOLO、Faster R-CNN、SSD等主流目标检测框架的训练与评估。资源共1903个文件包含jpg原图634份、VOC格式xml标注634份、YOLO格式txt标注635份压缩包整体229.59MB按图片、xml、txt三个文件夹分类存放解压后无需解压密码即可对照原图与标注文件快速使用。标注由labelImg工具完成遵循准确框选目标边界、尽量覆盖图像中全部目标、标注后进行一致性检查的原则有助于降低标注噪声并提升模型泛化能力。已有110人学习既适合入门者熟悉VOC/YOLO两种格式也可作为算法实测、课程设计或毕业设计的基准数据集。1. 只看634张图的量级猪数据集的VOC与YOLO格式意味着什么一份猪数据集VOC和yolo格式的目标标注634张左右。这个数字在目标检测里属于典型的小样本但它并不是不能用的废料。做猪只盘点、出栏计数、行为识别或者栏舍异常预警的起步阶段634张足够把完整训练流程跑通并验证算法在真实场景里的表现。真正会拖垮你的往往不是图片数量而是标注格式不统一VOC的XML里写的是绝对坐标yolo的txt里写的是归一化中心点接口一错模型训练出来的框就会乱成一团。这篇文章从数据检查、格式转换、训练参数、常见坑位一路讲到验证技巧适合正准备入坑目标检测的从业者也适合手头已有标好数据但不知道怎么喂进训练框架的工程师。2. 先吃透VOC和YOLO两种标注结构目录、字段、坐标换算2.1 两种格式长什么样XML的绝对框与txt的归一化中心点VOC格式来自Pascal VOC比赛是目标检测领域最通用的“交换格式”。它的标准布局是JPEGImages目录放原始图片Annotations目录放同名XML文件ImageSets/Main目录放训练和验证的图片名清单。每个XML文件用object标签描述一个目标bndbox子节点里存的是xmin、ymin、xmax、ymax四个值单位是像素坐标原点在图片左上角。我一般拿到数据集后会先找一个XML打开看判断它是不是标准Pascal VOC结构annotation filenamepig_00001.jpg/filename size width1920/width height1080/height depth3/depth /size object namepig/name bndbox xmin245/xmin ymin310/ymin xmax868/xmax ymax704/ymax /bndbox /object /annotationyolo格式则是训练框架直接读取的“运行时格式”。每张图片对应一个txt文件每一行描述一个目标五个值依次是class_id、x_center、y_center、width、height。注意中间的四个数值全部是以图片宽高做分母的归一化值不再是像素。比如上面这个猪框图片1920乘1080那么 yolo 的 txt 里应该写0 0.290104 0.469444 0.324479 0.364815换算逻辑是中心点x (245868)/2 / 1920中心点y (310704)/2 / 1080宽 (868-245)/1920高 (704-310)/1080。这个换算并不复杂但它是后续所有训练问题的最常见源头只要有人手动改过XML坐标或者标注工具缩放显示时记错了像素值归一化后的数字就会超过1或者变成负数模型训练直接崩。还有一个容易忽略的点yolo的class_id不是类别名字符串而是从0开始的整数。因此任何yolo训练项目都必须要有一份类别映射文件常见做法是data.yaml里用names列表或者在训练目录下维护一个classes.txt。拿到数据先看这两个东西在不在比急着训练更重要。2.2 数据集一致性三查图片数、标注数、类别数对得上吗把634张的底子摸清楚我一般会按三个步骤做一致性检查每一步都花不了两分钟但能省掉后面一整天的排查时间。第一步是看数量对不对。统计JPEGImages和Annotations两个目录下的文件数最直接的方式是bash命令find JPEGImages -name *.jpg | wc -l find Annotations -name *.xml | wc -l如果图片数和XML数不一致多半是标注过程中漏存了文件或者同一张图被重复导出。对不上时不要急着补先找出具体缺哪个用文件名差集定位。第二步是检查图片和XML的一一对应关系。很多时候数量对上了但文件名对不上训练时照样报错找不到标注。用一段Python脚本做差集最省事from pathlib import Path img_dir Path(JPEGImages) xml_dir Path(Annotations) imgs {p.stem for p in img_dir.glob(*.jpg)} xmls {p.stem for p in xml_dir.glob(*.xml)} print(图片总数:, len(imgs)) print(XML总数:, len(xmls)) print(缺标注的图片:, len(imgs - xmls)) print(缺图片的标注:, len(xmls - imgs))这段脚本的原理是把两个目录下的文件名取出来去掉后缀后分别存成集合然后求差集。jpg和xml后缀不同的情况不影响因为stem只看主文件名。如果输出显示缺了某个编号就去原标注目录里找人工补导出对应文件。第三步是统计类别名和出现次数。这一步最关键因为猪数据集的类别可能是单一pig也可能是母猪、育肥猪、仔猪等多个类别类别名字串不一致会直接导致class_id映射错位import xml.etree.ElementTree as ET from collections import Counter counter Counter() for xml_file in xml_dir.glob(*.xml): root ET.parse(xml_file).getroot() for obj in root.iter(object): counter[obj.findtext(name).strip()] 1 print(counter.most_common())输出可能是[(pig, 1200)]也可能是((sow, 600), (piglet, 400))。看到多种类别名时先想清楚训练任务是单类还是多类。如果业务上并不需要区分母猪和仔猪直接把类别名统一成pig能显著降低训练难度。类别名有拼写差异的比如pig和Pig同时存在也要在转yolo之前先合并。提示这三查做完数据集的“底子”才算真正立住。数量一致只能说明文件齐全类别一致才说明标注语义可用。3. VOC转YOLO一个能跑的脚本加转换后必查的三个位置3.1 为什么两个都要留标注工具写VOC训练框架读YOLO现在绝大多数开源标注工具比如LabelImg、labelme的DET模式默认导出的是XML或者JSON格式而不是yolo的txt。原因很简单标注工具面对的是人人在看一张图的时候更容易理解“这个框是从左上角245像素到右下角868像素”而不是“中心点在0.29、0.47宽高是0.32、0.36”。yolo格式是给模型看的它不关心像素只关心相对位置。常见做法是XML永远作为原始底稿保留。标注阶段只用XML训练阶段不直接吃XML而是先跑一遍转换脚本生成txt。好处有两个第一XML可读性强发现标注错了可以直接改坐标第二换个训练框架时只需要重新生成一次txt不用重新人工标注。634张图的转换脚本跑一次只要几秒钟没必要手动去改txt。3.2 手写VOC转YOLO转换脚本从XML到txt的完整过程下面这段Python脚本是我在实际项目里常用的最小实现不依赖第三方库Python 3.6以上就能跑import xml.etree.ElementTree as ET from pathlib import Path def xml_to_yolo(xml_path: Path, class_map: dict, img_w: int, img_h: int, out_dir: Path): root ET.parse(xml_path).getroot() lines [] for obj in root.iter(object): name obj.findtext(name).strip() if name not in class_map: print(f未映射类别: {name}, 已跳过) continue box obj.find(bndbox) xmin float(box.findtext(xmin)) ymin float(box.findtext(ymin)) xmax float(box.findtext(xmax)) ymax float(box.findtext(ymax)) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) w min(max(w, 0.0), 1.0) h min(max(h, 0.0), 1.0) lines.append( f{class_map[name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f} ) out_dir.mkdir(parentsTrue, exist_okTrue) (out_dir / (xml_path.stem .txt)).write_text(\n.join(lines), encodingutf-8)逻辑说明脚本的核心是从bndbox里读出左上角和右下角的绝对像素坐标然后换算成中心点加宽高的归一化值。这里有两个关键设计。第一class_map必须是一个预先定义好的字典比如{pig: 0}不能直接用字符串类别名否则yolo训练时无法识别类别顺序。第二换算出结果后特意加了clip限制把x_center、y_center、w、h全部钳制在0到1之间。为什么要clip因为标注工具在鼠标拖拽越界时xmax可能大于图片实际宽度ymax可能大于高度。不处理的话训练时标签坐标超出边界软件会报错或直接把这些样本当坏数据丢掉。clip的代价是边界框位置略有误差但至少训练不会中断。严格的做法是在转换前自动提示越界框让人工确认是修改标注还是保留。调用脚本时需要传入每张图的宽高。不推荐从XML的size节点读因为有些标注工具写进去的size和原图不一致。最稳的方式是在转换前用PIL批量读取图片尺寸存成字典再传入from PIL import Image from pathlib import Path img_dir Path(JPEGImages) size_map {} for img_path in img_dir.glob(*.jpg): with Image.open(img_path) as im: size_map[img_path.stem] im.size # (w, h) class_map {pig: 0} out_dir Path(yolo_labels) for xml_path in Path(Annotations).glob(*.xml): w, h size_map[xml_path.stem] xml_to_yolo(xml_path, class_map, w, h, out_dir)参数说明size_map的作用是避免每转一个XML就打开一次图片634张图用字典缓存尺寸后转换速度会快很多。class_map按训练需求调整如果业务里区分sow和piglet就写成{sow: 0, piglet: 1}顺序就是训练时的类别编号这个顺序不能随便换否则之前标注的所有txt都要重新生成。3.3 转换后必查的三个位置类别ID、坐标越界、空标注脚本跑完不等于转换成功。我每次转换完都会做三个验证用不了五分钟但能避免后面训练到一半才发现数据有问题。第一个位置是类别ID。检查生成的所有txt里每一行的第一列是否都在0到类别数减1之间。可以用一行命令快速完成awk {print $1} yolo_labels/*.txt | sort -n | uniq -c如果输出的编号里有超出预期的值说明class_map写错了或者XML里有未纳入映射的类别名。此时重新检查XML里的object name把漏掉的类别名补进class_map再重新跑一遍脚本即可。第二个位置是坐标越界。虽然脚本里做了clip但clip只能防止数值超过1不能保证数值和原图视觉位置吻合。抽3到5个txt把归一化值乘回图片宽高和原XML里的bndbox对比python -c from pathlib import Path p Path(yolo_labels/00001.txt) w, h 1920, 1080 for line in p.read_text().strip().splitlines(): cid, cx, cy, bw, bh map(float, line.split()) xmin (cx - bw/2) * w ymin (cy - bh/2) * h xmax (cx bw/2) * w ymax (cy bh/2) * h print(cid, int(xmin), int(ymin), int(xmax), int(ymax)) 对比时重点看xmax是否接近原XML里的值ymax是否超出图片高度。如果明显对不上问题多半出在size_map里读到的尺寸和拍摄原图不一致需要检查数据集是否存在压缩后的副本。第三个位置是空标注。有些XML里object为空或者所有类别名都被class_map跳过这种情况下生成的txt是空文件。yolo训练框架对空标注文件的态度各家不一有的直接忽略有的报错。稳妥起见转换脚本里加一行判断如果lines为空就跳过不生成txt文件同时打印警告。这样在后续检查时可以用图片数减txt数定位到哪些图缺标注便于人工补标。4. 用634张启动yolo训练划分、预训练权重与参数设定4.1 训练集和验证集怎么分按猪舍与拍摄时间别全随机634张图如果全部扔进训练集模型在训练集上会表现得很好但validation一旦暴露真实场景mAP会惨不忍睹。问题不全是数据量少而是划分方式不对。全随机划分最常见的毛病是同一个猪舍、同一台摄像机、几乎同一个角度连续抽帧得到的图片被同时分进了训练集和验证集。模型在验证集上看到的是“训练时见过的那头猪的另一个角度”分数虚高部署到新栏舍时直接露馅。我一般会先看文件名携带的信息。猪场数据集的文件名通常带栏舍编号或拍摄时间比如A1_20240318_091230.jpg。按文件名前缀分组再整组划分能保证同一个场景的图片不会横跨训练集和验证集from pathlib import Path import random files list(Path(images).glob(*.jpg)) groups {} for f in files: prefix f.name.split(_)[0] # 按栏舍编号分组 groups.setdefault(prefix, []).append(f) all_groups list(groups.values()) random.Random(42).shuffle(all_groups) train_ratio 0.8 val_ratio 0.1 n_train int(len(all_groups) * train_ratio) n_val int(len(all_groups) * val_ratio) train_files [p for g in all_groups[:n_train] for p in g] val_files [p for g in all_groups[n_train:n_train n_val] for p in g] test_files [p for g in all_groups[n_train n_val:] for p in g] print(ftrain: {len(train_files)}, val: {len(val_files)}, test: {len(test_files)})参数说明这里的核心是按文件名前缀分组而不是对634个独立文件做随机洗牌。random.shuffle用的是固定种子42保证每次运行结果一致方便复现。如果文件名没有可分组的前缀退而求其次的做法是每隔N帧抽一张做验证这也能降低相邻帧的相似性。需要注意634张分成8:1:1后验证集只有60张左右测试集更少。验证指标会有一定抖动所以后面看mAP时不能只看一次数值要结合loss曲线一起判断。4.2 预训练权重、输入尺寸和batch size小数据往稳里调634张的小数据集从头训练yolo几乎必翻车。没有预训练权重时网络前几十个epoch全靠随机初始化感受野还没稳定loss下降非常慢甚至直接发散。常见做法是下载官方yolo预训练模型权重在自己的数据上做迁移学习。对于猪只检测这种目标清晰、场景单调的任务用轻量级模型就够不需要一上来就上最大规格的模型。数据配置方面先建一个data.yamlpath: dataset train: images/train val: images/val names: 0: pignames列表的类顺序必须和转换脚本里的class_map一致。如果class_map里猪是0names里第一项就是pig。yolo训练框架会用names列表去匹配txt里的class_id顺序错了模型就会把猪学成别的类。训练命令我一般这样起yolo train \ datadata.yaml \ modelyolov8n.pt \ imgsz640 \ batch16 \ epochs100 \ lr00.01 \ patience30参数说明model后接yolo官方预训练权重文件yolov8n是轻量级网络634张数据下比大模型更容易收敛。imgsz640是输入尺寸如果原图是1920乘1080训练时会等比缩放到640这个值决定了小目标的检测下限猪只体量大640足够。batch16是单卡常见设置显存不够就减到8但低于8时BN层统计不稳定容易出问题。lr00.01是初始学习率小数据刻意调低防止前几个batch把预训练权重冲坏。patience30表示30个epoch验证集没提升就提前停。4.3 训练命令与损失曲线判读出现BN崩溃时先查这三样训练跑起来后我基本都是盯三个loss曲线box_loss、cls_loss、dfl_loss。yolo的损失函数不是单一值box_loss衡量预测框和真实框的位置误差cls_loss衡量类别判断误差dfl_loss负责边界框的分布建模。三者下降速度不一样box_loss下降最快dfl_loss次之cls_loss最后趋稳这都正常。需要警惕的异常情况有两种。第一种是loss完全不降前10个epoch基本是平线。原因通常是学习率过大或者预训练权重没加载成功。检查方式很简单看命令行输出的model字段是不是显示fromyolov8n.pt如果显示从头训练就去把权重文件路径补齐。第二种更隐蔽也更容易在猪只数据集上出现loss在某一个epoch突然跳到几十甚至NaN之后再也回不来。这种叫BN崩溃常见诱因是训练后期某个batch里含有极端标注比如框的归一化坐标刚好卡在0或者1导致BN层的均值和方差统计被污染。遇到这个情况时先做三件事第一把batch size提到16或32增加统计稳定性第二把lr0从0.01降到0.001第三重点检查clip到0或1的边界框这些框在数据增强时很容易被裁掉一半形成“半个猪”的极端样本。注意BN崩溃不是模型不行是数据里有一个或几个坏样本在捣乱。先排查那个导致loss跳变的epoch之前新增的模型预测输出再配合逐张抽检标注框不要一上来就换模型结构。5. 训练过程中的常见坑标注、转换、训练三个环节的血泪排查5.1 猪只重叠导致标注互相吞并模型学出一团糊框现象训练出来的模型在单头猪的场景下框得很准一旦出现两三头猪挤在一起就输出一个大框把好几头猪全包进去或者两头猪中间生成一个无意义的框。原因标注阶段标注员在猪只重叠时图省事只标了最外层的轮廓把好几头猪画成一个框。更常见的情况是先给A猪画了框画B猪时鼠标稍微拖大了一点直接把A猪的框吞掉B猪的框覆盖了A猪大部分区域。模型从这些样本里学到的不是“猪的轮廓”而是一个模糊的“猪群范围”。解决回到XML里找出所有高重叠度的框计算两两之间的IoU超过0.5的样本单独抽出来人工复核。复核时把图片放大到200%先标视觉上最完整的猪再标被遮挡的猪允许被遮挡的猪框只有一小块可见区域。这类样本对模型学习遮挡鲁棒性非常重要宁可框小一点也不要框大一整片。5.2 坐标越界或类别ID错位转换后模型无法收敛现象训练到十几个epoch时cls_loss下不去输出类别总是集中在某一个ID上比如所有预测都是类别0。或者训练前置检查直接报错提示某个标签的坐标在0到1之外。原因坐标越界最常见的原因是转换脚本读图片尺寸时读到了EXIF信息里的旧尺寸或者原图被压缩后XML的bndbox没有重新生成xmax还是压缩前的像素值。类别ID错位的根源是class_map的字典顺序和data.yaml里的names列表顺序不一致转换时用的class_map把pig设为0训练时data.yaml里第一项却写了别的类。解决坐标越界用前面第3章的抽样脚本把归一化坐标乘回当前图片实际宽高逐张对比。发现越界统一改成clip处理并在转换日志里输出警告警告数量超过总样本数5%就说明原图有批量替换最好重新标注。类别ID错位没有捷径只能统一class_map和data.yaml的顺序改完重新转一遍txt不要手动改txt里的数字。5.3 损失曲线不掉或NaNBN崩溃与学习率过高现象box_loss一开始停在0.1附近不动或者训练到第20个epoch突然跳到NaN之后全部输出变为无意义数值。原因BN崩溃的触发点通常是某个batch里有一个极端标注框它的宽或高趋近于0或者中心点坐标刚好等于0。这样的框在数据增强的随机裁剪里几乎被完全裁掉模型被迫学习预测一个不存在的目标梯度瞬间爆炸。学习率过高则是另一种情况小数据集上lr00.01配合batch8时BN统计量波动大很容易在初期就把权重冲飞。解决先把lr0降到0.001batch提到16如果还是NaN就把数据增强里mosaic和mixup的比例调低或者暂时关闭其中一个。这些增强虽然对提升小样本泛化能力有帮助但它们会把标注框做随机拼贴坐标一旦拼接出错NaN就来了。跑通一遍后再逐步加回增强。5.4 验证mAP低但训练loss很低样本不平衡与过拟合现象训练集和验证集的数据分布差异大或类别不完全。例如训练集全部来自母猪舍验证集却包含一些仔猪图片更典型的是训练时验证集上loss停止了下降但mAP只有0.5左右而训练集上的loss很漂亮。原因634张的小数据集很容易过拟合到具体场景。模型记住的不是“猪的通用特征”而是“训练集里那几头猪身上的花纹、栏舍背景、光照角度”。验证集如果恰好是另一个栏舍、另一种光照模型就表现得很差。解决验证集一定要按场景留出“没见过的猪”。前面按栏舍分组划分就是为了这个。其次在训练时提高HSV饱和度、亮度的增强强度让模型不依赖具体的皮毛颜色。最后如果mAP依旧低检查验证集里猪只的尺度分布。母猪舍的图片猪占画面很大仔猪舍的图片猪很小模型训练时大多数学到的是大尺度特征小尺度检测自然翻车。这种情况可以试试把imgsz从640调到768或832给模型更多像素去分辨小目标。5.5 混淆矩阵总和看着不对行归一化的正常现象现象训练结束后打开confusion_matrix.png每一行的数值相加不是100甚至不是整数有人会误以为是训练出错或者类别统计漏了。原因yolo混淆矩阵按目标框计数并且按行归一化。每一行代表某个真实类别行内各列表示该类别被预测成各个类别的比例因此每行加起来是100%左右。但由于测试图片里目标数量不同不同图片的目标数差异会让数量级的感知产生偏差显示的百分数保留小数值时行内数字相加也可能在99或101附近这都不是错误。解决看混淆矩阵时重点看对角线值和最容易混淆的类别对。比如矩阵显示pig被预测成background的比例过高说明漏检严重pig被预测成sow的比例偏高说明两类外观太接近。对于634张的小数据如果两类之间混淆超过30%最实际的做法是把它们合并成一个pig类先保证单类检测的准确率再考虑细分。矩阵“总合不唯一”本身不是问题不要因为数字不好看而去修改测试集。6. 样本不够时的验证技巧先过拟合再看混淆矩阵6.1 先做20张的过拟合冒烟测试再谈优化训练前我习惯先做一个冒烟测试从训练集里随机抽20张图不设验证集直接训练30到50个epoch。如果loss能一路降到很低说明数据读取、标注坐标、类别映射、模型配置这条链路是通的如果连20张都过拟合不了问题一定在数据或配置层面而不是模型容量不够。这个测试不要跨epoch早停让它跑就行10分钟就能出结果。冒烟测试通过后再回到634张的全量训练。看指标时别只盯着mAP先看验证集上的混淆矩阵确认漏检和错检集中在哪。小样本的验证集可能只有60张图、几百个目标混淆矩阵数值会有抖动所以更信IoU0.5的mAP曲线是否平稳上升。如果某两类始终互相混淆就按第5章的思路合并类别。数据增强在小样本场景下的作用比模型结构改动更明显。我一般保持yolo默认的mosaic和HSV增强额外把scale打开让模型见过不同尺度的猪。634张数据量下模型结构越复杂越容易过拟合花力气改transformer结构不如先把标注质量和数据划分做好。吃过几次亏之后我养成了一个习惯任何模型跑全量训练之前先抽20张过一遍把可能性都验证过再谈参数。这个习惯治好了我大半的“玄学调参病”希望帮到你。本文还有配套的精品资源点击获取
热门专题

继续阅读更多专题内容

围绕企业服务、数字化转型与官网运营的常青话题,持续输出深度内容

企业官网建设指南 企业托管服务模式 财税政策与解读 企业数字化转型 官网SEO与获客 网站安全与运维
配套服务

读完这篇文章,了解更多服务

从整站搭建到SEO布局,17项核心服务助您打造高转化的企业官网

01

企业托管整站搭建

从信息架构到栏目预留,搭建可生长的企业站点骨架,每个页面独立原创设计。...

了解详情
02

规整可信网页设计

雪地靴温暖风原创设计,金属铜线条贯穿全页,拒绝通用模板与AI流水线。...

了解详情
03

企业服务SEO布局

关键词体系与语义化结构,从建站源头为搜索排名而生。...

了解详情
04

业务预约咨询表单

多场景表单与线索收集体系,把访问流量转化为可追踪的销售线索。...

了解详情
05

企业服务站点运维

安全巡检、数据备份与内容更新支持,全年守护网站稳定运行。...

了解详情
06

全终端商务适配

电脑、平板、手机一致呈现,移动端体验与转化同样出色。...

了解详情
需要专业建议?

让专业顾问为您解读行业趋势

关于企业官网建设、SEO获客与数字化转型的任何疑问,欢迎一对一咨询我们的专业顾问。