news 2026/10/11 11:34:34

猪脸识别工程实战:从数据集到推理脚本的完整落地路径

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
猪脸识别工程实战:从数据集到推理脚本的完整落地路径

简介:这份资源是面向深度学习与计算机视觉学习者的猪脸识别工程文件及代码包,基于目标检测思路实现猪只面部特征的检测与识别,适合具备Python基础、希望了解深度学习在农业场景落地实践的中高级开发者参考。压缩包共34个文件,约178.95MB,以zip模块包为主,辅以py脚本、分卷压缩文件及说明文档,涵盖数据准备、模型定义、训练、评估与推理等环节,并附有依赖与配置说明。资源围绕Objectdetectionapi-master展开,包含数据集、预处理、模型结构、训练与测试脚本、推理接口及配置文件等模块,可帮助读者理解YOLO、SSD、Faster R-CNN等检测算法在猪脸任务中的组织方式,并思考品种差异、光照变化、遮挡等问题的处理思路。目前已有1573人学习下载,适合作为深度学习目标检测项目实战与农业智能化应用的参考案例。

1. 猪脸识别工程文件拆包:从数据集到推理脚本的完整落地路径

猪脸识别这个方向,第一次听到的人多半会愣一下——人脸、车牌、条码都见过,猪脸是个什么场景?其实在规模化养殖里,个体识别是刚需:采食量、发情周期、体重曲线、用药记录,全都得绑定到具体某一头猪身上。耳标会掉、会磨损、会被咬烂,而猪脸不会。这套工程文件及代码,解决的就是「用深度学习做猪脸个体识别」从数据到推理的全链路问题,技术栈是 Python + 深度学习框架,覆盖数据标注、模型训练、评估、导出和单张图片推理。适合两类人:一是想找一个非人脸生物特征识别项目练手的算法工程师,二是养殖行业里想做智能化改造、需要一份可跑通基线代码的技术负责人。下面我按自己拆包复现的顺序,把这份资源讲透。

2. 工程目录与数据管线:先搞清楚文件往哪放

拿到一个深度学习项目包,最怕的就是上来就python train.py,然后满屏FileNotFoundError。猪脸识别这类项目,目录结构决定了你后面改配置、换数据集顺不顺手。我一般会先把整个包解压,用tree或者文件管理器把两级目录看一遍,再决定从哪个脚本切入。

2.1 典型目录结构与各文件职责

这类工程常见的组织方式是按「数据—代码—配置—输出」四块切分,下面是我拆过的包里边比较合理的一种布局:

pigface/ ├── configs/ │ └── default.yaml # 训练超参、路径、模型名集中在这里 ├── data/ │ ├── raw/ # 原始图片,按个体 ID 分子文件夹 │ │ ├── pig_001/ │ │ ├── pig_002/ │ │ └── ... │ ├── train/ # 划分后的训练集 │ ├── val/ # 验证集 │ └── labels.csv # 图片路径与个体 ID 的映射表 ├── src/ │ ├── dataset.py # Dataset 类,负责读图与增强 │ ├── model.py # 网络定义 │ ├── train.py # 训练入口 │ ├── evaluate.py # 验证集评估 │ └── infer.py # 单张/批量推理 ├── weights/ │ └── best.pth # 训练产出的权重 └── requirements.txt

这个结构里,configs/default.yaml是全局开关,data/raw下按个体 ID 建子文件夹是最省事的标注方式——文件夹名就是类别名,dataset.py直接遍历子目录生成标签,不用额外写标注文件。如果你的数据是散在一堆图里、靠 CSV 记录标签,那就得改dataset.py里的读取逻辑,让它走labels.csv而不是ImageFolder。

2.2 数据划分与增强策略

猪脸数据和常见图像分类数据有个明显区别:同一头猪在不同光照、不同姿态下的照片差异可能很大,而不同猪在某些角度下又长得很像。所以划分数据集时不能纯随机,我一般会保证每头猪在训练集和验证集里都有足够样本,避免某头猪只在验证集出现导致指标虚低。

