当前位置: 首页 > 科技观察

如何使用OpenCV进行高动态范围(HDR)成像

时间:2023-03-16 10:26:04 科技观察

在本教程中,我们将学习如何使用以不同曝光设置拍摄的多张图像来创建高动态范围(HDR)图像。我们将共享C++和Python中的代码。什么是高动态范围成像?大多数数码相机和监视器以24位矩阵捕获或显示彩色图像。每个颜色通道有8位,因此每个通道都有一个0-255范围内的像素值。换句话说,普通相机或监视器的动态范围是有限的。然而,我们周围世界的动态范围是巨大的。关掉车库里的灯会很黑,直视太阳会很亮。即使撇开这些极端情况,8位通道也只能勉强捕捉日常生活中的现场场景。因此,相机会尝试评估照明并自动设置曝光,以便图像中最有趣的区域具有良好的动态范围,并且将太暗和太亮的部分相应地剪裁为0和255。在下图中,左边的图像是正常曝光的图像。请注意,由于相机决定使用主体(我儿子)的设置,背景中的天空已完全丢失,但明亮的天空也因此被刷掉了。右边的图像是由iPhone生成的HDR图像。高动态范围(HDR)iPhone如何捕捉HDR图像?它实际上在三种不同的曝光下拍摄了3张图像,并且3张图像的拍摄速度非常快,3张图像之间几乎没有偏移。然后将这三幅图像组合起来生成HDR图像。我们将在下一节中看到一些细节。将在不同曝光设置下获取的同一场景的不同图像组合在一起的过程称为高动态范围(HDR)成像。高动态范围(HDR)成像如何工作?在本节中,我们将了解使用OpenCV创建HDR图像的步骤。要轻松学习本教程,请在此处下载C++和Python代码和图像。如果您有兴趣了解更多关于人工智能、计算机视觉和机器学习的知识,请订阅我们的电子杂志。第1步:拍摄多张不同曝光度的图像当我们用相机拍照时,每个通道只有8位来表示场景的动态范围(亮度范围)。然而,通过改变快门速度,我们可以在不同的曝光条件下拍摄场景的多张图像。大多数单镜头反光相机(SLR)都有一项称为自动包围曝光(AEB)的功能,只需按一下按钮,我们就可以在不同的曝光下拍摄多张照片。如果您使用的是iPhone,则可以使用这款自动包围式HDR应用,如果您是Android用户,则可以尝试更好的相机应用。使用相机上的AEB或手机上的AEB应用程序,我们可以在场景不变的情况下快速拍摄多张照片。当我们在iPhone中使用HDR模式时,会拍摄三张照片。曝光不足的图像:图像比正确曝光的图像暗。目标是捕获图像中非常明亮的部分。正确曝光的图像:这是相机根据其估计光照拍摄的常规图像。曝光过度的图像:图像比正确曝光的图像更亮。目标是捕获图像的非常暗的部分。但是,如果场景的动态范围很大,我们可以拍摄三张以上的图片来合成HDR图像。在本教程中,我们将使用4张图像,曝光时间分别为1/30秒、0.25秒、2.5秒和15秒。缩略图如下所示。AutoExposureBracketedHDRimagesequence有关DSLR或手机的曝光时间和其他设置的信息通常存储在JPEG文件的EXIF元数据中。查看此链接以查看存储在Windows和Mac上的JPEG文件中的EXIF元数据。或者,您可以使用我最喜欢的名为EXIFTOOL的命令行工具来查看EXIF。我们首先读取分配给不同曝光时间的图像。C++voidreadImagesAndTimes(vector&images,vector×){intnumImages=4;}//曝光时间列表staticconstfloattimesArray[]={1/30.0f,0.25,2.5,15.0};次.assign(timesArray,timesArray+numImages);//图像文件名列表staticconstchar*filenames[]={"img_0.033.jpg","img_0.25.jpg","img_2.5.jpg","img_15.jpg"};for(inti=0;ialignMTB=createAlignMTB();alignMTB->process(images,images);Python#对齐输入图像alignMTB=cv2.createAlignMTB()alignMTB.process(images,images)Step3:提取相机响应函数典型相机的响应与场景亮度不成线性关系。这意味着什么?假设同一台相机拍摄了两个物体,其中一个的亮度是现实世界中另一个的两倍。当您测量照片中两个物体的像素亮度时,较亮物体的像素不会是较暗物体的两倍。如果不估计相机响应函数(CRF),我们将无法将图像合并为一张HDR图像。将多重曝光图像合并为HDR图像是什么意思?只考虑图像某个位置(x,y)处的一个像素。如果CRF是线性的,则像素值将与曝光时间成正比,除非像素在特定图像中太暗(即接近0)或太亮(即接近255)。我们可以过滤掉这些坏像素(太暗或太亮),并将像素值除以曝光时间来估计像素的亮度,然后对所有具有好像素(太暗或太亮)的图像平均亮度值)。我们可以对所有像素执行此操作,并通过对“好”像素进行平均来获得所有像素的单个图像。但是CRF不是线性的,我们需要评估CRF以使图像强度线性化,然后才能将它们组合或平均。好消息是,如果我们知道每张图像的曝光时间,就可以从图像中估计CRF。与计算机视觉中的许多问题一样,寻找CRF的问题本质上是一个最优解问题,其目标是最小化由数据项和平滑项组成的目标函数。这些问题通常被降维为线性最小二乘问题,可以使用所有线性代数包中的奇异值分解(SVD)来解决。CRF提取算法的详细信息可以在从照片中提取高动态范围辐射图一文中找到。使用OpenCV的CalibrateDebevec或CalibrateRobertson在2行代码中找到CRF。在本教程中我们使用CalibrateDebevecC++//获取图像响应函数(CRF)=cv2.createCalibrateDebevec()responseDebevec=calibrateDebevec.process(images,times)下图是使用红绿蓝通道提取图像的CRF。CameraResponseFunctionStep4:MergeImagesCRF评估完成后,我们可以使用MergeDebevec将曝光的图像合并为一张HDR图像。C++和Python代码如下所示。C++//合并图像为HDR线性图像MathdrDebevec;PtrmergeDebevec=createMergeDebevec();mergeDebevec->process(images,hdrDebevec,times,responseDebevec);//保存图像imwrite("hdrDebevec.hdr",hdrDebevec);Python#MergeimagesintoHDRlinearimagesmergeDebevec=cv2.createMergeDebevec()hdrDebevec=mergeDebevec.process(images,times,responseDebevec)#保存图像可以在Photoshop中加载和色调映射。示例图像如下所示。HDRPhotoshop色调映射第5步:色调映射我们现在已经将曝光的图像合并为一个HDR图像。你能猜出这张图片的最小和最大像素值吗?对于黑色情况,最小值显然是0。理论最大值是多少?无限!在实践中,最大值在不同情况下是不同的。如果场景中包含非常明亮的光源,那么最大值将会非常大。虽然我们已经使用多张图像恢复了相对亮度信息,但我们现在面临一个新的挑战:将这些信息保存为24位图像以供显示。将高动态范围(HDR)图像转换为8位单通道图像的过程称为色调映射。这个过程还需要保留尽可能多的细节。有几种色调映射算法。OpenCV实现了其中的四个。要记住的是,没有一种正确的方法来进行色调映射。通常,我们希望在色调映射图像中看到比曝光图像更多的细节。有时色调映射的目标是生成逼真的图像,而通常目标是生成超现实的图像。在OpenCV中实现的算法往往会产生逼真的结果,而不是那么引人注目。让我们来看看各种选项。下面列出了不同色调映射算法的一些常用参数。Gamma伽玛:此参数通过应用伽玛校正来压缩动态范围。当gamma等于1时,不应用校正。小于1的伽玛会使图像变暗,而大于1的伽玛会使图像变亮。饱和度:此参数用于增加或减少饱和度。饱和度高时,颜色更丰富、更强烈。接近于零的饱和度值会导致颜色褪色为灰度。Contrast对比度:控制输出图像的对比度(即log(maxPixelValue/minPixelValue))。让我们探索OpenCV中可用的四种色调映射算法。Drago色调映射Drago色调映射的参数如下:createTonemapDrago(floatgamma=1.0f,floatsaturation=1.0f,floatbias=0.85f)其中bias是[0,1]范围内的偏置函数的值。从0.7到0.9的值通常效果很好。默认值为0.85。有关更多技术细节,请参阅本文。C++和Python代码如下所示。这些参数是通过反复试验获得的。最终结果乘以3只是因为它给出了最令人满意的结果。C++//使用Drago色调映射算法得到24位彩色图像MatldrDrago;PtrtonemapDrago=createTonemapDrago(1.0,0.7);tonemapDrago->process(hdrDebevec,ldrDrago);ldrDrago=3*ldrDrago;imwrite("ldr-Drago.jpg",ldrDrago*255);Python#使用Drago色调映射算法得到24位彩色图像tonemapDrago=cv2.createTonemapDrago(1.0,0.7)ldrDrago=tonemapDrago.process(hdrDebevec)ldrDrago=3*ldrDragocv2.imwrite("ldr-Drago.jpg",ldrDrago*255)结果如下:HDRtonemappingDurandusingtheDragoalgorithmf,floatsigma_space=2.0f,floatsigma_color=2.0f);该算法基于将图像分解为基础层和细节层。基础层是使用称为双边滤波器的边缘保留滤波器获得的。sigma_space和sigma_color是双边滤波器的参数,分别控制空间域和颜色域中的平滑量。有关详细信息,请查看本文。C++//使用Durand色调映射算法得到24位彩色图像MatldrDurand;PtrtonemapDurand=createTonemapDurand(1.5,4,1.0,1,1);tonemapDurand->process(hdrDebevec,ldrDurand);ldrDurand=3*ldrDurand;imwrite("ldr-Durand.jpg",ldrDurand*255);Python#使用Durand色调映射算法获取24位彩色图像tonemapDurand=cv2.createTonemapDurand(1.5,4,1.0,1,1)ldrDurand=tonemapDurand.process(hdrDebevec)ldrDurand=3*ldrDurandcv2.imwrite("ldr-Durand.jpg",ldrDurand*255)结果如下:HDRtonemappingusingDurandalgorithmReinhardtonemappingcreateTonemapReinhard(floatgamma=1.0f,floatintensity=0.0f,floatlight_adapt=1.0f,floatcolor_adapt=0.0f)强度参数应在[-8,8]范围内。更高的亮度值会产生更亮的结果。light_adapt控制[0,1]范围内的光照。值为1表示仅基于像素值进行自适应,而值为0表示全局自适应。中间值可以用于两者的加权组合。参数color_adapt控制颜色,范围是[0,1]。如果该值设置为1,则独立处理通道,如果该值设置为0,则每个通道的自适应级别相同。中间值可以用于两者的加权组合。有关详细信息,请查看本文。C++//使用Reinhard色调映射算法得到24位彩色图像MatldrReinhard;ptrtonemapReinhard=createTonemapReinhard(1.5,0,0,0);tonemapReinhard->process(hdrDebevec,ldrReinhard);imwrite("ldr-Reinhard.jpg",ldrReinhard*255);Python#使用Reinhard色调映射算法得到24位彩色图像.jpg",ldrReinhard*255)结果如下:HDR色调映射Mantiuk使用Reinhard算法色调映射createTonemapMantiuk(floatgamma=1.0f,floatscale=0.7f,floatsaturation=1.0f)参数scale是对比度比例因子。0.7到0.9的值通常效果更好查看这篇论文了解更多细节。C++//使用Mantiuk色调映射算法得到24位彩色图像MatldrMantiuk;PtrtonemapMantiuk=createTonemapMantiuk(2.2,0.85,1.2);tonemapMantiuk->process(hdrDebevec,ldrMantiuk);ldrMantiuk=3*ldrMantiuk;imwrite("ldr-Mantiuk.jpg",ldrMantiuk*255);Python#使用Mantiuk色调映射算法获得24位彩色图像tonemapMantiuk=cv2.createTonemapMantiuk(2.2,0.85,1.2)ldrMantiuk=tonemapMantiuk.process(hdrDebevec)ldrMantiuk=3*ldrMantiukcv2。imwrite("ldr-Mantiuk.jpg",ldrMantiuk*255)结果如下:HDRtonemappingusingMantiukalgorithm