高动态范围成像(HDRI或HDR)
当前功能貌似是原PDF作者自行扩展的内容,不在PDF文档中,博主这里也顺便整理一下。
当前系列所有demo下载地址:
https://github.com/GaoRenBao/OpenCv4-Demo
https://gitee.com/fuckgrb/OpenCv4-Demo
不同编程语言对应的OpenCv版本以及开发环境信息如下:
语言 | OpenCv版本 | IDE |
C# | OpenCvSharp4.4.8.0.20230708 | Visual Studio 2022 |
C++ | OpenCv-4.5.5-vc14_vc15 | Visual Studio 2022 |
Python | OpenCv-Python (3.4.2.16) | PyCharm Community Edition 2022.1.3 |
注意:
1、createTonemapDurand被作者生气专利了,高版本python无法使用该函数,目前测试opencv-python 3.4.2.16版本可以使用。
离线安装包下载地址:
https://pypi.tuna.tsinghua.edu.cn/simple/opencv-python/
https://pypi.tuna.tsinghua.edu.cn/simple/opencv-contrib-python/

然后安装命令:
pip install numpy==1.14.5 -i https://mirror.baidu.com/pypi/simple
pip install opencv_python-3.4.2.16-cp37-cp37m-win_amd64.whl opencv_contrib_python-3.4.2.16-cp37-cp37m-win_amd64.whl
2、C#的opencv4版本缺了好多方法。。。
官方教程:
http://docs.opencv.org/3.2.0/d2/df0/tutorial_py_hdr.html
1、了解如何从曝光序列生成和显示HDR图像。
2、使用曝光融合合并曝光序列。
高动态范围成像(HDRI或HDR)是一种用于成像和摄影的技术,可以重现比标准数字成像或摄影技术更大的动态光度范围。虽然人眼可以调整到广泛的光线条件,但大多数成像设备每通道使用8位,因此我们仅限于256级。当我们拍摄现实世界的场景时,明亮的地区可能曝光过度,而黑暗的区域可能曝光不足,所以我们无法使用单次曝光拍摄所有细节。HDR图像与每个通道使用超过8位(通常为32位浮点值)的图像一起使用,允许更宽的动态范围。
有不同的获取HDR图像的方法,但最常见的是使用不同曝光值拍摄的场景的照片。要组合这些曝光,了解您的相机的响应功能是有用的,并且有算法来估计它。合并HDR图像后,必须将其转换回8位才能在通常的显示屏上进行查看。这个过程叫做tonemapping。当场景或相机的对象在拍摄之间移动时,会出现附加的复杂性,因为具有不同曝光的图像应该被注册和对齐。
在本教程中,我们展示了两种算法(Debvec,Robertson)从曝光序列生成和显示HDR图像,并展示了一种称为曝光融合(Mertens)的替代方法,它产生低动态范围图像,不需要曝光时间数据。此外,我们估计对于许多计算机视觉算法具有重要价值的相机响应函数(CRF)。HDR管道的每一步都可以使用不同的算法和参数来实现,因此请参考参考手册来查看。
测试原图如下:
从左到右:1tl.jpg、2tr.jpg

从左到右:3bl.jpg、4br.jpg

C#版本代码如下:
参考博客:https://blog.csdn.net/jimtien/article/details/119026930

代码如下:
using OpenCvSharp;
using OpenCvSharp.XPhoto;
using System.Collections.Generic;
namespace demo
{
internal class Program
{
static void Main(string[] args)
{
List<Mat> images = new List<Mat>()
{
Cv2.ImRead("../../../images/tbs/1tl.jpg"),
Cv2.ImRead("../../../images/tbs/2tr.jpg"),
Cv2.ImRead("../../../images/tbs/3bl.jpg"),
Cv2.ImRead("../../../images/tbs/4br.jpg"),
};
float[] exposures = new float[] { 15.0f, 2.5f, 0.25f, 0.0333f };
// 估计相机响应
Mat response = new Mat();
CalibrateDebevec calibrate = CalibrateDebevec.Create();
calibrate.Process(images, response, exposures);
// 生成HDR图片
Mat hdr = new Mat();
MergeDebevec merge_debevec = MergeDebevec.Create();
merge_debevec.Process(images, hdr, exposures, response);
/* 找不到 createMergeRobertson 的对应方法 */
// Tonemap
Mat ldr = new Mat();
TonemapDurand tonemap = TonemapDurand.Create(0.5f);
tonemap.Process(hdr, ldr);
// Exposure fusion using Mertens
// 这里我们展示了一种可以合并曝光图像的替代算法
Mat fusion = new Mat();
MergeMertens merge_mertens = MergeMertens.Create();
merge_mertens.Process(images, fusion);
Cv2.ImShow("fusion.png", fusion);
Cv2.ImShow("ldr.png", ldr);
Cv2.ImShow("hdr.hdr", hdr);
Cv2.WaitKey(0);
}
}
}
C++版本代码如下:
C++版本也没有 createTonemapDurand 方法,我这里用 createTonemapDrago 代替了
效果如下:

代码如下:
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/photo.hpp"
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
vector<Mat> img_list;
img_list.push_back(imread("../images/tbs/1tl.jpg"));
img_list.push_back(imread("../images/tbs/2tr.jpg"));
img_list.push_back(imread("../images/tbs/3bl.jpg"));
img_list.push_back(imread("../images/tbs/4br.jpg"));
vector<float> exposure_times = { 15.0f, 2.5f, 0.25f, 0.0333f };
Mat hdr_debvec, hdr_robertson;
Ptr<MergeDebevec> merge_debvec = createMergeDebevec();
merge_debvec->process(img_list, hdr_debvec, exposure_times);
Ptr<MergeRobertson> merge_robertson = createMergeRobertson();
merge_robertson->process(img_list, hdr_robertson, exposure_times);
// Tonemap HDR image
// 没有 createTonemapDurand 方法,只能用 createTonemapDrago 代替了
Mat res_debvec;
Ptr<TonemapDrago> tonemap1 = createTonemapDrago(2.2);
tonemap1->process(hdr_debvec, res_debvec);
Mat res_robertson;
Ptr<TonemapDrago> tonemap2 = createTonemapDrago(1.3);
tonemap2->process(hdr_robertson, res_robertson);
// Exposure fusion using Mertens
Mat res_mertens;
Ptr<MergeMertens> merge_mertens = createMergeMertens();
merge_mertens->process(img_list, res_mertens);
imshow("ldr_debvec.jpg", res_debvec);
imshow("ldr_robertson.jpg", res_robertson);
imshow("fusion_mertens.jpg", res_mertens);
waitKey(0);
return 0;
}
Python版本代码如下:
python包版本说明:

运行效果:

测试代码如下:
import cv2
import numpy as np
from re import match
# 通过这个代码,可以查看opencv中是否包含createTonemapDurand
cv2_filtered = filter(lambda v: match('.*Tonemap', v), dir(cv2))
[print(val) for val in cv2_filtered]
# 第一阶段只是将所有图像加载到列表中。此外,我们将需要常规HDR算法的曝光时间。注意数据类型,因为图像应为1通道或3通道8位(np.uint8),曝光时间需要为float32,以秒为单位。
# Loading exposure images into a list
img_fn = ["../images/tbs/1tl.jpg", "../images/tbs/2tr.jpg", "../images/tbs/3bl.jpg", "../images/tbs/4br.jpg"]
img_list = [cv2.imread(fn) for fn in img_fn]
exposure_times = np.array([15.0, 2.5, 0.25, 0.0333], dtype=np.float32)
# Merge exposures to HDR image
# 在这个阶段,我们将曝光序列合并成一个HDR图像,显示了我们在OpenCV中的两种可能性。第一种方法是Debvec,第二种是Robertson。请注意,HDR图像的类型为float32,而不是uint8,因为它包含所有曝光图像的完整动态范围。
merge_debvec = cv2.createMergeDebevec()
hdr_debvec = merge_debvec.process(img_list, times=exposure_times.copy())
merge_robertson = cv2.createMergeRobertson()
hdr_robertson = merge_robertson.process(img_list, times=exposure_times.copy())
# Tonemap HDR image
# 我们将32位浮点HDR数据映射到范围[0..1]。实际上,在某些情况下,值可能大于1或低于0,所以注意我们以后不得不剪切数据,以避免溢出。
tonemap1 = cv2.createTonemapDurand(gamma=2.2)
res_debvec = tonemap1.process(hdr_debvec.copy())
tonemap2 = cv2.createTonemapDurand(gamma=1.3)
res_robertson = tonemap2.process(hdr_robertson.copy())
# Exposure fusion using Mertens
# 这里我们展示了一种可以合并曝光图像的替代算法,我们不需要曝光时间。我们也不需要使用任何tonemap算法,因为Mertens算法已经给出了[0..1]范围内的结果。
merge_mertens = cv2.createMergeMertens()
res_mertens = merge_mertens.process(img_list)
# Convert datatype to 8-bit and save
# 为了保存或显示结果,我们需要将数据转换为[0..255]范围内的8位整数。
res_debvec_8bit = np.clip(res_debvec * 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')
cv2.imshow("ldr_debvec.jpg", res_debvec_8bit)
cv2.imshow("ldr_robertson.jpg", res_robertson_8bit)
cv2.imshow("fusion_mertens.jpg", res_mertens_8bit)
cv2.waitKey()
exit(0)
# Estimate camera response function (CRF)
# 相机响应功能(CRF)给出了场景辐射度与测量强度值之间的连接。如果在一些计算机视觉算法中非常重要,包括HDR算法,CRF。这里我们估计反相机响应函数并将其用于HDR合并。
cal_debvec = cv2.createCalibrateDebevec()
crf_debvec = cal_debvec.process(img_list, times=exposure_times)
hdr_debvec = merge_debvec.process(img_list, times=exposure_times.copy(), response=crf_debvec.copy())
cal_robertson = cv2.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())