☰
Python + AI 超分辨率:马赛克还原与图像修复实战
2026/10/11 21:44:07 网站建设 项目流程

简介:基于生成对抗网络(GANs)的马赛克照片高清还原Python项目,面向图像处理与深度学习方向的学习者、开发者,解决低清晰度人像重建问题。项目采用PULSE框架与StyleGAN模型,通过生成器与判别器的对抗训练,将马赛克化人脸逐步恢复出高清细节。资源共19个文件,以9个Python脚本(含PULSE.py、stylegan.py、align_face.py等核心代码)为主,辅以jpeg/png示例图、gif效果演示、md说明文档及模型参数pt文件,压缩包大小10.07MB。已有1761人学习下载。随包可获取完整源码结构、训练与推理流程、人脸对齐预处理工具及相关配置,便于复现实验,深入理解GANs在图像超分辨率与修复中的应用。

1. 马赛克还原不是玄学:Python + AI 超分辨率能修到什么程度

视频监控里截到一块模糊的车牌、老相册里扫出来的人脸全是色块、甲方丢来一张被微信压缩了十遍的截图——这种时候很多人会问:用 pythonAI 把马赛克照片还原高清,到底能不能做到?先给结论:能,但有一条明确的技术底线。所谓“还原”不是把马赛克下方的原始像素从黑匣子里掏出来,而是倚靠超分辨率模型重建可置信的高频纹理,让画面从“看不清”变成“看得清”。擅长这条路线的工具有 Real-ESRGAN、SwinIR、GFPGAN,配合 OpenCV 做前后处理,足以应付老照片修复、截图增强、监控图像预处理。本文按一条可落地的流水线来写:先选模型,再搭环境,再调参,最后把坑踩平。

2. 拆解还原链路:先分清马赛克类型,再选模型

2.1 你手上的“马赛克”到底属于哪三类损坏

很多人把“马赛克”当成一个笼统的词,但到了算法层面,三类的修复路径完全不同。第一类是像素化打码,也就是照片上被刻意打成一格一格的色块。第二类是 JPEG 压缩块效应,放大后能看到明显的方块边界和振铃纹,微信传图、视频截图大多是这种。第三类是低分辨率加噪点,比如老照片扫描件、手机夜景抓拍,画面上到处是颗粒和涂抹感。三者的退化模型不一样,修复时的前后处理也完全不同。

这里要说一句泼冷水的话:如果打码格只有几个像素宽,比如一个人脸被 8×8 的方块糊死,那原始信息在物理上已经永久丢失,模型能做的就是“编造”一张看起来合理的脸,并不是恢复你记忆里的那一个人。因此在正式投入之前,你得先评估退化程度。理性的做法是:把可用的修复范围限定在“格宽小于画面宽度 1/20、且保留了五官轮廓和边缘朝向”的图片上,这一类照片确实能被还原到可辨识级别;超过这个尺度,再好的模型也会翻车。

明确了这一点,再去选模型就不容易跑偏。我的经验是先把图片退化类型分出来:有大量可见的人脸就优先做人脸修复,有文字和表格就优先做结构保真,只有整体模糊就只做超分。

2.2 超分模型选型:Real-ESRGAN、SwinIR、GFPGAN怎么组合

现阶段不会有人再用 SRCNN 那种老古董去做落地项目,它训练时只模拟了高斯模糊下采样,遇到真实 JPEG 伪影和传感器噪声几乎毫无还手之力。行业里默认的三板斧是这样的:

模型适用场景典型优点主要缺点
Real-ESRGAN通用照片、视频截图、风景使用真实退化建模,抗锯齿和伪影能力强对文字和结构符号会“脑补”
SwinIR文字、二维码、结构边缘基于 Transformer 做局部注意力,结构更稳推理慢,显存占用高
GFPGAN / CodeFormer人脸特写、老照片修脸内置人脸先验,重建五官自然全身照过度“塑料化”,背景受影响
Real-ESRGAN + GFPGAN 串行带人脸的照片先恢复整体,再精修人脸参数组合多,需要手工调

