简介:猪圈摄像头场景下的生猪检测数据集,面向目标检测入门与实战人群,解决猪场监控画面中密集目标识别与标注数据缺乏的问题。数据为640-1080分辨率的RGB图像,每张包含多个目标,统一标注为单一类别pig,并按YOLOv5标准目录整理出训练集与验证集,txt标签与图片一一对应,下载后无需额外处理即可直接送入模型训练。从内容预览看,图像多来自监控视频画面,视角贴近实际养殖场景。压缩包共2000个文件,以jpg图像、txt标签为主,另附一个可直接运行的Python可视化脚本,可随机读取图片并绘制边界框,方便快速预览标注质量与密集场景效果。整体约97MB,轻量易用。目前已有608人浏览学习,适合需要标准格式猪只检测数据用于算法验证或课程设计的开发者。
1. 生猪检测数据集:不是把照片塞进文件夹就完事
养猪场里装摄像头容易,让算法在猪圈这种又脏又暗、猪还老挤在一起的环境里把每头猪数清楚,就是另一回事了。市面上的开源数据集大多是行车记录仪视角或者农场俯拍,真正贴着猪圈摄像头拍出来的、带遮挡和弱光的样本少得可怜。如果要做猪场盘点、异常行为预警或者出栏计数,第一步卡住你的往往不是模型,而是一份整理干净的、符合YOLOV5目录格式的生猪检测数据集。这个标题里的数据集只有1个类别,意味着所有标注框都指向“猪”,不需要区分母猪、仔猪还是育肥猪,恰好是能把检测流程跑通的最小闭环。
本文会按我实际整理这一类数据集的路径来写:从摄像头场景下的采集和标注,到组装出标准YOLOV5目录,再到训练校验和常见翻车点。目标很明确——你照着做,能把一份猪圈场景图片变成可以直接丢进YOLOV5训练的数据集,并且知道每个参数为什么这么设、出错了看哪里。新手能跟着搭完,熟手可以把我写的边界条件当对照清单。
2. 采集与标注:猪圈摄像头场景下先把图片和标签搞干净
2.1 摄像头怎么架、选什么时段拍:数据分布决定模型上限
很多人的第一反应是直接把监控录像抽帧,但录像抽出来的帧相邻之间高度相似,模型在几乎一样的图上反复迭代,泛化能力很差。我常用的做法是把摄像头按不同机位拍,尽量覆盖栏位全景、食槽近景和通道尽头三个角度,然后每隔5秒抽一帧,再手动剔除连续帧和完全模糊的帧。猪圈里的猪不是静止的,5秒间隔基本能保证同一头猪在相邻帧里有位移,避免训练集和验证集之间出现太多“双胞胎图”。
时段上,白天自然光、夜间红外补光、黄昏这种光线变化剧烈的时刻都要分别抽帧。这里有个反直觉的点:夜间红外图虽然人在肉眼看时对比度低、偏灰白,但猪的体温区和环境温差在红外下边界反而清晰,模型往往能学到比白天更强的轮廓特征。所以不要因为肉眼觉得暗就删掉夜间帧,这类帧往往是夜间巡检场景的主力样本。
抽帧之后要做初筛,把没有猪的空栏位图、维修人员入镜的图、镜头被猪拱歪导致的大面积遮挡图挑出去。这一步骤不能省,它直接影响标注工时——你找一个标注员标1000张全是猪的图和标1000张只有300张有猪的图,成本差好几倍。
2.2 标注工具选型:LabelImg与CVAT的取舍
单机小规模标注,用LabelImg就够,它输出的是Pascal VOC格式的XML,后续可以转成YOLO的txt。但LabelImg有个让人烦躁的点:它保存的是XML路径,如果你移动了图片目录,再次打开会提示找不到图片,需要重新选择目录。我的习惯是一开始就把图片集和标注目录的相对路径固定下来,比如workdir/images和workdir/annotations,整个标注期间不去移动任何文件。
如果是多人协作标几千张图,我会推荐CVAT。CVAT可以部署在带Docker的服务器上,三个人同时标同一个任务,标注结果导出为YOLO格式时,它会自动生成每个类别对应的txt文件,省去了自己写XML转txt脚本的步骤。但CVAT的问题在于部署和权限配置有点繁琐,团队里没人熟悉Docker的话,学习成本会把标注效率拖垮。
标注框的大小和紧致度也直接影响训练结果。我见过不少人把猪连同栏杆、食槽一起框进去,以为框大一点“背景信息多模型更好学”。实际上YOLO系列是回归框坐标的,背景噪点太多会拉偏回归目标。正确做法是框紧贴猪的可见轮廓——如果猪头被栏杆挡住,那就只框露出来的身体部分,那截栏杆不要包含进框里。遮挡严重的猪可以单独标一个尽量贴合可见区域的框,模型会通过大量这样的样本学会“只检测可见部分”。
2.3 标注规范:母猪、仔猪怎么定义边界
这个数据集只有1个类别,看起来标注规则很简单,实际协作时最容易出分歧。比如产房栏位里,母猪身下趴着一堆仔猪,有的标注员把整片猪群框成一个框,有的则每头仔猪单独框。这两种框法训练出来的模型行为完全不同:反复输入整片框,模型学到的是“检测猪群区域”;反复输入单独框,模型学到的是“检测每头猪个体”。如果你的最终目标是盘点数量,那应该选后者,哪怕仔猪被母猪挡住一大半,只要肉眼能分辨边界就单独框。
我一般会写一份极简标注说明放在数据集根目录:类别ID为0(只有一个类别,从0开始);可见面积大于整头猪20%的个体必须单独框;完全无法分辨边界的重叠区可以不标;同一帧画面中,同一头猪在两个框中不得重复出现。标注说明这步看似琐碎,但在你后续扩展数据集、换标注员的时候,能省掉大量返工。
3. 组装YOLOV5目录格式:目录树、分割脚本与yaml配置
3.1 标准目录树与命名规则
YOLOV5的目录格式有严格的约定,但它的要求其实比很多人想象中简单:images放图片,labels放对应的txt标注,两个目录下按train和val再分一层。目录树长这样:
pig_dataset/ ├── images/ │ ├── train/ │ │ ├── 001.jpg │ │ └── 002.jpg │ └── val/ │ ├── 003.jpg │ └── 004.jpg ├── labels/ │ ├── train/ │ │ ├── 001.txt │ │ └── 002.txt │ └── val/ │ ├── 003.txt │ └── 004.txt └── pig_data.yaml注意标注文件的主文件名必须和图片完全一致,只是后缀从.jpg变成.txt。YOLOV5在训练时是根据这个同名规则去找标注的,文件名对不上会直接报错或者跳过该图。图片格式上,jpg和png都能用,但建议统一,不要混着放——有些图像处理库对单通道png和四通道png的处理逻辑不一样,混用容易在数据加载时报错。
3.2 数据集划分脚本:随机还是按时间切
划分训练集和验证集时,不要直接随机全量打乱。猪圈场景有个特殊问题:同一天同一个栏位的猪,姿态和位置有一定连续性,如果随机划分,验证集里可能混入大量与训练集高度相似的帧,导致验证指标虚高。等你部署到新猪舍时,性能立刻现原形。我一般按时间切分,比如取前70%天数的帧做训练,后30%天数的帧做验证,这样才能测出模型面对“没见过的时间段”时的表现。
下面这个Python脚本做的事情是:读取图片列表,按文件名中的时间戳排序,然后按比例切成train和val两组,再为每组生成对应的目录和标注文件:
import os import shutil from glob import glob # 按时间戳切分数据的示例脚本 image_dir = "raw_frames" train_ratio = 0.7 images = sorted(glob(os.path.join(image_dir, "*.jpg"))) split_idx = int(len(images) * train_ratio) train_images = images[:split_idx] val_images = images[split_idx:] # 建立目录结构 for subset in ["train", "val"]: os.makedirs(f"pig_dataset/images/{subset}", exist_ok=True) os.makedirs(f"pig_dataset/labels/{subset}", exist_ok=True) for img_path in train_images: shutil.copy(img_path, "pig_dataset/images/train/") txt_path = img_path.replace(".jpg", ".txt").replace("raw_frames", "labels") if os.path.exists(txt_path): shutil.copy(txt_path, "pig_dataset/labels/train/") for img_path in val_images: shutil.copy(img_path, "pig_dataset/images/val/") txt_path = img_path.replace(".jpg", ".txt").replace("raw_frames", "labels") if os.path.exists(txt_path): shutil.copy(txt_path, "pig_dataset/labels/val/") print(f"train: {len(train_images)}, val: {len(val_images)}")这段代码里排序是关键,文件名里如果没有时间信息,需要先从数据库或监控系统的导出表里把拍摄时间关联上,再按时间排序。如果素材来源不统一、时间戳缺失,退而求其次的做法是按栏位ID分桶:同一个栏位的所有帧要么全进train要么全进val,避免训练时“见过这个栏位”的假象。
3.3 类别yaml与校验脚本:train能跑通才算数
pig_data.yaml是YOLOV5的数据配置入口,内容很少但容易写错路径:
# pig_data.yaml train: /absolute/path/to/pig_dataset/images/train val: /absolute/path/to/pig_dataset/images/val nc: 1 names: ['pig']这里的train和val指向的是images目录,不是labels目录。YOLOV5会自动把路径里的images替换成labels来找标注文件,如果你在yaml里写了labels路径,训练时会报找不到文件。nc是类别数,只有一个类别就写1,别写成0。names列表的长度必须和nc一致,不然训练启动时就崩。
写好后先跑一遍校验脚本,看每个标注文件是否有效:
python -c " from pathlib import Path import cv2, numpy as np labels_dir = Path('pig_dataset/labels/train') imgs_dir = Path('pig_dataset/images/train') bad = 0 for txt in labels_dir.glob('*.txt'): img_path = imgs_dir / (txt.stem + '.jpg') if not img_path.exists(): print('missing image:', img_path) bad += 1 continue h, w = cv2.imread(str(img_path)).shape[:2] if len(txt.read_text().strip().split(chr(10))) == 0: print('empty label:', txt) bad += 1 continue for line in txt.read_text().strip().split(chr(10)): parts = line.split() if len(parts) != 5: print('bad format:', txt, line) bad += 1 x, y, bw, bh = map(float, parts[1:]) if x <= 0 or y <= 0 or x >= 1 or y >= 1 or bw <= 0 or bh <= 0: print('out of range:', txt, line) bad += 1 print('done, bad files:', bad) "这个脚本会检查三件事:图片文件是否存在、txt内容是否为空、坐标是否超出[0,1]范围。坐标超出范围是最常见的坑,比如标注框贴边时,归一化后的中心点坐标可能变成1.0000001这类微越界数值,训练时模型可能忽略这一行或直接崩。发现越界不要手动改txt,回到LabelImg里重新保存一遍最稳妥——手动改坐标容易把中心点x改成别的数据,反而引入新错误。
4. 训练与验证:超参数、类别权重与指标解读
4.1 数据配置:img、batch、epochs怎么定
数据准备好了,第一次训练的参数设置我一般按基准配置来:img分辨率选640,batch选16(显存不够就降到8),epochs先跑100轮。分辨率这里有个权衡:猪圈摄像头大多是2K或4K的鱼眼或广角画面,直接缩到640会丢失小目标细节——那些分散在画面远处的仔猪可能只有二三十个像素。但你把分辨率拉到1280,显存占用会翻好几倍,训练速度也直线下降。我的建议是第一版跑640,把流程跑通,确认loss下降趋势正常,再针对小目标漏检的问题专门用1280精调一版。
训练启动命令:
python train.py --data pig_data.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100 --project pig_run--project pig_run指定输出目录,YOLOV5会在里面按每次运行时间自动建子目录,保存权重和日志。用yolov5s.pt作为预训练权重,比从零训练收敛快得多,COCO上预训练出来的底层特征对猪的轮廓、纹理有一定迁移价值。如果是新手,不要一上来就换yolov5x或yolov5m,训练时间和显存占用都会放大,而且在这个数据集规模下精度提升有限。
训练过程中主要看两个东西:box_loss和obj_loss是否持续下降,以及验证集上的mAP@0.5是否在稳步提升。如果loss前几十轮不降反升,大概率是数据配置有问题,先停掉排查,别硬等100轮跑完。
4.2 类别权重与图像数量不平衡
只有一个类别时,类别不平衡的问题基本不存在,但样本难度不平衡是真实存在的。比如大部分图片里的猪都是站立状态,卧着的猪样本很少。模型会把“卧姿”当成一种少见变异,漏检率明显升高。解决思路有两个:一个是数据层面多抽卧姿的帧,另一个是训练时提高难例权重——但YOLOV5原版并没有直接暴露这个参数,常见的做法是调整训练集里卧姿图的比例,而不是去改损失函数。
还有个容易被忽略的不平衡是尺寸不平衡。猪圈摄像头通常架在高处往下俯拍,近处的猪可能占据半个画面,远处的猪可能只有几十像素。YOLOV5本身的anchor机制能处理一定尺度变化,但跨度太大的话,大目标会把小目标的梯度淹没。我一般会在训练前统计一下所有标注框的面积分布,如果最大值和最小值差两个数量级以上,考虑把图片按远近距离切分成两组分别训练,再在推理时合并结果。这个方案工程量大,但对猪舍这类固定场景很有效——你甚至可以针对每个摄像头单独训练一个模型。
4.3 训练结果指标:P、R、mAP@0.5到底看哪个
训练结束会输出一堆指标,最常用的三个是Precision、Recall和mAP@0.5。对于猪场盘点场景,我优先看Recall——漏检一头猪比误检一头猪严重得多,漏检意味着数量统计错误,而误检可以通过后续的时间序列滤波(比如连续N帧都检测到才算有效)来消除。如果P很高但R很低,说明模型趋于保守,这时可以降低置信度阈值到0.15左右再测一次;如果R很高但P很低,说明模型把食槽、水渍、墙上的污渍都当成了猪,这时要回看训练集的负样本是否太少。
YOLOV5的验证命令会输出confusion matrix和PR曲线:
python val.py --data pig_data.yaml --weights runs/train/exp/weights/best.pt --img 640best.pt是训练过程中在验证集上mAP最高的权重,做实验对比时用它,不要用last.pt。如果PR曲线在低置信度区间出现明显掉点,说明模型对难样本的响应差,优先去补难样本而不是调超参数。训练产生的results.png里还有每一轮的指标曲线,注意看val曲线在训练后期是否出现震荡——如果震荡幅度很大,是lr过高的信号,把--lr0从默认的0.01降到0.005重跑一版。
5. 避坑清单:猪圈场景下的数据坑,一次说清
标注了这个数据集的坑,挑几个一定会遇到的写在这里,每一条都是真实翻车场景。
5.1 夜间红外模式下猪眼反光被误检成多个小目标
现象:模型在白天测试集上P和R都超过0.9,一到夜间红外画面,猪的眼睛和反光的耳标被检测成若干小框,同一头猪出现三四个框。原因不是模型变笨了,而是夜间帧里猪体边界低对比度、眼睛高对比度的纹理特征和白天样本差异巨大,模型没学会“猪的轮廓在红外下长什么样”。解决方法是刻意的在夜间帧上做数据增强:把训练集里的夜间图做高斯模糊、亮度扰动、局部对比度拉伸,让模型适应红外图像的纹理分布。更直接的思路是先统计夜间帧的误检率,把误检框当成负样本单独存一份,下次训练时加入这些图的背景区域作为hard negative。
5.2 同一头猪在连续帧里被分配不同ID,导致数量统计翻倍
现象:模型逐帧检测都正常,但把连续帧的检测结果叠加统计数量时,猪的总数远超实际存栏数。原因是相邻帧之间同一头猪的检测框位置有轻微偏移,没有做IOU匹配或跟踪算法直接按每帧独立计数。这不是数据集的问题,而是评估方式的问题,但很多人会误以为是模型不稳定,反复调整训练参数。正确做法是用简单的IOU追踪或者直接按检测框中心点做最近邻匹配,连续帧IOU大于0.5就判定为同一头猪。用代码验证时,可以先将测试视频按帧检测后,用deep_sort或最简单的bytetrack跑一遍再统计。
5.3 标注框坐标越界导致训练直接报错
现象:训练启动后几秒钟就报assert (cx > 0.0 and cx < 1.0)类错误。原因是标注时框边缘贴着图片边界,保存出来的归一化坐标中心点等于1.0000或者宽高比画幅还大。LabelImg默认不会阻止这种边缘框,所以容易出现越界值。解决方法是跑一遍我在3.3节写的校验脚本,把所有越界txt找出来,回LabelImg里重新保存或手动把坐标修正到0.999以内。不推荐写代码批量clip坐标值——clip会把框中心点也移动掉,导致框整体偏移,模型学到的是偏移后的错误位置。
5.4 验证集和训练集图片高度相似,mAP虚高
现象:训练结束所有指标都好得吓人,mAP@0.5接近1,但一部署到新猪舍就崩。原因是抽帧间隔太短,相邻帧几乎一样,随机划分后同一天同一角度的相似帧被分到了两边。这类问题只能靠划分策略来防:按时间段或按栏位划分,而不是按整张图片随机划分。验证集的作用是模拟“第一次见到这个场景”,如果验证集的图片在训练集里能找到近乎复制品,它就没有任何说服力。
5.5 标注文件里用tab分隔而不是空格,训练时数据解析失败
现象:训练时loss正常下降,但某个epoch突然报错或者记录大量WARNING: ignoring corrupt image/label。原因是某些标注工具默认用tab分隔字段,而YOLOV5的解析器只接受空格。肉眼看着一样的txt,读进去字符编码完全不同。解决方法是先跑一次批量替换:把\t替换成空格,再检查文件行尾是否统一为\n而不是\r\n——Windows下编辑过的txt很容易带\r,会在解析时悄悄引入空行。这类问题在单机标注时几乎不会出现,一旦用了Excel或者某些在线标注平台导出,就很容易踩到。
5.6 图片EXIF方向信息导致检测框错位
现象:标注时看到的猪是正的,训练出来的模型在部分图片上检测框整体偏移约90度。原因在手机或部分监控截图带有EXIF旋转信息,标注工具读取时自动校正了方向,但YOLOV5在训练时用OpenCV读图,不会读EXIF方向,导致图片是旋转过的而标注框坐标没跟着转。猪圈摄像头一般不存在这个问题,但如果你的素材里混入手机拍摄的照片,就一定要在预处理阶段全部去掉EXIF信息或者统一旋转回标准方向。脚本很简单:用PIL打开图片后img = ImageOps.exif_transpose(img)再覆盖保存。
6. 进阶:难例挖掘与增量训练,让数据集滚起来
6.1 难例挖掘:先训一版,再挖误检补数据
第一版数据集训完之后,不要急着部署,先做一轮难例挖掘。把训练集和验证集里所有的图片用训练好的模型跑一遍推理,把那些漏检或者误检的帧单独拷出来——漏检的猪如果在画面里肉眼清晰可见,说明标注漏标了,补标;误检的帧说明模型对背景纹理没学明白,把这些帧作为纯负样本加入训练。YOLOV5的数据加载器遇到完全没有标注框的图片不会报错,但需要在labels/train里放一个空txt,否则图片会被跳过。
这个过程可以迭代三轮以上。第一轮补漏标的图,第二轮重点加夜间和逆光的图,第三轮加入摄像头被猪拱歪时产生的极端视角图。三轮下来数据集规模可能只增加了20%,但模型在真实场景的表现往往能提升一个档次。难例挖掘本质上是把模型当成标注检查员,用它的错误告诉你数据缺什么。
6.2 增量训练:老数据集与新数据集的合并策略
猪场场景有个实际问题:老猪舍的数据和新猪舍的摄像头角度、栏位颜色、光照条件都不一样。如果新数据来了直接和老数据合并重新训练,训练时间翻倍不说,老数据里那些低质量样本还会拖累新场景的精度。我一般用两段式策略:先用老数据集训练到收敛,再用新数据集以较小的学习率(--lr0 0.001)继续训练50轮左右,老数据集按10:1的比例混入新数据,防止模型遗忘老的通用特征。
这个策略的触发条件是:新场景的验证集mAP低于0.5,且新数据量小于老数据量的三分之一。如果新数据量已经超过老数据量,直接合并重训更合适。增量训练不是万能药,它对数据集分布偏移较小的场景有效,如果新旧数据之间差异极大——比如从白天监控变成夜间热成像——那还是从头训一版更靠谱。
6.3 部署前验证:用TensorRT量化检查数据质量问题
训练完的模型要上树莓派或Jetson这类边缘设备,一般会转成TensorRT的engine格式。不少人以为TensorRT只是加速,其实它还能暴露数据质量问题——INT8量化对数据分布非常敏感,如果训练集和实际部署场景的亮度分布差距大,量化后精度会断崖式下跌。我遇到过训练时验证mAP有0.92,转成INT8后掉到0.71的情况,回查发现是训练集里夜间帧占比不到5%,而部署现场夜间是主要使用时段。
解决办法是在量化校准阶段单独准备一组校准图,这批图不要从训练集里随机抽,而是从现场摄像头的历史录像里抽,数量在500到1000张之间,覆盖白天、黄昏、夜间、雨天四种光线条件。校准图的分布决定了量化后各层参数的敏感度,用现场真实光线去校准,比堆更多训练图更直接有效。这个步骤做完,就说明你的数据集和模型真正适配了实际猪圈场景,可以放心用了。
做数据集这件事,我栽过最多跟头的地方不是模型训练,而是前期数据分布没想清楚就猛干标注。现在每拿到一批新数据,我第一件事是看摄像头机位图和时间覆盖范围,而不是急着开LabelImg。先想清楚模型要在什么条件下跑,再决定数据集怎么摆布,整个流程能顺很多。希望帮到你。
本文还有配套的精品资源,点击获取