ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PyTorch人脸性别识别GUI:从模型到毕设交付全流程

PyTorch人脸性别识别GUI:从模型到毕设交付全流程 简介这份资源面向计算机、人工智能相关专业的本科生与自学者提供一套基于 Python 与 PyTorch 实现的人脸性别识别完整方案可直接作为课程大作业或毕业设计参考。数据集涵盖白种人、黄种人、黑种人等多种族样本并包含姿态、光照、年龄等干扰因素训练、验证、测试集按 40%、10%、50% 随机划分具有一定挑战性。压缩包共 18 个文件约 1.21MB以 13 个 py 脚本为核心涵盖数据加载、网络模型定义、多版本训练与推理流程另附 docx 实验报告、md 说明文档、txt 姓名标签及 license 文件结构清晰便于复现。资源已有 648 人学习下载。读者可获得从数据预处理、模型搭建到测试评估的完整代码链路报告中对所用模型方法、测试结果及学术文献引用均有说明并额外提供 GUI 界面脚本支持输入图像后直接显示性别识别结果适合需要独立完成建模与界面演示的读者参考。1. 从一张毕设封面说起人脸性别识别 GUI 到底要交付什么每年五六月实验室里总有人抱着一台笔记本问同一个问题能不能用 Python 和 PyTorch 做一个能点开、能选图片、能出结果的人脸性别识别 GUI最好还能当毕设交上去。这个标题拆开看其实有三层交付物一个能跑通的 PyTorch 性别分类模型、一个能交互的图形界面、一份能写进论文的报告。很多人卡住不是因为模型难而是因为把这三件事搅在一起做最后界面能点但模型不收敛或者模型准了但界面一打包就崩。我做过几版类似的系统最稳的路线是先把推理脚本跑通再套 GUI最后补报告。性别识别本身是二分类输入是人脸区域输出是男/女加置信度。它不需要你从零训练一个 ResNet迁移学习加几千张图就能到 95% 以上。GUI 用 Tkinter 或 PyQt 都行Tkinter 自带、打包简单PyQt 好看但体积大。这篇笔记按「模型怎么来 → 界面怎么接 → 坑在哪 → 怎么验证」的顺序讲适合要交毕设的学生也适合想快速搭一个本地人脸属性 demo 的工程师。下面所有代码都是可复现的最小版本不依赖任何不存在的仓库。2. 模型选型与数据准备为什么用迁移学习而不是从零训2.1 性别识别的本质与迁移学习为什么更稳性别识别在人脸属性里属于低维二分类特征主要来自眉骨、下颌、胡须区域和发型轮廓。从零训一个 CNN 需要几十万张标注图而公开性别数据集通常只有几万张直接训容易过拟合。迁移学习的做法是拿 ImageNet 上预训练的 ResNet18 或 MobileNetV3把最后的全连接层换成二输出冻结前面的卷积层只训分类头。这样即使只有 5000 张图也能在 10 个 epoch 内收敛到 90% 以上。选 ResNet18 还是 MobileNetV3 取决于部署环境。如果 GUI 要打包成 exe 发给老师MobileNetV3-Small 权重只有 10MB 左右CPU 推理单张 30msResNet18 权重 45MBCPU 单张 80ms。毕设场景我一般推荐 MobileNetV3因为老师不会等你装 CUDA纯 CPU 也能演示。如果你有显卡ResNet18 精度会高 1 到 2 个百分点。数据方面常见做法是用 Kaggle 的 Gender Classification Dataset 或 UTKFace 里带性别标签的部分。UTKFace 文件名里直接编码了年龄、性别、种族解析方便。注意要自己做一次人脸检测裁剪否则背景会干扰分类。检测用 OpenCV 的 DNN 人脸检测器或 MTCNN前者轻量后者准但慢。2.2 数据清洗与 Dataset 代码拿到图之后先做三件事去掉性别标签缺失的、去掉人脸检测失败的、把尺寸统一到 224×224。下面是一个自定义 Dataset假设数据已经按male/和female/分好文件夹。import os from PIL import Image from torch.utils.data import Dataset, DataLoader from torchvision import transforms class GenderDataset(Dataset): def __init__(self, root, modetrain): self.samples [] # 约定root/male/*.jpg 标签为 0root/female/*.jpg 标签为 1 for label, name in enumerate([male, female]): d os.path.join(root, name) for f in os.listdir(d): if f.lower().endswith((.jpg, .png, .jpeg)): self.samples.append((os.path.join(d, f), label)) # 训练集做增强验证集只做归一化 if mode train: self.tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(0.2, 0.2, 0.2), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) else: self.tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) def __len__(self): return len(self.samples) def __getitem__(self, idx): path, label self.samples[idx] img Image.open(path).convert(RGB) return self.tf(img), label这段代码的关键点是标签映射必须固定male0、female1后面推理时才能对应。RandomHorizontalFlip对性别识别是安全的因为左右翻转不改变性别但不要用RandomRotation大角度人脸转 90 度会破坏结构。归一化参数用的是 ImageNet 的均值方差因为骨干网络是在 ImageNet 上预训练的必须保持一致。DataLoader 的num_workers在 Windows 上设 0 或 2设大了容易在打包后卡死。batch_size显存 4G 用 32纯 CPU 用 16。2.3 训练脚本与关键参数训练部分用标准的交叉熵加 AdamW学习率分两段先冻结骨干只训分类头3 个 epoch 后解冻最后两个 block 微调。import torch import torch.nn as nn from torchvision.models import mobilenet_v3_small from torch.utils.data import DataLoader def build_model(): model mobilenet_v3_small(weightsIMAGENET1K_V1) # 替换分类头为二分类 in_features model.classifier[3].in_features model.classifier[3] nn.Linear(in_features, 2) return model def train(root, epochs15, bs32, lr1e-3): device torch.device(cuda if torch.cuda.is_available() else cpu) model build_model().to(device) train_ds GenderDataset(root, train) val_ds GenderDataset(root, val) train_loader DataLoader(train_ds, batch_sizebs, shuffleTrue, num_workers0) val_loader DataLoader(val_ds, batch_sizebs, shuffleFalse, num_workers0) # 第一阶段只训分类头 for p in model.features.parameters(): p.requires_grad False optimizer torch.optim.AdamW(filter(lambda p: p.requires_grad, model.parameters()), lrlr) criterion nn.CrossEntropyLoss() for epoch in range(epochs): if epoch 3: # 解冻最后两个 block 微调 for p in model.features[-2:].parameters(): p.requires_grad True optimizer torch.optim.AdamW(model.parameters(), lrlr * 0.1) model.train() for x, y in train_loader: x, y x.to(device), y.to(device) optimizer.zero_grad() loss criterion(model(x), y) loss.backward() optimizer.step() # 验证略见完整脚本 torch.save(model.state_dict(), gender_mobilenetv3.pth)参数说明lr1e-3是分类头阶段解冻后降到1e-4这是血泪经验不降的话预训练特征会被冲掉验证集准确率会先升后崩。epochs15对 5000 张图足够再多就过拟合。保存的是state_dict而不是整个模型这样加载时不受类定义路径影响打包更安全。3. GUI 搭建Tkinter 从选图到出结果的最小闭环3.1 为什么选 Tkinter 而不是 PyQt毕设 GUI 的核心需求是选图片、显示图片、点按钮出结果、显示置信度。Tkinter 是 Python 自带的不需要额外装 Qt 库PyInstaller 打包后体积小、依赖少。PyQt 界面更现代但打包时容易缺platforms插件老师电脑上双击没反应这种翻车我见过不止一次。如果你追求好看可以用ttkbootstrap给 Tkinter 换主题成本很低。界面布局分三块左边是图片显示区右边是按钮和结果文本区。图片用 PIL 读入后转成ImageTk.PhotoImage才能显示在 Label 上。注意PhotoImage必须保持引用否则会被垃圾回收导致图片不显示这是 Tkinter 最经典的坑。3.2 推理函数与界面绑定推理时要先做人脸检测裁剪出人脸再送模型否则整张图缩放后性别特征会被背景稀释。import cv2 import torch import numpy as np from PIL import Image, ImageTk import tkinter as tk from tkinter import filedialog from torchvision import transforms class GenderGUI: def __init__(self, model_path): self.device torch.device(cpu) self.model build_model() self.model.load_state_dict(torch.load(model_path, map_locationcpu)) self.model.eval() self.tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) # OpenCV 人脸检测器 self.face_net cv2.dnn.readNetFromCaffe( deploy.prototxt, res10_300x300_ssd_iter_140000.caffemodel) self._build_ui() def _build_ui(self): self.root tk.Tk() self.root.title(人脸性别识别系统) self.root.geometry(720x420) self.img_label tk.Label(self.root, text请选择图片, width50, height20) self.img_label.pack(sideleft, padx10, pady10) right tk.Frame(self.root) right.pack(sideright, filly, padx10, pady10) tk.Button(right, text选择图片, commandself.pick).pack(pady5) tk.Button(right, text开始识别, commandself.predict).pack(pady5) self.result tk.Label(right, text结果--, font(Arial, 14)) self.result.pack(pady20) self.photo None # 必须保留引用 def pick(self): path filedialog.askopenfilename(filetypes[(图片, *.jpg *.png *.jpeg)]) if not path: return self.path path img Image.open(path).convert(RGB) img.thumbnail((400, 400)) self.photo ImageTk.PhotoImage(img) self.img_label.config(imageself.photo, text) def predict(self): img cv2.imread(self.path) h, w img.shape[:2] blob cv2.dnn.blobFromImage(cv2.resize(img, (300, 300)), 1.0, (300, 300), (104.0, 177.0, 123.0)) self.face_net.setInput(blob) det self.face_net.forward() # 取置信度最高的人脸 best, box 0, None for i in range(det.shape[2]): conf det[0, 0, i, 2] if conf best: best conf box det[0, 0, i, 3:7] * np.array([w, h, w, h]) if box is None or best 0.5: self.result.config(text未检测到人脸) return x1, y1, x2, y2 box.astype(int) face Image.fromarray(cv2.cvtColor(img[max(0,y1):y2, max(0,x1):x2], cv2.COLOR_BGR2RGB)) tensor self.tf(face).unsqueeze(0) with torch.no_grad(): out torch.softmax(self.model(tensor), dim1)[0] label 男 if out.argmax().item() 0 else 女 self.result.config(textf结果{label} 置信度{out.max():.2%}) def run(self): self.root.mainloop()逻辑说明pick里thumbnail只改显示尺寸不改原图推理仍用原图保证精度。predict先用人脸检测网络拿到最高置信度框阈值 0.5 以下直接提示未检测到避免把背景当人脸。self.photo保留引用是必须的否则图片区域空白。torch.no_grad()关掉梯度CPU 推理快一倍。参数说明人脸检测输入固定 300×300均值(104, 177, 123)是 OpenCV 官方模型的要求不能改。置信度阈值 0.5 可以调到 0.6 减少误检但侧脸可能漏检。模型加载用map_locationcpu这样在有显卡的机器上训练、在没显卡的机器上演示都不会报错。4. 避坑与排查打包、路径、显存这五件事最容易翻车4.1 打包成 exe 后提示找不到模型文件现象PyInstaller 打包后双击 exe界面能开点识别报FileNotFoundError。原因代码里用的是相对路径gender_mobilenetv3.pth打包后工作目录变了。解决用sys._MEIPASS判断是否在打包环境把模型和 prototxt 一起打进datas。import sys, os def resource_path(rel): base getattr(sys, _MEIPASS, os.path.abspath(.)) return os.path.join(base, rel)打包命令加--add-data gender_mobilenetv3.pth;.Windows 用分号Linux 用冒号。4.2 训练时 loss 不降反而震荡现象前 3 个 epoch loss 正常下降解冻后突然飙到 2.0 以上。原因解冻后学习率没降预训练权重被大梯度破坏。解决解冻时把 lr 降到原来的十分之一并且只解冻最后两个 block不要全解冻。如果还震荡加weight_decay1e-4。4.3 验证集准确率 99% 但实际用全是男现象报告里准确率很高演示时女生照片也识别成男。原因数据集性别不平衡male 占 80%模型学会全预测 male。解决训练时用WeightedRandomSampler按类别频率加权或者损失函数加weighttorch.tensor([1.0, 3.0])。验证时看混淆矩阵不要只看准确率。4.4 GUI 点第二次选图后图片不刷新现象第一次选图正常第二次选图后显示区还是旧图。原因PhotoImage被覆盖后旧引用释放但 Label 没更新。解决每次pick都重新创建PhotoImage并赋给self.photo再config(image...)。不要复用同一个变量名而不重新赋值。4.5 CPU 推理一次要 3 秒现象演示时点按钮后卡 3 秒才出结果老师以为死机。原因模型在 CPU 上跑 224×224 的 MobileNetV3 正常只要 30ms卡是因为人脸检测用了 MTCNN 或者重复加载模型。解决模型在__init__里加载一次不要每次predict都load_state_dict。人脸检测用 OpenCV DNN不要用 MTCNN。如果还慢把输入降到 160×160精度掉 1% 左右速度翻倍。5. 报告怎么写才像做过指标、对比和可复现的验证方法5.1 毕设报告里必须有的三张表和一张图报告不是代码注释的堆砌评审老师看的是你有没有量化验证。我一般会放三张表数据集划分表训练/验证/测试各多少张男女各多少、模型对比表ResNet18 vs MobileNetV3 的准确率、参数量、CPU 单张推理时间、消融表冻结训练 vs 全量微调 vs 加数据增强。一张图是混淆矩阵热力图用 seaborn 画比准确率数字有说服力。测试集一定要单独留出来不能和验证集混。常见错误是训练时用验证集调参报告里又拿验证集当测试集报准确率老师一问就露馅。正确做法是 7:1:2 划分测试集只在最后跑一次。5.2 用测试集跑一次完整评估的脚本from sklearn.metrics import classification_report, confusion_matrix import seaborn as sns import matplotlib.pyplot as plt def evaluate(model, test_loader, devicecpu): model.eval() ys, preds [], [] with torch.no_grad(): for x, y in test_loader: out model(x.to(device)) preds.extend(out.argmax(1).cpu().numpy()) ys.extend(y.numpy()) print(classification_report(ys, preds, target_names[male, female])) cm confusion_matrix(ys, preds) sns.heatmap(cm, annotTrue, fmtd, xticklabels[male,female], yticklabels[male,female]) plt.savefig(confusion_matrix.png, dpi150)classification_report会给出每个类别的精确率和召回率如果 female 召回率明显低于 male说明数据不平衡问题没解决。混淆矩阵图直接放进报告比文字描述直观。跑完把confusion_matrix.png和报告一起交老师会觉得你确实跑过实验。5.3 一个我常用的验证习惯每次改完模型或数据增强我不会只看最终准确率而是先跑 20 张图的推理肉眼核对结果。因为准确率是统计值可能被某些类别拉高但肉眼能发现「所有戴眼镜的女生都被识别成男」这种系统性问题。这个习惯帮我省过很多次返工。另外模型文件命名带上日期和关键参数比如mobilenetv3_lr1e3_bs32_0612.pth不然一周后你自己都分不清哪个是哪个。希望帮到你。本文还有配套的精品资源点击获取
返回列表