简介这是一份面向计算机视觉研究者与深度学习初学者的图像去雾算法工程包基于VisionTransformer架构实现端到端去雾模型训练与测试。资源针对NH-HAZE等标准数据集提供从图像切分为256×256小图的数据预处理、Uformer网络训练、参数配置到结果可视化的完整代码链路适合需要复现去雾SOTA方案或开展毕业设计、论文实验的读者。压缩包共340个文件约156.42MB其中包含204个Python源码文件、16个YAML配置、12个CSV训练日志、10个GIF演示图以及多组损失景观与精度对比数据另有9个IPython Notebook便于分步讲解实验过程9个TXT说明辅助理解项目结构。当前已有343人学习下载资源组织清晰Py文件与配置分离可直接在Linux环境下按说明运行。除核心训练推理代码外包内还附带项目说明、目录图示与可视化结果能够帮助使用者快速搭建去雾实验环境理解VisionTransformer在底层视觉任务中的调参与优化思路。1. VisionTransformer 图像去雾复现这份包能帮你解决什么雾天图像对比度低、细节糊常规暗通道先验在轻雾下还能应付浓雾场景透射率估计失真输出图偏色重。基于 VisionTransformer 的图像去雾把它当成图像重建任务用自注意力建模整张图的雾分布在真实雾图上表现更稳。这份资源是完整的 Python 复现包generate_patches_SIDD.py 负责把 NH-HAZE 真实雾图切成 256×256 补丁My_train.py 负责训练 Uformer 主干项目说明里给了数据路径压缩包还带一批实验用 loss landscape CSV。适合做相关方向毕业设计、想对比 CNN 与 Transformer 去雾差距、或要在 Uformer 上二次开发的从业者。前置条件是 Linux 环境加一块 16GB 以上显存的 NVIDIA 卡准备好后按预处理、训练、验证三步走就能复现。2. 去雾问题的建模选择从暗通道先验到 Uformer 自注意力2.1 大气散射模型和真实雾图的差异图像去雾绝大多数方法都从大气散射模型出发这个模型把有雾观测图 I(x) 描述成无雾图 J(x) 经过透射率 t(x) 衰减后再叠加全局大气光 A 的结果I(x) J(x)·t(x) A·(1 - t(x))。x 是像素坐标t(x) 在 0 到 1 之间雾越浓的地方 t 越小。传统暗通道先验的核心假设是无雾图像的每个局部窗口里至少有一个颜色通道的像素值接近 0。基于这个假设可以从有雾图的暗通道里估出透射率再结合大气光反解出 J。这套思路对蓝天、绿树、柏油路这类场景非常有效因此被广泛应用。但暗通道假设不是永远成立。大片白色墙体、天空区域、玻璃反光面都会让暗通道失效浓雾均匀区里透射率 t 接近 0反解出来的 J 对误差极其敏感两个误差叠加输出图就会出现明显光晕和色偏。我用暗通道先验处理 NH-HAZE 这类真实雾图时最常见的结果是远景发灰、近景偏绿这就是估计误差放大的表现。学习式方法绕开“先估 t 再反解 J”的显式分解让网络直接从 I 映射到 J。这里有个关键区别RESIDE 这类合成雾图是用渲染器按深度加雾的雾分布相对均匀和暗通道假设较契合而 NH-HAZE 是自然雾雾的浓度受湿度、气温、光照方向影响同一张 4K 图里薄雾和浓雾区域交织空间变化复杂得多。模型如果只看局部区域很容易把浓雾区判断成“本来就该模糊”的场景所以必须有全局感知能力。提示判断一个去雾模型能不能上真实场景先看它在 NH-HAZE 或 O-HAZE 上的结果别只拿 RESIDE 的合成雾测试集说事。2.2 从 ViT 到 Uformer自注意力怎么改善去雾VisionTransformer 进入视觉的第一步是把图像切成固定大小的 patch比如 16×16然后每个 patch 拉平成向量、做线性投影得到一串 token再加上位置编码表示每个 patch 在哪。Transformer encoder 里的自注意力层会计算任意两个 token 之间的相关性也就是说任何一个 patch 的输出特征都可以参考整张图任意位置的信息。放在去雾任务里这正好对应“远处雾浓、近处雾薄”这种跨空间依赖模型可以直接看到整张图的雾浓度变化而不是靠卷积一层层往外扩。位置编码在去雾里也不是可有可无的。patch 本身是局部块没有位置信息的话模型分不清它看到的是天空还是地面这对判断大气光影响范围很不利。标准做法是用可学习的位置编码让网络自己学到不同位置 patch 的相对关系这个细节在 Uformer 里也有保留。那为什么不能直接拿标准 ViT 做去雾主要卡在两点一是标准 ViT 输出的是下采样后的 token 序列要恢复到原分辨率得接很重的解码器二是全局自注意力的计算复杂度是序列长度的平方4K 输入根本跑不动。Uformer 的解法是用 U 型编码器-解码器结构下采样路径逐层缩小特征图每层内部用局部窗口自注意力LeWin block控制计算量再通过移位窗口让信息跨窗口流动上采样路径用跳跃连接把细节带回来。这样既保留了 Transformer 全局交互的长处又继承了 U-Net 多尺度重建的结构是复原任务里比较稳的落地形态。三种结构的取舍用表格看更清楚结构感受野特点真实雾图表现显存成本U-NetCNN靠堆层数扩大深层信息易丢均匀雾可用浓雾边缘糊中标准 ViT全局自注意力一步到位理论强数据量和显存门槛高很高Uformer局部窗口加跨窗口细节与全局平衡复现性好中高这就是为什么训练命令里 --arch 填 Uformer仓库把 VisionTransformer 的核心思想浓缩在自注意力机制里而不是让你去硬套一个为分类设计的 ViT。你拿到源码后可以直接比较这两种主干在 NH-HAZE 上的 loss 曲线。2.3 先看清包里有什么再动手把压缩包解开后重点看四样东西generate_patches_SIDD.py 是切块预处理脚本My_train.py 是训练脚本项目说明文档记录了数据集下载位置和路径结构还有一批 CSV 文件。CSV 文件名字像 cifar100_vit_ti_losslandscape.csv、cifar100_resnet_dnn_50_losslandscape.csv这些是作者做损失地形分析时采样的数据不是去雾训练集别误当成数据集喂进模型。真正的训练数据在 NH-HAZE 的 train 目录下里面是成对的有雾图和清晰图原图分辨率很高。环境上我建议 Python 3.8 到 3.10PyTorch 1.10 以上CUDA 11.x。Uformer 对显存比较敏感16GB 显存是起步24GB 会更舒服如果你的卡只有 12GB后面会讲怎么调参硬跑。3. 数据预处理用 generate_patches 把 NH-HAZE 切成 256×256 补丁3.1 为什么要先把 4K 原图切块NH-HAZE 的高分辨率原图拿来做训练第一关就过不去显存根本装不下。就算勉强塞进去图像里大部分区域的雾变化其实很平缓一整张大图只算一个样本信息利用率很低。切成 256×256 的补丁后一张原图能产生几十个训练样本放到现在这个数据量规模下几乎等于是免费做了一次数据增广。预处理这一步做对了后面训练和评估都会顺很多。切块时候要注意配对关系有雾图和清晰图必须是同一场景、同一坐标。常见做法是先用文件名排序把两张图配成一对然后按同一个滑动窗口坐标去切。我见过有人图省事有雾图和清晰图各切各的结果训练时模型学到的映射关系完全错乱。3.2 跑通 generate_patches_SIDD.py作者在项目说明里给出的命令是这样的python3 generate_patches_SIDD.py \ --src_dir /home/dell/桌面/TPAMI2022/Dehazing/#dataset/NH_haze/train \ --tar_dir /home/dell/桌面/2022毕业设计/Datasets/NH-HAZE/train_patches这段命令本身没什么问题但注意路径里有空格和中文在 bash 里直接跑很容易被解析拆开。我复现时把数据复制到纯英文路径下再执行python3 generate_patches_SIDD.py \ --src_dir /data/NH_Haze/train \ --tar_dir /data/NH_Haze/train_patches--src_dir 指向 NH-HAZE 训练集路径里面应该有两个子目录一个放有雾图一个放清晰图--tar_dir 是补丁输出目录脚本会自动创建。跑完之后先别急着训练打开输出目录抽查几张尺寸必须是 256×256而且有雾图和清晰图文件名应该一一对应。3.3 切块脚本的核心逻辑和参数调整这段逻辑是脚本里最常见的主循环我按常见实现还原的# generate_patches_SIDD.py 核心循环示例 import cv2 import os patch_size 256 # 补丁尺寸可按需调整 def iter_patches(img): h, w img.shape[:2] for y in range(0, h - patch_size 1, patch_size): for x in range(0, w - patch_size 1, patch_size): yield y, x, img[y:y patch_size, x:x patch_size] for hazy_path, gt_path in paired_files: # 先按文件名排序配对 hazy cv2.imread(hazy_path) gt cv2.imread(gt_path) for (y, x, ph), (_, _, pg) in zip(iter_patches(hazy), iter_patches(gt)): cv2.imwrite(os.path.join(tar_dir, f{basename(hazy_path)}_{y}_{x}.png), ph) cv2.imwrite(os.path.join(tar_dir, f{basename(gt_path)}_{y}_{x}.png), pg)滑窗步长等于 patch_size块与块不重叠避免同一像素被反复算进多个样本导致过拟合某块区域。hazy 和 gt 共用同一个 (y,x) 坐标文件名后缀也一致配对关系不会乱。如果原图尺寸不是 256 的整数倍脚本会丢边缘我一般会先把原图做一次中心裁剪裁到 256 的整数倍再切减少信息浪费。参数怎么调patch_size 调大单块上下文更足但样本数量变少调小样本多但每块只覆盖很小范围模型学不到跨区域的雾分布。256 是我试下来比较稳的值最低不建议低于 128。切完补丁后训练阶段还可以再加增广水平翻转、90 度旋转、随机亮度扰动注意有雾图和清晰图必须用同一组变换否则配对又废了。这块脚本里如果没写我一般会在 My_train.py 的数据加载器里补。4. 训练一个去雾模型My_train.py 七个参数逐个拆解4.1 训练命令和每个参数的含义预处理完就可以进训练阶段。作者给的命令是python3 ./My_train.py \ --arch Uformer \ --nepoch 270 \ --batch_size 32 \ --env My_Infor_CR \ --gpu 1 \ --train_ps 128 \ --train_dir /media/dell/这七个参数是这份资源里最值得先读明白的部分参数含义我的建议--arch模型结构仓库默认 Uformer理解为 Transformer 去雾主干--nepoch训练轮数270 是作者设定实际按验证集曲线早停--batch_size批大小32 在 128×128 输入下约吃掉 20GB 显存按卡减半--env实验标识改成 NH-HAZE_lr1e4 这种日志权重好区分--gpu显卡编号1 是第二张卡单卡填 0--train_ps训练输入随机裁剪尺寸128显存不足降到 96--train_dir训练数据目录应指向 train_patches不是 NH_haze 根目录特别说下 --train_dir。作者原命令写到 /media/dell/ 就停了这是他机器上的挂载点照抄肯定读不到数据。我在复现时直接指向上一步生成的 train_patches 绝对路径。很多人训练 loss 不降排查半天最后发现就是这里指到了数据根目录数据集本身没被真正加载。4.2 损失函数、优化器和检查点设置图像复原任务的损失函数基本有一个标配组合L1 损失加感知损失。L1 负责像素级接近感知损失用预训练分类网络常见是 VGG的中间层特征计算距离防止模型把输出磨得太光滑。My_train.py 里具体权重我记不太清但结构不外乎 loss L1 λ * L_perceptual。想调的话 λ 从 0.01 到 0.1 之间试真实雾图上 λ 太小结果偏糊λ 太大纹理区域会冒出不自然的细节。优化器用 AdamW初始学习率 1e-4 到 2e-4前 5 轮 warmup再走 cosine 退火。270 轮不是硬指标我在 24GB 卡上跑到 150 轮左右验证集 PSNR 就不怎么涨了这时候早停比硬跑完省一半时间。长时间训练最怕中途断掉。我习惯在源码里补一段检查点保存逻辑# 每个 epoch 结束后保存用于中断恢复 torch.save({ model: model.state_dict(), optimizer: optimizer.state_dict(), epoch: epoch, best_psnr: best_psnr, }, fcheckpoint_{env}.pth)说明保存 state_dict 而不是直接保存整个 model是为了换显卡或换一个小版本 PyTorch 后还能加载。恢复时先重新实例化模型和优化器再 load_state_dict并把 epoch 和 best_psnr 取出来继续。如果不存 optimizer恢复之后的学习率会重置训练曲线会出现一个明显的回落。4.3 判断模型是否真在收敛训练过程中每 5 轮跑一次验证集记录 PSNR 和 SSIM。真实雾图上的 PSNR 绝对数值不高NH-HAZE 上 20dB 出头已经算正常别用合成雾图 30dB 的标准来对照。判断收敛主要看趋势训练 loss 和验证 loss 一起降正常训练 loss 降、验证 loss 不降过拟合加强增广或提前停两边都平先怀疑学习率太低再怀疑数据没加载对。验证时容易犯的错是用单张 patch 的输出去拼整图拼出来的图有接缝PSNR 被拉低。后面避坑章专门讲重叠滑窗推理这里先记住评估方式和论文对齐结果才有可比性。5. 常见问题排查五次复现翻车记录与修复方法5.1 训练阶段的三个高频翻车点现象 1训练 loss 平稳下降但输出图整张偏绿或偏蓝。原因真实雾图存在明显的颜色统计偏移模型学到的重建结果是训练集颜色均值的回归。解决在预处理里对输入做 per-channel 归一化统计训练集的 RGB 均值方差减均值除标准差输出后再反归一化。这一个改动就能把色偏问题压掉大半成本极低。现象 2batch_size 32 在 12G 或 16G 显卡上直接 OOM。原因Uformer 在多尺度特征图上显存开销大128×128 输入加 batch 32 不是小开销。解决先把 batch_size 降到 8train_ps 降到 96还不行就开梯度累积——每 4 个 batch 的梯度累加完再更新一次等效于 batch 32 的更新步数。显存问题在这类 Transformer 复原项目里太常见先看 GPU 利用率再决定降哪个参数别一上来就换模型结构。现象 3训练从头到尾 loss 在一个高位不降。原因八成是 --train_dir 配错了路径模型在一个空的 DataLoader 上空转剩下两成是学习率太大权重一直在震荡。解决启动后先打印 len(dataset)确认补丁数量在几百以上再打印一个 batch 的输入张量形状确认是 [B, 3, 128, 128] 而不是 None。这两行日志能定位掉 80% 以上的“不收敛”问题。5.2 数据处理与评估阶段的易错点现象 4训练时发现有雾图和清晰图对不上模型学到的映射是乱的。原因配对时两个子目录文件名规则不一致或者切块用了不同起点。解决生成补丁后随机抽 3 组用 OpenCV 横向拼接肉眼核对同时坚持用同一个 (y,x) 坐标切块这一步直接写死在预处理脚本里不要依赖手动操作。现象 5测试阶段 PSNR 比预期低 3-4dB怀疑模型没训练好。原因测试时直接把 256×256 补丁输入并拼接回原图补丁边界产生接缝伪影。解决用重叠滑窗推理步长设为补丁的一半也就是 128重叠区域输出取平均评估指标统一在 YCbCr 空间的 Y 通道计算和论文对齐。注意真实雾图测试集的 PSNR 天然偏低。先确认你对比的基线是在同一个测试集划分上跑的再谈模型好坏跨数据集比数字没有意义。这几条坑都是我实际踩过或者帮别人排查过的。每次翻车之后回溯根子大多在路径、配对、评估方式这三类问题上模型结构本身反而很少出幺蛾子。6. 进阶技巧用 loss landscape CSV 判断模型是否真的收敛包里的 CSV 文件cifar100_resnet_dnn_50_losslandscape.csv、cifar100_vit_ti_losslandscape.csv初看容易当成训练数据其实它们是损失地形分析实验的采样结果。loss landscape 的思想是训练完成后固定网络权重选两个方向向量 d1、d2把权重临时扰动成 θ θ0 α·d1 β·d2然后在 α 和 β 组成的网格上重新计算损失得到一张二维平面图。平面平坦说明模型落在宽的极小值里泛化好平面崎岖、像刀锋一样说明停在尖锐极小值里换一组测试数据性能可能就崩。CSV 里每一行就是这个网格上的一个采样点至少包含两个方向坐标和一个 loss 值。画出来只要一段脚本import pandas as pd import numpy as np import matplotlib.pyplot as plt df pd.read_csv(cifar100_vit_ti_losslandscape.csv) print(df.columns) # 先看列名常见为 x / y / loss x df.iloc[:, 0].values y df.iloc[:, 1].values z df.iloc[:, 2].values n int(np.sqrt(len(df))) # 采样网格通常是 n x n X x.reshape(n, n) Y y.reshape(n, n) Z z.reshape(n, n) plt.figure(figsize(6, 5)) plt.contourf(X, Y, np.log(Z 1e-8), levels50, cmapviridis) plt.colorbar() plt.xlabel(direction 1) plt.ylabel(direction 2) plt.title(ViT-Ti loss landscape) plt.savefig(vit_ti_landscape.png, dpi200)读入后先打印列名确认坐标和 loss 列的位置reshape 前检查数据行数是不是完全平方数如果不是说明采样网格不完整先补齐再画。画出来以后看中心区域等高线密集说明当前权重处于尖锐区域可以考虑换更小的学习率、加权重平均或者延长 warmup把模型往平坦区域推一推。我把包里 cifar100_resnet_dnn_50_losslandscape.csv 和 cifar100_vit_ti_losslandscape.csv 各画了一张能明显看出 ViT 的平面更崎岖——这解释了 Transformer 在小数据集上为什么更容易过拟合。从那以后我每次训练去雾模型都会固定 seed并顺手存一份 loss landscape 采样数据它不直接给 PSNR但比 PSNR 更快告诉我该不该换优化策略。希望帮到你。本文还有配套的精品资源点击获取