简介:这份资源是面向计算机、人工智能、电子信息等相关专业学生与初级开发者的本科毕业设计项目包,主题为基于Python深度学习的模糊人脸图像增强系统,可用于课程设计、大作业、毕设答辩或初期项目立项演示。压缩包共20个文件,约359KB,以9个py源码文件为核心,辅以6个pyc编译文件、2张png效果图、1个xml配置、1个txt日志和1个md说明文档,涵盖数据输入、模型定义、测试脚本与训练日志等模块,结构清晰便于二次开发。项目代码经过实际运行测试,功能正常,读者可参考其中的网络结构设计、数据加载流程与增强效果对比图,快速理解模糊人脸增强的完整实现思路,并在此基础上调整参数或替换数据集完成自己的课题。目前已有194人学习下载,适合作为深度学习入门实战与毕设参考案例。
1. 模糊人脸增强到底在做什么:从一张糊脸到可交付系统的距离
毕业设计选题里,「基于 Python 深度学习的模糊人脸图像增强系统」出现的频率高得离谱,但真正把它做成能演示、能答辩、能写进简历的系统,比例并不高。原因不是模型太难,而是大多数人一开始就没想清楚:模糊人脸增强不是简单地把图片调清晰,它要解决的是退化建模、人脸先验、感知质量三件事的联合问题。运动模糊、失焦、低分辨率、压缩噪声往往同时存在,单一去模糊网络直接套上去,结果通常是脸变「塑料」——纹理被抹平,五官边缘出现光晕。
这个方向适合两类人:一类是本科毕设需要完整跑通「数据—训练—推理—界面」链路的同学,另一类是已经会调cv2和 PyTorch,但没做过图像复原任务的工程师。它不需要你从零推导损失函数,但要求你能把 SRGAN、GFPGAN、Restormer 这类现成结构改对、训稳、接上界面。读完你应该能判断:自己的显卡能不能训、数据集怎么造、PSNR 和 LPIPS 打架时听谁的、答辩时老师问「为什么不用传统维纳滤波」该怎么答。
2. 系统拆解:数据、模型、损失、界面四段怎么串
2.1 先定退化管线,再谈模型选型
很多人上来就找「最强去模糊网络」,这是典型的翻车起点。人脸增强的效果上限由训练数据的退化分布决定,而不是网络深度。常见做法是构造一条可控退化管线:先对高清人脸做随机缩放(模拟低分辨率),再叠加高斯模糊或运动模糊核,最后加 JPEG 压缩噪声。这样你手里每一张低质图都有对应的 GT,训练才有监督信号。
import cv2 import numpy as np def degrade_face(img, scale=4, ksize=15, jpeg_q=40): """构造低质人脸样本:下采样 -> 高斯模糊 -> JPEG压缩""" h, w = img.shape[:2] # 1. 低分辨率退化 low = cv2.resize(img, (w // scale, h // scale), interpolation=cv2.INTER_CUBIC) low = cv2.resize(low, (w, h), interpolation=cv2.INTER_CUBIC) # 2. 模糊核,ksize 必须为奇数 if ksize % 2 == 0: ksize += 1 low = cv2.GaussianBlur(low, (ksize, ksize), sigmaX=0) # 3. JPEG 压缩噪声 _, enc = cv2.imencode('.jpg', low, [int(cv2.IMWRITE_JPEG_QUALITY), jpeg_q]) low = cv2.imdecode(enc, cv2.IMREAD_COLOR) return low逻辑说明:scale控制分辨率退化强度,毕设常用 4 或 8;ksize控制模糊半径,15 左右能模拟明显失焦;jpeg_q越低压缩块效应越重。参数说明:训练时这三个值应当随机采样而不是固定,否则模型只对一种退化有效,答辩演示换张图就崩。注意cv2.GaussianBlur的核尺寸必须是奇数,写偶数会直接抛异常,这是新手最常见的报错之一。
2.2 模型选型:三条路线的取舍
本科毕设的时间预算通常只有 8 到 12 周,模型选型要务实。下面这张表是我带过几届学生后总结的对比,参数是常见配置区间,不是某个仓库的固定值。
| 路线 | 代表结构 | 显存需求 | 训练难度 | 适用场景 |
|---|---|---|---|---|
| 轻量超分 | SRResNet / RRDB | 6–8G | 低 | 显卡一般,求稳毕业 |
| 生成式增强 | GFPGAN / CodeFormer | 10–12G | 中 | 要人脸细节,答辩好看 |
| Transformer 复原 | Restormer / SwinIR | 12G+ | 高 | 有基础,想冲优秀 |
选型理由:如果你的卡是 3060 12G 以下,老老实实走 SRResNet 加感知损失,能训完、能出图、能写论文。GFPGAN 这类带人脸先验的模型效果好,但它依赖人脸检测和对齐,多了一张脸没检测到就整张图不增强,这个坑后面会细说。Restormer 效果好但训练慢,毕设周期内调参空间小,除非你已经有训练经验。
2.3 损失函数:PSNR 高不等于看着好
这是整个系统最容易被忽略、也最影响答辩观感的部分。只用 L1 或 MSE 损失,模型会输出「平均脸」——PSNR 数值漂亮,但细节全糊。常见做法是组合损失:
import torch import torch.nn as nn class CombinedLoss(nn.Module): def __init__(self, w_pix=1.0, w_per=0.1, w_gan=0.01): super().__init__() self.w_pix, self.w_per, self.w_gan = w_pix, w_per, w_gan self.l1 = nn.L1Loss() def forward(self, pred, gt, feat_pred=None, feat_gt=None, d_out=None): loss = self.w_pix * self.l1(pred, gt) # 感知损失:VGG 特征层距离,缓解过度平滑 if feat_pred is not None: loss += self.w_per * self.l1(feat_pred, feat_gt) # 对抗损失:提升纹理真实感 if d_out is not None: loss += self.w_gan * (-d_out.mean()) return loss逻辑说明:像素损失保底结构,感知损失补纹理,对抗损失提真实感。参数说明:w_per一般 0.05–0.2,太大画面会飘;w_gan通常 0.001–0.01,太大训练震荡、出现棋盘伪影。注意对抗损失一旦开启,PSNR 会下降 0.5–1.5 dB,这是正常的,答辩时要能解释「感知质量和像素指标存在 trade-off」,而不是被老师一问就慌。
2.4 推理与界面:把模型接成能演示的系统
毕设答辩现场最忌讳「只能跑命令行」。用 PyQt5 或 Gradio 包一层,输入模糊图、输出增强图、显示耗时,就够用了。核心是推理时把模型切到eval()并关掉梯度,否则显存翻倍、速度减半。
import torch from PIL import Image import torchvision.transforms as T def enhance(model, img_path, device='cuda'): model.eval() tf = T.Compose([T.ToTensor()]) img = Image.open(img_path).convert('RGB') x = tf(img).unsqueeze(0).to(device) with torch.no_grad(): out = model(x) out = out.squeeze(0).clamp(0, 1).cpu() return T.ToPILImage()(out)逻辑说明:eval()关闭 BN 和 Dropout 的训练行为,no_grad()省显存。参数说明:clamp(0,1)防止输出越界导致保存出黑块。注意如果模型用了反射填充或分块推理,输入尺寸要能被整除,否则边缘会出现接缝,这个在演示大图时特别明显。
3. 训练流程:从数据准备到收敛的完整命令
3.1 环境配置与依赖版本
环境是毕设第一道坎。Python 建议 3.8–3.10,PyTorch 与 CUDA 版本必须匹配,否则torch.cuda.is_available()返回 False,你会对着黑屏调一整天。常见做法是用 conda 建独立环境,避免和系统 Python 打架。
conda create -n face_enhance python=3.9 -y conda activate face_enhance # 按显卡驱动选择对应 CUDA 版本,这里以 cu118 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python pillow numpy tqdm pyqt5逻辑说明:先建环境再装包,避免污染全局。参数说明:--index-url指定 PyTorch 官方源,比默认源快且版本全。注意不要混用 pip 和 conda 装同一个包,容易出现 DLL 冲突,Windows 上尤其明显。装完先跑一句python -c "import torch; print(torch.cuda.is_available())",输出 True 再往下走。
3.2 数据集组织与加载
人脸数据集常见的有 CelebA、FFHQ,毕设用 CelebA 的子集就够。目录按train/hr、train/lr分开,或者只放 HR,退化在__getitem__里实时做。后者更省磁盘,也更灵活。
from torch.utils.data import Dataset from PIL import Image import os class FaceDataset(Dataset): def __init__(self, hr_dir, transform=None, degrade_fn=None): self.files = [f for f in os.listdir(hr_dir) if f.endswith(('.jpg', '.png'))] self.hr_dir = hr_dir self.transform = transform self.degrade_fn = degrade_fn def __len__(self): return len(self.files) def __getitem__(self, idx): hr = Image.open(os.path.join(self.hr_dir, self.files[idx])).convert('RGB') if self.transform: hr = self.transform(hr) lr = self.degrade_fn(hr) if self.degrade_fn else hr return lr, hr逻辑说明:实时退化让每个 epoch 看到的低质样本都不同,相当于数据增强。参数说明:transform里做随机裁剪到 256×256,太大显存吃不住,太小细节学不到。注意os.listdir顺序不保证稳定,多卡训练时建议先排序,否则各进程数据对不齐。
3.3 训练循环与关键超参
训练循环本身不复杂,难的是超参和日志。下面是一个最小可跑版本,重点是梯度裁剪和定期保存。
import torch from torch.optim import Adam from tqdm import tqdm def train(model, loader, epochs=100, lr=1e-4, device='cuda', ckpt='ckpt.pth'): model.to(device).train() opt = Adam(model.parameters(), lr=lr, betas=(0.9, 0.99)) criterion = torch.nn.L1Loss() for ep in range(epochs): pbar = tqdm(loader, desc=f'Epoch {ep}') for lr_img, hr_img in pbar: lr_img, hr_img = lr_img.to(device), hr_img.to(device) pred = model(lr_img) loss = criterion(pred, hr_img) opt.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) opt.step() pbar.set_postfix(loss=loss.item()) if (ep + 1) % 10 == 0: torch.save(model.state_dict(), ckpt)逻辑说明:betas=(0.9, 0.99)是图像复原任务常用配置,比默认 0.999 收敛快。参数说明:lr=1e-4是起点,训练后期可降到 1e-5;clip_grad_norm_的max_norm=1.0防止梯度爆炸,生成式模型尤其需要。注意每 10 个 epoch 存一次,别只存最后一个,训练崩了还有后悔药。
3.4 验证指标与可视化
训练时只看 loss 会被骗,必须定期在验证集上算 PSNR 和 SSIM,并保存对比图。PSNR 用skimage.metrics.peak_signal_noise_ratio,SSIM 用structural_similarity,注意输入要转成 float 且范围一致。
from skimage.metrics import peak_signal_noise_ratio as psnr from skimage.metrics import structural_similarity as ssim import numpy as np def evaluate(pred, gt): pred = pred.detach().cpu().numpy().transpose(1, 2, 0) gt = gt.detach().cpu().numpy().transpose(1, 2, 0) p = psnr(gt, pred, data_range=1.0) s = ssim(gt, pred, channel_axis=2, data_range=1.0) return p, s逻辑说明:data_range=1.0对应归一化后的图像,写错会得到离谱数值。参数说明:channel_axis=2是 skimage 新版本参数,老版本用multichannel=True,版本不对会报错。注意 PSNR 对亮度敏感,SSIM 对结构敏感,两个一起看才靠谱。
4. 避坑与排查:那些让毕设延期的真实问题
4.1 现象:训练 loss 不降,输出全是灰图
原因:学习率过大或数据归一化不一致。常见的是 HR 用ToTensor()归一化到 [0,1],但退化函数里cv2读进来是 [0,255],两者混用模型直接学废。解决:统一在 tensor 层面做退化,或者退化后手动除以 255,训练前打印一个 batch 的 min/max 确认范围。
4.2 现象:人脸检测不到,整张图不增强
原因:GFPGAN 这类模型依赖人脸对齐,侧脸、遮挡、小脸都会漏检。解决:加一个兜底分支,检测失败时走通用超分网络,或者放宽检测阈值并做多尺度检测。答辩演示前一定要用现场要展示的图测一遍,别用训练集里的图自欺欺人。
4.3 现象:显存溢出,batch size 降到 1 还报错
原因:输入分辨率太大,或者推理时没关梯度。解决:训练裁剪到 256,推理用分块处理;确认torch.no_grad()加上;如果还不行,把模型中间特征通道数减半,毕设不追求 SOTA,能跑通优先。
4.4 现象:输出图有网格状接缝
原因:分块推理时块与块之间没有重叠,或者填充方式不一致。解决:分块时设置 overlap 为块大小的 1/4,拼接时取重叠区中心;或者直接用整图推理,显存不够再考虑分块。
4.5 现象:PSNR 很高但肉眼看着糊
原因:只用像素损失,模型输出平均解。解决:加入感知损失和对抗损失,接受 PSNR 下降;答辩时准备一组对比图,左边纯 L1、右边组合损失,直观说明感知质量差异,这比堆指标更有说服力。
5. 进阶技巧:用参考指标和消融实验把毕设做出深度
走到这一步,系统能跑、能演示、能出图,但想拿优秀还得有「分析」。最省力的进阶方式是做消融实验:固定其他条件,分别去掉感知损失、去掉对抗损失、换不同退化强度,记录 PSNR/SSIM/LPIPS 三组指标。LPIPS 用lpips库,它比 PSNR 更贴近人眼判断,答辩时放一张指标表,老师立刻能看出你做过对比。
import lpips import torch loss_fn = lpips.LPIPS(net='alex').to('cuda') # 输入需归一化到 [-1, 1] def calc_lpips(pred, gt): pred = pred * 2 - 1 gt = gt * 2 - 1 return loss_fn(pred, gt).item()逻辑说明:LPIPS 基于 AlexNet 特征,值越低越接近人眼感知。参数说明:net='alex'比vgg轻,速度快适合批量评估;输入范围必须是 [-1,1],忘了这步结果全错。注意 LPIPS 首次运行会下载权重,提前在能联网的环境跑一次,别到答辩现场才下载。
另一个加分项是做退化强度鲁棒性曲线:横轴是模糊核大小或压缩质量,纵轴是 PSNR,画出模型在不同退化下的表现。这条曲线能回答「你的系统到底能扛多糊的图」,比一句「效果良好」有分量得多。我一般会取 5 个退化等级,每个等级 50 张验证图,跑完画折线,代码不到 30 行,但论文里能占一整节。
最后一个血泪经验:答辩前一周不要再改模型结构。把时间花在整理对比图、录演示视频、写清楚每个参数的含义上。模型训到 80% 效果就够毕业,剩下 20% 的提升往往要再花两周,性价比极低。把系统跑稳、把故事讲圆,比追那零点几个 dB 重要得多。希望帮到你。
本文还有配套的精品资源,点击获取