# src/dataset.py 关键片段 import os import random from PIL import Image from torch.utils.data import Dataset, DataLoader from torchvision import transforms class PigFaceDataset(Dataset): def __init__(self, root_dir, transform=None): self.samples = [] self.class_to_idx = {} self.transform = transform # 遍历每个个体文件夹,文件夹名即类别 for idx, cls_name in enumerate(sorted(os.listdir(root_dir))): cls_dir = os.path.join(root_dir, cls_name) if not os.path.isdir(cls_dir): continue self.class_to_idx[cls_name] = idx for fname in os.listdir(cls_dir): if fname.lower().endswith(('.jpg', '.png', '.jpeg')): self.samples.append((os.path.join(cls_dir, fname), idx)) def __len__(self): return len(self.samples) def __getitem__(self, index): path, label = self.samples[index] img = Image.open(path).convert('RGB') if self.transform: img = self.transform(img) return img, label # 训练集增强:随机裁剪、翻转、颜色抖动,模拟猪只走动和光照变化 train_tf = transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomRotation(15), transforms.ColorJitter(brightness=0.3, contrast=0.3), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])

这段代码里,class_to_idx把文件夹名映射成 0 到 N-1 的整数标签,samples列表存的是「图片路径 + 标签」二元组。增强部分用了水平翻转、±15 度旋转和颜色抖动,这几个是针对猪脸场景比较稳的组合——猪在栏里左右走动、低头抬头,翻转和旋转能覆盖大部分姿态变化,颜色抖动则对抗猪舍里忽明忽暗的灯光。注意Normalize用的还是 ImageNet 的均值和方差,如果你从零训练而不是用预训练权重,可以改成自己数据集的统计值,但用预训练权重时保持默认就行。

提示:划分训练集和验证集时,建议按个体分层抽样,而不是把所有图片混在一起随机切。否则可能出现某头猪的图片全在训练集、验证集里一头都没有的情况,指标会好看但没意义。

3. 模型选型与训练:为什么用迁移学习而不是从零训

猪脸识别本质上是一个细粒度分类问题——类别之间差异小,类别内部差异大。从零训练一个卷积网络,在几千张图的规模下基本训不动,准确率会卡在很低的水平。所以这份工程里用的是迁移学习路线:拿在 ImageNet 上预训练好的骨干网络,换掉最后的全连接层,用猪脸数据微调。

3.1 骨干网络选择与修改

常见做法是选 ResNet18 或 ResNet50 作为骨干。ResNet18 参数少、推理快,适合边缘设备部署;ResNet50 精度更高,但显存占用和推理延迟都上去了。如果猪只数量在几十到几百头之间,ResNet18 微调通常够用;超过五百头、且个体之间花纹差异很小,再考虑换 ResNet50 或 EfficientNet。

# src/model.py import torch.nn as nn from torchvision import models def build_model(num_classes, backbone='resnet18', pretrained=True): if backbone == 'resnet18': model = models.resnet18(pretrained=pretrained) in_features = model.fc.in_features elif backbone == 'resnet50': model = models.resnet50(pretrained=pretrained) in_features = model.fc.in_features else: raise ValueError(f'不支持的骨干网络: {backbone}') # 替换最后的全连接层,输出维度改为猪只个体数 model.fc = nn.Linear(in_features, num_classes) return model

num_classes就是你的猪只个体总数,等于data/raw下的文件夹数量。pretrained=True会加载预训练权重,第一次运行需要联网下载,如果环境不能联网,得提前把权重文件放到缓存目录。替换model.fc之后,新加的全连接层是随机初始化的,训练时这一层的梯度会比较大,所以常见做法是给全连接层设一个更大的学习率,或者先冻结骨干只训分类头几轮,再解冻整体微调。

3.2 训练循环与关键超参

