尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从ArcFace到工程落地:猪脸识别技术解析与实现

从ArcFace到工程落地:猪脸识别技术解析与实现 简介京东JDD大赛猪脸识别项目以商品猪个体身份识别为赛题涵盖数据预处理、模型训练、测试与可视化全流程适合计算机、数学、电子信息等专业学生用于课程设计、期末大作业或毕业设计参考。压缩包共61个文件其中24个Python脚本构成核心代码覆盖模型定义、数据加载、训练与测试流程22个Markdown文档提供多份项目说明、代码注解与实验记录14张图片为网络结构示意或训练过程可视化结果整体仅5.62MB轻量却包含完整工程结构。项目代码基于PyTorch等深度学习框架包含VGG16、VGG19、ResNet50、FCN等经典网络实现同时提供视频抽帧、图片增强、测试集CSV生成等辅助脚本并划分data、classify、fcn、model、summary等清晰目录从数据准备到模型评估均有对应脚本便于按模块阅读。当前已有233人学习下载既有赛题方案又有工程细节适合希望掌握图像分类/分割实战项目或快速搭建完整训练流程的开发者参考。1. 猪脸识别不是换个人脸模型就能跑通拿到一份 JDD 猪脸识别项目源码最先要认清猪脸识别和人脸识别的技术栈高度重合但数据分布完全不同。猪的个体差异集中在鼻子纹路、眼周斑块和耳廓轮廓上这些区域在整幅监控画面里占比很小姿态、光照和泥污随时会盖掉特征。直接套用给人脸设计的 ArcFace 流程往往验证集 90% 以上一上测试集就掉到七八成问题大多出在数据管线和损失函数。下面按这类赛题的标准路线——数据管线、骨干网络与损失函数、训练调参、推理优化——逐层给出可复现方案适合做动物个体识别、细粒度检索或正在备赛的工程师参考。2. 猪脸识别数据管线抽帧策略、检测裁剪与关键点对齐2.1 视频抽帧先过滤模糊帧再做感知哈希去重赛题给的原始数据一般是监控视频直接用 OpenCV 按固定间隔抽帧会带回来大量模糊帧和静止重复帧。常见做法是每 30 帧取一帧然后用拉普拉斯方差过滤低清晰度帧。拉普拉斯算子对图像求二阶导方差越小说明边缘越少、图像越模糊。阈值取值要看实际分辨率720p 视频我一般先用 80 做初筛再根据分布回调。import cv2 def extract_sharp_frames(video_path, interval30, blur_thresh80): cap cv2.VideoCapture(video_path) frames [] idx 0 while True: ret, frame cap.read() if not ret: break if idx % interval ! 0: # 每隔 interval 帧取一帧 idx 1 continue gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) var cv2.Laplacian(gray, cv2.CV_64F).var() # 拉普拉斯方差越小越模糊 if var blur_thresh: frames.append((idx, frame, var)) idx 1 cap.release() return framesinterval30对应 30fps 视频每秒取一个候选帧blur_thresh80是拉普拉斯方差门槛。这个参数不建议直接抄先跑一遍统计分布取方差中位数的 1.2 到 1.5 倍作为阈值更稳妥否则猪快速走动时会把相对清晰的运动帧也滤掉。抽帧之后还要做感知哈希去重猪在食槽前长时间不动会产生大量近似帧这些重复样本会让训练集的有效多样性大幅缩水去重时把哈希距离小于 5 的相邻帧只保留方差最大的一张。2.2 检测与裁剪微调 YOLO 比从零训练更稳妥抽帧得到的画面里有栏杆、地面、食槽等大量背景直接整图送入分类网络会引入强烈的背景干扰模型很可能学到站在食槽左边的是猪 A这种伪规律。自己从零标几万张检测框不现实常见做法是拿 COCO 预训练的 YOLOv8n用二三十张图人工标注猪脸框冻结 backbone 微调 50 到 60 个 epoch就能得到一个可用的检测器。from ultralytics import YOLO model YOLO(yolov8n.pt) model.train( datapig_face.yaml, epochs60, imgsz640, lr00.005, freeze10, batch16, )freeze10表示冻结前 10 层 backbone只更新后半部分和检测头这样小样本标注也能收敛。lr00.005比默认的 0.01 低一半迁移学习场景下学习率过大容易把预训练权重冲坏。pig_face.yaml里只保留一个类别 pig_face标注用 YOLO 的 txt 格式每行是class x_center y_center width height坐标归一化到 0 到 1。微调完成后用model.predict(source..., conf0.5)批量推理把检测框裁出来并向外扩展 20 像素左右避免猪鼻子或耳朵被切掉。2.3 关键点对齐仿射变换统一猪脸朝向检测裁剪之后猪脸的朝向仍然混乱有的正对镜头有的侧转 30 度。直接把图送进特征提取网络模型要额外学习姿态不变性训练成本翻倍。常见做法是标注双眼和鼻尖三个关键点用仿射变换把这三个点映射到固定模板位置。import cv2 import numpy as np def align_face(image, eye_left, eye_right, nose, size(112, 112)): src np.array([eye_left, eye_right, nose], dtypenp.float32) dst np.array([[30, 38], [82, 38], [56, 72]], dtypenp.float32) # 标准模板坐标 M cv2.getAffineTransform(src, dst) return cv2.warpAffine(image, M, size)dst中的三组坐标对应标准模板里左眼、右眼、鼻尖的位置(112, 112)是输出尺寸。cv2.getAffineTransform用三对点求解仿射矩阵warpAffine完成重采样。对齐之后所有样本的鼻子都落在同一区域网络可以把更多容量用在学习个体纹理差异上。需要注意类别里若有少量照片无法标注关键点直接丢弃比硬对齐好错位样本对训练的伤害远大于少一个样本。检测与对齐的质量决定了整个识别系统的天花板这部分做得粗糙后面换再大的模型也补不回来。3. 骨架网络与损失函数猪脸识别为什么用 ArcFace3.1 个体识别本质是度量学习问题赛题通常要求判断这张猪脸属于哪头猪看起来是分类任务但实际部署时会遇到训练集之外的新个体因此要学的不是记住类别 ID而是一个特征空间同一头猪不同照片的特征距离近不同猪的照片特征距离远这就是度量学习。纯 Softmax 分类头也能得到可用的特征向量但 Softmax 只追求类别可分不显式约束类内紧凑。在类别少、每类样本多的数据集上它和 ArcFace 的差距不明显但猪脸数据通常每个个体只有十几张图Softmax 学出来的特征在空间中散布较大检索时容易误判。所以赛题源码里几乎无一例外使用带 margin 的度量学习损失核心思路是在分类目标之外再压紧类内距离。3.2 ArcFace 损失的设计逻辑与 PyTorch 实现ArcFace 在 Softmax 的基础上给特征向量和权重向量之间的夹角加一个角度裕度 m把目标类别的 logit 从 cos(θ) 变成 cos(θ m)迫使网络把每个类别的特征压缩到更小的角度范围。m 越大类内约束越强训练也越难收敛。人脸任务里 m 通常取 0.5猪脸赛题我建议从 0.3 起调因为猪脸类间差异更小margin 太大容易训不上去。import torch import torch.nn as nn import torch.nn.functional as F class ArcFaceLoss(nn.Module): def __init__(self, in_features, out_features, s32.0, m0.3): super().__init__() self.weight nn.Parameter(torch.FloatTensor(out_features, in_features)) nn.init.xavier_normal_(self.weight) self.s s self.m m def forward(self, inputs, labels): cosine F.linear(F.normalize(inputs), F.normalize(self.weight)) theta torch.acos(torch.clamp(cosine, -1.0, 1.0)) # 反余弦求角度 target_logits torch.cos(theta self.m) # 只对目标类加角度裕度 one_hot F.one_hot(labels, num_classescosine.size(1)) logits torch.where(one_hot.bool(), target_logits, cosine) return F.cross_entropy(logits * self.s, labels)F.normalize分别对特征和权重做 L2 归一化让内积等价于余弦相似度。s32.0是缩放因子把余弦值从 [-1, 1] 放大到 [-32, 32] 再算 softmax避免数值区间过小导致梯度消失。m0.3加在目标类别的夹角上torch.where保证只有目标类别的 logit 被修改。需要特别注意torch.acos的输入先 clamp 到 [-1, 1]否则特征归一化后的浮点误差会直接产出 NaN。提示m从 0.3 开始若训练 loss 下降正常但验证集分数徘徊先调m而不是调学习率。每降 0.05 看一次验证集 Recall1找到不震荡的最大值。3.3 骨干网络选型与输出层改造赛题项目里最常见的骨干是 ResNet50输入 112x112 对齐图输出 512 维特征最后接一个 BN 层和 Dropout 再进 ArcFace。ResNet34 参数量小一半在样本量只有几千张时泛化性往往更好两个都跑一遍用验证集决定。如果训练资源有限MobileNetV3 也能用但特征维度建议降到 256轻量网络在小数据集上的过拟合风险更低上限也低一截。import torchvision.models as models backbone models.resnet50(pretrainedTrue) backbone.fc nn.Sequential( nn.BatchNorm1d(2048), nn.Dropout(0.4), nn.Linear(2048, 512), )pretrainedTrue的 ImageNet 权重对猪脸这类自然图像有明确的迁移收益即便数据集中没有猪脸类别底层边缘和纹理特征仍然通用。把最后全连接层替换为 BN-Dropout-Linear是为了让特征输出经过归一化减少各维度尺度不一致对余弦距离的影响。Dropout 设 0.4 在小数据集上是保守值训练损失下降缓慢时降到 0.2验证集波动大时优先加大 Dropout 而不是减学习率。一个常见误区是训练时冻结整个 backbone只训练新加的层。人脸识别任务里底层特征确实通用但猪脸的纹理模式与 ImageNet 类别差异较大冻结 backbone 会导致特征不足。我一般只冻结前两三个 stage后面的 stage 与全连接层一起更新这样既保留通用边缘特征又让高层语义适应猪脸分布。4. 训练策略与推理优化让模型在猪脸数据上稳定收敛4.1 数据增强针对猪脸场景的组合猪脸数据的干扰主要来自光照变化、泥污遮挡和姿态偏转增强策略对准这三类即可。随机亮度对比度扰动模拟不同时段的监控光照RandomErasing 模拟泥污或栏杆遮挡随机旋转 15 度以内模拟姿态偏移。不要用随机裁剪当主要增强手段裁剪会破坏对齐后的关键点位置语义让模型学到错误的对应关系。推荐的增强组合如下。增强操作参数范围作用注意事项RandomBrightnessContrastbrightness0.3 contrast0.3模拟光照变化与 HSV 扰动二选一不要叠加RandomErasingp0.3, scale(0.02, 0.1)模拟泥污遮挡遮挡面积超过 10% 等于制造噪声Rotatelimit15模拟姿态偏转超过 20 度会让对齐失效HorizontalFlipp0.5增加样本多样性猪脸左右不对称时先验证再开RandomErasing的 scale 控制在图片面积的 2% 到 10%覆盖泥污和栏杆遮挡的常见比例再大就会把鼻子这块关键判别区域整片抹掉。HorizontalFlip 是否安全取决于数据分布如果训练集里猪总是从固定方向进食翻转后会大量出现与真实姿态冲突的样本先用小模型对比翻转前后的验证集分数再决定开不开启。注意验证集必须按个体划分不能按图片划分否则同一头猪的图片同时出现在训练集和验证集分数虚高到完全失去参考价值。4.2 关键超参与训练循环小样本个体识别任务里优化器、学习率和 batch size 三者互相牵制。我常用的配置是 AdamW 初始学习率 1e-4 batch size 64。batch size 小于 32 时 ArcFace 的角度裕度在梯度上体现不充分类别间区分力下降显存不够时优先降低输入分辨率而不是调小 batch size。from torch.utils.data import DataLoader train_loader DataLoader(train_dataset, batch_size64, shuffleTrue, num_workers8, drop_lastTrue) # 丢弃不完整 batch稳定 BN 统计量 optimizer torch.optim.AdamW(model.parameters(), lr1e-4, weight_decay5e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max60)drop_lastTrue丢弃最后一个不完整 batch避免 BN 在 batch 过小时统计量抖动。weight_decay5e-4走 AdamW 的解耦权重衰减路径对 BN 层参数没有影响这是期望行为。T_max60与总 epoch 数一致余弦退火让学习率从 1e-4 平滑降到接近 0最后几个 epoch 的特征空间会被精细打磨对检索任务尤其重要。训练结束后不要只保存最后一个 checkpoint。ArcFace 训练中验证集最佳出现在最后 10 个 epoch 内的概率很高每个 epoch 结束都算验证集 Recall1保存分数最高的权重。我在实际项目里见过最后 epoch 比最佳 epoch 低 2 到 3 个点的情况直接拿最后一个 checkpoint 上线会白白损失精度。4.3 推理特征 L2 归一化与余弦相似度检索推理阶段把对齐图送入 backbone 得到 512 维特征L2 归一化后与库里所有个体的特征做余弦相似度排名第一且相似度超过阈值才判定为对应个体否则判为未知。候选库的特征在训练结束后离线提取一次不需要每次查询都重新过网络。import numpy as np def build_gallery(model, gallery_loader, device): model.eval() gallery, ids [], [] with torch.no_grad(): for images, labels in gallery_loader: emb model(images.to(device)) emb F.normalize(emb, dim1) # 查询与库特征统一做 L2 归一化 gallery.append(emb.cpu().numpy()) ids.extend(labels.numpy()) return np.concatenate(gallery), np.array(ids) def query(model, image, gallery, ids, device, threshold0.4): model.eval() with torch.no_grad(): emb model(image.unsqueeze(0).to(device)) emb F.normalize(emb, dim1).cpu().numpy() sims gallery emb.T # 矩阵乘计算全库余弦相似度 best_idx sims.argmax() if sims[best_idx] threshold: return int(ids[best_idx]), sims[best_idx] return -1, sims[best_idx]gallery emb.T是矩阵乘实现的全库相似度计算因为两侧特征都做过 L2 归一化内积等价于余弦相似度。threshold0.4是初始值最终取值要在验证集上画相似度分布确定。model.eval()必须调用否则 BN 层会继续更新统计量推理特征会漂移而且这个漂移在类别多的时候会被放大。5. 猪脸识别提分的三个细节阈值、多帧投票与类别加权5.1 阈值取相似度分布曲线的交点模型精度不是全部实际系统里误判和拒绝是方向不同的两类错误。在按个体划分的验证集上把同类相似度和异类相似度各画一条分布曲线取两条曲线的交点作为阈值同类误拒和异类误识的概率大致相等。业务更看重少误识时把阈值向异类分布方向移 0.05 到 0.1。若两条曲线大量重叠调阈值只是在转移错误要回头检查增强策略和训练 epoch。5.2 多帧投票比单帧 TTA 更划算TTA 用水平翻转做特征平均在猪脸赛题里一般只有 1 到 2 个点收益。监控视频给查询端带来了连续多帧把同一头猪连续几帧的余弦相似度取平均再判阈值能显著抑制单帧模糊或遮挡造成的偶发误判。多帧平均和 TTA 可以叠加但推理耗时翻倍时限紧时优先保多帧投票。5.3 类别加权采样缓解样本不均衡猪脸数据里每头猪的照片数差异很大样本多的个体在 ArcFace 特征空间里会撑大角度区域挤压少数类别。用 WeightedRandomSampler 按类别样本数倒数做加权采样让不同个体在每个 epoch 中出现的次数接近相等通常稳定提升 1 到 3 个点且不增加训练时间。import collections from torch.utils.data import WeightedRandomSampler counts collections.Counter(dataset.labels) weights [1.0 / counts[label] for label in dataset.labels] # 样本越少权重越高 sampler WeightedRandomSampler(weights, len(weights), replacementTrue) loader DataLoader(dataset, batch_size64, samplersampler)replacementTrue表示有放回采样num_samples与原始样本数保持一致迭代步数不变。权重取倒数后少数类别会被重复抽到配合数据增强能缓解过拟合若少数类别本身图像质量差权重别设过高否则会把噪声样本反复喂给模型我一般把权重上限截断在 5 倍。本文还有配套的精品资源点击获取
返回列表