猪脸识别工程文件与代码全解析:从目录结构到边缘部署 简介这份资源是面向深度学习与计算机视觉方向的开发者、学生及农业智能化研究者的猪脸识别工程文件与代码包基于目标检测思路实现猪只面部特征的检测与识别可用于课程设计、科研实验或农业场景的算法验证。压缩包共34个文件约178.95MB以多个zip模块包为主辅以py脚本、分卷压缩文件、txt说明与md文档涵盖数据解码、网络结构、锚框生成、模型构建、训练与推理等环节并附有Pascal VOC记录生成脚本与运行命令说明便于按模块查阅与复现。目前已有1573人学习下载。资源提供了从数据准备、模型训练到评估应用的完整流程参考读者可借此理解YOLO、SSD、Faster R-CNN等检测框架在猪脸任务中的落地方式掌握数据增强、配置管理与推理接口的实践思路对深入农业领域的目标检测应用具有较高参考价值。1. 猪脸识别项目对应的工程文件及代码从一堆散装脚本到能跑通的流水线猪脸识别项目对应的工程文件及代码最让人头疼的不是模型选型而是拿到手的一堆文件根本串不起来。我见过太多人把数据集、标注、训练脚本、推理代码分别放在四个文件夹里跑起来就报路径错误。这个方向的核心价值在于养殖场需要个体身份绑定而猪脸在姿态、光照、污渍上的变化远比人脸复杂。适合谁有 Python 基础、做过至少一次目标检测或图像分类、想切入农业视觉的工程师。读完你能拿到一套可复现的目录结构、关键代码片段和参数配置逻辑而不是零散的文件列表。2. 工程目录怎么切数据、配置、模型、服务四层分离2.1 为什么不能把所有文件堆在根目录猪脸识别项目的工程文件如果全部平铺三个月后连自己都找不到哪份标注对应哪次训练。我一般按四层切data/放原始图像和标注configs/放 YAML 参数models/放网络定义和权重services/放推理接口和批处理脚本。这样切的好处是换数据集只动data/调参只动configs/部署只动services/。常见做法是用一个project.yaml记录全局路径其他脚本通过环境变量或配置加载器读取避免硬编码。# configs/project.yaml paths: raw_images: ./data/raw # 原始采集图按日期分文件夹 annotations: ./data/labels # YOLO 格式 txt与图片同名 train_list: ./data/train.txt # 训练集列表每行一个图片路径 val_list: ./data/val.txt weight_dir: ./models/weights train: input_size: 640 batch_size: 16 epochs: 120 lr0: 0.001逻辑说明raw_images和annotations分离是为了标注工具能独立读写train_list和val_list用纯文本而不是 CSV是因为训练脚本逐行读取更快也方便用shuf随机切分。参数说明input_size设 640 是猪脸检测的常用值猪脸在画面中占比通常比人脸小再低会丢小目标batch_size16 是 8GB 显存下的保守值显存够可以翻倍。2.2 标注文件格式与转换脚本猪脸识别项目对应的工程文件里标注格式不统一是最大的坑。有人用 COCO JSON有人用 Pascal VOC XML但训练时最省事的是 YOLO 格式每张图一个 txt每行class_id x_center y_center width height全部归一化到 0~1。下面这个转换脚本把 VOC XML 转成 YOLO txt同时生成训练列表。import os import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, img_w, img_h, class_map): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in class_map: continue cls_id class_map[cls_name] bbox obj.find(bndbox) x1 float(bbox.find(xmin).text) y1 float(bbox.find(ymin).text) x2 float(bbox.find(xmax).text) y2 float(bbox.find(ymax).text) # 归一化并转中心点格式 xc (x1 x2) / 2.0 / img_w yc (y1 y2) / 2.0 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{cls_id} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}) return lines # 使用示例假设图片尺寸已知或从文件读取 class_map {pig_face: 0} xml_dir Path(./data/annotations_xml) out_dir Path(./data/labels) out_dir.mkdir(parentsTrue, exist_okTrue) for xml_file in xml_dir.glob(*.xml): # 实际项目中应从对应图片读取宽高这里用占位值演示 lines voc_to_yolo(xml_file, 1920, 1080, class_map) txt_path out_dir / (xml_file.stem .txt) txt_path.write_text(\n.join(lines))逻辑说明class_map把类别名映射为整数猪脸识别通常只有一类但多猪场合并时可能有多类。参数说明xc、yc、w、h必须保留六位小数否则小目标坐标会因舍入误差偏移。注意图片宽高必须从实际图像读取不能写死否则不同分辨率的数据集会全部错位。2.3 训练列表生成与路径校验训练列表是工程文件里的“胶水”它把图片路径和标注路径绑在一起。我一般写一个校验脚本生成列表的同时检查每张图是否有对应标注、标注是否为空。import random from pathlib import Path img_dir Path(./data/raw) lbl_dir Path(./data/labels) all_imgs sorted(img_dir.glob(*.jpg)) valid [] for img in all_imgs: lbl lbl_dir / (img.stem .txt) if not lbl.exists(): print(f缺失标注: {img.name}) continue if lbl.stat().st_size 0: print(f空标注: {img.name}) continue valid.append(str(img.resolve())) random.seed(42) random.shuffle(valid) split int(len(valid) * 0.8) Path(./data/train.txt).write_text(\n.join(valid[:split])) Path(./data/val.txt).write_text(\n.join(valid[split:])) print(f训练集 {split} 张验证集 {len(valid)-split} 张)逻辑说明random.seed(42)保证每次切分一致方便复现。参数说明80/20 切分是常规做法猪脸数据如果单头猪图片少可以按个体分层切分避免某头猪只在验证集出现。注意路径用resolve()转绝对路径训练脚本从任意工作目录启动都不会找不到文件。3. 模型代码怎么组织骨干、颈部、头部与损失函数3.1 骨干网络选型与猪脸适配猪脸识别项目对应的工程文件里模型代码最忌讳直接抄人脸识别网络。人脸关键点密集、纹理丰富猪脸则更依赖轮廓和耳朵形状。我一般用轻量级骨干比如 MobileNetV3 或 ShuffleNetV2在models/backbone.py里定义输出多尺度特征图。选型理由养殖场边缘设备算力有限大骨干推理慢而且猪脸数据集规模通常只有几千到几万张大模型容易过拟合。import torch import torch.nn as nn from torchvision.models import mobilenet_v3_small class PigBackbone(nn.Module): def __init__(self, pretrainedTrue): super().__init__() base mobilenet_v3_small(pretrainedpretrained) # 取前若干层作为特征提取输出通道数按需调整 self.stem base.features[:4] # 下采样 8 倍 self.stage1 base.features[4:9] # 下采样 16 倍 self.stage2 base.features[9:] # 下采样 32 倍 def forward(self, x): c1 self.stem(x) c2 self.stage1(c1) c3 self.stage2(c2) return c1, c2, c3逻辑说明features[:4]等切片索引取决于具体 torchvision 版本实际项目中应先打印base.features结构确认。参数说明pretrainedTrue在猪脸数据少时能加速收敛但若数据分布与人脸差异极大可以冻结前两层只训练后层。注意多尺度输出通道数要记录颈部网络拼接时维度必须对齐。3.2 颈部特征融合与头部输出颈部负责把不同尺度的特征融合猪脸检测中耳朵和鼻子的尺度差异大FPN 结构比较合适。头部输出分类和回归分类用交叉熵回归用 CIoU Loss。class FPN(nn.Module): def __init__(self, in_channels[24, 48, 96], out_channels64): super().__init__() self.lateral nn.ModuleList([ nn.Conv2d(c, out_channels, 1) for c in in_channels ]) self.smooth nn.ModuleList([ nn.Conv2d(out_channels, out_channels, 3, padding1) for _ in in_channels ]) def forward(self, features): # 自顶向下融合简化版 laterals [l(f) for l, f in zip(self.lateral, features)] for i in range(len(laterals)-1, 0, -1): laterals[i-1] nn.functional.interpolate( laterals[i], sizelaterals[i-1].shape[-2:], modenearest) outs [s(l) for s, l in zip(self.smooth, laterals)] return outs逻辑说明lateral把不同通道数统一到out_channelssmooth消除上采样伪影。参数说明out_channels64是轻量级模型的常用值太大增加计算量太小融合能力不足。注意interpolate的size必须与目标特征图完全一致否则相加时报维度错误。3.3 损失函数与正负样本分配猪脸检测中正样本极少因为一张图里可能只有一两头猪。我一般用 SimOTA 或 ATSS 做正负样本分配损失函数组合分类焦点损失和 CIoU 回归损失。def compute_loss(pred_cls, pred_reg, gt_cls, gt_reg, num_pos): # 简化示意实际需实现动态分配 cls_loss nn.functional.binary_cross_entropy_with_logits( pred_cls, gt_cls, reductionsum) / max(num_pos, 1) reg_loss ciou_loss(pred_reg, gt_reg).sum() / max(num_pos, 1) return cls_loss 2.0 * reg_loss逻辑说明num_pos是正样本数除它做归一化避免不同 batch 正样本数波动导致 loss 尺度变化。参数说明回归损失权重设 2.0 是经验值猪脸定位比分类更关键时可以调到 3.0。注意max(num_pos, 1)防止除零训练初期正样本可能为 0。4. 训练与推理代码从配置文件到可执行脚本4.1 训练脚本的参数加载与日志训练脚本不要写死超参全部从 YAML 读。下面是一个最小训练循环包含学习率预热和余弦退火。import yaml import torch from torch.utils.data import DataLoader from models.backbone import PigBackbone from models.fpn import FPN from data.dataset import PigDataset cfg yaml.safe_load(open(configs/project.yaml)) dataset PigDataset(cfg[paths][train_list], cfg[train][input_size]) loader DataLoader(dataset, batch_sizecfg[train][batch_size], shuffleTrue) model torch.nn.Sequential(PigBackbone(), FPN()) optimizer torch.optim.AdamW(model.parameters(), lrcfg[train][lr0]) scheduler torch.optim.lr_scheduler.CosineAnnealingLR( optimizer, T_maxcfg[train][epochs]) for epoch in range(cfg[train][epochs]): model.train() for imgs, targets in loader: preds model(imgs) loss compute_loss(preds, targets) optimizer.zero_grad() loss.backward() optimizer.step() scheduler.step() print(fepoch {epoch} lr {scheduler.get_last_lr()[0]:.6f})逻辑说明AdamW比Adam对权重衰减处理更正确适合小数据集。参数说明lr00.001是 AdamW 的常用起点若 loss 震荡可降到 0.0005。注意scheduler.step()放在 epoch 末尾不是 batch 末尾否则学习率下降过快。4.2 推理脚本与批处理推理脚本要支持单张图和文件夹批处理输出检测框和置信度。猪脸识别项目对应的工程文件里推理代码往往被忽略但实际部署时它才是入口。import cv2 import torch from pathlib import Path def infer_image(model, img_path, input_size640, conf_thres0.5): img cv2.imread(str(img_path)) h, w img.shape[:2] resized cv2.resize(img, (input_size, input_size)) tensor torch.from_numpy(resized).permute(2,0,1).float().unsqueeze(0) / 255.0 with torch.no_grad(): preds model(tensor) # 后处理解码框、NMS、映射回原图尺寸 boxes decode_and_nms(preds, conf_thres) for box in boxes: x1, y1, x2, y2, score box x1 int(x1 / input_size * w) y1 int(y1 / input_size * h) x2 int(x2 / input_size * w) y2 int(y2 / input_size * h) cv2.rectangle(img, (x1,y1), (x2,y2), (0,255,0), 2) cv2.putText(img, f{score:.2f}, (x1,y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,255,0), 2) return img # 批处理 model.eval() for p in Path(./data/test).glob(*.jpg): out infer_image(model, p) cv2.imwrite(f./outputs/{p.name}, out)逻辑说明decode_and_nms需要根据模型输出格式实现通常包括 sigmoid 激活、网格解码和 IoU 阈值过滤。参数说明conf_thres0.5是保守值猪脸遮挡严重时可降到 0.3 提高召回。注意坐标映射回原图时要用原图宽高不能直接用input_size。4.3 配置文件与代码的版本绑定工程文件里最容易被忽视的是配置和代码的版本对应。我一般把configs/project.yaml和models/一起打 tag训练脚本启动时打印 git commit 或文件哈希。# 训练前记录版本 git rev-parse HEAD outputs/version.txt md5sum configs/project.yaml outputs/version.txt python train.py --config configs/project.yaml逻辑说明git rev-parse记录代码版本md5sum记录配置版本出问题时能快速定位是哪次改动导致。参数说明outputs/目录按日期分子目录避免覆盖。注意如果不用 git至少手动复制一份配置到输出目录。5. 避坑与排查猪脸识别工程文件最常见的五类翻车5.1 标注坐标越界导致训练 loss 为 NaN现象训练几个 batch 后 loss 突然变成 NaN。原因VOC 转 YOLO 时某些框的xmax超过图片宽度归一化后xc大于 1解码时产生非法值。解决转换脚本里加裁剪x1 max(0, min(x1, img_w))x2 max(0, min(x2, img_w))y 方向同理。5.2 图片路径含中文或空格导致 DataLoader 报错现象FileNotFoundError或UnicodeDecodeError。原因OpenCV 的imread对中文路径支持不稳定Windows 下尤其明显。解决统一用cv2.imdecode(np.fromfile(path, dtypenp.uint8), cv2.IMREAD_COLOR)读取或者把所有路径改成英文。5.3 验证集 loss 不降但训练集 loss 正常下降现象训练集 loss 降到 0.1验证集 loss 停在 2.0 不动。原因猪脸数据集中同一头猪的多张图被随机切分到了训练集和验证集导致验证集“见过”这些猪。解决按个体 ID 分层切分确保同一头猪的图片只出现在训练集或验证集之一。5.4 推理时检测框全部偏移现象训练时 loss 正常推理画框时框的位置整体偏移。原因训练时用了 letterbox 填充推理时直接 resize两者坐标变换不一致。解决推理也实现 letterbox记录填充偏移量解码后再减掉偏移。5.5 模型保存后加载报 key 不匹配现象load_state_dict报 missing keys 或 unexpected keys。原因训练时用nn.Sequential包装保存的 key 带0.、1.前缀加载时模型结构不同。解决保存时用model.state_dict()并记录模型类名加载时先实例化相同结构或者用torch.save(model, path)保存整个模型不推荐但省事。6. 进阶技巧用 ONNX 导出与量化把猪脸模型塞进边缘设备训练完的 PyTorch 模型直接部署到养殖场边缘盒子往往推理速度不够。我一般先导出 ONNX再用 ONNX Runtime 的量化工具做 INT8 量化。下面是一个导出脚本注意动态轴设置。import torch from models.backbone import PigBackbone from models.fpn import FPN model torch.nn.Sequential(PigBackbone(pretrainedFalse), FPN()) model.load_state_dict(torch.load(./models/weights/best.pth)) model.eval() dummy torch.randn(1, 3, 640, 640) torch.onnx.export( model, dummy, ./models/weights/pig_face.onnx, input_names[input], output_names[cls, reg], dynamic_axes{input: {0: batch}, cls: {0: batch}, reg: {0: batch}}, opset_version11 )逻辑说明dynamic_axes让 batch 维度可变部署时能一次处理多张图。参数说明opset_version11兼容性较好INT8 量化需要 opset 11 以上。注意导出后要用onnxruntime跑一遍验证输出与 PyTorch 一致误差应在 1e-4 以内。量化时用onnxruntime.quantization.quantize_dynamic把权重转 INT8激活保持 FP32。实测在 ARM 边缘设备上量化后推理速度提升约 2 倍精度下降通常不到 1 个点。但要注意猪脸检测的回归分支对量化更敏感如果 mAP 掉超过 2 个点就只量化骨干部分头部保留 FP32。验证量化模型时我习惯用同一批验证集跑 PyTorch 和 ONNX 两个版本逐张对比检测框的 IoU。如果某张图差异大就单独拿出来看是不是小目标或遮挡样本。这个习惯帮我省了很多后悔药——曾经有一次量化后模型在白天正常夜间红外图上全部漏检后来发现是量化把低对比度区域的激活值截断了。最后说一个血泪经验猪脸识别项目的工程文件里configs/目录一定要写注释每个参数旁边标上“调大/调小会怎样”。三个月后你回头看注释比代码值钱。希望帮到你。本文还有配套的精品资源点击获取