训练脚本的核心就是数据加载、前向传播、算损失、反向传播、更新参数这几步。猪脸识别用交叉熵损失就够了,优化器选 Adam 或 SGD 都行,我一般先用 Adam 跑通,再换 SGD 调精度。

# src/train.py 核心训练循环 import torch import torch.nn as nn from torch.optim import Adam from torch.utils.data import DataLoader from dataset import PigFaceDataset, train_tf from model import build_model device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') train_ds = PigFaceDataset('data/train', transform=train_tf) train_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4) num_classes = len(train_ds.class_to_idx) model = build_model(num_classes, backbone='resnet18').to(device) criterion = nn.CrossEntropyLoss() # 分类头学习率设大一点,骨干设小一点 optimizer = Adam([ {'params': model.fc.parameters(), 'lr': 1e-3}, {'params': model.layer4.parameters(), 'lr': 1e-4}, ], lr=1e-4) for epoch in range(30): model.train() total_loss, correct, total = 0, 0, 0 for imgs, labels in train_loader: imgs, labels = imgs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(imgs) loss = criterion(outputs, labels) loss.backward() optimizer.step() total_loss += loss.item() * imgs.size(0) correct += (outputs.argmax(1) == labels).sum().item() total += imgs.size(0) print(f'Epoch {epoch+1}, Loss: {total_loss/total:.4f}, Acc: {correct/total:.4f}') torch.save(model.state_dict(), 'weights/best.pth')

这里有几个参数值得说。batch_size=32是显存够的情况下的常用值,显存不够就降到 16 或 8,但太小会让 BatchNorm 统计不稳。num_workers=4是数据加载的并行进程数,设成 CPU 核心数的一半左右比较合适,设太大反而会因为进程切换拖慢。优化器用了参数组,分类头lr=1e-3、layer4用lr=1e-4、其余骨干用lr=1e-4,这是微调的常见做法——新层学得快一点,预训练层动得慢一点,避免把学到的通用特征冲掉。训练轮数 30 是个起点,实际要看验证集准确率什么时候不再涨,涨不动了就停,别硬跑。

注意:每个 epoch 都覆盖保存best.pth其实不是最优做法,应该只在验证集指标提升时才保存。上面为了代码简洁直接存了,实际用的时候加一个best_acc变量做判断。

4. 评估与推理:指标怎么看、单张图怎么跑

训练完不是看训练准确率就完事了,训练集上的准确率没有任何参考价值——模型可能只是记住了训练样本。真正要看的是验证集上的表现,以及推理脚本能不能对一张新图给出合理结果。

4.1 验证集评估与混淆矩阵

评估脚本要做的事:加载权重、在验证集上跑一遍、算准确率和混淆矩阵。猪脸识别里,混淆矩阵比单一准确率有用得多,因为你能看出哪两头猪容易被搞混。

# src/evaluate.py import torch from torch.utils.data import DataLoader from sklearn.metrics import confusion_matrix, classification_report from dataset import PigFaceDataset, train_tf from model import build_model device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') val_ds = PigFaceDataset('data/val', transform=train_tf) val_loader = DataLoader(val_ds, batch_size=32, shuffle=False) model = build_model(len(val_ds.class_to_idx)).to(device) model.load_state_dict(torch.load('weights/best.pth', map_location=device)) model.eval() all_preds, all_labels = [], [] with torch.no_grad(): for imgs, labels in val_loader: imgs = imgs.to(device) outputs = model(imgs) preds = outputs.argmax(1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) print(classification_report(all_labels, all_preds, target_names=list(val_ds.class_to_idx.keys()))) print(confusion_matrix(all_labels, all_preds))

classification_report会给出每个类别的精确率、召回率和 F1,confusion_matrix则是一个 N×N 的矩阵,对角线是分对的,非对角线是分错的。如果发现某两头猪之间互相错分特别多,说明它们的脸部特征在模型看来太接近,可以考虑给这两类多补一些不同角度的样本,或者换更强的骨干网络。

4.2 单张图片推理与结果解读

