简介:这份PDF文献面向图像处理、计算机视觉方向的研究者与消防信息化技术人员,聚焦火场灰度监控画面因烟雾导致的清晰度与对比度下降问题,提出一套基于深度学习的去烟方案。资源包仅含1个PDF文件,大小约1.26MB,便于在移动端或桌面端直接查阅与引用。文中网络由检测子网络与去除子网络串联构成:前者借助残差学习网络定位烟雾区域,缓解深层网络梯度消失;后者采用密集连接的U型网络,通过Dense Block将低层特征复用到高层,在保留背景的同时去除烟雾。实验从主观视觉效果与PSNR、SSIM等客观指标两方面验证,其清晰度与实时性均优于传统方法,并可推广至森林火灾、工业事故等烟雾场景。目前已有376人学习,适合作为深度学习图像复原方向的参考文献与算法设计参考。
1. 火场灰度图去烟:为什么单通道图像反而更难做
消防机器人和无人机回传的画面里,烟是头号干扰源。可见光摄像头在浓烟下几乎失效,而红外热成像、低照度工业相机输出的往往是单通道灰度图——没有色彩信息,烟和背景的灰度分布高度重叠,传统基于颜色先验的去烟方法直接失效。这就是「基于深度学习的火场灰度图像去烟算法」要解决的核心问题:在只有亮度、没有色度的条件下,把烟从画面里剥离出来,恢复被遮挡的结构。
它适合三类人:做消防视觉、工业巡检的算法工程师;手里只有灰度数据集、想上深度学习去烟的研究者;以及需要把去烟模块塞进边缘设备、对推理延迟敏感的产品开发者。难点不在网络多深,而在灰度域缺乏颜色线索、火场真实配对数据几乎拿不到、烟又分薄烟和浓烟两种截然不同的退化模式。下面按「数据怎么造、网络怎么搭、训练怎么调、部署怎么压」这条线走一遍。
2. 灰度去烟的数据构造与网络选型
2.1 灰度域为什么不能直接套彩色去烟模型
彩色去烟(如 DCP、MSBDN 这类)依赖暗通道先验或颜色衰减先验,本质是假设「无雾图像的暗通道趋近于零」。灰度图只有一个通道,暗通道退化成局部最小值,先验几乎不成立。更麻烦的是火场里烟和火焰的灰度值可能都在高亮区,直接套彩色模型会出现「把火焰当烟去掉」或「烟没去掉、对比度先崩」两种极端。
常见做法是放弃物理先验,走「成对数据 + 监督回归」路线:让网络学一个从含烟灰度图到无烟灰度图的映射。选型上,轻量级编码器-解码器(类似 U-Net 骨架)加残差学习是性价比最高的起点,因为去烟本质是「预测一层烟,再减掉」,残差结构能让网络只学烟的分布,而不是重建整幅图。
2.2 用大气散射模型合成灰度含烟数据
真实火场配对数据(同一场景有烟/无烟)极难采集,工业界普遍用合成数据起步。灰度域的大气散射模型简化为:
I(x) = J(x) * t(x) + A * (1 - t(x)) t(x) = exp(-beta * d(x))其中I是含烟图,J是无烟图,A是大气光(灰度标量),t是透射率,beta是散射系数,d是深度图。灰度图没有深度信息,用随机平滑噪声场近似d即可。
import numpy as np import cv2 def synth_smoke_gray(clean_gray, beta_range=(0.6, 2.2), A_range=(0.75, 0.98)): """对单通道灰度图合成烟尘退化,返回含烟图与透射率标签""" h, w = clean_gray.shape # 用低分辨率随机场上采样模拟深度,避免逐像素噪声导致烟不均匀 low = np.random.rand(h // 16 + 1, w // 16 + 1).astype(np.float32) depth = cv2.resize(low, (w, h), interpolation=cv2.INTER_CUBIC) depth = (depth - depth.min()) / (depth.max() - depth.min() + 1e-6) beta = np.random.uniform(*beta_range) # 控制烟的浓度 A = np.random.uniform(*A_range) # 大气光,灰度标量 t = np.exp(-beta * depth).astype(np.float32) # 透射率图 t = np.clip(t, 0.05, 1.0) # 防止除零 clean = clean_gray.astype(np.float32) / 255.0 smoky = clean * t + A * (1 - t) return (np.clip(smoky, 0, 1) * 255).astype(np.uint8), t逻辑说明:beta越大烟越浓,A越接近 1 画面越灰白。depth用 16 倍下采样的随机场再插值,是为了让烟呈现块状不均匀,而不是全图均匀加雾——真实火场烟就是团块状的。t裁剪到 0.05 下限,避免后续做物理反演时除零。
参数建议:训练集beta覆盖 0.6~2.2,测试集单独留一档beta=2.5做浓烟泛化测试。A不要固定,否则网络会学到「恒定大气光」这个捷径。
2.3 网络结构:残差 U-Net 与注意力门的取舍
| 结构 | 参数量 | 灰度去烟表现 | 适用场景 |
|---|---|---|---|
| 普通 U-Net | 中 | 薄烟好,浓烟残留 | 快速验证 |
| 残差 U-Net | 中 | 浓烟明显更好 | 推荐起点 |
| 加通道注意力 | 偏大 | 提升有限 | 数据量大时 |
| 加空间注意力门 | 偏大 | 边缘保留好 | 结构恢复要求高 |
我一般从残差 U-Net 起步:编码器 4 层下采样,解码器对称上采样,跳跃连接改成「拼接后过一层 1x1 卷积再相加」。输出不是直接重建无烟图,而是预测透射率t,再用J = (I - A*(1-t)) / t反演。这样网络只需学一个物理量,收敛更快。
import torch import torch.nn as nn class ResidualBlock(nn.Module): def __init__(self, ch): super().__init__() self.conv = nn.Sequential( nn.Conv2d(ch, ch, 3, padding=1), nn.ReLU(inplace=True), nn.Conv2d(ch, ch, 3, padding=1), ) def forward(self, x): return x + self.conv(x) # 残差连接,缓解梯度消失 class SmokeNet(nn.Module): def __init__(self, base=32): super().__init__() self.enc1 = ResidualBlock(base) self.enc2 = ResidualBlock(base * 2) self.enc3 = ResidualBlock(base * 4) self.pool = nn.MaxPool2d(2) self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=False) self.dec2 = ResidualBlock(base * 2) self.dec1 = ResidualBlock(base) self.head = nn.Conv2d(base, 1, 1) # 输出单通道透射率 def forward(self, x): e1 = self.enc1(x) e2 = self.enc2(self.pool(e1)) e3 = self.enc3(self.pool(e2)) d2 = self.dec2(self.up(e3) + e2) d1 = self.dec1(self.up(d2) + e1) return torch.sigmoid(self.head(d1)) # t 约束到 (0,1)逻辑说明:sigmoid把透射率压到 0~1,符合物理意义。跳跃连接用相加而非拼接,是为了控制显存,灰度图本身信息量小,拼接带来的增益不明显。base=32在 1080p 输入下显存约 4~6GB,单卡可训。
3. 训练策略与损失函数怎么调
3.1 复合损失:L1 打底,SSIM 保结构,透射率监督加速收敛
只用 L1 会导致结果偏模糊,火场里结构(门框、通道边缘)必须保住。我一般用三项加权:
import torch.nn.functional as F def ssim_loss(pred, target, window=11): # 简化版 SSIM,用平均池化近似局部均值 mu_p = F.avg_pool2d(pred, window, 1, window // 2) mu_t = F.avg_pool2d(target, window, 1, window // 2) var_p = F.avg_pool2d(pred ** 2, window, 1, window // 2) - mu_p ** 2 var_t = F.avg_pool2d(target ** 2, window, 1, window // 2) - mu_t ** 2 cov = F.avg_pool2d(pred * target, window, 1, window // 2) - mu_p * mu_t c1, c2 = 0.01 ** 2, 0.03 ** 2 ssim = ((2 * mu_p * mu_t + c1) * (2 * cov + c2)) / \ ((mu_p ** 2 + mu_t ** 2 + c1) * (var_p + var_t + c2)) return 1 - ssim.mean() def total_loss(pred_t, gt_t, pred_img, gt_img): l1 = F.l1_loss(pred_img, gt_img) l_ssim = ssim_loss(pred_img, gt_img) l_t = F.l1_loss(pred_t, gt_t) # 透射率直接监督 return l1 + 0.5 * l_ssim + 0.3 * l_t逻辑说明:pred_img是用预测透射率反演出的无烟图,gt_img是干净图。l_t让网络在早期就学到透射率分布,避免只优化最终图像导致透射率乱跳。权重0.5和0.3是经验值,SSIM 权重过高会让画面过锐、出现伪影。
参数建议:window=11是 SSIM 标准窗口。如果显存紧张,把 SSIM 换成1 - 余弦相似度也能用,但结构保持会弱一些。
3.2 学习率、epoch 与早停的实操参数
灰度去烟数据量通常不大(合成几千到几万对),训练容易过拟合。我一般这样设:
- 优化器 Adam,初始学习率
2e-4,每 20 个 epoch 乘 0.5 - batch size 8~16,取决于输入分辨率(256x256 可到 16,512x512 用 8)
- epoch 80~120,配合早停:验证集 L1 连续 10 个 epoch 不降就停
- 数据增强只做水平翻转和随机裁剪,不做颜色抖动(灰度图没有颜色)
注意:不要用彩色去烟常用的「随机加雾」增强,灰度域加雾和加烟在数值上无法区分,会让网络学混。
3.3 训练不收敛时先查这三处
第一,检查透射率标签是否被裁剪到 0.05 以下又被反演,导致J爆炸。第二,确认输入归一化到[0,1],灰度图常被误当[0,255]直接喂进去。第三,看损失里l_t是否远大于l1,如果是,说明透射率监督权重过大,网络只顾拟合t而忽略最终图像质量,把0.3降到0.1再试。
4. 推理部署与浓烟场景的边界处理
4.1 从透射率反演无烟图的数值稳定写法
推理时最容易踩的坑是t接近 0 导致除零。工业部署里我一般加一个下限并做引导滤波平滑:
import cv2 import numpy as np def recover_gray(smoky_gray, t_pred, A=0.9, t_min=0.1): """由含烟灰度图和预测透射率反演无烟图""" I = smoky_gray.astype(np.float32) / 255.0 t = np.clip(t_pred, t_min, 1.0) # 引导滤波:用含烟图做引导,平滑透射率,保留边缘 t_refined = cv2.ximgproc.guidedFilter( guide=(I * 255).astype(np.uint8), src=(t * 255).astype(np.uint8), radius=32, eps=1e-3 ).astype(np.float32) / 255.0 J = (I - A * (1 - t_refined)) / t_refined return (np.clip(J, 0, 1) * 255).astype(np.uint8)逻辑说明:t_min=0.1是硬下限,防止浓烟区反演溢出。引导滤波用含烟图当引导,是因为烟边缘和图像边缘高度重合,滤波后透射率不会在物体边界处突变。radius=32适合 1080p,小图按比例缩小。
参数建议:A在推理时不要用训练时的随机值,取验证集上使 PSNR 最高的固定值,通常 0.85~0.95。
4.2 浓烟区该不该强行去烟
浓烟区t往往低于 0.1,此时反演出来的像素基本是噪声放大。强行去烟会得到一片雪花。我的做法是设一个阈值t_thresh=0.15,低于它的区域不做反演,直接输出原图并标记为「不可恢复区」,交给上层做告警或切换红外通道。
| 透射率区间 | 处理策略 | 输出 |
|---|---|---|
| t > 0.6 | 正常反演 | 清晰无烟图 |
| 0.15 < t ≤ 0.6 | 反演 + 引导滤波 | 可读图 |
| t ≤ 0.15 | 不反演 | 原图 + 不可恢复标记 |
4.3 边缘设备上的量化与推理加速
消防机器人算力有限,常见做法是训练后做 INT8 量化。PyTorch 动态量化对卷积层收益明显:
import torch.quantization as tq model.eval() qmodel = tq.quantize_dynamic( model, {torch.nn.Conv2d}, dtype=torch.qint8 ) # 导出后单帧 512x512 推理约 15~25ms(视 CPU 而定)逻辑说明:只量化Conv2d,因为激活函数和上采样对精度敏感,量化后 PSNR 掉得少。动态量化不需要校准数据集,适合快速上线。如果掉点超过 1dB,改用静态量化并准备 200 张校准图。
注意:量化后务必用同一批测试图对比 PSNR/SSIM,灰度去烟对量化误差比彩色任务更敏感,因为单通道没有冗余。
5. 用 PSNR 之外的三类指标验证去烟效果
PSNR 在灰度去烟里经常骗人:烟去掉但结构糊了,PSNR 可能还很高。我一般补三类验证。第一,结构相似度 SSIM,重点看边缘区域,把图像分成烟区和非烟区分别算。第二,透射率误差 MAE,直接衡量物理量预测准不准,比图像指标更早暴露问题。第三,下游任务指标——把去烟图送进目标检测器,看召回率提升多少,这是最贴近落地的验证。
from skimage.metrics import structural_similarity as ssim import numpy as np def eval_pair(pred, gt, t_pred, t_gt): psnr = 10 * np.log10(1.0 / np.mean((pred - gt) ** 2)) s = ssim(pred, gt, data_range=1.0) t_mae = np.mean(np.abs(t_pred - t_gt)) return {"psnr": psnr, "ssim": s, "t_mae": t_mae}逻辑说明:data_range=1.0对应归一化后的灰度图。t_mae低于 0.05 说明透射率预测已经可用,此时再调图像损失收益递减。
一个具体技巧:把测试集按beta分档(薄烟 0.6~1.2、中烟 1.2~1.8、浓烟 1.8~2.5),分别报告指标。很多模型薄烟 PSNR 35+,浓烟直接掉到 22,只看平均值会掩盖这个断层。分档后你会清楚知道模型能不能上真实火场——浓烟档 SSIM 低于 0.7 的,建议先别部署,回去补浓烟合成数据或加对抗训练。
本文还有配套的精品资源,点击获取