【HDR】曝光融合(Exposure Fusion)

0 前言

在曝光融合(Exposure Fusion)算法问世之前,多曝光序列合成用于显示的HDR需要两个步骤,第一步是将多张不同曝光的低动态范围图像合成为HDR(例如Debevec提出的加权融合方法),通常HDR为12bit或者16bit;第二步是通过tonemapping对高动态范围HDR进行压缩以支持低动态范围显示设备(例如Durand提出的基于双边滤波的tonemapping算法),一般会压缩至8bit。

曝光融合算法的优势在于不需要标定相机响应曲线,并且跳过tonemapping步骤,直接合成用于显示的高动态范围图像。

1 算法细节

1.1 Naive

1.1.1 主要思想

对于多曝光图像序列,取每一张图像中最有价值的部分用于合成。例如,曝光时间长的图像中暗区细节丰富同时噪声水平低,那么暗区就是有价值的部分。显然,需要一个指标来衡量每张图像中哪些像素有价值,然后通过计算每张图每个像素的价值指标当作对应的权重,最终通过加权融合的方式得到HDR。

1.1.2 权重计算

从对比度、饱和度和亮度三个维度对像素的价值进行评估:

  • 对比度
    这里的对比度指的是图像的梯度,对于边缘和纹理等重要的信息分配很大的权重。具体地,对图像的灰度图执行拉普拉斯滤波,结果取绝对值作为对比度指标 C ( I k ) C(I_k) C(Ik)
    C ( I k ) = ∣ △ g r a y ( I k ) ∣ C(I_k)=|\triangle_{gray}(I_k)| C(Ik)=gray(Ik)
  • 饱和度
    RGB三通道之间差异大的可视为饱和度高的区域,反之,对于过曝或者欠曝区域RGB三通道的值趋于一致,饱和度较低。因此,可将RGB三个通道之间的标准差作为饱和度指标。
    S k , i , j = ( I k , i , j B − M k , i , j ) 2 + ( I k , i , j G − M k , i , j ) 2 + ( I k , i , j R − M k , i , j ) 2 3 S_{k,i,j}=\sqrt{\frac{(I_{k,i,j}^{B}-M_{k,i,j})^2+(I_{k,i,j}^{G}-M_{k,i,j})^2+(I_{k,i,j}^{R}-M_{k,i,j})^2}{3}} Sk,i,j=3(Ik,i,jBMk,i,j)2+(Ik,i,jGMk,i,j)2+(Ik,i,jRMk,i,j)2 M k , i , j = I k , i , j B + I k , i , j G + I k , i , j R 3 M_{k,i,j}=\frac{I_{k,i,j}^{B}+I_{k,i,j}^{G}+I_{k,i,j}^{R}}{3} Mk,i,j=3Ik,i,jB+Ik,i,jG+Ik,i,jR
  • 亮度
    对于归一化至0~1范围的图像,将取值在0.5左右的像素视为曝光良好,应该分配很大的权重;接近0和1的分别为欠曝和过曝应该分配很小的权重。像素值与其对应权重的关系符合均值为0.5的高斯分布:
    E k . i , j = e − ( I k , i , j B − 0.5 ) 2 2 σ 2 ⋅ e − ( I k , i , j G − 0.5 ) 2 2 σ 2 ⋅ e − ( I k , i , j R − 0.5 ) 2 2 σ 2 E_{k.i,j}=e^{-\frac{(I_{k,i,j}^{B}-0.5)^2}{2\sigma^2}} \cdot e^{-\frac{(I_{k,i,j}^{G}-0.5)^2}{2\sigma^2}} \cdot e^{-\frac{(I_{k,i,j}^{R}-0.5)^2}{2\sigma^2}} Ek.i,j=e2σ2(Ik,i,jB0.5)2e2σ2(Ik,i,jG0.5)2e2σ2(Ik,i,jR0.5)2

