☰
基于波形扩散模型的低光照图像增强算法Python实现源码拆解
2026/10/12 17:22:50 网站建设 项目流程

简介:本资源为基于波形扩散模型的低光照图像增强算法Python实现源码,面向计算机、人工智能、电子信息等相关专业的在校学生、教师及企业研发人员,可用于课程设计、毕业设计、大作业或初期项目立项演示,帮助解决低光照图像质量提升的算法实现与复现问题。压缩包共34个文件,约21.1MB,以15个py源码文件为核心,辅以14个pyc编译缓存、2张png效果对比图、1个yml配置文件及txt、md说明文档,涵盖模型、数据集、工具与训练评估等模块。项目提供预训练模型和多个公开数据集,包含训练与评估脚本,并实现波形扩散、U-Net、图像恢复等关键组件,便于读者直接运行、理解算法流程并在此基础上修改扩展。目前已有491人学习关注,适合希望入门扩散模型与图像增强方向的学习者借鉴参考。

1. 低光照增强还能这么玩:波形扩散模型源码包拆解

夜里十一点,监控画面糊成一团,人脸只剩轮廓,车牌全是噪点——这是低光照图像增强最典型的战场。传统做法无非两条路:Retinex 系分解光照,或者 GAN 硬怼亮度,前者容易过曝、颜色发灰,后者训练玄学、细节靠猜。这份「基于波形扩散模型的低光照图像增强算法 python 实现源码.zip」走的是第三条路:把扩散模型里的加噪-去噪过程搬到小波域,让模型在频域里逐步恢复光照和纹理,而不是在像素空间里盲猜。包里带了train.py、evaluate.py、configs/LOLv1.yml、models/下的wavelet.py、ddm.py、unet.py、restoration.py,还有datasets/和utils/的采样、优化、日志模块,属于能直接跑训练和评估的完整工程,不是只丢一个模型定义的半成品。适合谁?做图像复原方向的学生、要交毕设或课程设计的同学,以及想拿扩散模型练手但不想从零搭训练框架的工程师。下面按「它怎么工作 → 怎么跑起来 → 坑在哪 → 怎么改」的顺序拆。

2. 波形扩散模型到底在做什么:从像素扩散到小波域扩散

2.1 为什么不在像素空间直接扩散

标准 DDPM 在像素空间加高斯噪声,低光照增强任务里有两个硬伤。第一,低光图像本身信噪比低,像素级噪声和真实纹理混在一起,扩散过程容易把暗部细节当成噪声抹掉。第二,像素空间维度高,LOLv1 这种 600×400 的图,扩散步数动辄上千,显存和推理时间都吃不消。波形扩散的思路是先把图像做小波变换,拆成 LL、LH、HL、HH 四个子带,LL 保留低频光照结构,高频子带承载边缘和纹理。扩散过程只在高频子带上做,低频部分用 restoration 模块单独回归。这样模型要学的分布更集中,步数也能压下来。常见做法是用 Haar 小波,计算量小、可逆,models/wavelet.py里大概率就是 DWT/IDWT 的实现。

2.2 源码里几个核心文件的分工

拿到包先别急着python train.py,花十分钟把文件关系理清楚,后面调参才不会抓瞎。按目录结构,核心链路是这样的:

文件职责你改它的频率
models/wavelet.py小波正变换与逆变换低,除非换小波基
models/ddm.py扩散调度:加噪、去噪、采样中,调步数和噪声表
models/unet.py去噪网络主干高,改容量和注意力
models/restoration.py低频光照恢复分支中,改损失权重时看
models/mods.py基础模块(卷积、归一化等)低
utils/sampling.py采样策略,DDIM 之类中,推理加速靠它
utils/optimize.py优化器与学习率调度中
configs/LOLv1.yml数据集与训练超参高,每次实验都动
datasets/dataset.py数据加载与配对中,换数据集要改
datasets/data_augment.py增强策略低

train.py是入口,读 config、建模型、跑循环;evaluate.py加载权重算 PSNR/SSIM。理解这个分工,你才知道改一个参数该去哪个文件。

2.3 扩散前向与反向在小波域的形式

前向过程还是标准的高斯加噪,只是作用对象换成了高频子带系数:

# 伪代码,对应 models/ddm.py 里的加噪逻辑 import torch def q_sample(x_high, t, noise, alphas_cumprod): """ x_high: 高频子带系数, shape [B, C, H, W] t: 时间步, shape [B] noise: 同 shape 的高斯噪声 alphas_cumprod: 累积 alpha 表, shape [T] """ sqrt_alpha = alphas_cumprod[t] ** 0.5 sqrt_one_minus = (1 - alphas_cumprod[t]) ** 0.5 # 按 batch 维度对齐形状 sqrt_alpha = sqrt_alpha.view(-1, 1, 1, 1) sqrt_one_minus = sqrt_one_minus.view(-1, 1, 1, 1) return sqrt_alpha * x_high + sqrt_one_minus * noise

逻辑说明:alphas_cumprod是预先算好的噪声调度表,t越大,sqrt_alpha越小,信号被噪声淹没得越彻底。参数上,T一般设 1000,但小波域扩散可以降到 250~500,configs/LOLv1.yml里应该有对应字段。反向过程由 UNet 预测噪声,再用采样器逐步还原,utils/sampling.py里如果实现了 DDIM,可以把推理步数从 1000 压到 50 步以内,这是实际部署时最值得动的地方。

3. 把源码跑起来:环境、数据、训练与评估的完整链路

3.1 环境依赖与安装顺序

requirements.txt在根目录,先看它再动手。PyTorch 版本是第一个坑,扩散模型对版本敏感,建议按下面顺序来:

# 1. 建虚拟环境,别用系统 python conda create -n wddm python=3.7 -y conda activate wddm # 2. 先装 PyTorch,按 CUDA 版本选,这里以 cu11.3 为例 pip install torch==1.10.0+cu113 torchvision==0.11.0+cu113 \ -f https://download.pytorch.org/whl/torch_stable.html # 3. 再装其余依赖 pip install -r requirements.txt

逻辑说明:先装 torch 再装 requirements,是因为 requirements 里可能写了torch>=1.8这类宽松约束,pip 会去拉最新版,和 CUDA 驱动对不上就报CUDA error: no kernel image is available。参数上,python 3.7 是包里__pycache__里.cpython-37.pyc透露的版本,用 3.8 以上可能遇到collections.Iterable这类废弃警告,能跑但烦。装完用python -c "import torch; print(torch.cuda.is_available())"验证,返回 True 再往下走。

3.2 数据集准备与 config 对齐

LOLv1 是低光照增强的经典配对数据集,结构是our485/low、our485/high、eval15/low、eval15/high。下载后按这个结构放,然后改configs/LOLv1.yml:

# configs/LOLv1.yml 关键字段示意 data: root: ./data/LOLv1 # 数据集根目录 train_split: our485 # 训练子集 val_split: eval15 # 验证子集 patch_size: 256 # 训练裁剪尺寸 batch_size: 8 # 显存 8G 以下建议降到 4 model: base_channels: 64 # UNet 基础通道 diffusion_steps: 500 # 小波域扩散步数 wavelet: haar # 小波基 train: lr: 1e-4 epochs: 200 save_interval: 10

逻辑说明:patch_size和batch_size是显存杀手,256×256 配 batch 8 大约吃 10G 显存,不够就砍 batch 或降到 128。diffusion_steps设 500 是精度和速度的折中,设 1000 训练慢一倍,设 250 暗部细节会糊。wavelet字段要和models/wavelet.py里支持的基一致,改之前先看代码里if wavelet == 'haar'这类分支。

3.3 启动训练与日志观察

python train.py --config configs/LOLv1.yml --gpu 0

逻辑说明:--config指定配置文件,--gpu选卡。训练开始后看utils/logging.py输出的日志,重点盯三个量:loss是否稳定下降、lr是否按调度衰减、psnr在验证集上是否爬升。如果 loss 在前 100 步就炸成 NaN,八成是学习率太大或数据没归一化到 [-1,1],去datasets/dataset.py里确认ToTensor之后有没有Normalize。如果 loss 不动,检查models/restoration.py的损失权重,低频分支权重过高会压制高频扩散的学习。

3.4 评估与指标复现

python evaluate.py --config configs/LOLv1.yml \ --checkpoint checkpoints/epoch_200.pth

逻辑说明:evaluate.py会加载权重,在eval15上算 PSNR 和 SSIM。LOLv1 上这类方法的 PSNR 通常在 20~24 dB 区间,SSIM 0.8 上下,具体看训练轮数和采样步数。如果指标远低于预期,先确认评估时用的采样步数和训练一致,DDIM 步数设太少会掉点。comparison.png是作者给的对比图,可以拿来对照自己的输出,看颜色偏移和细节恢复是否在同一水平。

4. 避坑与排查:跑不通、指标低、显存爆的常见原因

4.1 现象:训练启动就报ModuleNotFoundError: No module named 'models'