以最常见的人像照片为例,我不会直接用 GFPGAN 处理整图,而是先跑一遍 Real-ESRGAN 做全局 4 倍放大,再用人脸检测把脸框切出来,单独用 GFPGAN 增强后再贴回去。这样背景的砖墙、草地在超分后追求纹理真实感,而五官由人脸先验模型来保底。

选型时的另一个现实约束是硬件。Real-ESRGAN 的 x4plus 模型约 64MB,GFPGAN 的 v1.4 权重约 340MB,这些都不是问题;问题在于大图推理时显存占用是分块大小决定的。如果机器只有 4GB 显存,就不要上手跑 1920×1080 的整图,老老实实开 tile=256。没有独显的机器跑 CPU 推理,一张 720p 图耗时大概两三分钟,批量处理建议先做小图实验再上量。

2.3 “多AI协作”的流水线:检测、超分、人脸增强不是同一个模型

新手最容易有的误解是:装一个“去马赛克”的库,然后输入一张图,输出就是高清图。真实落地的流水线长这样:

先做人脸检测,拿到坐标框;然后把脸框区域放大,单独走人脸增强模型;增强完把人脸贴回原图对应位置;接着整图走超分模型放大;最后做降噪、锐化、颜色空间校正。这一整条链路本质上是一次小型的“多AI协作”:检测网络负责定位,人脸增强负责重建五官,超分网络负责纹理,OpenCV 负责把它们粘在一起。

为什么要这么绕?因为单独跑整图人脸增强,背景会被修成“蜡像”,纹理和光泽全部失真;单独跑超分,人脸虽然清晰了,但五官会像假人。串联部署之后,照片才同时获得锐利的边缘和可信的五官。我一般在代码里把流水线写成可配置的开关:--face_enhance决定是否启用第二段,--denoise_strength控制前置降噪强度。这样在面对不同来源的图片时,不用改代码,只用改命令行参数。

3. 用 Python 3.8 搭环境并跑通第一张还原图

3.1 安装 Python 与依赖:conda 环境里装 cv2、numpy、PyTorch

先从安装说起。我习惯用 conda 创建独立环境,Python 版本固定用 3.8,不是因为它最新,而是 PyTorch、basicsr、facexlib 这一串依赖对 3.8 的兼容性磨合得最久。去 python 官网下载安装包也行,但如果你用的是 Linux 系统安装 python,务必不要动系统自带的那个解释器,否则 yum 和 gnome 的依赖会断。

conda create -n restore python=3.8 conda activate restore pip install opencv-python numpy torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install basicsr facexlib gfpgan realesrgan

第一行创建名为 restore 的新环境,第二行把它激活。第三条里的--index-url是指定 PyTorch 官方预编译的 wheel 地址,cu118 对应 CUDA 11.8;如果你的显卡不支持,就把 cu118 改成 cpu,代码逻辑不受影响。第四条里的basicsr是超分模型的基础工具包,facexlib负责辅助人脸检测,gfpgan和realesrgan是上层的可调用接口。

这一步最常出问题的是 OpenCV 和 numpy 版本冲突,症状是cv2导入时报numpy.ndarray size changed。解决办法是升级 numpy 到 1.24 以上,或者反过来锁定 numpy 1.23.5。装完之后先跑一句python -c "import cv2, torch; print(torch.cuda.is_available())",看到True再继续,这一步省得后面所有推理都默默跑 CPU。

3.2 用官方预训练模型跑通命令行版 Real-ESRGAN

环境就绪后,最快见效的方式是用 Real-ESRGAN 仓库里自带的推理脚本。从它的 GitHub Release 页面把RealESRGAN_x4plus.pth权重下载下来,放进weights目录,然后执行:

python inference_realesrgan.py -n RealESRGAN_x4plus -i input/ -o output/ -s 4 -t 512

参数里的-n指定模型结构名,-i是输入目录,-o是输出目录。-s 4表示把图片等比放大 4 倍,一张 320×240 的截图会变成 1280×960。-t 512是最关键的分块参数:推理时把图片切成 512×512 的小块逐块处理,避免整张大图把显存撑爆。如果输出图片之间有横向接缝,把-t调到 256 或 512 并增加内部tile_pad的默认值,一般能解决。

这条命令跑完后,先在输出目录里用小图检查效果:边缘有没有锯齿,皮肤有没有变成磨皮,远处楼房窗户有没有被画成乱码。如果一切正常,再进入下一节的批量处理。第一次跑如果显存不足崩溃,可以参考最后一条命令把环境变量CUDA_VISIBLE_DEVICES指到空闲显卡,或干脆换成 CPU 版推理。

3.3 写一个 Python 脚本:批量还原文件夹里的全部照片

命令行方式适合验证,真到了给几十张老照片批量修复时,还是得写 Python 脚本。下面是一个最小可用的批量版:

import cv2 import glob import os import numpy as np from basicsr.archs.rrdbnet_arch import RRDBNet from realesrgan import RealESRGANer model = RRDBNet( num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32 ) upsampler = RealESRGANer( scale=4, model_path='weights/RealESRGAN_x4plus.pth', model=model, tile=512, tile_pad=10, half=False ) os.makedirs('restored', exist_ok=True) for path in glob.glob('raw/*.jpg'): img = cv2.imread(path, cv2.IMREAD_COLOR) if img is None: print('跳过无法读取的文件:', path) continue out, _ = upsampler.enhance(img, outscale=4) cv2.imwrite('restored/' + os.path.basename(path), out) print('已处理:', path, '->', out.shape)

逻辑说明:RRDBNet构造了一个 23 个残差块的生成器网络,这个结构与x4plus权重是配套的,不能拿它去加载x2之类其他结构的文件。RealESRGANer里的tile=512启用分块,tile_pad=10是每块外扩 10 像素,让块与块之间有机会做融合。half=False表示关闭半精度推理;在 30 系以上显卡上可以改成 True,速度会快近一倍。

这里有一个新手最容易踩的坑:cv2.imread读进来的是 BGR 顺序,而很多人在深度学习里习惯了 RGB,顺手就对图片做了cvtColor。Real-ESRGAN 的内部约定是 BGR,你一旦先转了 RGB,输出颜色就会偏蓝偏黄。所以我在这段脚本里没有做任何颜色转换,直接让 OpenCV 的 BGR 输出和模型无缝衔接。

4. 针对不同照片调参数:人脸、老照片、大图的三种做法

4.1 人脸照片:接入 GFPGAN/CodeFormer 做第二段增强

全局超分对人脸只是让边缘更锐利,却无法重建被马赛克毁掉的眼睛和嘴型。带上人脸先验的 GFPGAN 可以理解“人脸应该长什么样”,在修复中实现五官定制化重建。下面是接入逻辑:

from gfpgan import GFPGANer face_enhancer = GFPGANer( model_path='weights/GFPGANv1.4.pth', upscale=2, arch='clean', channel_multiplier=2 ) _, _, out = face_enhancer.enhance( img, has_aligned=False, only_center_face=True, paste_back=True )

upscale=2控制人脸区域放大倍数,不要设得比全局超分还大,否则脸会喧宾夺主。only_center_face=True表示只增强画面中心的主体人脸,适合单人照;合影照片需要改成False,让检测器遍历所有人脸。paste_back=True会把修复后的人脸贴回原图位置,背景保持不变。