获取以上3个指标后,就能计算每个像素对应的权重:
W i , j , k = ( C i , j , k ) w c ⋅ ( S i , j , k ) w s ⋅ ( E i , j , k ) w e W_{i,j,k}=(C_{i,j,k})^{w_c}\cdot (S_{i,j,k})^{w_s}\cdot (E_{i,j,k})^{w_e} Wi,j,k=(Ci,j,k)wc(Si,j,k)ws(Ei,j,k)we默认 w c = w s = w e = 1 w_c=w_s=w_e=1 wc=ws=we=1;另外,为了保证多张图像在同一位置的权重和为1,需要在图像数量维度上对权重进行归一化:
W ^ i j , k = W i j , k ∑ k ′ = 1 N W i j , k ′ \hat{W}_{{ij,k}}=\frac{{W}_{{ij,k}}}{\sum_{k^{\prime}=1}^{N}{W}_{{ij,k^{\prime}}}} W^ij,k=k=1NWij,kWij,k

1.1.3 融合

根据计算的权重对原始图像进行加权求和,即可得到融合后的图像:
H i j = ∑ k = 1 N W ^ i j , k ⋅ I i j , k H_{ij}=\sum_{k=1}^{N}\hat{W}_{{ij,k}}\cdot I_{ij,k} Hij=k=1NW^ij,kIij,k

这样粗糙融合的结果存在一个问题,在权重尖锐过渡的区域,由于每张图像的曝光时间不同,绝对强度也不同,导致融合后灰度跳变太大,图像中呈现很多黑色和白色斑点,与噪声形态类似。

权重尖锐过渡区会出现问题,那么可以让其平滑一点,提到平滑自然而然就想到了高斯滤波。作者对权重图做高斯滤波后再进行合成,虽然斑点问题得到了缓解,但是在边缘处会出现光晕现象

既然光晕是由于边缘处的权重被平滑所导致的,可以考虑使用保边滤波代替高斯滤波。

1.2 Multi-resolution

由于naive版本的融合方式不能完全解决黑白斑点的问题,并且会引入光晕这样的新问题。因此,作者提出了使用拉普拉斯金字塔融合的方式,其流程如下图所示:
在这里插入图片描述
简单来说,就是从不同曝光的原始图像中分解出拉普拉斯金字塔,对应的权重图中分解出高斯金字塔,然后分别在每个尺度下进行融合,得到融合后的拉普拉斯金字塔。最后,从拉普拉斯金字塔的顶层开始向上采样,叠加同尺度的拉普拉斯细节,再向上采样和叠加细节,递归至最高分辨率,得到最终的结果。(此处有一个点需要注意,拉普拉斯金字塔的顶层就是原始图像高斯金字塔的顶层)
在这里插入图片描述

  • 为什么拉普拉斯金字塔融合效果好?
    将平坦区和尖锐过渡区(如边缘)分开融合,平坦区融合使用的是经过多次高斯滤波和下采样后的权重图,仅在比较大的边缘纹理处变化剧烈;由于拉普拉斯金字塔中只保留了边缘等高频信息,因此在拉普拉斯金字塔上对尖锐过渡区进行融合不会影响平坦区。

2 实验

import os
import sys
import glob
import numpy as np
import cv2
import argparse


def show_image(message, src):
    cv2.namedWindow(message, 0)
    cv2.imshow(message, src)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


def gauss_curve(src, mean, sigma):
    dst = np.exp(-(src - mean)**2 / (2 * sigma**2))
    return dst


