简介:猪圈摄像头场景下的生猪检测数据集,按YOLOV5目录格式整理,可直接作为目标检测训练数据使用。面向目标检测学习者与智慧养殖场景开发者,尤其适合需要密集目标检测样本的实战项目。压缩包共2000个文件,以1066个txt标注文件、933张jpg图像为主,另含1个Python可视化脚本,总体积约97.43MB。图像分辨率为640至1080,每张图片包含多个目标,训练集929张图片及对应标签、验证集136张图片及对应标签,类别为pig。可视化脚本可随机读取图片并绘制边界框,便于快速预览标注效果。资源已有609人学习,能够用于YOLOV5系列模型训练、数据增强实验或迁移学习基线,节省自行采集标注的时间。
1. 猪圈摄像头下的生猪检测:这份 YOLOV5 数据集到底要解决什么
养猪场装摄像头的成本早就不高了,真正卡住落地的是算法侧的数据——猪圈场景和公开数据集里的猫狗、行人完全是两回事:俯视视角、铁栏杆遮挡、猪与猪互相叠压、粪污反光、灯光昏暗,还有随时凑到镜头前的猪鼻子带来的极端近距离畸变。你说的“YOLOV5目录格式数据集(1类别)”,本质就是把这类真实场景下的猪图像,按 YOLOV5 训练器能直接读取的 images/labels 目录结构组织好,只用猪一个类别去训练检测模型。适合谁?想用自己的猪场摄像头数据训一个可用生猪检测模型、但不清楚标注规范和目录划分方式的人。这份笔记不聊论文,只讲怎么把摄像头里的原始视频变成能被 YOLOV5 训练器直接吃进去的数据集,以及这个过程中必然踩到的坑。
2. 数据采集与标注规范:猪圈场景的图不是随便截的
2.1 摄像头安装角度与图像筛选决定了数据集的上限
生猪检测的数据集质量,七成由采集环节决定。猪圈摄像头通常是俯视或近俯视安装,这和 COCO、VOC 里的平视目标差异极大。我一般建议在栏舍走道上方 2.5~3 米处安装,角度略向下倾斜,这样单头猪在画面里的像素面积比较稳定,不会出现一头猪占满全图、另一头猪只有几十个像素的极端情况。采集时间要覆盖白天、夜间补光、喂食前、喂食后这几个典型时段,否则模型很容易在某个光照条件下翻车。
原始素材通常是视频流,抽帧后要做筛选。我见过最省事的做法是每秒抽 1 帧,然后用感知哈希算法做去重,把画面基本不变的帧丢掉。筛选时重点去掉三类图:严重运动模糊的、镜头被猪拱歪导致画面大部分是墙或地面的、以及完全无人无猪的空栏图。空栏图不是完全没用,但单独分一类做背景负样本更合适,混进目标检测数据集里只会浪费训练时间。
import os import cv2 from PIL import Image import imagehash def dedup_frames(frame_dir, threshold=10): seen_hash = {} remove_list = [] for fname in sorted(os.listdir(frame_dir)): fpath = os.path.join(frame_dir, fname) try: img = Image.open(fpath) h = imagehash.phash(img) for existing_h, existing_f in seen_hash.items(): if h - existing_h < threshold: remove_list.append(fpath) break seen_hash[h] = fpath except Exception as e: remove_list.append(fpath) # 损坏图片直接删 for f in remove_list: os.remove(f) print(f"removed {len(remove_list)} duplicate/corrupt frames")这段脚本的思路是计算每张图的感知哈希值,两张图的汉明距离小于 threshold 就视为重复帧,后出现的被删掉。threshold 一般取 10,取值越小去重越严格;如果发现删完以后一段连续动作的中间帧没了,就把 threshold 调大一点,比如 15。
2.2 用 LabelImg 标出猪的边界框:一个类别也要守规矩
标注工具用 LabelImg 就够,不需要上 CVAT 这类重型平台。安装和启动命令很简单,但真正决定数据集可用性的是标注规范。虽然只有一个类别,标注时仍然要统一三个约定:一是“猪”的边界框必须贴合可见身体,不包括铁栏杆和食槽;二是猪与猪互相遮挡时,被挡住的猪只要可见面积大于 30% 就要单独标框,小于 30% 就忽略,这是个硬性约定,否则同一张图不同人标的框差距巨大;三是贴在镜头上的猪鼻子、猪耳朵这种极端特写,如果画面里没有完整的猪身,干脆不要标,这种目标对检测器来说是不可学习的。
LabelImg 默认输出 Pascal VOC 的 XML 格式,但我们要的是 YOLO 的 txt 格式。在软件左侧选择 PascalVOC 或 YOLO 输出格式,选 YOLO 后每个标注会自动生成一个同名 .txt 文件,每行内容为“类别id x_center y_center width height”,四个坐标值都是相对于图片宽高的归一化小数。类别 id 只有 1 个,所以永远是 0。
标注时把图片放大到能清楚看到猪的轮廓再画框。猪圈摄像头分辨率低于 720p 的,建议直接放弃或做超分辨率预处理,因为小猪和远处猪的像素太小,标注出来也是噪声。
2.3 标注质量抽检:别信自己的手,要信脚本
标几百张图以后人会产生惯性,容易把边界框画大或画小。每标完一批我习惯跑一遍抽检脚本,统计每个框的宽高比分布和面积分布。猪的体型决定了边界框宽高比通常在 0.5 到 2.0 之间,面积占比一般不会超过整张图的 40%。如果发现某个 batch 里出现大量宽高比大于 3 的框,十有八九是把栏杆或地面阴影框进去了。
import os import numpy as np def check_labels(label_dir, img_dir): ratios = [] areas = [] for fname in os.listdir(label_dir): if not fname.endswith('.txt'): continue with open(os.path.join(label_dir, fname)) as f: for line in f: parts = line.strip().split() if len(parts) != 5: print(f"bad line in {fname}: {line.strip()}") continue _, x_c, y_c, w, h = map(float, parts) if w <= 0 or h <= 0 or x_c < 0 or x_c > 1 or y_c < 0 or y_c > 1: print(f"out of range in {fname}: {line.strip()}") continue ratios.append(w / h) areas.append(w * h) ratios = np.array(ratios) areas = np.array(areas) print(f"ratio: p5={np.percentile(ratios,5):.2f} p95={np.percentile(ratios,95):.2f}") print(f"area: p5={np.percentile(areas,5):.4f} p95={np.percentile(areas,95):.4f}") check_labels("labels", "images")这段脚本检查两件事:标签文件里每行是否有 5 个合法数值且坐标在 0~1 之间;框的宽高比和面积百分位是否落在合理范围。出现异常值先别急着改数据,回到对应图片上看一眼是标注错误还是场景本身特殊(比如猪躺平了宽高比会很大)。这种抽检是数据集发布前最便宜的后悔药。
3. YOLOV5 目录格式拆分:train/val 划分与脚本实现
3.1 YOLOV5 目录结构:images 和 labels 必须严格对位
YOLOV5 的数据集目录约定看起来简单,但照着做错的人不少。标准结构是数据集根目录下有 images 和 labels 两个文件夹,各自内部再按 train、val 划分,注意不是把 train/val 放在根目录下再各含 images/labels。我直接给出推荐布局:
pig_dataset/ ├── images/ │ ├── train/ │ │ ├── 000001.jpg │ │ └── ... │ └── val/ │ ├── 000501.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── 000001.txt │ │ └── ... │ └── val/ │ ├── 000501.txt │ └── ... └── pig.yamlimages/train 里的每张 jpg 必须能在 labels/train 里找到同名同前缀的 txt,反过来也一样。这里的“同名”指的是主文件名完全一致,扩展名不同不要紧。训练时 YOLOV5 通过把 .jpg 替换成 .txt 来找标签文件,一旦主文件名对不上,那张图会被自动当作无目标图片,而且不会报错——这是最阴的坑,后面避坑章节细说。
3.2 数据划分脚本:8:2 划分 + 同名校验一步到位
标注完成后,最常见的操作是把所有图片和标签按 8:2 或 9:1 随机划分到 train/val。划分时有一个硬性要求:图片和它对应的标签必须同时进 train 或同时进 val,不能因为 random 两次就分家。正确做法是对文件名集合做一次随机打乱,再按比例切分。
import os import random import shutil src_images = "path/to/all_images" src_labels = "path/to/all_labels" dst_root = "pig_dataset" val_ratio = 0.2 random.seed(42) all_files = [f[:-4] for f in os.listdir(src_images) if f.endswith(".jpg")] random.shuffle(all_files) val_count = int(len(all_files) * val_ratio) val_set = set(all_files[:val_count]) for split in ["train", "val"]: os.makedirs(f"{dst_root}/images/{split}", exist_ok=True) os.makedirs(f"{dst_root}/labels/{split}", exist_ok=True) moved = {"train": 0, "val": 0} for name in all_files: split = "val" if name in val_set else "train" img_src = f"{src_images}/{name}.jpg" lbl_src = f"{src_labels}/{name}.txt" if not os.path.exists(lbl_src): print(f"WARN: {name}.jpg has no label, skipped") continue shutil.copy(img_src, f"{dst_root}/images/{split}/{name}.jpg") shutil.copy(lbl_src, f"{dst_root}/labels/{split}/{name}.txt") moved[split] += 1 print(f"train={moved['train']} val={moved['val']}")这段脚本的核心是只打乱一次文件名列表,然后根据集合判断归属,从根源上杜绝图标签错位。随机种子固定为 42,保证每次跑结果一致,方便复现。注意这里用了复制而不是移动,目的是保留原始标注数据,万一划分后发现问题还能重来。如果图片数量很大(几万张),可以改成 shutil.move 省磁盘空间,但第一次做建议复制。
3.3 data.yaml 文件:类别名和路径的写法有讲究
YOLOV5 训练时需要一个 yaml 文件来告诉训练器数据在哪里、有几类、类名是什么。这个文件写错是训练报错的重灾区。标准内容如下:
# pig.yaml train: ./pig_dataset/images/train val: ./pig_dataset/images/val nc: 1 names: ['pig']train 和 val 指向的是 images 目录而非 labels 目录,YOLOV5 会自动找同级 labels 目录。我建议用相对路径而不是绝对路径,因为项目目录换机器后绝对路径必挂。nc 是类别数,这里固定为 1,names 列表长度必须等于 nc,否则训练会崩。类名可以随便起,不影响训练,但最好用有意义的英文名。这个文件在训练时通过 --data 参数传入,路径写错会报 FileNotFoundError,路径写对但 yaml 语法错误会报解析异常,这两种错见得多。
提示:把你的数据集和 YOLOV5 项目放在同一级目录下,yaml 里的相对路径就永远不会因为换机器而失效。
4. 用 YOLOV5 训练并验证生猪检测:最小可行命令与参数
4.1 训练命令:从 yolov5s 起步,跑通链路再谈精度
目录和 yaml 就绪后,第一次训练的目标不是追求精度,而是跑通 pipeline。用官方 YOLOV5 仓库,最小命令如下:
cd yolov5 pip install -r requirements.txt python train.py \ --data ../pig_dataset/pig.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 50 \ --name pig_experiment这条命令里每个参数都值得解释。--weights yolov5s.pt 会下载 COCO 预训练权重,用迁移学习方式在猪数据集上微调,这是数据集几百张时最快收敛的路径;--img 640 是训练输入尺寸,猪圈摄像头原图如果是 1080p,640 能保留足够细节又不会让显存爆掉;--batch 16 取决于显存,8GB 显卡建议 8,16GB 以上可以 16;--epochs 50 对单类别数据集通常够了,如果 val 精度还在稳步上升可以追加。训练日志里重点关注 box_loss 和 obj_loss 的下降曲线,如果 20 轮后还在持续下降,说明数据量够,可以加轮次;如果曲线横盘震荡,问题多半在数据本身。
显存不够时优先降 batch 而不是降 img,因为检测器对输入分辨率更敏感。batch=4 虽然训练慢一点,但至少能跑通。
4.2 验证命令:用 val.py 拿到 mAP 和混淆矩阵
训练完以后不要直接拿去部署,先用验证集测一遍。YOLOV5 提供了独立的验证脚本和导出的权重:
python val.py \ --data ../pig_dataset/pig.yaml \ --weights runs/train/pig_experiment/weights/best.pt \ --img 640 \ --conf 0.25 \ --iou 0.45 python detect.py \ --weights runs/train/pig_experiment/weights/best.pt \ --source ../pig_dataset/images/val \ --conf 0.25 \ --save-txtval.py 输出的指标里,mAP@0.5 是最先看的:单类别任务里一般跑到 0.9 以上才算合格。mAP@0.5:0.95 是更严格的指标,对小幅位错敏感,猪圈场景的遮挡多,这个指标低于 0.6 是常态,不用太焦虑。混淆矩阵会显示猪这一类有没有被漏检或误检成背景。detect.py 跑一遍 val 图片,输出带框图片,人眼扫一遍比任何指标都直观——这也是你判断标注质量好坏的最后一关。
4.3 第一次训练最少需要多少张图
单类别检测的收敛速度比多类别快得多。我的经验是:猪圈场景下,800 张训练图、每张平均 3~5 头猪,就能训出一个 mAP@0.5 在 0.85 以上的可用模型;如果只有 300 张,模型能跑通但泛化差,换一个栏舍就漏检。低于 200 张就不要想训练了,先去补数据。数据增强只能延缓过拟合,不能创造信息。
5. YOLOV5 数据集制作避坑清单:5 个血泪教训
5.1 标签类别 id 从 0 开始,不是从 1 开始
现象:训练正常开始,loss 下降,但验证时一个目标都检测不出来,conf 全在 0.01 以下。原因:标注时把唯一一个类别设成了 1,而 YOLOV5 的类别索引从 0 开始。标签文件里写的“1”,实际对应的是第二类,而 yaml 里 nc=1 只有一个类,于是所有标签都被当成无效类别丢弃。解决:检查 labels 下所有 txt 的第一列数字,确保全部是 0。
grep -r "^1 " labels/ | head -20这条命令找出所有第一列为 1 的标签行。如果有输出,用 sed 批量替换回 0,或者重新标注。这类错是标完图以后最容易翻车的点。
5.2 图片和标签文件名不一致,训练器不会提醒你
现象:训练日志里显示“warning: 找不到 labels...”,部分图片被跳过,训练出的模型精度极低。原因:数据集来自多个摄像头或由多人标注,图片改名了但标签没同步改,或者 Windows 下遇见过文件名大小写问题。解决:训练前用脚本强制校验。
def verify_alignment(img_train, lbl_train): imgs = {f[:-4] for f in os.listdir(img_train) if f.endswith('.jpg')} lbls = {f[:-4] for f in os.listdir(lbl_train) if f.endswith('.txt')} only_img = imgs - lbls only_lbl = lbls - imgs print(f"imgs without label: {len(only_img)}") print(f"labels without img: {len(only_lbl)}") return only_img, only_lbl原理就是把两个文件名集合做差集。只要 only_img 或 only_lbl 不为空,就必须处理——把缺失的补上,或者把多出来的删掉。这一步不做,后面的训练质量无从谈起。
5.3 data.yaml 路径写绝对路径,换机器就崩
现象:在自己的电脑上训练一切正常,代码推到服务器或者同事电脑上跑,报 FileNotFoundError。原因:yaml 里写的是 /home/username/pig_dataset 这类绝对路径,换机后路径不存在。解决:统一用相对路径,yaml 放在数据集根目录下,train 和 val 写相对于数据集根目录的路径。YOLOV5 训练命令从项目根目录执行时,相对路径天然有效。
5.4 猪圈场景光线变化大,验证集要按天数而不是按帧随机分
现象:验证集 mAP 很高,模型上线后白天好用夜间全废。原因:验证集是打乱随机抽的,同一头猪不同帧可能分别进了 train 和 val,模型“记住”了场景而不是学到了泛化特征。解决:按日期划分数据集,比如前 5 天的视频抽帧做 train,最后 1 天的抽帧做 val。这样验证集才真正模拟了模型没见过的新时间点、新光照条件下的表现。
5.5 过度依赖预训练权重,忽略了自己数据的 domain gap
现象:加载 COCO 预训练权重训练,loss 很快就降下来,但实际场景测试效果远不如预期。原因:COCO 里没有俯视猪这种目标,预训练权重提供的先验对猪圈场景帮助有限。解决:第一轮用 50 轮训练出的权重作为第二轮训练的初始权重,继续用同一数据集再训 50 轮。这个操作的收敛速度明显快于从头训练,而且模型见过的猪特征,第二轮会被强化。
6. 从数据集到可用模型:数据增强调参与难例挖掘
数据集做完了,训练也能跑了,最后这一步决定了模型能不能真正投入使用。
最简单有效的是用 YOLOV5 自带的数据增强参数,在训练命令里直接调。猪圈场景最突出的两个问题是光照不均和母猪压在小猪身上造成的密集遮挡。对应地,我一般在 hyperparameters 里提高 hsv_s(饱和度)和 hsv_v(明度)的扰动范围,让模型对不同光照不那么敏感;fliplr 保持默认 0.5,因为猪的左右对称性很好,水平翻转等于白嫖一倍数据。增强参数不是越大越好,hsv_v 超过 0.5 后猪的颜色会失真,模型反而学歪。
难例挖掘是另一个回报极高的步骤。第一次训练完,把 val 集里 conf 在 0.3~0.6 之间的漏检图片找出来,人工重新看一遍,通常会发现两类问题:一是标注漏了,这张图里明明有猪但当时没标,模型是“照着错的学”;二是严重遮挡导致确实难检,这类图单独复制出来,人工再标一遍,并把它们的重点区域在训练时多裁几次。我一般会为这种难例专门建一个目录,并把它们设置成更高的采样权重。这比盲目加数据量有效得多,因为一张难例在图里贡献的 loss 可能是普通图的几十倍。
模型部署前的最后一个验证,我习惯用一段当时的监控视频,截取连续 3 分钟、包含喂食前后两个阶段来跑 detect.py。指标只能告诉你模型整体水平,视频会告诉你模型在真实帧间表现——有没有抖动、漏检是不是集中在某个固定区域、检测框是否稳定贴合猪身。如果视频测试暴露出某个固定区域频繁漏检,回去核对这部分图像在数据集里的占比,大概率是严重不足。
我自己做过的项目里,被坑最多的永远是目录结构里的一个小字母——labels 拼成 lables,或者 train/val 放错层级。这类问题不检查数据分布根本看不出来,模型默默把错误数据学进去了。多做一步脚本校验,等于给自己留了张后悔药。希望这些经验帮到你,让你的猪圈摄像头数据集少走几趟弯路。
本文还有配套的精品资源,点击获取