主观感受上,GFPGAN 和 CodeFormer 各有偏向。GFPGAN 的脸更亮更平滑,适合皮肤状态差的老照片;CodeFormer 的保真度更高,适合需要保留人物特征的情况。我的做法是两颗模型都跑一遍,然后放大了看眼睛虹膜和发丝边缘,谁更自然就选谁。这个环节没有标准答案,属于典型的“玄学”,但放大对比 200% 后你会有判断。

4.2 老照片和扫描件:先用去噪再超分,避免放大噪点

老照片的退化不只是低分辨率,还有大量胶片的颗粒噪声和扫描时的划痕。直接扔进超分模型,模型会把噪声当成纹理去增强,结果放大 4 倍后噪点跟着变大,画面反而更脏。正确顺序是先降噪、再超分。

denoised = cv2.fastNlMeansDenoisingColored( img, None, h=7, hColor=7, templateWindowSize=7, searchWindowSize=21 )

这段用的是 OpenCV 内置的非局部均值去噪。h是滤波强度,值越大抹得越平;对于轻度噪点 h=3 就够,中度噪点 h=7,过度曝光的老照片不要超过 10,否则脸会变成蜡像。templateWindowSize=7配合searchWindowSize=21是性能和效果的平衡点,想提速可以把 searchWindowSize 降到 11。

降噪之后我还习惯接一个轻量锐化,因为非局部均值去噪的本质是平滑,会让边缘变软:

sharpen_kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], dtype=np.float32) img = cv2.filter2D(denoised, -1, sharpen_kernel)

这个 3×3 卷积核中央为 5,周围为 -1,作用是增强相邻像素的差值。注意只做一遍,做两遍会出现白边。黑白照片则建议先把彩色图转成灰度,用fastNlMeansDenoising(不带 Colored 版本),去噪后再转回三通道交给超分模型。

4.3 大图分块推理:Tile 大小与接缝处理

大图分块是超分落地最核心的参数工程。tile 太小,块与块风格不统一,会出现马赛克般的接缝;tile 太大,显存直接溢出。我在一张 6000×4000 的扫描老照片上踩过的实用组合是这样的:

显存推荐 tiletile_pad备注
4GB25610适合批量处理
8GB384~51210~20画质与速度均衡
12GB+512~102420可跑高倍放大
CPU128~2565慢但稳定

如果你的业务图片尺寸超大,又不想被固定 tile 卡死,可以手动把图片切成有重叠的补丁,推理后只取每块中心区域回填。伪代码示意如下:

size, pad = 512, 20 step = size - pad * 2 for y in range(0, H, step): for x in range(0, W, step): patch = img[y:y+size, x:x+size] # 对 patch 单独 enhance,然后去掉边缘 pad 再写回 enhanced = upsampler.enhance(patch, outscale=4)[0] canvas[y:y+step, x:x+step] = enhanced[pad:-pad, pad:-pad, :]

重叠区域被抛弃后,接缝会大幅减少。这个方案的代价是重复推理区域比较多,速度会慢 20% 左右。如果你用的是 RealESRGANer,直接把tile=0让它内部自动处理即可,但前提是显卡得扛得住整图。

5. 避坑:马赛克还原最常见的五个翻车现场

5.1 现象:输出图整体偏色,绿叶变蓝,人脸发青

原因:绝大多数情况下是 RGB 与 BGR 通道顺序被来回交换。OpenCV 读图得到 BGR,模型内部按 BGR 训练,但很多模板代码里习惯先转成 RGB,导致模型看到的是被交换过的通道,输出自然偏色。解决:统一约定,全程 BGR,只在最终保存前判断后端格式。如果用了自研推理代码,用一张纯红图片做端到端测试,输出的红色值若是(0, 0, 255)说明通道被交换了。

5.2 现象:人脸被修成“塑料面具”,皮肤像橡胶

原因:GFPGAN 的人脸先验太强,配合过高的upscale,会把真实皮肤纹理全部抹掉;另外only_center_face=False时侧面脸也被强行“摆正”,效果更假。解决:把人脸增强的 upscale 降到 1~2,并把paste_back保持为 True;如果仍然发假,先用 Real-ESRGAN 放大到 2 倍,再跑人脸增强,最后再整体放大到 4 倍。

