一文读懂5种图像加雾算法

🌞欢迎莅临我的个人主页👈🏻这里是我专注于深度学习领域、用心分享知识精粹与智慧火花的独特角落!🍉

🌈如果大家喜欢文章,欢迎:关注🍷+点赞👍🏻+评论✍🏻+收藏🌟,如有错误敬请指正!🪐

🍓“请不要相信胜利就像山坡上的蒲公英一样唾手可得,但是请相信生活中总有美好值得我们全力以赴,哪怕粉身碎骨!”🌹

前言

        图像加雾不仅是一种艺术效果的实现手段,它还涉及到复杂的数学模型和计算方法。通过对图像的像素值进行调整,算法能够模拟出不同密度和类型的雾,从而让图像看起来像是在雾中拍摄的。这通常涉及到对图像的亮度、对比度以及颜色通道的细致调整。本文主要对5种加雾算法进行说明!

目录

基于蒙版实现图像加雾

基于大气散射模型实现加雾

基于Lightroom Classic软件加雾

基于深度学习实现加雾

基于深度图实现加雾

5种方法的效果对比

去雾数据集


基于蒙版实现图像加雾

这种方法实际上就是图像的叠加过程,创建一个雾霾的颜色模板与原图像按照一定的权重比进行混合,实现也很简单,具体如下:

import cv2
import numpy as np


def add_hazy(image, hazy_intension=0.5, hazy_color_intension=255):
    '''
    :param image:   输入图像
    :param hazy_intension:  雾强 (0-1)
    :param hazy_color_intension:    雾的颜色强度(150-255)
    :return:    雾图
    '''
    hazy_intension = np.clip(hazy_intension, 0, 1)
    hazy_layer = np.ones_like(image) * hazy_color_intension
    hazy_image = cv2.addWeighted(image, 1 - hazy_intension, hazy_layer, hazy_intension, 0)
    return hazy_image


if __name__ == '__main__':
    path = r'00504.png'
    img = cv2.imread(path)
    hazy_img = add_hazy(img, hazy_intension=0.5, hazy_color_intension=200)

    result = cv2.hconcat([img, hazy_img])
    result = cv2.resize(result, None, fx=0.5, fy=0.5)
    cv2.imshow("RESULT", result)
    cv2.waitKey()
    cv2.destroyAllWindows()
  • image:这是要进行处理的原始图像。
  • hazy_intension:它表示雾的强度。这个值在 0 到 1 之间,0 表示没有雾,1 表示雾最浓。
  • hazy_color_intension:可以理解为雾的颜色的深浅程度,它的取值范围是 150 到 255。

        首先创建一个和原始图像大小一样的图像,它的像素值都被设置为hazy_color_intension,它就像是一层雾的颜色模板,然后将原图像与该模板按照比例进行混合,1 - hazy_intension是原始图像的混合比例,hazy_intension是雾模板的混合比例。通过这种加权相加的方式,就得到了有雾效果的图像。

基于大气散射模型实现加雾

原理:大气中的雾气颗粒或尘埃对光线的散射和吸收会导致远处物体的对比度降低、颜色淡化等现象,导致视野变得模糊。(物理模型)

上面的解释有点抽象对吧,下面我们用通俗的语言重新说明一下

        想象你在一个有雾的天气里看远处的东西。当没有雾的时候,你能很清楚地看到物体本来的样子。但是当有雾的时候,情况就变得不一样了。
        首先,有一个光源,比如太阳或者其他发光的物体。这个光源发出的光在没有雾的情况下,可以直接照到物体上,然后再反射到我们的眼睛里,我们就能看到物体清晰的样子。
        但是当有雾的时候,光在传播的过程中会被空气中的微小颗粒(就像雾中的小水滴或者尘埃)散射。一部分光会直接照到物体上然后反射回来,就像没有雾的时候一样。但是还有一部分光会在空气中被散射,然后从各个方向进入我们的眼睛。
        这就导致我们看到的物体变得不那么清晰了。雾越浓,被散射的光就越多,我们看到的物体就越模糊。
        大气散射模型就是通过一些数学公式来描述这种现象。它可以根据雾的浓度、光源的强度、物体的距离等因素,计算出我们最终看到的图像应该是什么样子。

大气散射模型数学公式如下:

实现方法如下:

import numpy as np
import cv2