class ExposureFusion(object):
    def __init__(self, sequence, best_exposedness=0.5, sigma=0.2, eps=1e-12, exponents=(1.0, 1.0, 1.0), layers=7):
        self.sequence = sequence  # [N, H, W, 3], (0..1), float32
        self.img_num = sequence.shape[0]
        self.best_exposedness = best_exposedness
        self.sigma = sigma
        self.eps = eps
        self.exponents = exponents
        self.layers = layers

    @staticmethod
    def cal_contrast(src):
        gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
        laplace_kernel = np.array([[0, 1, 0], [1, -4, 1], [0, 1, 0]], dtype=np.float32)
        contrast = cv2.filter2D(gray, -1, laplace_kernel, borderType=cv2.BORDER_REPLICATE)
        return np.abs(contrast)

    @staticmethod
    def cal_saturation(src):
        mean = np.mean(src, axis=-1)
        channels = [(src[:, :, c] - mean)**2 for c in range(3)]
        saturation = np.sqrt(np.mean(channels, axis=0))
        return saturation

    @staticmethod
    def cal_exposedness(src, best_exposedness, sigma):
        exposedness = [gauss_curve(src[:, :, c], best_exposedness, sigma) for c in range(3)]
        exposedness = np.prod(exposedness, axis=0)
        return exposedness

    def cal_weight_map(self):
        weights = []
        for idx in range(self.sequence.shape[0]):
            contrast = self.cal_contrast(self.sequence[idx])
            saturation = self.cal_saturation(self.sequence[idx])
            exposedness = self.cal_exposedness(self.sequence[idx], self.best_exposedness, self.sigma)
            weight = np.power(contrast, self.exponents[0]) * np.power(saturation, self.exponents[1]) * np.power(exposedness, self.exponents[2])
            # Gauss Blur
            # weight = cv2.GaussianBlur(weight, (21, 21), 2.1)
            weights.append(weight)
        weights = np.stack(weights, 0) + self.eps
        # normalize
        weights = weights / np.expand_dims(np.sum(weights, axis=0), axis=0)
        return weights

    def naive_fusion(self):
        weights = self.cal_weight_map()  # [N, H, W]
        weights = np.stack([weights, weights, weights], axis=-1)  # [N, H, W, 3]
        naive_fusion = np.sum(weights * self.sequence * 255, axis=0)
        naive_fusion = np.clip(naive_fusion, 0, 255).astype(np.uint8)
        return naive_fusion

    def build_gaussian_pyramid(self, high_res):
        gaussian_pyramid = [high_res]
        for idx in range(1, self.layers):
            gaussian_pyramid.append(cv2.GaussianBlur(gaussian_pyramid[-1], (5, 5), 0.83)[::2, ::2])
        return gaussian_pyramid

    def build_laplace_pyramid(self, gaussian_pyramid):
        laplace_pyramid = [gaussian_pyramid[-1]]
        for idx in range(1, self.layers):
            size = (gaussian_pyramid[self.layers - idx - 1].shape[1], gaussian_pyramid[self.layers - idx - 1].shape[0])
            upsampled = cv2.resize(gaussian_pyramid[self.layers - idx], size, interpolation=cv2.INTER_LINEAR)
            laplace_pyramid.append(gaussian_pyramid[self.layers - idx - 1] - upsampled)
        laplace_pyramid.reverse()
        return laplace_pyramid

    def multi_resolution_fusion(self):
        weights = self.cal_weight_map()  # [N, H, W]
        weights = np.stack([weights, weights, weights], axis=-1)  # [N, H, W, 3]

        image_gaussian_pyramid = [self.build_gaussian_pyramid(self.sequence[i] * 255) for i in range(self.img_num)]
        image_laplace_pyramid = [self.build_laplace_pyramid(image_gaussian_pyramid[i]) for i in range(self.img_num)]
        weights_gaussian_pyramid = [self.build_gaussian_pyramid(weights[i]) for i in range(self.img_num)]

        fused_laplace_pyramid = [np.sum([image_laplace_pyramid[n][l] *
                                         weights_gaussian_pyramid[n][l] for n in range(self.img_num)], axis=0) for l in range(self.layers)]

        result = fused_laplace_pyramid[-1]
        for k in range(1, self.layers):
            size = (fused_laplace_pyramid[self.layers - k - 1].shape[1], fused_laplace_pyramid[self.layers - k - 1].shape[0])
            upsampled = cv2.resize(result, size, interpolation=cv2.INTER_LINEAR)
            result = upsampled + fused_laplace_pyramid[self.layers - k - 1]
        result = np.clip(result, 0, 255).astype(np.uint8)

        return result


if __name__ == '__main__':
    root_path = sys.argv[1]
    sequence_path = [os.path.join(root_path, fname) for fname in os.listdir(root_path)]
    sequence = np.stack([cv2.imread(path) for path in sequence_path], axis=0)

    mef = ExposureFusion(sequence.astype(np.float32) / 255.0)
    naive_fusion_result = mef.naive_fusion()
    multi_res_fusion = mef.multi_resolution_fusion()

    show_image('muti-resolution', multi_res_fusion)

3 参考

