今天要整理记录的笔记是与高动态范围HDR成像相关的内容,主要参考学习的来源为OpenCV官方文档。

首先需要了解什么是高动态范围HDR,由维基百科定义可知:

高动态范围成像(英语:High Dynamic Range Imaging,简称HDRI或HDR),在计算机图形学与电影摄影术中,是用来实现比普通数位图像技术更大曝光动态范围(即更大的明暗差别)的一组技术。高动态范围成像的目的就是要正确地表示真实世界中从太阳光直射到最暗的阴影这样大的范围亮度。

也就是说,HDR图像相比起我们平常所见的图像来说,其像素取值范围更大、深度更深,一般的图像可能是8位的无符号字节类型,而HDR图像则一般为32位浮点类型,所以HDR图像所能存储的图像信息相比起一般图像要多得多,最直观的视觉感受就是:HDR图像的明亮处和灰暗处都能够呈现出比较好的细节,不会出现严重的过曝和欠曝问题。

但也正因为HDR图像存储的信息量之大、深度之深,以至于我们不能直接进行显示,会出现一片白茫茫的情况。而需要先进行色调映射后才可以正常显示。

下面就跟随着官方文档的步骤,来学习实现HDR图像的合成与显示。在OpenCV中,提供了三种合成HDR图像的方式,分别是Debevec方法、Robertson方法、Mertens方法,由于这三种方式使用起来具有一定的相似性,所以下面并行进行演示。

  1. 加载曝光图像序列
    首先,不管是哪一种方法,我们都需要加载一系列不同曝光时间(以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);}
  1. 加载曝光时间序列
    对于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);}
  1. 进行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, vector<float>exposureTimes)
{//auto Debevec_response = createCalibrateDebevec();//Mat response;//Debevec_response->process(exposureImages, response, exposureTimes);auto  merge_Debevec = createMergeDebevec();Mat hdr;merge_Debevec->process(exposureImages, hdr, exposureTimes);//由于是在普通的LDR显示器上进行显示,因此我们必须将HDR图像映射到保留大多数细节的8位范围的低动态范围LDR图像。//这是进行色调映射的主要目标,伽玛校正值设置为2.2f适用于大多数情况Mat ldr;auto tonemap = createTonemap(2.2f);tonemap->process(hdr, ldr);//HDR图像进行色调映射后得到的LDR图像取值范围在 [ 0 , 1 ] ,所以乘255将范围拉伸到 [ 0 , 255 ] ldr = ldr * 255;//将float32类型转化为uchar8类型ldr.convertTo(ldr, CV_8UC3);imshow("Debevec_HDR", hdr);imshow("Debevec_LDR", ldr);imwrite("D:\\opencv_c++\\Learning-OpenCV\\高动态(HDR)图像\\Debevec_LDR.png", ldr);
}
void Robertson(vector<Mat>exposureImages, vector<float>exposureTimes)
{//auto Robertson_response = createCalibrateRobertson();//Mat response;//Robertson_response->process(exposureImages, response, exposureTimes);auto  merge_Robertson = createMergeRobertson();Mat hdr;merge_Robertson->process(exposureImages, hdr, exposureTimes);//由于是在普通的LDR显示器上进行显示,因此我们必须将HDR图像映射到保留大多数细节的8位范围的低动态范围LDR图像。//这是进行色调映射的主要目标,伽玛校正值设置为2.2f适用于大多数情况Mat ldr;auto tonemap = createTonemap(2.2f);tonemap->process(hdr, ldr);//HDR图像进行色调映射后得到的LDR图像取值范围在 [ 0 , 1 ] ,所以乘255将范围拉伸到 [ 0 , 255 ] ldr = ldr * 255;ldr.convertTo(ldr, CV_8UC3);imshow("Robertson_HDR", hdr);imshow("Robertson_LDR", ldr);imwrite("D:\\opencv_c++\\Learning-OpenCV\\高动态(HDR)图像\\Robertson_LDR.png", ldr);}

(2)Mertens方法
对于Mertens方法来说,只需要传入不同曝光时间的图像序列就可以了,该方法使用对比度、饱和度和曝光度的好坏来对像素加权,而不是使用拉普拉斯金字塔来对图像进行加权。得到的图像权重被构造为对比度、饱和度和曝光度良好的加权平均值。
由于Mertens方法是进行不同曝光时间图像的融合,并且直接生成由HDR图像转化来的低动态范围图像,所以生成的图像不需要进行色调映射。
生成的图像取值范围在 [ 0 , 1 ] 之间,可以通过乘以255将取值范围拉伸到 [ 0 , 255 ],并且转换为8位图像进行显示。在官方文档中还建议应用伽马校正或线性色调映射来提升显示效果,OpenCV中提供了三种色调映射方法,这里使用的是createTonemap(1.0f),其中参数的默认值是1.0f,不过取2.2f能适用于大多数场景。
当我们不清楚图像的曝光时间,或者不需要得到HDR图像时,可以直接使用Mertens方法来进行曝光融合,得到的低动态范围图像也是由HDR图像转换而来,同样在明亮处和灰暗处都有更好的细节体现。
下面是Mertens方法的代码演示:

void Mertens(vector<Mat>exposureImages)
{auto merge_mertens = createMergeMertens();Mat fusion;merge_mertens->process(exposureImages, fusion);//进行伽马矫正,需根据实际图像调节参数,2.2f可满足大多数显示情况/*auto tonemap = createTonemap(2.2f);tonemap->process(fusion, fusion);*/fusion = fusion * 255;fusion.convertTo(fusion, CV_8UC3);imshow("Mertens", fusion);imwrite("D:\\opencv_c++\\Learning-OpenCV\\高动态(HDR)图像\\Mertens.png", fusion);
}

到这里就分别以不同方法给出了OpenCV中HDR图像的合成步骤,下面是完整的代码演示:

#include<opencv2/opencv.hpp>
#include<iostream>
#include<vector>
#include<fstream>
#include<stdlib.h>
using namespace std;
using namespace cv;void Debevec(vector<Mat>exposureImages, vector<float>exposureTimes);
void Robertson(vector<Mat>exposureImages, vector<float>exposureTimes);
void Mertens(vector<Mat>exposureImages);
int main()
{//加载曝光图像序列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);}//加载曝光时间序列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合成Debevec(exposure_images, exposureTimes);Robertson(exposure_images, exposureTimes);Mertens(exposure_images);waitKey(0);return 0;
}//使用对比度、饱和度和曝光度好坏来对像素加权,而不是使用拉普拉斯金字塔来对图像进行加权。得到的图像权重被构造为对比度、饱和度和曝光度良好度量的加权平均值。
//生成的图像不需要进行色调映射,并且可以通过乘以255转换为8位图像,但是建议应用伽马校正或线性色调映射。
//不需要对应曝光时间, 可以在不需要HDR图像时使用,可直接得到低动态范围图像
void Mertens(vector<Mat>exposureImages)
{auto merge_mertens = createMergeMertens();Mat fusion;merge_mertens->process(exposureImages, fusion);//进行伽马矫正,需根据实际图像调节参数,2.2f可满足大多数显示情况/*auto tonemap = createTonemap(2.2f);tonemap->process(fusion, fusion);*/fusion = fusion * 255;fusion.convertTo(fusion, CV_8UC3);imshow("Mertens", fusion);imwrite("D:\\opencv_c++\\Learning-OpenCV\\高动态(HDR)图像\\Mertens.png", fusion);
}//使用Debevec方法进行HDR合成,将多张曝光时间不同的照片及其对应的曝光值进行加权平均,得到float32类型的高动态范围HDR图像
//需要传入曝光图像序列和对应的曝光时间
void Debevec(vector<Mat>exposureImages, vector<float>exposureTimes)
{//auto Debevec_response = createCalibrateDebevec();//Mat response;//Debevec_response->process(exposureImages, response, exposureTimes);auto  merge_Debevec = createMergeDebevec();Mat hdr;merge_Debevec->process(exposureImages, hdr, exposureTimes);//由于是在普通的LDR显示器上进行显示,因此我们必须将HDR图像映射到保留大多数细节的8位范围的低动态范围LDR图像。//这是进行色调映射的主要目标,伽玛校正值设置为2.2f适用于大多数情况Mat ldr;auto tonemap = createTonemap(2.2f);tonemap->process(hdr, ldr);//HDR图像进行色调映射后得到的LDR图像取值范围在 [ 0 , 1 ] ,所以乘255将范围拉伸到 [ 0 , 255 ] ldr = ldr * 255;//将float32类型转化为uchar8类型ldr.convertTo(ldr, CV_8UC3);imshow("Debevec_HDR", hdr);imshow("Debevec_LDR", ldr);imwrite("D:\\opencv_c++\\Learning-OpenCV\\高动态(HDR)图像\\Debevec_LDR.png", ldr);
}//使用Robertson方法进行HDR图像合成,将多张曝光时间不同的照片及其对应的曝光值进行加权平均,得到float32类型的高动态范围HDR图像
//需要传入曝光图像序列和对应的曝光时间
void Robertson(vector<Mat>exposureImages, vector<float>exposureTimes)
{//auto Robertson_response = createCalibrateRobertson();//Mat response;//Robertson_response->process(exposureImages, response, exposureTimes);auto  merge_Robertson = createMergeRobertson();Mat hdr;merge_Robertson->process(exposureImages, hdr, exposureTimes);//由于是在普通的LDR显示器上进行显示,因此我们必须将HDR图像映射到保留大多数细节的8位范围的低动态范围LDR图像。//这是进行色调映射的主要目标,伽玛校正值设置为2.2f适用于大多数情况Mat ldr;auto tonemap = createTonemap(2.2f);tonemap->process(hdr, ldr);//HDR图像进行色调映射后得到的LDR图像取值范围在 [ 0 , 1 ] ,所以乘255将范围拉伸到 [ 0 , 255 ] ldr = ldr * 255;ldr.convertTo(ldr, CV_8UC3);imshow("Robertson_HDR", hdr);imshow("Robertson_LDR", ldr);imwrite("D:\\opencv_c++\\Learning-OpenCV\\高动态(HDR)图像\\Robertson_LDR.png", ldr);}

分别看一下不同方法合成的HDR图像的效果如何,首先看一下所使用的曝光图像序列:

我们使用上述16张不同曝光时间的图像进行合成HDR图像,三种方法效果图如下:



从上到下分别是Debevec方法、Robertson方法、Mertens方法得到的HDR图像显示效果,总的来说在亮处和暗处都呈现出更多的细节,但是Debevec方法和Robertson方法在色调映射这一步骤上仍然需要更多的调试,可以选取不同参数取值或选取不同的色调映射方法来进行尝试,以获得更好的效果。在这里,我们只是尝试还原了官方文档中的HDR效果,虽然官网中演示代码在合成HDR图像时加入了CRF参数而我们的演示代码中并没有,但是效果还是比较相似的。而一旦加入相机响应函数CRF,就会导致效果变得很差,甚至严重偏色,至于原因我还没找到,希望有了解这个的朋友能指导我一下。

最后,虽然在这组图像中,Mertens方法得到的HDR效果是最好的,但并不意味着对于所有图像都是如此,我们在实际使用时还需要分别测试不同方法对于我们实际图像的呈现效果,并且还需要对色调映射的不同方法、不同参数进行调试。只有经过不断调试、对比,才能得到最符合我们实际情况的HDR成像方法以及参数设置。

PS:本人的注释比较杂,既有自己的心得体会也有网上查阅资料时摘抄下的知识内容,所以如有雷同,纯属我向前辈学习的致敬,如果有前辈觉得我的笔记内容侵犯了您的知识产权,请和我联系,我会将涉及到的博文内容删除,谢谢!

OpenCV4学习笔记(59)——高动态范围(HDR)成像相关推荐

  1. OPENCV-4 学习笔记

    OPENCV-4 学习笔记 ROI-设定感兴趣的区域(region of interest) 定义: Mat imageROI; //方法一:通过Rect指定矩形区域 imageROI=image(R ...

  2. OpenCV4学习笔记(57)——基于GrabCut图像分割算法实现背景替换与背景虚化效果

    在上一篇笔记<OpenCV4学习笔记(56)>中,整理了关于在OpenCV中使用GrabCut图像分割算法的相关内容,那么本次笔记就以GrabCut算法为基础来实现对图像的背景替换和背景虚 ...

  3. OpenCV4学习笔记(55)——基于KNN最近邻算法实现鼠标手写数字识别

    在上一篇博客<OpenCV4学习笔记(54)>中,整理了关于KNN最近邻算法的一些相关内容和一个手写体数字识别的例子.但是上次所实现的手写体数字识别,每次只能固定地输入测试图像进行预测,而 ...

  4. OpenCV4学习笔记(76)——基于ArUco模块+QT实现增强现实(AR)

    在<OpenCV4学习笔记(75)>中,整理记录了对于一副静态图像如何实现一个简单的增强现实效果,今天我们就结合ArUco模块和QT来实现对于实时视频流的AR效果. 我们需要先创建一个QT ...

  5. OpenCV4学习笔记(41)——ORB特征提取描述算法

    今天要整理记录的笔记内容是特征算法中比较常用的一种--ORB特征提取描述算法,顾名思义,ORB算法包含了对特征点的提取和描述这两个部分.而在上次的博文<OpenCV4学习笔记(39)>中, ...

  6. OpenCV4学习笔记(47)——BRISK特征提取描述算法

    今天要整理记录的是OpenCV中BRISK特征提取描述算法的运用. BRISK特征提取描述算法全称为 Binary Robust Invariant Scalable Keypoints(二进制鲁棒不 ...

  7. OpenCV4学习笔记(23)——几何矩、中心矩、归一化矩和Hu矩的计算,以及基于Hu矩的轮廓匹配

    在上次的笔记中,整理记录了有关轮廓发现及轮廓信息提取的一部分内容,同时还记录了Hu矩的计算方式,今天就来记录一下Hu矩的一个应用--轮廓匹配. 在<OpenCV学习笔记(19)--模板匹配> ...

  8. OpenCV4学习笔记(30)——透视变换(投影变换)

    今天要整理的笔记是关于图像的透视变换的内容. 首先需要了解什么是透视变换,这里引用百度百科上的内容: 透视变换(Perspective Transformation)是指利用透视中心.像点.目标点三点 ...

  9. OpenCV4学习笔记(31)——视频背景、前景提取分离及运动检测

    这次要整理的笔记是视频背景.前景提取及运动检测,是通过对视频前面的一系列帧图像来提取背景模型,从而分离出前景目标和背景,进而对运动的前景目标进行检测.OpenCV中实现的背景模型提取算法有两种,一种是 ...

  10. OpenCV4学习笔记(19)——模板匹配

    本次要整理记录的内容是:模板匹配. 模板匹配可以说是一种最简单的模式识别方法,它的实现主要是通过模板图像在被匹配图像中的平移,在被匹配图像中逐个区域寻找和模板图像相似的区域,如果存在某区域的相似度大于 ...

最新文章

  1. 【微信小程序企业级开发教程】前台收集数据更新数据库表方法
  2. boost::intrusive::bucket_traits用法的测试程序
  3. Linux shell编程:状态变量
  4. 使用MySQL处理百万级以上数据时,几个好习惯常识
  5. ReferenceError: regeneratorRuntime is not defined解决方法
  6. Numpy——浅拷贝与深拷贝
  7. Python使用MongoDB简记
  8. oracle 仲宏伟,Oracle数据库的一些操作
  9. 使用悲观锁还是乐观锁
  10. DOM元素节点属性outerHTML和innerHTML
  11. 密码编码学初探——数论和有限域
  12. 基于SSM框架的个人博客系统项目毕业设计(代码及论文)
  13. android闹铃唤醒软件,温柔唤醒闹钟app_温柔的闹钟铃声有哪些_华为智能闹钟智能唤醒-多特软件站安卓网...
  14. 笔记本电脑散热风扇声音比较大解决方法
  15. [049] 微信公众平台开发视频公开课第1讲-基础入门
  16. Ubuntu的Spark 搭建实验(ssh配置没好,需要的看新发的,完整版)
  17. 如何对pdf进行编辑修改linux,Xournal: 随心编辑PDF
  18. Linux应用编程和网络编程(3)------- Linux中文件的属性
  19. 【权限提升】61 Redis Postgresql数据库提权
  20. 华为鸿蒙如何添加桌面小组件,万能小组件怎么添加到桌面上

热门文章

  1. HttpClient介绍
  2. 数据库系统概论(高级篇)
  3. Shell脚本初学习
  4. Calendar根据日期获取年份和周、当前周的所有日期
  5. 个人计算机与工作站 服务器有什么区别,工作站与服务器有什么区别?
  6. 计算机网络一小时总结 明天考试
  7. java n_javan是什么意思_javan怎么读_javan翻译_用法_发音_词组_同反义词_爪哇人[语]-新东方在线英语词典...
  8. 3D模型实现「唱、跳、Rap、篮球」
  9. 矩阵计算与AI革命:可将计算性能提高150倍的异构计算
  10. 分析Kettle性能测试过程中的STARTDATE时间问题