
简介这份资源是一篇西南财经大学学士学位毕业论文主题为基于改进YOLOv3的猪脸识别面向计算机视觉方向的学生、农业智能化研究者及目标检测入门者帮助理解YOLOv3在特定目标识别任务中的改进思路与实现流程。压缩包内仅含1个docx文档约38KB完整呈现论文的章节结构与研究内容。论文从YOLOv3算法原理与优缺点出发梳理猪脸数据采集标注、特征提取与分类算法重点提出两种改进方法一是通过数据增强、扩充样本或优化损失函数增强猪脸特征学习能力二是调整网络深度宽度、引入注意力机制或采用迁移学习提升模型专注度并给出精确度、召回率、F1分数等评估对比。目前已有219人学习适合需要参考目标检测改进方案、撰写相关论文或开展动物识别项目的读者研读。1. 猪脸识别为什么值得从 YOLOv3 改起一个被低估的落地场景猪场里给猪建档、称重、记录采食量如果还靠耳标和人工抄号一天下来饲养员腿都软了数据还未必对得上。猪脸识别要解决的就是这件事用摄像头替代人工把每一头猪的身份认出来进而关联它的生长曲线、健康状态和采食行为。而基于改进 YOLOv3 的猪脸识别本质上是把目标检测框架从「通用物体」迁移到「猪脸」这个特殊目标上再针对猪脸的特点做结构层面的调整。它适合两类人一类是想把检测模型落到养殖场景的算法工程师另一类是手里有猪场数据、想跑通一套识别流程的技术负责人。这一章先把「为什么是 YOLOv3、为什么必须改」讲清楚后面再动手。YOLOv3 在猪脸识别任务上有天然优势单阶段检测、速度快、对小目标有一定容忍度而且开源实现多改起来方便。但直接拿 COCO 预训练权重去跑猪脸翻车是大概率事件。原因不复杂——猪脸在图像里往往只占很小一块猪的肤色和地面、栏杆颜色接近猪还会低头、侧脸、互相遮挡。通用模型的特征金字塔对这种细粒度、低对比度的目标不够敏感。所以「改进」不是赶时髦而是被场景逼出来的。常见改法集中在三处骨干网络换成更轻或更强的结构、特征融合层加注意力、检测头针对小目标调整 anchor。下面几章会把这些改动拆成能复现的步骤也会把参数和踩坑点讲透。2. 改进 YOLOv3 猪脸识别的网络结构怎么选骨干、注意力和检测头2.1 为什么原版 Darknet-53 在猪脸数据上不够用Darknet-53 是为通用目标设计的卷积核感受野偏大对猪脸这种纹理细节丰富但尺寸偏小的目标浅层特征利用不充分。猪脸的关键区分点——鼻盘形状、耳朵姿态、面部斑纹——大多落在浅层和中层特征里。原版 YOLOv3 在三个尺度上做检测最小的 13×13 特征图对应大目标而猪脸在 416×416 输入下往往只有 30×30 到 60×60 像素落在中尺度甚至小尺度上。如果猪只密集小尺度特征图的分辨率不够相邻猪脸会糊在一起。我一般会做两件事第一把骨干换成 MobileNetV3 或 CSPDarknet53 的轻量变体减少参数量同时保留浅层细节第二在 neck 部分引入注意力模块让网络自己学会「看哪里」。注意力不是玄学它解决的是特征融合时背景噪声压过猪脸信号的问题。常见做法是在 FPN 的每一层输出后加一个 SE 模块或 CBAM通道注意力和空间注意力各管一摊。2.2 骨干替换与注意力插入的具体改法下面这段代码展示的是在 PyTorch 里把 YOLOv3 的骨干替换为 MobileNetV3并在 FPN 输出后插入 CBAM 的核心逻辑。注意这里只写结构改动部分训练循环和损失函数沿用 YOLOv3 原版即可。import torch import torch.nn as nn from torchvision.models import mobilenet_v3_small class CBAM(nn.Module): def __init__(self, channels, reduction16): super().__init__() # 通道注意力先全局池化再两层全连接 self.avg_pool nn.AdaptiveAvgPool2d(1) self.max_pool nn.AdaptiveMaxPool2d(1) self.fc nn.Sequential( nn.Linear(channels, channels // reduction), nn.ReLU(inplaceTrue), nn.Linear(channels // reduction, channels) ) self.sigmoid nn.Sigmoid() # 空间注意力对通道维做池化后卷积 self.spatial nn.Conv2d(2, 1, kernel_size7, padding3) def forward(self, x): b, c, _, _ x.size() avg_out self.fc(self.avg_pool(x).view(b, c)) max_out self.fc(self.max_pool(x).view(b, c)) channel_att self.sigmoid(avg_out max_out).view(b, c, 1, 1) x x * channel_att # 空间注意力 avg_map torch.mean(x, dim1, keepdimTrue) max_map, _ torch.max(x, dim1, keepdimTrue) spatial_att self.sigmoid(self.spatial(torch.cat([avg_map, max_map], dim1))) return x * spatial_att class YOLOv3PigFace(nn.Module): def __init__(self, num_classes10): super().__init__() # 用 MobileNetV3 的前几层作为骨干取三个不同尺度的输出 backbone mobilenet_v3_small(pretrainedTrue).features self.stage1 backbone[:2] # 浅层分辨率高 self.stage2 backbone[2:7] # 中层 self.stage3 backbone[7:] # 深层 # 假设三个尺度通道数分别为 16, 24, 48实际需根据输入尺寸调整 self.cbam1 CBAM(16) self.cbam2 CBAM(24) self.cbam3 CBAM(48) # 检测头沿用 YOLOv3 的卷积结构输出通道为 3*(5num_classes) self.head1 nn.Conv2d(16, 3 * (5 num_classes), 1) self.head2 nn.Conv2d(24, 3 * (5 num_classes), 1) self.head3 nn.Conv2d(48, 3 * (5 num_classes), 1) def forward(self, x): f1 self.stage1(x) f2 self.stage2(f1) f3 self.stage3(f2) # 插入注意力 f1 self.cbam1(f1) f2 self.cbam2(f2) f3 self.cbam3(f3) # 三个尺度分别输出 out1 self.head1(f1) out2 self.head2(f2) out3 self.head3(f3) return out1, out2, out3这段代码的逻辑是骨干换成 MobileNetV3 后参数量从 Darknet-53 的约 4100 万降到 250 万左右推理速度提升明显适合猪场边缘设备部署。CBAM 插在三个尺度输出之后通道注意力负责筛选「哪些特征通道对猪脸更重要」空间注意力负责定位「猪脸在特征图的哪个位置」。检测头保持 YOLOv3 原版结构输出维度是 3 个 anchor 乘以 (5 类别数)其中 5 是 x、y、w、h 和置信度。参数上需要注意num_classes 要改成你实际猪只数量如果只是识别几头猪可以设小一点如果要做身份分类类别数就是猪的个体数。输入尺寸建议保持 416×416 或 608×608太小会丢细节太大边缘设备跑不动。2.3 anchor 重新聚类猪脸不是 COCO 目标YOLOv3 默认的 9 个 anchor 是在 COCO 上聚类出来的尺寸偏大直接拿来框猪脸IOU 会很低。我一般会用 K-means 在自己的猪脸数据集上重新聚类聚出 9 个框再按面积分给三个尺度。具体做法是把标注文件里的宽高提取出来跑一遍 K-means距离度量用 1-IOU。聚类完把 anchor 写进配置文件重新训练。这一步对召回率提升很明显尤其是猪脸密集、大小不一的时候。提示聚类前先把标注框的宽高归一化到 0 到 1 之间否则大框会主导距离计算。3. 猪脸数据怎么准备采集、清洗和 YOLO 格式转换3.1 猪脸图像采集的四个现实约束猪场不是实验室采集数据要面对四个约束光照变化大、猪只不配合、遮挡频繁、背景重复。我一般建议在一天中的三个时段各采一批清晨、正午、傍晚覆盖不同色温。摄像头安装高度在 1.2 到 1.5 米俯角 30 度左右这样能拍到猪抬头时的正脸。每头猪至少采 200 张有效样本包含正脸、侧脸、低头、遮挡四种姿态。如果猪场有多个栏舍每个栏舍单独采避免模型把栏舍背景当成身份线索。采集完的原始图不能直接标先做清洗。清洗规则很简单模糊的删、猪脸小于 30×30 像素的删、完全侧脸看不到鼻盘的删。这一步会砍掉三成左右的图但剩下的质量高标起来快训起来稳。3.2 从 VOC 到 YOLO 格式转换脚本与边界坑标注工具常用 LabelImg默认输出 VOC 格式的 XML。YOLOv3 训练需要的是每张图一个 txt每行是类别 x_center y_center width height全部归一化到 0 到 1。下面这个脚本把 VOC 转成 YOLO 格式同时处理几个边界情况。import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_dir, img_dir, out_dir, class_map): xml_dir: VOC标注文件夹 img_dir: 对应图片文件夹用于读取宽高 out_dir: 输出txt文件夹 class_map: 类别名到索引的字典如 {pig_face: 0} if not os.path.exists(out_dir): os.makedirs(out_dir) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() # 图片宽高从XML里读避免和实际图片不一致 size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in class_map: continue # 处理difficult标记一般跳过 difficult obj.find(difficult) if difficult is not None and int(difficult.text) 1: continue bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 边界裁剪防止标注超出图片范围 xmin max(0, min(xmin, img_w)) xmax max(0, min(xmax, img_w)) ymin max(0, min(ymin, img_h)) ymax max(0, min(ymax, img_h)) if xmax xmin or ymax ymin: continue # 归一化中心点和宽高 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 lines.append(f{class_map[cls_name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if lines: txt_name os.path.splitext(xml_file)[0] .txt with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines)) # 调用示例 voc_to_yolo(annotations/, images/, labels/, {pig_face: 0})这段脚本的关键点有三个。第一宽高从 XML 的 size 节点读而不是用 PIL 重新打开图片避免图片被旋转或缩放后尺寸对不上。第二做了边界裁剪有些标注框会超出图片边缘不裁剪会导致归一化后坐标大于 1训练时直接报错。第三跳过了 difficult 标记的样本这些通常是遮挡严重或模糊的猪脸留着反而拉低模型精度。参数上class_map 要根据你的实际类别改如果只做猪脸检测就一个类如果要做个体识别每个猪一个类类别数就是猪的数量。3.3 数据集划分与配置文件写法转换完标签按 8:1:1 划分训练、验证、测试集。注意同一个猪只的图片不能同时出现在训练集和验证集里否则验证精度会虚高。YOLOv3 需要两个配置文件pigface.data和pigface.names。data 文件里写类别数、训练集路径、验证集路径、类别名文件路径、权重保存路径。names 文件每行一个类别名。这些文件都是纯文本用记事本就能改但路径别写中文血泪经验。4. 训练参数怎么设从 warmup 到学习率衰减的实操配置4.1 输入尺寸、batch size 和显存的三角关系训练 YOLOv3 猪脸识别输入尺寸、batch size 和显存三者互相牵制。输入 416×416 时batch size 可以设 16 到 32输入 608×608 时batch size 降到 8 到 16。如果显存不够优先降 batch size别降输入尺寸因为猪脸本身像素就少再降分辨率会丢关键特征。我一般会在 2080Ti 上跑 416×416、batch 16显存占用约 8G训练 200 个 epoch 大约 6 小时。学习率用 warmup 加余弦衰减。前 1000 步从 0 线性升到初始学习率之后按余弦降到 0。初始学习率设 0.001如果 loss 震荡就降到 0.0005。优化器用 SGD动量 0.9权重衰减 0.0005。这些参数不是拍脑袋是 YOLOv3 原版论文和大量复现实验里比较稳的组合。4.2 损失函数的三部分与调参重点YOLOv3 的损失由三部分组成边界框回归损失、置信度损失、类别损失。边界框回归用 CIOU 或 GIOU比原版的 MSE 收敛更快。置信度损失和类别损失用二分类交叉熵。调参重点是置信度损失的权重猪脸数据里背景框远多于目标框如果权重不调模型会倾向于把所有框都预测成背景。常见做法是给置信度损失加一个系数正样本系数设 1.0负样本系数设 0.5这样既抑制背景又不会漏检。# 损失权重配置示例 loss_weights { box: 0.05, # 边界框回归权重 obj: 1.0, # 正样本置信度权重 noobj: 0.5, # 负样本置信度权重 cls: 0.5 # 类别损失权重 }box 权重设小是因为 CIOU 本身数值范围小设大了会压过其他损失。obj 和 noobj 的比例根据你的数据调整如果猪脸密集noobj 可以再降一点。cls 权重在单类别检测时可以设 0因为只有一个类分类损失没意义。4.3 训练过程监控看什么指标什么时候停训练时盯三个指标total loss、验证集 mAP、学习率。total loss 下降到平缓后如果验证 mAP 还在涨继续训如果验证 mAP 连续 10 个 epoch 不涨就停。学习率降到初始值的百分之一以下时基本可以收尾。另外注意看正样本置信度均值如果一直低于 0.3说明模型没学到东西检查 anchor 是不是没换、标签是不是转错了。注意不要只看 total loss它会被负样本置信度拉低看起来降得很好但模型可能什么都没学到。5. 猪脸识别避坑与排查5 个真实翻车现场5.1 现象训练 loss 正常下降但验证集 mAP 始终为 0原因标签格式转错了最常见的是坐标没归一化或者类别索引从 1 开始而不是 0。YOLOv3 要求类别索引从 0 开始坐标归一化到 0 到 1。解决打开一个 txt 标签文件检查数值是否都在 0 到 1 之间类别是不是从 0 开始。如果不是重新跑转换脚本。5.2 现象模型把同一头猪的不同角度识别成不同个体原因训练数据里同一头猪的样本太少或者数据增强过度导致模型学不到稳定特征。解决每头猪至少 200 张样本数据增强只做水平翻转、亮度调整、随机裁剪别做旋转和透视变换猪脸旋转后鼻盘位置变了模型会懵。5.3 现象推理时框出一大片背景猪脸只占框的一小部分原因anchor 没重新聚类默认 anchor 太大。解决用 K-means 在猪脸数据上重新聚 9 个 anchor按面积分给三个尺度。聚类时用 1-IOU 做距离迭代 50 次基本收敛。5.4 现象训练到一半 loss 突然变成 NaN原因学习率太大或者某张图的标签坐标超出 0 到 1 范围。解决先把学习率降一个数量级如果还 NaN检查标签文件里有没有大于 1 或小于 0 的坐标。另外检查图片有没有损坏损坏的图片读出来是全黑或全白也会导致梯度爆炸。5.5 现象模型在训练集上精度很高换一个猪舍就认不出来原因过拟合到背景了模型学的是栏舍地面纹理而不是猪脸特征。解决采集数据时覆盖多个栏舍训练时加随机裁剪和颜色抖动让模型关注猪脸本身。如果已经过拟合用 MixUp 或 CutMix 增强把不同图的猪脸拼在一起强迫模型学局部特征。6. 把猪脸识别推到可用模型量化与边缘部署的一个具体技巧训练完的模型要落到猪场边缘设备上直接跑 PyTorch 权重不现实推理速度跟不上。我一般会做两步先转 ONNX再用 TensorRT 做 FP16 量化。转 ONNX 时注意把输入尺寸固定成 416×416动态轴只留 batch 维。TensorRT 量化后推理速度能提升 2 到 3 倍精度掉不到 1 个点。# 转ONNX python export.py --weights pigface_best.pt --img-size 416 416 --batch 1 --simplify # TensorRT量化FP16 trtexec --onnxpigface.onnx --saveEnginepigface_fp16.engine --fp16 --workspace2048导出时有个细节容易忽略YOLOv3 的输出是三个尺度转 ONNX 后输出节点名可能变成数字TensorRT 解析时对不上。解决方法是导出前在模型里给三个输出加名字比如output_small、output_mid、output_large这样 TensorRT 能正确识别。量化完在验证集上跑一遍确认 mAP 掉幅在可接受范围内再部署。部署到边缘设备后建议加一个简单的跟踪逻辑连续 5 帧识别到同一头猪才确认身份单帧结果不输出。这样能过滤掉遮挡和运动模糊导致的误识别。摄像头帧率设 15 到 20 帧就够太高了边缘设备处理不过来反而丢帧。我自己踩过最深的坑是拿训练集里的猪脸去测部署效果结果现场换了个角度就认不出来。后来养成习惯部署前一定用现场新采的、没参与训练的数据跑一遍mAP 掉超过 5 个点就回去补数据。这个习惯帮我省了很多返工。希望帮到你。本文还有配套的精品资源点击获取