Mertens T, Kautz J, Van Reeth F. Exposure fusion[C]//15th Pacific Conference on Computer Graphics and Applications (PG’07). IEEE, 2007: 382-390.
https://zhuanlan.zhihu.com/p/455674916

  • 12
    点赞
  • 76
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Halcon是一种图像处理软件,拥有许多强大的功能和算法,其中包括HDR图像融合HDR图像融合是将多张拍摄同一场景但曝光程度不同的图片合成一张更具动态范围的图像。HDR融合的目的是将亮处和暗处的细节都完整地呈现在最终合成的图像中,使其更加真实、饱满和有层次感。 在Halcon中进行HDR图像融合,我们首先需要提供多张曝光不同的图片作为输入。然后,通过扫描这些图片,Halcon能够自动识别每张图片中的亮度信息,并将其映射到最终合成图像中。 Halcon中的HDR融合算法会对不同曝光程度的图片进行加权融合,使得每张图片中的细节都能在最终图像中得到展示。通过对每个像素点的亮度进行分析和计算,Halcon能够准确地合成一张具有更广泛动态范围的图像。 此外,Halcon的HDR融合功能还具有一些参数调整选项,可以根据实际应用需求进行自定义设置。用户可以根据自己的需求选择合适的曝光权重、亮度范围和色调映射等参数,以便得到满足自己要求的最终图像。 综上所述,Halcon提供了强大的HDR图像融合功能,能够将多张曝光不同的图片合成一张具有更广泛动态范围的图像,从而使图像更加真实、饱满和有层次感。 ### 回答2: Halcon HDR图像融合是一种用于合成高动态范围(High Dynamic Range,HDR)图像的技术。HDR图像融合可以将不同曝光条件下得到的图像合成为一张具有更广泛亮度范围的图像。 在Halcon中,HDR图像融合可以通过以下步骤完成: 1. 首先,获取不同曝光条件下的图像序列。这些图像序列应该包括一张暗曝光图像、一张标准曝光图像和一张亮曝光图像。 2. 将这些图像序列导入Halcon软件中,并使用其提供的图像处理工具进行预处理。例如,你可以使用图像增强算法进一步增强图像质量,以减少图像中的噪声和伪影。 3. 使用Halcon中的图像融合算法将不同曝光条件下的图像进行合成。Halcon提供了几种不同的图像融合算法,包括平均化、加权平均化和最大值融合等。你可以根据自己的需求选择适合的算法。 4. 在图像融合之后,可以根据需要进行一些后处理操作,以进一步优化合成后的图像。例如,你可以使用Halcon的图像调整函数对亮度、对比度和色彩进行调整,以使合成后的图像更加鲜明和清晰。 Halcon的HDR图像融合功能可以广泛应用于计算机视觉和图像处理领域,特别是在需要处理高动态范围图像的应用中,如工业检测、医学影像和卫星图像等。通过使用Halcon软件进行HDR图像融合,用户可以得到更丰富、更准确的图像信息,提高图像处理的效果和质量。 ### 回答3: Halcon HDR图像融合是一种用于合并不同曝光程度下的图像的技术。它可以将来自不同曝光条件的图像的亮度信息进行融合,并生成一张具有更高动态范围的图像。 在Halcon中,HDR图像融合可以通过以下步骤实现: 1. 首先,需要获取一系列曝光时间不同的图像。这些图像应该覆盖整个场景的不同曝光范围,包括低曝光、标准曝光和高曝光的图像。 2. 然后,将这些图像加载到Halcon中,并使用图像预处理的方法,如平滑和背景校正,来优化图像的质量。 3. 接下来,通过应用一种适当的图像对齐方法,将这些图像对齐到一个参考图像上。对齐的目标是使得图像中的特征位置都一致,这样才能进行下一步的融合处理。 4. 在完成图像对齐后,通过应用Halcon中的HDR融合方法,将对齐后的图像进行融合。该方法会根据每个像素点在不同曝光图像中的亮度信息,计算出最终的亮度值。融合后的图像将具有更高的动态范围,能够同时呈现低曝光细节和高曝光细节。 5. 最后,可以根据需要进行后处理的步骤,如图像增强、去噪等,以进一步改善融合后图像的质量。 总的来说,Halcon HDR图像融合是一种通过将不同曝光条件下的图像进行对齐和融合,生成具有更高动态范围的图像的方法。它可以应用在诸如医学影像、无人机航拍等领域,提供更准确和清晰的图像信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值