高动态范围(HDR)

目标

在本章中,我们将

  • 了解如何从曝光序列生成和显示 HDR 图像。
  • 使用曝光融合来合并曝光序列。

理论

高动态范围成像(HDRI 或 HDR)是一种用于成像和摄影的技术,可以再现比标准数字成像或照相技术更大的动态光度范围。虽然人眼可以适应各种光线条件,但大多数成像设备每通道使用 8 位,因此我们仅限于 256 级。当我们拍摄现实世界场景的照片时,明亮区域可能会过度曝光,而暗区域可能曝光不足,因此我们无法使用单次曝光捕捉所有细节。 HDR 成像适用于每通道使用 8 位以上(通常为 32 位浮点值)的图像,从而允许更宽的动态范围。

获得 HDR 图像的方法有很多种,但最常见的方法是使用不同曝光值拍摄的场景照片。要结合这些曝光,了解相机的响应函数是有用的,并且有估算它的算法。合并 HDR 图像后,必须将其转换回 8 位以在通常的显示器上查看。此过程称为色调映射。当场景或相机的物体在镜头之间移动时会出现额外的复杂性,因为应该注册和对齐具有不同曝光的图像。

在本教程中,我们展示了 2 种算法(Debevec,Robertson),用于从曝光序列生成和显示 HDR 图像,并演示了一种称为曝光融合(Mertens)的替代方法,该方法产生低动态范围图像,不需要曝光时间数据。此外,我们估计相机响应函数(CRF),这对许多计算机视觉算法具有重要价值。 HDR 流水线的每个步骤都可以使用不同的算法和参数来实现,因此请查看参考手册以查看所有步骤。

曝光顺序 HDR

在本教程中,我们将查看以下场景,其中我们有 4 个曝光图像,曝光时间为:15,2.5,1 / 4 和 1/30 秒。 (您可以从维基百科下载图像)

exposures.jpg

1.将曝光图像加载到列表中

第一阶段只是将所有图像加载到列表中。此外,我们还需要常规 HDR 算法的曝光时间。注意数据类型,因为图像应该是 1 通道或 3 通道 8 位(np.uint8),曝光时间需要为 float32,并且以秒为单位。

  1. import cv2 as cv
  2. import numpy as np
  3. # Loading exposure images into a list
  4. img_fn = ["img0.jpg", "img1.jpg", "img2.jpg", "img3.jpg"]
  5. img_list = [cv.imread(fn) for fn in img_fn]
  6. exposure_times = np.array([15.0, 2.5, 0.25, 0.0333], dtype=np.float32)

2.将曝光合并到 HDR 图像中

在这个阶段,我们将曝光序列合并为一个 HDR 图像,显示我们在 OpenCV 中有两种可能性。第一种方法是 Debevec,第二种方法是 Robertson。请注意,HDR 图像的类型为 float32,而不是 uint8,因为它包含所有曝光图像的完整动态范围。

  1. # Merge exposures to HDR
  2. imagemerge_debevec = cv.createMergeDebevec()
  3. hdr_debevec = merge_debevec.process(img_list, times=exposure_times.copy())
  4. merge_robertson = cv.createMergeRobertson()
  5. hdr_robertson = merge_robertson.process(img_list, times=exposure_times.copy())

3. Tonemap HDR 图像

我们将 32 位浮点 HDR 数据映射到范围[0..1]。实际上,在某些情况下,值可以大于 1 或小于 0,因此请注意我们稍后将不得不剪切数据以避免溢出。

  1. # Tonemap HDR image
  2. tonemap1 = cv.createTonemapDurand(gamma=2.2)
  3. res_debevec = tonemap1.process(hdr_debevec.copy())
  4. tonemap2 = cv.createTonemapDurand(gamma=1.3)
  5. res_robertson = tonemap2.process(hdr_robertson.copy())

4.使用 Mertens 融合合并曝光

在这里,我们展示了一种合并曝光图像的替代算法,我们不需要曝光时间。我们也不需要使用任何色调图算法,因为 Mertens 算法已经给出了[0..1]范围内的结果。

  1. # Exposure fusion using Mertens
  2. merge_mertens = cv.createMergeMertens()
  3. res_mertens = merge_mertens.process(img_list)

5.转换为 8 位并保存

为了保存或显示结果,我们需要将数据转换为[0..255]范围内的 8 位整数。

  1. # Convert datatype to 8-bit and save
  2. res_debevec_8bit = np.clip(res_debevec*255, 0, 255).astype('uint8')
  3. res_robertson_8bit = np.clip(res_robertson*255, 0, 255).astype('uint8')
  4. res_mertens_8bit = np.clip(res_mertens*255, 0, 255).astype('uint8')
  5. cv.imwrite("ldr_debevec.jpg", res_debevec_8bit)
  6. cv.imwrite("ldr_robertson.jpg", res_robertson_8bit)
  7. cv.imwrite("fusion_mertens.jpg", res_mertens_8bit)

结果

您可以看到不同的结果,但考虑到每个算法都有额外的额外参数,您应该适合获得所需的结果。最佳做法是尝试不同的方法,看看哪种方法最适合您的场景。

德普外阁(Debevec):

ldr_debevec.jpg

罗伯逊(Robertson):

ldr_robertson.jpg

梅特内斯·福森(Mertenes Fusion):

fusion_mertens.jpg

估计相机响应功能

相机响应功能(CRF)为我们提供了场景辐射与测量强度值之间的连接。 CRF 在一些计算机视觉算法中非常重要,包括 HDR 算法。在这里,我们估计逆相机响应函数并将其用于 HDR 合并。

  1. # Estimate camera response function (CRF)
  2. cal_debevec = cv.createCalibrateDebevec()
  3. crf_debevec = cal_debevec.process(img_list, times=exposure_times)
  4. hdr_debevec = merge_debevec.process(img_list, times=exposure_times.copy(),response=crf_debevec.copy())
  5. cal_robertson = cv.createCalibrateRobertson()
  6. crf_robertson = cal_robertson.process(img_list, times=exposure_times)
  7. hdr_robertson = merge_robertson.process(img_list, times=exposure_times.copy(), response=crf_robertson.copy())

相机响应函数由每个颜色通道的 256 长度矢量表示。对于此序列,我们得到以下估计:

crf.jpg

其他资源

  1. Paul E Debevec 和 Jitendra Malik。从照片中恢复高动态范围辐射图。在 ACM SIGGRAPH 2008 课程中,第 31 页.ACM,2008。 [44]
  2. Mark A Robertson,Sean Borman 和 Robert L Stevenson。通过多次曝光改善动态范围。在 Image Processing,1999。ICIP 99.会议录。 1999 年国际会议,第 3 卷,第 159-163 页。 IEEE,1999。 [166]
  3. Tom Mertens,Jan Kautz 和 Frank Van Reeth。曝光融合。在计算机图形和应用程序,2007 年.PG’07。第 15 届太平洋会议,第 382-390 页。 IEEE,2007。 [136]
  4. 来自 Wikipedia-HDR 的图片

演习

  1. 尝试所有色调图算法: cv :: TonemapDragocv :: TonemapDurandcv :: TonemapMantiukcv :: TonemapReinhard
  2. 尝试更改 HDR 校准和色调映射方法中的参数。