推理脚本是最终交付给业务方用的东西,输入一张猪脸图,输出个体 ID 和置信度。

# src/infer.py import torch from PIL import Image from torchvision import transforms from model import build_model CLASS_NAMES = ['pig_001', 'pig_002', 'pig_003'] # 按训练时的顺序填 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = build_model(len(CLASS_NAMES)).to(device) model.load_state_dict(torch.load('weights/best.pth', map_location=device)) model.eval() tf = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) img = Image.open('test.jpg').convert('RGB') tensor = tf(img).unsqueeze(0).to(device) with torch.no_grad(): logits = model(tensor) probs = torch.softmax(logits, dim=1) conf, pred = probs.max(1) print(f'预测个体: {CLASS_NAMES[pred.item()]}, 置信度: {conf.item():.4f}')

CLASS_NAMES的顺序必须和训练时class_to_idx的映射一致,否则预测出来的 ID 会张冠李戴。unsqueeze(0)是给单张图加一个 batch 维度,因为模型期望输入是[N, C, H, W]。置信度低于某个阈值时,业务上应该输出「未知个体」而不是硬给一个 ID,这个阈值一般设在 0.6 到 0.8 之间,看你对误识别的容忍度。

5. 避坑与排查:那些让我重跑训练的血泪经验

这一章是我拆包复现过程中真正卡过的地方,每条都按「现象 → 原因 → 解决」写,你照着排查能省不少时间。

现象一:训练 loss 一直不降,准确率停在随机水平。原因通常是标签映射错了。ImageFolder或自定义 Dataset 按文件夹名排序生成标签,但推理时CLASS_NAMES手写的顺序和训练时不一致,导致模型学的是 A 标签、你读出来是 B。另一种可能是数据增强里的Normalize用了错误的均值方差,把输入分布搞乱了。解决:训练前打印一次class_to_idx,把它存成 JSON 文件,推理时直接读这个文件,不要手写类别名。

现象二:验证集准确率很高,但实际拍的新图全错。这是典型的域偏移。训练图可能都是在固定光照、固定角度下拍的,而实际场景里猪在动、光线在变。解决:训练时增强力度加大,尤其是亮度和对比度抖动;如果条件允许,在真实场景里补拍一批图加入训练集。另外检查一下验证集是不是和训练集来自同一批拍摄,如果是,那验证集指标参考价值有限。

现象三:显存溢出,报 CUDA out of memory。原因可能是batch_size太大,或者num_workers设太高导致每个 worker 都缓存了一份数据。解决:先把batch_size降到 8 试,再逐步往上加;num_workers设成 2 或 4 就够,别设成 CPU 核心数。如果还不行,把图片输入尺寸从 224 降到 160 或 128,显存占用会明显下降。

现象四:训练到一半 loss 突然变成 NaN。学习率太大是首要嫌疑。Adam 的默认学习率 1e-3 在某些数据上会炸,尤其是分类头刚初始化的时候。解决:把分类头学习率降到 1e-4,或者加梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0)。另外检查数据里有没有损坏的图片,读进来是全黑或全白的,也会导致 loss 异常。

现象五:推理速度慢,单张图要几百毫秒。如果跑在 CPU 上,这个速度正常。如果跑在 GPU 上还这么慢,检查是不是每次推理都重新加载了模型权重。解决:模型加载一次,常驻内存,推理时只做前向传播。另外可以开torch.no_grad()关掉梯度计算,能省不少时间。

6. 进阶技巧:用特征向量做个体检索而不是死分类

分类模型有个天然限制:类别数在训练时就固定了。养殖场里猪只进进出出,今天 50 头明天可能 55 头,每加一头就重训一次模型不现实。更实用的做法是把模型当特征提取器用——去掉最后的分类层,输出一个 512 维(ResNet18)或 2048 维(ResNet50)的特征向量,然后用向量相似度做检索。

