目标
在本章中,我们将
- 学习如何从曝光序列生成并显示 HDR 图像。
- 使用曝光融合合并曝光序列。
原理
高动态范围成像(HDRI 或 HDR)是成像和摄影中使用的一种技术,用于再现比标准数字成像或摄影技术更大的亮度动态范围。虽然人眼可以适应各种光线条件,但大多数成像设备每个通道使用 8 位,因此我们只能看到 256 级。当我们拍摄真实世界的场景时,明亮的区域可能曝光过度,而黑暗的区域可能曝光不足,因此我们无法通过一次曝光捕捉到所有细节。HDR 图像使用每个通道超过 8 位的图像(通常是 32 位浮点数值),动态范围更广。
获取 HDR 图像的方法多种多样,但最常见的方法是使用不同曝光值拍摄的场景照片。要合并这些曝光,了解相机的响应函数非常有用,有一些算法可以估算出响应函数。合并 HDR 图像后,必须将其转换回 8 位,以便在普通显示器上查看。这一过程称为色调映射。当场景中的物体或相机在不同镜头之间移动时,就会产生额外的复杂性,因为不同曝光的图像需要进行注册和对齐。
在本教程中,我们展示了从曝光序列生成和显示 HDR 图像的两种算法(Debevec 和 Robertson),并演示了一种称为曝光融合(Mertens)的替代方法,该方法可生成低动态范围图像,且无需曝光时间数据。此外,我们还估算了相机响应函数(CRF),这对许多计算机视觉算法具有重要价值。HDR 流水线的每一步都可以使用不同的算法和参数来实现,因此请查看参考手册了解所有这些算法和参数。
HDR 曝光序列
在本教程中,我们将查看以下场景,其中有 4 张曝光图像,曝光时间分别为 15、2.5、1/4 和 1/4: 15、2.5、1/4 和 1/30 秒。(您可以从维基百科下载图片)
1. 将曝光图像载入列表
第一阶段是将所有图像载入列表。此外,我们还需要常规 HDR 算法的曝光时间。请注意数据类型,图像应为单通道或三通道 8 位(np.uint8),曝光时间应为 float32,单位为秒。
import cv2 as cv
import numpy as np
# 将曝光图像载入列表
img_fn = ["img0.jpg", "img1.jpg", "img2.jpg", "img3.jpg"]
img_list = [cv.imread(fn) for fn in img_fn]
exposure_times = np.array([15.0, 2.5, 0.25, 0.0333], dtype=np.float32)
2. 将曝光合并为 HDR 图像
在这一阶段,我们将曝光序列合并为一张 HDR 图像,这显示了 OpenCV 中的两种可能性。第一种方法是 Debevec,第二种是 Robertson。请注意,HDR 图像的类型是 float32,而不是 uint8,因为它包含了所有曝光图像的完整动态范围。
# 将曝光合并为 HDR 图像
merge_debevec = cv.createMergeDebevec()
hdr_debevec = merge_debevec.process(img_list, times=exposure_times.copy())
merge_robertson = cv.createMergeRobertson()
hdr_robertson = merge_robertson.process(img_list, times=exposure_times.copy())
3. 调色板 HDR 图像
我们将 32 位浮点 HDR 数据映射到 [0…1] 范围内。实际上,在某些情况下,数据值可能大于 1 或小于 0,因此我们稍后必须对数据进行剪切,以避免溢出。
# Tonemap HDR 图像
tonemap1 = cv.createTonemap(gamma=2.2)
res_debevec = tonemap1.process(hdr_debevec.copy())
4. 使用梅尔腾斯融合法(Mertens fusion)合并曝光图像
这里我们将展示另一种合并曝光图像的算法,在这种算法中我们不需要曝光时间。我们也不需要使用任何色调分布算法,因为默滕斯算法已经给出了 [0…1] 范围内的结果。
# 使用默滕斯进行曝光融合
merge_mertens = cv.createMergeMertens()
res_mertens = merge_mertens.process(img_list)
5. 转换为 8 位并保存
为了保存或显示结果,我们需要将数据转换为范围为 [0…255] 的 8 位整数。
# 将数据类型转换为 8 位并保存
res_debevec_8bit = np.clip(res_debevec*255, 0, 255).astype('uint8')
res_robertson_8bit = np.clip(res_robertson*255, 0, 255).astype('uint8')
res_mertens_8bit = np.clip(res_mertens*255, 0, 255).astype('uint8')
cv.imwrite("ldr_debevec.jpg", res_debevec_8bit)
cv.imwrite("ldr_robertson.jpg", res_robertson_8bit)
cv.imwrite("fusion_mertens.jpg", res_mertens_8bit)
结果
您可以看到不同的结果,但需要注意的是,每种算法都有额外的参数,您应该根据自己的需要进行调整,以获得理想的结果。最佳做法是尝试不同的方法,看看哪种方法最适合您的场景。
Debevec:
Robertson:
Mertenes Fusion:
估算相机响应函数
相机响应函数(CRF)为我们提供了场景辐射度与测量强度值之间的联系。在一些计算机视觉算法(包括 HDR 算法)中,CRF 至关重要。在此,我们将估算逆相机响应函数,并将其用于 HDR 合并。
# Estimate camera response function (CRF)
cal_debevec = cv.createCalibrateDebevec()
crf_debevec = cal_debevec.process(img_list, times=exposure_times)
hdr_debevec = merge_debevec.process(img_list, times=exposure_times.copy(), response=crf_debevec.copy())
cal_robertson = cv.createCalibrateRobertson()
crf_robertson = cal_robertson.process(img_list, times=exposure_times)
hdr_robertson = merge_robertson.process(img_list, times=exposure_times.copy(), response=crf_robertson.copy())
摄像机响应函数由每个颜色通道的 256 个长度向量表示。对于这个序列,我们得到了以下估计值:
其他资源
- Paul E Debevec 和 Jitendra Malik. 从照片中恢复高动态范围辐射图。In ACM SIGGRAPH 2008 classes, page 31. ACM,2008 年。[57]
- Mark A Robertson、Sean Borman 和 Robert L Stevenson。通过多次曝光改善动态范围。图像处理》,1999 年。ICIP 99. 论文集。1999 国际会议,第 3 卷,第 159-163 页。IEEE, 1999. [209]
- Tom Mertens, Jan Kautz, and Frank Van Reeth. 曝光融合。计算机图形学与应用》,2007 年。PG’07. 第 15 届太平洋会议,第 382-390 页。IEEE, 2007. [172]
- 图片来自**维基百科-HDR**
练习
- 尝试所有调色板算法:cv::TonemapDrago、cv::TonemapMantiuk 和 cv:::TonemapReinhard
- 尝试更改 HDR 校准和调色板方法中的参数。