def add_hazy(image, beta=0.05, brightness=0.5):
    '''
    :param image:   输入图像
    :param beta:    雾强
    :param brightness:  雾霾亮度
    :return:    雾图
    '''
    img_f = image.astype(np.float32) / 255.0
    row, col, chs = image.shape
    size = np.sqrt(max(row, col))  
    center = (row // 2, col // 2) 
    y, x = np.ogrid[:row, :col]
    dist = np.sqrt((x - center[1]) ** 2 + (y - center[0]) ** 2)
    d = -0.04 * dist + size
    td = np.exp(-beta * d)
    img_f = img_f * td[..., np.newaxis] + brightness * (1 - td[..., np.newaxis])
    hazy_img = np.clip(img_f * 255, 0, 255).astype(np.uint8)
    return hazy_img


if __name__ == '__main__':
    path = r'00504.png'
    image = cv2.imread(path)

    image_fogv2 = add_hazy(image, beta=0.05, brightness=0.8)
    result = cv2.hconcat([image, image_fogv2])
    result = cv2.resize(result, None, fx=0.5, fy=0.5)
    cv2.imshow("RESULT", result)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
  • image:要进行处理的原始图像。
  • beta:可以理解为雾的强度参数。不同的beta值会产生不同强度的雾效果。
  • brightness:这个参数控制雾霾的亮度,决定了雾的明亮程度。

        首先计算雾化的尺寸大小(这里取输入图像行数和列数种较大值的平方根)和雾化中心,然后计算图像中每个像素点到雾化中心的距离,根据距离计算一个与雾化效果和雾透明度相关的值,img_f * td[..., np.newaxis] + brightness * (1 - td[..., np.newaxis])这里对原始图像进行处理,根据透明度值td和雾霾亮度brightness来混合原始图像和一个与亮度相关的部分,得到有雾效果的图像数据。(这里np.newaxis的作用是增加一个维度,以便进行正确的广播操作)

基于Lightroom Classic软件加雾

原理:基于对比度的调整,通过控制图像的全局或局部透明度,通过将去朦胧值调至负值,模拟雾气环境。

下载链接:https://pan.baidu.com/s/14h931jytMX6VDSCglvYH5A?pwd=6789

提取码:6789

操作流程如下:

调整效果如下:

基于深度学习实现加雾

原理:训练大量的带有雾气和清晰图像对,神经网络可以掌握如何在清晰图像上生成真实感强的雾气效果。常用的方法包括生成对抗网络(GAN)和变分自编码器(VAE)。

该部分的实现我使用的是CycleGAN网络,详情请查看:基于 CycleGAN 对抗网络的自定义数据集训练

基于深度图实现加雾

原理:结合图像的深度信息大气散射模型,深度图提供了场景中每个像素的距离信息,远处的物体会被更多的雾气覆盖,而近处的物体则相对清晰。这种方法通过调整雾气的浓度、颜色以及随着距离变化的透明度,生成更加逼真的雾气效果。

当前获取图像深度图的方法主要由以下几种:

  • 深度相机

    • LiDAR:激光雷达通过发射激光脉冲并测量返回时间来计算深度,常用于自动驾驶和测绘领域。
    • 结构光:如Kinect,使用投影特定模式的光图案并通过相机捕捉其在物体上的变形来计算深度。
    • ToF(飞行时间)相机:通过测量光线从相机发出到返回的时间差来计算深度。
  • 双目立体视觉

    • 使用相机从不同角度拍摄相同的场景,通过比较相同镜头在两张图像中的位置差异(视差)来推算深度。
  • 单目估计

    • 使用单张RGB图像,借助深度学习模型(如CNN、Transformer)来估计场景中的深度。常见的模型包括MiDaS、Monodepth等。

方式一使用深度相机拍摄自己的数据集得到深度图的方法可以参考:

深度图的方法实现加雾,Synscapes数据集以及D455相机拍摄为例

CODE:

链接:https://pan.baidu.com/s/1smdyJtszEolHCLUdUE2vwg

提取码: sh8r 

方式二:单目深度估计(Pytorch):MiDas和Monodepth

这些模型的权重都是已经训练好的,可以直接拿来用,这就很方便了,下面我们直接来看我已经写好的代码吧

MiDas

import torch
import cv2
import os
import numpy as np
from pathlib import Path
import matplotlib.pyplot as plt
import matplotlib

matplotlib.use('TkAgg')
from tqdm import tqdm

# -----------------输入图像、深度图、雾图路径--------------#
img_path = Path(r'D:\Anaconda3\torch\test')
depth_path = Path(r'D:\Anaconda3\torch\save_test')
hazy_path = Path(r'D:\Anaconda3\torch\hazy_test')
# -----------------雾气强度控制因子----------------------#
fog_strength = 1.0
# ----------------雾气颜色 (浅灰色雾气)-------------------#
fog_color = np.array([200, 200, 200], dtype=np.uint8)
# ------------------------------------------------------#
#   可选择的model: 'MiDas'、'MiDaS_small'、'DPT_Hybrid'
#   'MiDas'生成的深度图比MiDaS_small精度更高,适合一般的深度估计任务
#   'DPT_Hybrid'适合复杂场景下的深度估计
# ------------------------------------------------------#
model = 'DPT_Hybrid'

# ------------------------生成深度图---------------------#
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# 加载 MiDaS 模型
midas = torch.hub.load("intel-isl/MiDaS", model)
midas_transforms = torch.hub.load("intel-isl/MiDaS", "transforms")
midas.to(device)
midas.eval()

imglist = os.listdir(img_path)
with tqdm(total=len(imglist), desc=('深度图转换')) as pbar:
    for img in imglist:
        full_path = img_path / img
        image = cv2.imread(str(full_path))
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

        if model == 'MiDas':
            transform = midas_transforms.default_transform
        elif model == 'DPT_Hybrid':
            transform = midas_transforms.dpt_transform
        else:
            transform = midas_transforms.small_transform
        input = transform(image).to(device)

        with torch.no_grad():
            predict = midas(input)
        depth_map = predict.squeeze().cpu().numpy()
        depth_map_normalized = cv2.normalize(depth_map, None, 0, 1, cv2.NORM_MINMAX)
        # # 可视化深度图
        # plt.imshow(depth_map_normalized, cmap='plasma')
        # plt.colorbar()
        # plt.title('Estimated Depth Map')
        # plt.show()

        depth_map = (depth_map_normalized * 255).astype(np.uint8)
        depth_map = cv2.resize(depth_map, (image.shape[1], image.shape[0]), interpolation=cv2.INTER_LANCZOS4)

        # 保存深度图
        new_filename = depth_path / img
        new_filename = new_filename.with_suffix('.png')  # 可以自己更改深度图的格式,默认为png
        cv2.imwrite(str(new_filename), depth_map)

        pbar.update(1)

# -----------------------生成雾图----------------------------------#
with tqdm(total=len(imglist), desc=('雾图生成')) as pbar:
    for filename in os.listdir(img_path):
        if filename.endswith('.png') or filename.endswith('.jpg'):
            image_path = os.path.join(img_path, filename)
            depthmap = os.path.join(depth_path, filename)

            original_image = cv2.imread(image_path)
            original_image = cv2.cvtColor(original_image, cv2.COLOR_BGR2RGB)
            depth_map = cv2.imread(depthmap, cv2.IMREAD_GRAYSCALE)

            if depth_map is None or original_image is None:
                print(f"跳过文件: {filename},因为无法读取对应的深度图或原始图像。")
                print('请检查输入图像与深度图是否属于同一类型,如均为.png或.jpg,深度图的格式默认为.png,可自行更改其类型,详情请查看代码注释部分')
                continue

            depth_map_normalized = depth_map.astype(np.float32) / 255
            # 反转深度图,使得白色区域(近处)雾气浓,黑色区域(远处)雾气稀薄
            depth_map_inverted = 1 - depth_map_normalized
            # 雾气强度基于反转后的深度图,应用强度因子控制
            fog_intensity_map = depth_map_inverted * fog_strength
            # 限制雾气浓度的最大值为 1,避免过度曝光
            fog_intensity_map = np.clip(fog_intensity_map, 0, 1)
            fog_layer = np.ones_like(original_image, dtype=np.float32) * fog_color
            foggy_image = original_image * (1 - fog_intensity_map[:, :, np.newaxis]) + \
                          fog_layer * fog_intensity_map[:, :, np.newaxis]
            foggy_image = np.clip(foggy_image, 0, 255).astype(np.uint8)

            output_path = os.path.join(hazy_path, filename)
            foggy_image_bgr = cv2.cvtColor(foggy_image, cv2.COLOR_RGB2BGR)  # 转回 BGR 格式以保存
            cv2.imwrite(output_path, foggy_image_bgr)

        pbar.update(1)
  • img_path:原始图像文件夹
  • depth_path:MiDas模型生成的深度图保存文件夹
  • hazy_path:生成的雾图存放文件夹
  • fog_strength:雾的强度,值越大雾霾强度越高
  • fog-color:雾的颜色,一般是灰色(180-255间)
  • model:MiDas训练好的模型,可以选择MiDas_small、MiDas、DPT_Hybrid。MiDas和MiDas_small适合一般的深度估计任务(一般情况可以选择MiDas),DPT_small适合复杂场景的估计,毕竟是基于Vision Transformer得到的模型权重,如果对雾图的要求较高建议选择DPT_Hybrid
  • 想要查看伪彩色深度图可以取消下面的注释

从生成的深度图可以明显看到DPT_Hybrid模型在细节方面是最好的,当然最后加雾的效果也还可以吧,其实另外两种的效果也不赖,大家可以按照自己的需求选择合适的模型哈

Monodepth

链接:http:// https://pan.baidu.com/s/130VuUXk4dILC3mPWVd0fKA

提取码: wk5g 

文件目录如下:

该文件的创建基于Monodepth2开源项目:https://github.com/nianticlabs/monodepth2

下面直接进入正题吧

import os
import torch
import cv2
from torchvision import transforms
import numpy as np
from pathlib import Path
import matplotlib

matplotlib.use('TkAgg')
from tqdm import tqdm
from utils.resnet_encoder import ResnetEncoder
from utils.depth_decoder import DepthDecoder


# -----------------输入图像、深度图、雾图路径--------------#
img_path = Path(r'D:\Anaconda3\torch\test')
depth_path = Path(r'D:\Anaconda3\torch\save_test')
hazy_path = Path(r'D:\Anaconda3\torch\hazy_test')
# -----------------雾气强度控制因子----------------------#
fog_strength = 1.0
# ----------------雾气颜色 (浅灰色雾气)-------------------#
fog_color = np.array([200, 200, 200], dtype=np.uint8)
# ------------------------------------------------------#
#   可选择的model: 'mono_640x192'、'stereo_640x192'、'mono+stereo_640x192'、
#   'mono_1024x320'、'stereo_1024x320'、'mono+stereo_1024x320'、
# ------------------------------------------------------#
model = "models/mono+stereo_1024x320"
# ------------------------生成深度图------------------------#
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# ----------------------------加载模型----------------------#
encoder_path = os.path.join(model, "encoder.pth")
depth_decoder_path = os.path.join(model, "depth.pth")

encoder = ResnetEncoder(18, False)
loaded_dict_enc = torch.load(encoder_path, map_location=device)

feed_height = loaded_dict_enc['height']
feed_width = loaded_dict_enc['width']
filtered_dict_enc = {k: v for k, v in loaded_dict_enc.items() if k in encoder.state_dict()}
encoder.load_state_dict(filtered_dict_enc)
encoder.to(device)
encoder.eval()

depth_decoder = DepthDecoder(
    num_ch_enc=encoder.num_ch_enc, scales=range(4))
loaded_dict = torch.load(depth_decoder_path, map_location=device)
depth_decoder.load_state_dict(loaded_dict)
depth_decoder.to(device)
depth_decoder.eval()

imglist = os.listdir(img_path)
with tqdm(total=len(imglist), desc=('深度图转换')) as pbar:
    for img in imglist:
        full_path = img_path / img
        image = cv2.imread(str(full_path))
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        original_width, original_height = image.shape[:2]
        image = cv2.resize(image, (feed_width, feed_height), interpolation=cv2.INTER_LANCZOS4)
        image = transforms.ToTensor()(image).unsqueeze(0)
        image = image.to(device)

        with torch.no_grad():
            features = encoder(image)
            outputs = depth_decoder(features)

        disp = outputs[("disp", 0)]
        disp_resized = torch.nn.functional.interpolate(
            disp, (original_width, original_height), mode="bilinear", align_corners=False)

        depth_map = disp_resized.squeeze().cpu().numpy()
        depth_map_normalized = cv2.normalize(depth_map, None, 0, 1, cv2.NORM_MINMAX)
        # # 可视化深度图
        # plt.imshow(depth_map_normalized, cmap='plasma')
        # plt.colorbar()
        # plt.title('Estimated Depth Map')
        # plt.show()

        depth_map = (depth_map_normalized * 255).astype(np.uint8)
        # 保存深度图
        new_filename = depth_path / img
        new_filename = new_filename.with_suffix('.png')  # 可以自己更改深度图的格式,默认为png
        cv2.imwrite(str(new_filename), depth_map)

        pbar.update(1)

# -----------------------生成雾图----------------------------------#
with tqdm(total=len(imglist), desc=('雾图生成')) as pbar:
    for filename in os.listdir(img_path):
        if filename.endswith('.png') or filename.endswith('.jpg'):
            image_path = os.path.join(img_path, filename)
            depthmap = os.path.join(depth_path, filename)

            original_image = cv2.imread(image_path)
            original_image = cv2.cvtColor(original_image, cv2.COLOR_BGR2RGB)
            depth_map = cv2.imread(depthmap, cv2.IMREAD_GRAYSCALE)
            # 反转深度图,使得白色区域(近处)雾气浓,黑色区域(远处)雾气稀薄
            if depth_map is None or original_image is None:
                print(f"跳过文件: {filename},因为无法读取对应的深度图或原始图像")
                print('请检查输入图像与深度图是否属于同一类型,如均为.png或.jpg,深度图的格式默认为.png,可自行更改其类型,详情请查看代码注释部分')
                continue

            depth_map_normalized = depth_map.astype(np.float32) / 255

            depth_map_inverted = 1 - depth_map_normalized
            # 雾气强度基于反转后的深度图,应用强度因子控制
            fog_intensity_map = depth_map_inverted * fog_strength
            # 限制雾气浓度的最大值为 1,避免过度曝光
            fog_intensity_map = np.clip(fog_intensity_map, 0, 1)
            # 创建雾气层
            fog_layer = np.ones_like(original_image, dtype=np.float32) * fog_color
            # 将雾气强度与原始图像结合
            foggy_image = original_image * (1 - fog_intensity_map[:, :, np.newaxis]) + \
                          fog_layer * fog_intensity_map[:, :, np.newaxis]
            # 将结果转换为 uint8 类型
            foggy_image = np.clip(foggy_image, 0, 255).astype(np.uint8)

            output_path = os.path.join(hazy_path, filename)
            foggy_image_bgr = cv2.cvtColor(foggy_image, cv2.COLOR_RGB2BGR)  # 转回 BGR 格式以保存
            cv2.imwrite(output_path, foggy_image_bgr)

        pbar.update(1)
  • img_path:原始图像文件夹
  • depth_path:MiDas模型生成的深度图保存文件夹
  • hazy_path:生成的雾图存放文件夹
  • fog_strength:雾的强度,值越大雾霾强度越高
  • fog-color:雾的颜色,一般是灰色(180-255间)
  • model:MiDas训练好的模型,可以选择mono_640x192、stereo_640x192、mono+stereo_640x192、mono_1024x320、stereo_1024x320、mono+stereo_1024x320。对于该模型,我没有推荐使用的权重文件,大家根据需要选择就好

我个人认为mono+stereo_640x192和mono+stereo_1024x320得到的效果还是最好的,当然大家可以根据效果选择合适的模型实现自己的任务

5种方法的效果对比

说了这么多,相信大家对于这些方法得到的雾图肯定需要一个鲜明的对比吧,放心,我在下面已经整理好了,大家可以根据效果图选择合适的方法噢

深度图方法中我只选择了效果最好的模型进行对比。综合来看,DPT_Hybrid方法是效果最好的,当然这也取决于数据集的形式,比如鸟类这种,不具有明显的远近层次关系,并不适合这种方法,反而用软件进行加雾的效果更好。所以,使用什么加雾方法还取决于你想应用于什么形式的数据集,也可以交叉使用,增强数据质量。

去雾数据集

此外,我还整理了一些去雾数据集的下载地址,有需要的自取

人工合成

RESIDEhttps://sites.google.com/view/reside-dehaze-datasets/reside-v0

D-HAZY:

链接: https://pan.baidu.com/s/1rJE6UA3EjCacDb4V7Z1jVQ

提取码: 2563 

真实场景

Dense-Haze CVPR 2019Dense-Haze CVPR 2019

NH-HAZE:

链接: https://pan.baidu.com/s/1-8T5eymtJh1xsH6AhmFSYw

密码: 1955

I-HAZE:

链接: https://pan.baidu.com/s/1BL31XjHM4NiWoVbaBbvg4w

密码: 642d

O-HAZE:

链接: https://pan.baidu.com/s/1IfazGq2iG_RDwa0tGK0Dzg

提取码: 5861 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悠眠小虫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值