5.3 现象:显存不足,512×512 的块都跑不动

原因:half=False让模型以 FP32 精度推理,显存占用翻倍;或者tile设置过大。解决:优先把half改成True,在 30 系以上显卡上精度损失肉眼不可见;再把tile降到 256,tile_pad降到 10。如果还是溢出,检查是不是同时开了人脸检测,facexlib 的检测模型本身也要几百 MB 显存。把流水线改成“先超分保存结果,再另开进程做人脸增强”,显存压力会小很多。

5.4 现象:文字和二维码被 AI 重新画成了乱码

原因:生成式超分模型是按“自然纹理”训练的,看到文字时会在结构不确定的区域脑补相似笔画,结果把“第 3 行”画成“第 3 汗”。解决:对文字区单独用 SwinIR 这类保结构模型处理,或者把原图截出来,用 OpenCV 的插值放大后用cv2.adaptiveThreshold加强对比。马赛克太重的文字没有后悔药,只能人工描边后贴回。这是超分模型的边界,不该盲目追求“全自动”。

5.5 现象:黑白老照片被染上颜色,肤色发灰发黄

原因:模型默认输入是三通道彩色图,黑白图被当成低饱和彩色图去修复,色彩和亮度纠缠在一起。解决:检测输入图的饱和度,若cv2.calcHist发现彩色通道方差极小,就先把图转成灰度,用灰度模式跑超分,再把原图的明度通道与超分结果融合。更省事的做法是在 Real-ESRGAN 前把三通道复制成(gray, gray, gray),输出后再提取单通道,避免颜色猜测。

6. 最后一招:把还原质量量化,从“看着好用”到“可验收”

修图最容易陷入“自我感觉良好”。给业务方交付之前,我通常先跑两轮量化验证:有原图对照时算 PSNR 和 SSIM,没有原图参照时用 NIQE 做无参考评分。

import cv2, numpy as np def psnr(a, b): mse = np.mean((a.astype(np.float64) - b.astype(np.float64)) ** 2) if mse == 0: return float('inf') return 10 * np.log10(255.0 ** 2 / mse) def ssim(a, b, win=7): # 精简版,基于滑动窗口的均值与方差近似 C1, C2 = (0.01 * 255) ** 2, (0.03 * 255) ** 2 mu_a = cv2.blur(a, (win, win)); mu_b = cv2.blur(b, (win, win)) sigma_a2 = cv2.blur(a ** 2, (win, win)) - mu_a ** 2 sigma_b2 = cv2.blur(b ** 2, (win, win)) - mu_b ** 2 sigma_ab = cv2.blur(a * b, (win, win)) - mu_a * mu_b return np.mean(((2 * mu_a * mu_b + C1) * (2 * sigma_ab + C2)) / ((mu_a ** 2 + mu_b ** 2 + C1) * (sigma_a2 + sigma_b2 + C2)))

验证时我会故意把一张高清原图降采样再压缩,模拟被马赛克污染的过程,然后把 AI 还原结果和原图算 PSNR/SSIM。PSNR 超过 30 说明整体误差很小,SSIM 超过 0.9 说明结构一致性不错。没有原图时,N-IQE 分数越低越好,逻辑是把“自然图像统计特征”作为基准,评价画面是否符合真实世界的纹理分布。

这一套跑完,再拿放大镜看三类区域:发丝边缘、窗户横梁、文字笔画。前两者检查纹理真实感,第三个检查结构保真。我吃过一次亏:批量处理时直接让脚本覆盖了输入目录,结果质量不如意时,原图已经丢了,只能重新找甲方要大图。后来我养成了把所有原图复制一份到raw_backup的习惯,输出永远写进新目录,哪怕某个参数组合完全翻车,也能从头再来。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询