原因:train.py里用了相对导入或包导入,但你在错误的目录下执行,或者models/下缺__init__.py。包里models/__init__.py是有的,但如果你解压后手动挪过文件,可能丢了。

解决:确认在项目根目录执行,且models/、utils/、datasets/下都有__init__.py。缺的话手动建空文件,或者用python -m train代替python train.py。

4.2 现象:loss 正常下降,但验证集 PSNR 一直卡在 15 dB 以下

原因:低频和高频分支的损失权重失衡,或者数据配对错位。低光增强是配对任务,low和high文件名必须一一对应,dataset.py里如果按排序配对,文件名不一致就会错位。

解决:检查datasets/dataset.py的配对逻辑,打印几条样本的路径确认。损失权重在configs/LOLv1.yml或train.py里,把高频扩散损失权重调高,低频重建损失调低,常见比例是 1:0.5 到 1:0.2。

4.3 现象:CUDA out of memory,batch 已经降到 1 还爆

原因:UNet 通道数太大,或者patch_size没降。base_channels: 64在 256×256 下对 8G 卡就吃力,如果代码里还有中间特征缓存没释放,更紧张。

解决:先把patch_size降到 128,再把base_channels降到 32。另外在train.py里确认有没有torch.cuda.empty_cache(),没有的话在每个 epoch 结束后加一句。混合精度训练也能省一半显存,用torch.cuda.amp包一下前向。

4.4 现象:推理时输出全黑或全白

原因:采样过程的alphas_cumprod和设备不匹配,或者逆小波变换前没把子带拼回去。utils/sampling.py里如果用了torch.tensor建表但没.to(device),在 GPU 上会静默出错。

解决:检查采样代码里所有张量的 device,确保和输入一致。逆变换前打印一下四个子带的均值和方差,正常应该在合理范围,如果某个子带全是 0 或极大值,说明前向或反向某步断了。

4.5 现象:evaluate.py报KeyError或权重加载不上

原因:checkpoint 保存的是state_dict还是整个模型对象,加载方式不一样。train.py里如果torch.save(model.state_dict(), ...),评估时就要model.load_state_dict(torch.load(...)),多一层['model']或['state_dict']都会报错。

解决:用torch.load先打印 keys,看是OrderedDict还是嵌套字典,再决定取哪一层。常见做法是保存时统一存{'model': model.state_dict(), 'optimizer': ...},加载时按 key 取。

5. 进阶玩法:换小波基、压采样步数与自定义数据微调

跑通默认配置只是起点,这份源码真正的价值在于结构清晰,方便你按自己的任务改。下面三个方向是我实际会优先动的。

第一个是换小波基。models/wavelet.py里如果只实现了 Haar,可以换成 Daubechies 系列(db2、db4),高频子带保留的纹理更丰富,代价是计算量上升。改的时候注意正变换和逆变换必须成对,滤波器系数别抄错,否则重建图像会有网格伪影。换完在configs/LOLv1.yml里同步wavelet字段,跑 20 个 epoch 对比 PSNR,通常 db2 比 Haar 高 0.2~0.5 dB,但训练时间多 15% 左右。

第二个是压采样步数。训练用 500 步,推理用 DDIM 可以压到 50 步甚至 20 步。utils/sampling.py里找ddim_sample这类函数,把eta设为 0 就是确定性采样,步数用torch.linspace(0, T-1, num_steps)生成子序列。我一般会做一张步数-指标表:

采样步数PSNR (dB)单张推理耗时 (ms)
50023.1820
10022.8190
5022.4105
2021.648

从表里选拐点,50 步通常是性价比最高的位置,再往下掉点明显。

第三个是自定义数据微调。如果你手上有自己的低光-正常光配对数据,不用改模型结构,只改datasets/dataset.py的路径和configs里的root,然后把lr降到 1e-5,冻结models/wavelet.py和models/restoration.py的前几层,只微调 UNet 后半段。这样 50 个 epoch 就能适配新场景,比从头训练省一半时间。微调时记得把data_augment.py里的随机裁剪和翻转打开,小数据集上能防过拟合。

最后说个血泪经验:每次改完configs或模型结构,先跑python train.py --config ... --dry-run这类短跑模式(如果代码支持),或者手动把epochs设成 1,确认前向反向能走通、显存不爆、loss 不是 NaN,再开完整训练。我早期图省事直接上 200 epoch,结果第 3 个 epoch 才报错,一晚上白等。从那以后我每次动配置都强制走一遍单步验证,这个习惯帮我省了太多后悔药。希望帮到你。

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

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

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

立即咨询