基于MATLAB的图像去雾技术总结

张开发
2026/6/15 8:40:03 15 分钟阅读
基于MATLAB的图像去雾技术总结
基于MATLAB的图像去雾去年11月赶早爬青城山熬了大半夜等老君阁的云海日出云海是等来的但掏出手机一顿拍回去一看雾是雾裹了老君阁的角裹了远处的都江堰轮廓甚至裹了脸凑镜头拍的自拍——不是手抖是整个画面蒙了一层均匀的“灰雾纱”连后期APP拉对比度饱和度曝光都救不回来的那种“糊白死灰色彩全失”。后来翻MATLAB的工具箱摸鱼不对是正儿八经学课设找思路才知道这东西原来靠数学就能扒纱今天就唠唠这个事儿。首先得知道为什么雾里的照片成这样别整太玄的就说大气里的小水珠小颗粒把太阳或者远处物体的光给“拆”了一部分直接从物体反射到你手机/相机这个是我们要的“好光叫直接透射光另一部分先撞了水珠再弹过来这个是烦人的“坏光叫大气光散射”还有更气人的是太阳散射来的直接砸镜头这个叫全局大气光一般是画面最亮最匀的地方比如那天拍的空白天空。三个加起来画面就成了雾蒙蒙的样子。数学上大概可以简化成这个破公式I(x) J(x)t(x) A(1 - t(x))这里I(x)就是你拍到的破图J(x)是“原图去雾后的干净画面我们的目标t(x)是透射率——就是有多少比例的好光能穿透雾砸镜头0是全挡死1是没雾A是刚才说的全局大气光。搞懂目标和公式MATLAB去雾大概就拆成三步找A算t(x)最后倒推J(x)。倒推简单吧代数题嘛移项就行J(x) (I(x) - A) / max(t(x), t0) A哦对这里加了个t0一般取0.1或者0.2不然t(x)太小的时候分母炸天J(x)直接偏色或者出噪点。先搞找A。怎么找最笨的办法也最不容易出大错的是找整幅图里最亮的像素不对那天我青城山拍的空白天空可能被云顶反射的光更亮哦是云散射的全局光更匀。后来有个大佬叫何恺明搞了个暗通道先验顺带解决了找A和算t(x)的问题这个太牛了后来改改参数都能用我们今天就用这个暗通道。先插个暗通道先验的小铺垫啥是暗通道就是随便拿一张没有雾的彩色图不管是人、树、楼把RGB三个通道分开每个像素取三个里最小的那个值形成的灰度图这个灰度图里的像素值除了天空这种大面积亮纯色的地方基本都是趋近于0的——这个就是大佬统计了成千上万张图发现的“歪理邪说不是是客观规律。哦对那天翻大佬的论文截图都统计了特别准我找了一张青城山太阳出来后雾散了拍的同款位置翻朋友拍的后来补的做个暗通道看看% 读取干净的对比图 clear; clc; close all; img_clean imread(qingchengshan_clean.jpg); % 转成uint8防止乱码变double算数值 img_clean im2double(img_clean); % 做暗通道取RGB每个位置最小的 dark_clean min(img_clean, [], 3); % 显示对比 subplot(1,2,1); imshow(img_clean); title(太阳出来后的老君阁干净原图); subplot(1,2,2); imshow(dark_clean); title(干净图的暗通道黑糊糊的楼和树白的只有天空);运行一下你会发现朋友拍的那张干净图暗通道真的除了一点点山尖缝隙漏的太阳云顶亮剩下的树林台阶都是接近黑的暗通道先验yyds那暗通道怎么用在找A上大佬说不要随便找全局最亮要找暗通道里前0.1%最亮的像素然后回到原来的彩色破图I(x)里找这些像素对应的RGB三个通道的最大值平均一下或者就取那个位置整个RGB就行那个就是A因为暗通道里亮的地方刚好对应彩色图里的“全局最匀最接近纯大气光”的区域不会被比如汽车大灯这种局部亮瞎的东西干扰。基于MATLAB的图像去雾比如拍的破青城山雾图% 读取我那天拍的破图 img_foggy imread(qingchengshan_foggy.jpg); img_foggy im2double(img_foggy); [H, W, ~] size(img_foggy); % 第一步做破图的暗通道 dark_foggy min(img_foggy, [], 3); % 第二步找暗通道里前0.1%最亮的像素坐标 % 先把暗通道拉成一维数组 dark_vec dark_foggy(:); % 排序从大到小 [sorted_dark, sorted_idx] sort(dark_vec, descend); % 取前0.1% top_num ceil(H * W * 0.001); top_idx sorted_idx(1:top_num); % 回到彩色图里取这些位置的RGB找A A zeros(1,1,3); for c 1:3 channel img_foggy(:,:,c); A(1,1,c) max(channel(top_idx)); end % 把A扩展成和原图一样大的矩阵方便后面计算 A repmat(A, H, W);这里ceil是向上取整不然0.1%算出来是小数没法取像素数repmat是重复矩阵把113的A变成HW3和img_foggy一样大后面除法减法不用循环MATLAB矩阵运算爽歪歪。接下来算t(x)倒推暗通道那边干净图暗通道趋近于0代入最开始的破雾公式两边同时取三个通道最小的dark_I(x) min(I(x), [], 3) min(J(x)t(x) A(1-t(x)), [], 3)A是全局的三个通道都是接近的常数比如那天就是灰蒙蒙的白色偏一点点蓝因为天空是灰蓝所以可以把min拆成dark_I(x) ≈ t(x)min(J(x), [], 3) A(1-t(x))然后因为干净图暗通道≈0所以dark_I(x) ≈ A*(1-t(x))所以t(x) ≈ 1 - dark_I(x)/A哦对这里A是刚才算出来的全局大气光每个通道取最小的那个因为三个通道都差不多取最小的可以防止某个通道偏色导致t(x)算大算小或者直接用三个通道分别算不过大佬原来的论文里是取全局大气光的最大值不对等下我试一下那天的图直接用刚才的darkfoggy和全局大气光的单通道比如全局大气光的三个通道取平均的单通道Aavg mean(A(1,1,:));哦对还有个参数w大佬说不能让透射率完全是1 - dark_I/A不然太黑的地方比如干净图暗通道本来就是0的地方t(x)1没变化不对那天的图本来没雾的地方本来就是0代入进去就是1有雾的地方代入进去t(x)会小一点。不过大佬加了个w0.95保留一点点雾不然去雾太狠太黑太突兀像假图。% 第三步算透射率t(x) w 0.95; % 全局大气光取三个通道的平均值做成单通道 A_single mean(A(1,1,:)); % 先把dark_foggy扩展成H*W的 dark_foggy_single dark_foggy; % 算t t 1 - w * dark_foggy_single / A_single; % 限制t0刚才说的0.1或者0.2我那天取0.15 t0 0.15; t max(t, t0); % 把t扩展成H*W*3方便后面RGB三个通道分别算 t repmat(t, 1, 1, 3);好了现在所有参数都齐了直接倒推J(x)% 第四步倒推干净图J(x) img_dehazed (img_foggy - A) ./ t A; % 有些像素可能因为计算误差超出0-1的范围MATLAB显示会乱所以clamp一下 img_dehazed min(max(img_dehazed, 0), 1); % 最后显示对比一下 figure; subplot(1,3,1); imshow(img_foggy); title(11月蹲守失败的“灰蒙老君阁”); subplot(1,3,2); imshow(t); title(算出来的透射率图白的是雾少的黑的是雾多的其实反过来哦对透射率0-11是白的对白的地方好光多); subplot(1,3,3); imshow(img_dehazed); title(暗通道扒纱后的“勉强能看老君阁”); % 保存一下朋友圈能用了大概 imwrite(img_dehazed, qingchengshan_dehazed.jpg);运行一下哇那天的老君阁角出来了都江堰的轮廓也隐约可见了虽然还是有点暗通道带来的小噪点毕竟只是基础版还有天空边缘也有点暗比如云层缝隙漏的太阳有点过曝但比之前的全白好多了如果想进阶一下比如去掉天空边缘的暗化大佬后来又搞了个引导滤波平滑t(x)还有改进暗通道先验对天空的处理不过基础版暗通道已经够救我那天的废片了。感兴趣的可以去搜搜MATLAB里的guidedfilter函数或者自己写引导滤波玩。今天的摸鱼不对是技术分享就到这里下次再拍雾景记得先拍一张带雾的回来用MATLAB扒纱或者直接蹲到太阳出来雾散拍干净原图拍原图比扒纱香一万倍。

更多文章