ARTICLE DETAIL

资讯详情

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

基于Vision Transformer的图像去雾算法解析与Python工程实践

基于Vision Transformer的图像去雾算法解析与Python工程实践 简介资源包围绕基于Vision Transformer的图像去雾算法提供完整Python源码、预训练权重与使用说明适用于具备一定深度学习基础、希望复现并继续训练去雾模型的研究者或工程师。包内共340个文件约156.34MB主体为204个py脚本涵盖模型构建、训练与推理流程另有yaml配置文件、csv训练日志、ipynb演示笔记、png/gif图示及说明文档等便于按目录检索和二次开发。option.py集中管理训练参数例如--train_ps可设置输入patch大小--pretrain_weights可指定预训练权重路径方便按数据集切换模型。资源还包含损失景观等csv数据及多个演示图可辅助理解训练过程与损失变化同时提供预训练权重和项目介绍包内另含多个按数据集划分的预训练权重目录省去自行训练的前置时间能明显降低环境搭建与调参门槛。已有468人学习下载适合用于课程设计、算法对比或科研复现。1. 基于 Vision Transformer 的图像去雾算法从“看不清”到能跑通的 Python 工程监控、自动驾驶和户外视觉设备在雾天拍出来的图像暴露的不只是“模糊”对比度被压到很低远景细节直接丢失天空部分一片灰白。用暗通道先验这类经典手段去雾近景小图看还行一碰到大片天空和空间变化的雾就很容易出现偏色和光晕。基于 Vision Transformer 的图像去雾算法把去雾当成一个全局建模问题用自注意力抓取雾在空间上的分布在 RESIDE 这类基准上的 PSNR 明显高出传统方法一截。这套 Python 源码的价值是给出了一个从数据准备、训练到指标评估的完整闭环适合刚入手去雾方向的研究生复现 baseline也适合做视觉工程的工程师改骨干网络、改损失函数并迁移到自己的数据上。2. 为什么去雾要选 Vision Transformer物理模型与全局建模的取舍2.1 从暗通道先验到大气散射模型物理先验的瓶颈图像去雾问题的起点不是神经网络而是大气散射模型这是后续所有算法绕不开的物理基础。一张有雾图像 I 可以描述为清晰辐射图 J、全局大气光 A 和透射率图 t 的组合I(x)J(x)t(x)A(1-t(x))。去雾最朴素的思路是把 A 和 t 估计出来再用这个公式反解出 J。暗通道先验DCP是这一类方法的代表它观察到一个统计规律在图像大多数局部块里至少有一个颜色通道的强度非常低一旦有雾这个通道会被大气光托高暗通道的最小值就不再接近零。暗通道先验的透射率估计公式可以写成 t(x)1-ω·min_c(min_{y∈Ω(x)} I_c(y)/A_c)其中 Ω 是局部块ω 通常取 0.95用来保留少量雾以维持景深真实感。这个公式把全局大气光 A 当作已知常量实际估计 A 时又常常直接取暗通道中最亮的那部分像素于是近景出现大块亮色物体时t 会被低估去雾图随之偏暗。引导滤波虽然能把边缘修得干净但“局部块内雾均匀”的假设没有变本质矛盾并没有被解决。DCP 的失效模式也因此非常明确遇到大片天空、白色物体、或远景连成一片的浓雾时“局部块里至少一个通道强度低”的假设不再成立估出来的透射率就是错的最终结果图上出现明显偏色和块状伪影。这也是从业者把 DCP 定位成“能出图、难落地”的原因。更进一步看CNN 架构也不是不能做去雾像 AOD-Net 这类轻量模型在低分辨率下表现并不差但卷积本质上是局部算子要获得全局上下文只能靠加深网络和扩大感受野参数量和推理延迟都上去了对雾这种空间连续变化的退化来说效率远不如自注意力直接建立长距离依赖。提示合成数据集里的雾通常按 te^{-βd} 渲染β 是散射系数d 是场景深度。同一张清晰图可以生成多张不同浓度的雾图这也是 RESIDE 里一张清晰图对应多个雾图文件名的原因。2.2 ViT 的 Patch Embedding 与自注意力如何提取雾的分布Vision Transformer 的输入处理流程比 CNN 直接把一张 H×W×3 的图切成 N 个 P×P×3 的 patch每个 patch 展平后过线性投影变成 embedding再叠加位置编码送入 Transformer 编码器。这里的 P 是 patch size是全局建模能力与计算开销的核心平衡点。patch 切得越小序列长度 NHW/P² 越长自注意力复杂度按 O(N²·d) 上涨显存开销跟着暴涨patch 切得越大序列短了、计算省了但小尺度细节在 patch 内部被直接平均掉去雾结果容易出现纹理模糊。多头自注意力会把每个 embedding 分别映射成 query、key、value用点积计算每个 patch 与所有 patch 的相关性再按权重聚合全局信息。这个机制让模型在浅层就有能力把远处的雾和近处明显未受雾影响的路面关联起来而不是等深层卷积慢慢把感受野撑大。位置编码给每个 patch 补上它在原图中的空间坐标信息相对位置编码比绝对位置编码更适合图像恢复任务Swin Transformer 主要使用相对位置偏移这也是它在去雾任务上比原版 ViT 更容易收敛的原因之一。图像去雾落地时纯 ViT 其实不是最常用的选择。多数工程实现会用 Swin Transformer 这类带窗口注意力的变体自注意力只在局部窗口内计算再用移位窗口做跨窗口信息交互复杂度被压回与图像尺寸近似线性同时保留长距离建模能力。不少去雾开源项目标题写 ViT实际骨干是 Swin 或其变形这不算偷换概念Transformer 家族解决的是同一个问题让每个输出位置的预测都能看到全图范围的上下文。patch size 的选择要结合输入分辨率一起看常见做法是在 256×256 输入上用 patch_size4 或 8在 512×512 输入上用 14 或 16。显存不够时先降 batch size再考虑提高 patch size不要一上来就把输入缩到 128×128——雾的细节被压掉模型会把“模糊”和“雾”一起学进去。2.3 去雾任务建模透射率图回归 vs 端到端清晰图重建使用 ViT 做去雾时模型输出层决定了整个训练目标这个选择直接影响损失函数设计和最终视觉质量。第一种是透射率图回归模型输出一张 t 图配合全局估计的大气光 A再用大气散射模型反解出清晰图。这类方法物理可解释但 A 的估计误差会被放大到整张图上而且 t 图真值只在合成数据里容易获取真实雾图没有 t 的标签。第二种是端到端清晰图重建模型直接输出 J用 L1、感知损失、SSIM 损失联合约束工程上它是主流因为绕开了 t 真值问题网络也能把“去雾”和“细节重建”一步完成。选型上不要盲目跟风。数据集里只有雾图和清晰图对时端到端重建最省事、实践中也最稳。未来要做无人机、遥感这类有高度或深度先验的场景透射率回归的可解释性更值得保留t 图可以进一步转换成能见度估计服务后续决策。两类建模方式的差异可以汇总成一张选择表对比项透射率图回归端到端清晰图重建主要监督信号需要 t 图真值只需要清晰图物理可解释性强t 可转能见度弱内部表达隐式真实雾图适配很难获得 t 真值更直接后端模块需额外估计 A一步到位常见输出头sigmoid 限制到 01tanh 或线性加 clamp在这个表的基础上输出头设计也要跟着变。透射率回归路线常用 sigmoid 把输出限制在 01端到端重建路线可选 tanh 或线性加 clamp配合残差连接让网络更容易拟合高频细节。很多实现会把两者结合解码器同时输出 t 图和残差图再通过一个可微的大气散射层合成最终结果物理约束和端到端训练两不误代价是结构更复杂、收敛更慢。损失函数不能拍脑袋。只用 L1 或 L2输出容易被平滑只加感知损失纹理会被往训练数据集的风格上带。我一般用 L1 0.1×perceptual 0.05×SSIM 作为起点已经够稳。先把收敛曲线跑稳再在验证集上调权重才有可比性。3. 用 Python 源码跑通 ViT 去雾数据集、训练命令与推理评估3.1 数据集准备RESIDE 的目录结构与 Dataloader 约定RESIDE 是去雾方向被引用最多的基准数据集这套源码的训练和评估基本围绕它展开。常见做法是把 ITS室内训练子集和 OTS室外训练子集解压后整理成统一目录结构data 下分 hazy 和 clear 两个文件夹文件名一一对应。合成雾数据集的命名通常带生成参数比如 0001_0.8_0.2.png 表示清晰图 0001 在特定散射系数和方向参数下生成的雾图Dataloader 要能从雾图文件名反推出清晰图文件名。data/RESIDE/ ├── ITS_train/ │ ├── hazy/ # 有雾输入图如 0001_0.8_0.2.png │ └── clear/ # 清晰参考图如 0001.png └── SOTS_test/ ├── hazy/ └── clear/Dataloader 的写法不复杂但文件名配对逻辑必须和数据集命名一致。下面这段是这类项目里最常见的 Dataset 实现import os from PIL import Image from torch.utils.data import Dataset class DehazeDataset(Dataset): def __init__(self, root, transformNone): self.hazy_dir os.path.join(root, hazy) self.clear_dir os.path.join(root, clear) self.hazy_names sorted(os.listdir(self.hazy_dir)) self.transform transform def __len__(self): return len(self.hazy_names) def __getitem__(self, idx): hazy_name self.hazy_names[idx] hazy_path os.path.join(self.hazy_dir, hazy_name) # 从雾图文件名反推清晰图文件名 clear_name hazy_name.split(_)[0] .png clear_path os.path.join(self.clear_dir, clear_name) hazy Image.open(hazy_path).convert(RGB) clear Image.open(clear_path).convert(RGB) if self.transform: hazy, clear self.transform([hazy, clear]) return hazy, clear这段代码的关键在于 clear_name 的推导hazy_name.split(_)[0] 取出清晰图的基准文件名再补上 .png 后缀雾图和清晰图的配对就成立了。注意这里假设清晰图文件名不带下划线如果你的数据集命名规则不同这段逻辑要跟着改。很多训练跑了一半才发现图片配错的情况都是因为这个简单的推导没对准数据集命名所以拿到任何新数据集时第一件事是打印几个样本检查 hazy 和 clear 是否真的对应。如果你卡在环境配置这一步常见做法是先建一个 python 3.9 的虚拟环境再安装依赖多数基于 PyTorch 的 ViT 去雾项目对 python 版本没有强制要求3.8 到 3.10 都能跑但建议别直接在系统环境里装项目之间依赖打架会浪费一晚上。3.2 训练命令与核心超参数batch size、patch size、学习率训练入口一般是 train.py用 argparse 接收超参数。不同版本的项目参数名会略有差异比如混合精度有的脚本叫 --amp有的叫 --use_amp但下面几个核心项几乎每个 ViT 去雾训练脚本里都有。以常见的 Swin 骨干为例一个能起步的配置大概是python train.py \ --model swin_v2 \ --data_dir data/RESIDE/ITS_train \ --patch_size 14 \ --batch_size 8 \ --lr 1e-4 \ --weight_decay 0.01 \ --use_amp \ --epochs 300 \ --exp_name dehaze_swin_v2逐项说下--model 指定骨干网络swin_v2 指带相对位置编码的 Swin 变体--patch_size 14 是 Swin 在视觉任务里的常用配置patch 越大显存越省但细节损失越多--batch_size 8 是在 8GB 显存和 256×256 输入下的稳妥值--lr 1e-4 配合 warmup 使用Transformer 类模型对学习率比 CNN 敏感直接上 1e-3 大概率在训练初期就把 loss 冲乱--use_amp 开启自动混合精度能省大约 30%40% 显存代价是偶尔验证阶段出现轻微波动只要不 NaN 问题不大。300 个 epoch 看起来多但对合成雾数据集来说并不夸张。ViT 去雾模型通常在前 50 个 epoch 快速收敛后面进入平台期PSNR 每 20 个 epoch 可能只涨 0.1 到 0.2 dB。训练过程中建议每个 epoch 保存一次 checkpoint并记录验证集上最优 PSNR 对应的权重这个 best_psnr.pth 就是后续推理要用的文件。训练日志里的 PSNR 是训练集上的不能替代验证集指标两个数之间的差距过大说明出现过拟合需要加数据增强或调大权重衰减。3.3 测试与评估加载权重、输出去雾图、计算 PSNR/SSIM训练完成后使用说明里最该写清楚的是推理脚本。下面是用训练好的 checkpoint 对单张雾图做推理的标准流程import torch from PIL import Image from models import build_model model build_model(model_nameswin_v2, in_channels3) checkpoint torch.load(checkpoints/best_psnr.pth, map_locationcpu) model.load_state_dict(checkpoint[state_dict]) model.eval().cuda() hazy load_image(data/RESIDE/SOTS_test/hazy/0001_0.8_0.2.png) with torch.no_grad(): pred model(hazy.unsqueeze(0).cuda()).clamp(0, 1) # 从 0-1 浮点转回 uint8 并保存 out (pred.squeeze(0).permute(1, 2, 0).cpu().numpy() * 255).astype(uint8) Image.fromarray(out).save(result_0001.png)load_state_dict 之前要确认 checkpoint 里保存的键名。有的训练脚本保存的是 model.state_dict()有的保存的是整段字典 {state_dict: ..., epoch: ..., best_psnr: ...}两者加载方式不一样报 missing keys 或 unexpected keys 时先检查这里。clamp(0, 1) 是必须的模型输出可能落在 [0,1] 之外不 clamp 直接存图会出现异常亮斑。评估指标用下面两个函数即可不必引入重型框架import numpy as np def psnr(img1, img2): # 输入为 uint8 的 RGB 图范围 0-255 mse np.mean((img1.astype(np.float64) - img2.astype(np.float64)) ** 2) if mse 0: return float(inf) return 10 * np.log10(255.0 ** 2 / mse) # 建议直接使用 skimage 的 SSIM from skimage.metrics import structural_similarity as ssim计算 PSNR 前去雾图和真值图像素必须严格对齐裁边、缩放、通道顺序不一致都会让指标失真。SSIM 直接调 skimage 实现注意指定 win_size 和 data_range。skimage 不同版本对 data_range 的默认值不一致升了版本后指标会变这是很多人对比结果时发现数字和论文对不上的原因之一。3.4 推理新数据的边界模型工作范围与输入约束把模型用到来路不明的真实雾图之前先确认几个边界条件。首先是输入分辨率ViT 的 patch embedding 要求输入尺寸能被 patch size 整除否则序列长度不对前向传播直接报维度错误。处理办法是把输入缩放到 patch size 的整数倍比如 patch_size14 时输入可以是 224、280、336 这些 14 的倍数。其次是通道数模型如果在 RGB 三通道上训练灰度图直接喂进去会报错或出现奇怪偏色统一用 convert(RGB) 转一下再进模型。最后是图像来源监控视频抽帧和单张照片是两回事视频帧带噪声模型在合成雾图上没训练过噪声退化直接推理会连噪声一起放大。实时性也要提前评估。Swin 骨干的 ViT 去雾模型在 512×512 图上单张 RTX 3090 推理大约几十毫秒具体依赖 patch size 和模型层数。如果目标是嵌入式设备或视频流需要先用 ONNX 或 TensorRT 导出并压测不要指望 Python 原版推理脚本直接扛住线上流量。源码加使用说明的意义不在于把一切边界都说死而是让你在跑通之前先知道哪一步最容易出问题。4. 训练 ViT 去雾的 5 个避坑记录从显存溢出到真实雾图翻车这一章每条记录按“现象 → 原因 → 解决”组织都是跑 ViT 去雾时容易碰到的工程问题。4.1 显存溢出patch size、batch size 与混合精度的组合拳现象训练刚开始第一个 batch 还没走完直接报 CUDA out of memory有时连数据加载阶段就崩了。256×256 输入下patch_size4 的 ViT 即使 batch_size4 也能吃满 8GB 显存换成 patch_size14、batch_size812GB 显存未必扛得住。原因ViT 的自注意力矩阵和中间激活对显存的需求随序列长度平方增长。patch 切得越小序列越长显存占用涨得越快。CNN 时代能把 batch_size 拉到 32 的习惯在这里完全不适用。解决优先开启混合精度显存能省约三成再把 batch_size 降到 4 或 2配合梯度累积模拟较大 batch最后才考虑调大 patch size。如果 swap 掉 patch_size 后依然溢出剪掉最后一层 Transformer block 或减少 attention head 数比缩输入分辨率对最终效果的影响更小。显存优化的顺序应是降 batch → 混合精度 → 调大 patch → 减少层数不要一上来就缩输入尺寸。4.2 损失为 NaN学习率、权重衰减与梯度裁剪现象训练到第几十个 iterationloss 突然变 NaN之后一直 NaNcheckpoint 被污染重新加载就得从头开始。原因最常见是学习率过大导致梯度爆炸Transformer 对学习率敏感1e-3 以上的初始学习率很容易让 attention 的 softmax 在反向传播时溢出。其次是混合精度下 loss 数值范围过大梯度缩放到 fp16 时溢出为 inf继续回传就变成 NaN。第三个隐蔽来源是 AdamW 的 weight_decay 设得过大配合大 lr 在正则化项上出现数值不稳。解决把初始学习率压到 1e-4 级别加线性 warmup让模型在前 5 个 epoch 从小学习率起爬混合精度训练时检查是否漏了 GradScaler 缩放再加全局梯度裁剪max_norm 设在 1.0 到 2.0 之间。排查时先关掉 AMP 跑 100 个 iteration如果不再 NaN焦点放在混合精度处理上如果仍然 NaN再查学习率和数据里是否包含 NaN 像素。4.3 合成雾效果好、真实雾图翻车域差距与微调策略现象模型在 SOTS 测试集上 PSNR 28 dB、SSIM 0.88 这类常见水平拿摄像头在雾天拍的真实图一测输出发灰、去雾不彻底甚至出现伪影。这是把合成验证与真实场景混为一谈的典型教训。原因RESIDE 的合成雾是用大气散射模型渲染出来的雾的空间分布均匀、光照路径单一真实雾天还有大气湍流、传感器噪声、非均匀雾层和复杂光照合成数据学到的映射在真实域里不完全成立。解决收集几十张真实雾图加载预训练权重在真实数据上做短周期微调学习率降到 1e-5训练 1020 个 epoch。如果真实清晰图不可得用无参考评估指标挑选输出质量高的样本做伪标签再拿伪标签微调。更实用的方案是训练时加入域随机化对雾图做随机亮度、色温、噪声扰动提升模型对真实分布的容忍度。真实雾图翻车不是 ViT 一个模型的问题任何在合成域训练的去雾模型都会遇到关键不要把验证集全部寄托在 SOTS 上。4.4 Windows 路径分隔符与 checkpoint 加载失败现象Windows 上训练完换一台 Windows 机器推理torch.load 没问题但 load_state_dict 报 unexpected keys或者代码把 checkpoint 路径写死成带反斜杠的绝对路径项目一迁移到 Linux 就加载失败。原因Dataloader 或脚本里硬编码了 “/” 拼接路径Windows 下混用分隔符导致路径不存在checkpoint 的 state_dict 键名带路径前缀换机器后键名对不上还有的清单文件保存了绝对路径换目录后全部失效。解决统一用 os.path.join 或 pathlib.Path 拼接路径手写字符串加 “/” 的习惯改掉训练脚本固定用相对路径保存 checkpoint 前缀不要把机器的绝对路径写进文件。加载权重时遇到 unexpected keys先打印 state_dict 的前几个键和模型当前键名做对比常见是模块前缀不一致去掉前缀再重载即可。4.5 输出图像偏灰或偏暗归一化、clip 与评估口径现象去雾结果轮廓清晰、雾也去掉了但整张图灰蒙蒙或暗部细节被压死同时 PSNR 分数并不低让人以为是自己眼睛出了问题。原因模型输出范围与保存图片时的预期范围不一致。比如输出没有经过 sigmoid 或 clamp预测值落在 [-1,1]被直接当 [0,1] 存图整体偏灰或者训练时输入除以 255 归一化到 [0,1]推理时忘了做同样处理输入范围变成 [0,255]输出也跟着偏移。评估 PSNR 时真值转成 uint8 而预测值还是 [0,1] 浮点范围对不上指标也会虚高或虚低。解决统一规范化路径训练和推理都用除以 255 归一化模型输出经 sigmoid 或 clamp 后再乘 255 存图评估时确保两张图都转成相同位深再计算。调模型之前先打印预测输出的 min、max 和均值这个动作能省下一个下午的 debug 时间。图像偏灰很多时候不是模型质量问题而是数据前后处理不一致先查这个再动 loss。5. 从评估到落地PSNR/SSIM 的局限与三种进阶验证技巧先别只信 PSNR 和 SSIM 这两个数字。它们只能衡量合成域的像素一致性不能完全代表真实雾天的去雾效果。比较好用的做法是引入分层评估与交叉验证把调参与落地区别对待。第一种技巧是固定随机种子。ViT 去雾训练里存在大量初始化随机性不固定 seed 的话同样的代码跑两次 PSNR 差 0.5 dB 是常态这不是模型不稳是随机的黑匣子效应。训练脚本开头加上面这段代码保证实验可复现import random import numpy as np import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed)第二种技巧是按雾浓度分层评估。把测试集按雾浓度分成轻雾、中雾、重雾三组分别统计 PSNR/SSIM可以借助暗通道均值或 FADE 值来划分。很多模型整体分数不差重雾组却掉到 20 dB 以下这就是需要针对性优化的短板场景。把分层数字写进实验记录比总平均更有说服力。第三种技巧是真实雾图上的无参考验证。真实雾图没有真值PSNR/SSIM 算不了这时用 FADE雾密度评估和常见无参考指标做交叉验证。标准做法是评价去雾前后的两张图去雾后的 FADE 应明显下降如果上升说明模型把雾去掉的同时也破坏了图像结构大概率是过增强。我自己踩过的最深的坑就是只看重雾组平均 PSNR把真实雾图调得过度锐化天空区域出现明显色斑。后来在评估阶段把 PSNR 分层并加入无参考指标复核才真正把问题暴露出来。从那以后任何去雾模型在我手上都必须过“分层指标 真实图抽查”这两关才敢往下走。希望帮到你。本文还有配套的精品资源点击获取
返回列表