今天要整理记录的笔记是与高动态范围HDR成像相关的内容,主要参考学习的来源为OpenCV官方文档。
首先需要了解什么是高动态范围HDR,由维基百科定义可知:
高动态范围成像(英语:High Dynamic Range Imaging,简称HDRI或HDR),在计算机图形学与电影摄影术中,是用来实现比普通数位图像技术更大曝光动态范围(即更大的明暗差别)的一组技术。高动态范围成像的目的就是要正确地表示真实世界中从太阳光直射到最暗的阴影这样大的范围亮度。
也就是说,HDR图像相比起我们平常所见的图像来说,其像素取值范围更大、深度更深,一般的图像可能是8位的无符号字节类型,而HDR图像则一般为32位浮点类型,所以HDR图像所能存储的图像信息相比起一般图像要多得多,最直观的视觉感受就是:HDR图像的明亮处和灰暗处都能够呈现出比较好的细节,不会出现严重的过曝和欠曝问题。
但也正因为HDR图像存储的信息量之大、深度之深,以至于我们不能直接进行显示,会出现一片白茫茫的情况。而需要先进行色调映射后才可以正常显示。
下面就跟随着官方文档的步骤,来学习实现HDR图像的合成与显示。在OpenCV中,提供了三种合成HDR图像的方式,分别是Debevec方法、Robertson方法、Mertens方法,由于这三种方式使用起来具有一定的相似性,所以下面并行进行演示。
- 加载曝光图像序列
首先,不管是哪一种方法,我们都需要加载一系列不同曝光时间(以s为单位)的图像,如果把这一系列图像单独存储在一个文件夹中,可以用以下方式读取并组织成一个曝光图像序列。
//加载曝光图像序列
vector<string>imgPath;
glob("D:\\opencv_c++\\opencv_tutorial\\data\\images\\exposures\\", imgPath);
vector<Mat> exposure_images;
for (int i = 0;i < imgPath.size();i++)
{
Mat exposure_image = imread(imgPath[i]);
exposure_images.push_back(exposure_image);
}
- 加载曝光时间序列
对于Debevec方法和Robertson方法来说,在进行HDR合成时需要同时传入曝光图像序列和曝光时间序列,而对于Mertens方法来说只需要传入曝光图像序列即可。这里还是需要进行加载曝光时间序列这一步骤。
当把不同曝光时间用txt文件存储时,可以用以下方式来加载曝光时间序列。
//加载曝光时间序列
String exposureTimesPath = "D:\\opencv_c++\\Learning-OpenCV\\高动态(HDR)图像\\exposuresTime.txt";
ifstream fp(exposureTimesPath);
if (!fp.is_open())
{
cout << "can't open file" << endl;
exit(-1);
}
vector<float> exposureTimes;
while (!fp.eof())
{
string time;
getline(fp, time);
float t = float(atof(time.c_str()));
exposureTimes.push_back(t);
}
- 进行HDR合成
(1)Robertson方法与Debevec方法
使用Robertson方法和Debevec方法进行HDR图像合成时,需要将多张不同曝光时间的照片及其对应的曝光值进行加权平均,得到float32类型的高动态范围HDR图像,并且可以加入预先计算得到的相机响应函数(CRF)进行非线性合成。如果没有加入相机响应函数(CRF)这个参数,则会通过传入的不同曝光图像序列和不同曝光时间序列来进行计算得到一个线性CRF。
不过,在OpenCV中对于相机响应函数这个参数的设置应该有些问题。如果不传入CRF这个参数,那么合成的HDR图像效果比较好,而一旦加入CRF参数,那么得到的HDR图像将会呈现非常差的效果。也就是说,当在OpenCV中合成HDR图像时,使用线性CRF得到的HDR图像,其效果相比起预先计算CRF后传入参数来得到的HDR图像要好得多。
这个问题让我很疑惑,明明加入预先计算的相机响应函数后,应该得到更好的HDR效果才对,但是实际上恰恰相反,而且在网上找了很久也没有相关的解答。最终只发现在Stack Overflow上有一位网友也提出了相同的问题,并在讨论区有网友认为是OpenCV的合成HDR功能的API中已经加入了伽马矫正的内容,以至于再次进行非线性矫正的情况下反而会导致更差的效果。
总而言之 ,从理论上来说,我们需要预先计算CRF来参与HDR图像合成才能得到好的效果,但是在OpenCV中并不一定需要这一步,至少在我以官网中的图像做实验时,不加入CRF参数能得到更好的效果。
而且虽然官方文档中是有传入CRF参数的,但是我以相同的参数对相同的图像做处理时却得不到和官方文档中相同的HDR图像,反而是不加入CRF参数时效果更好。
当我们合成得到HDR图像后,由于是在普通的低动态范围LDR显示器上进行显示,因此我们必须将HDR图像映射到保留大多数细节的8位范围的低动态范围LDR图像。这一步也称为色调映射,目的就是为了将我们得到的HDR图像正常显示出来,不过从高动态范围到低动态范围,肯定会牺牲一些图像信息。
下面是Robertson方法与Debevec方法的代码演示:
void Debevec(vector<Mat>exposureImages