具体操作是:把model.fc换成一个恒等映射,前向传播拿到池化后的特征,对每头猪的注册图算一个平均特征向量存起来。新图来了,算特征向量,和库里所有向量算余弦相似度,取最高的那个作为预测结果。这样加新个体只需要加一条特征记录,不用重训。

# 特征提取模式 import torch.nn as nn def build_embedder(backbone='resnet18'): model = models.resnet18(pretrained=True) # 去掉最后的全连接层,保留池化输出 model.fc = nn.Identity() return model # 提取特征 embedder = build_embedder().to(device).eval() with torch.no_grad(): feat = embedder(tensor) # shape: [1, 512] feat = feat / feat.norm(dim=1, keepdim=True) # L2 归一化

L2 归一化之后,余弦相似度就等价于点积,算起来更快。检索时设一个相似度阈值,比如 0.75,低于这个值就判为「未知个体」,避免把新来的猪硬塞给某个已有 ID。这个阈值需要根据你的数据调,注册图越多、质量越好,阈值可以设得越高。

验证这套检索方案是否靠谱,我一般会做一件事:从每头猪的图里留出几张不参与注册,专门用来测试。如果这些留出的图能被正确检索到对应个体,且相似度明显高于其他个体,说明特征区分度够用。如果发现某两头猪的相似度一直在 0.9 以上,那要么是数据里这两头本来就难分,要么是特征维度不够,得换更强的骨干。

从那以后我每次拿到一个新的识别类工程包,都会先跑一遍特征提取、看一眼类间相似度分布,再决定是走分类还是走检索。这个习惯帮我省掉了好几次「训完才发现类别不可分」的后悔药。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 11:33:47

基于MAPPO的多无人机三维编队避障实现与训练调参实战

简介:MAPPO多无人机三维编队避障项目,围绕多智能体强化学习中的协同编队与动态避障问题展开,面向深度学习、人工智能方向的毕业设计、课程设计与期末大作业场景。压缩包共6个文件,含4个Python脚本、1个策略权重文件和1个Markdown说…

作者头像 李华
网站建设 2026/10/11 11:33:15

旧书数字化与AI数据管线:从扫描件到高质量训练语料

如果你关注人工智能行业动态,最近很可能刷到过一个话题:一些AI公司正在大量购买旧书,扫描完内容之后,甚至还会把纸质原书直接销毁。很多人把它当成猎奇新闻,但从数据工程师的视角看,这背后真正指向的&#…

作者头像 李华
网站建设 2026/10/11 11:31:35

Spring Boot家教管理系统:从业务闭环到工程化实践

1. 家教管理系统最容易被低估的部分:业务闭环这个题目在毕设和练手项目里出现频率极高,但十个人里有八个做成了"普通的后台增删改查":教师表、学生表、课程表、订单表,配上几个下拉框和表格页面,就能应付答辩…

作者头像 李华
网站建设 2026/10/11 11:29:18

手搓生产级 AI Agent 系统(29):MCP接入选型与架构梳理

传输方式:stdio与SSE的工程分野 根据当前搜索到的社区资料,MCP的传输方式被多次描述为两类:stdio和SSE,其中stdio被提及为更常用的方式。需要说明的是,这属于社区层面的归纳,并非官方规范原文,实…

作者头像 李华
网站建设 2026/10/11 11:29:16

复试倒计时14天:冲刺期最值得做好的几件事

复试第十四天。这个标题里的数字,我猜有两种读法:一种是倒计时,距离复试还有十四天;另一种是正计时,复试已经结束十四天。我是从第一种读法写起的,去年这个时候,我正坐在图书馆的角落&#xff0…

作者头像 李华
网站建设 2026/10/11 11:26:58

智慧学工一站式服务平台

✅作者简介:合肥自友科技 📌核心产品:智慧校园平台(包括教工管理、学工管理、教务管理、考务管理、后勤管理、德育管理、资产管理、公寓管理、实习管理、就业管理、离校管理、科研平台、档案管理、学生平台等26个子平台) 。公司所有人员均有多…

作者头像 李华