使用OpenCV进行高动态范围(HDR)成像(C ++ / Python)


https://blog.csdn.net/wc781708249/article/details/78490976

什么是高动态范围(HDR)成像?

大多数数码相机和显示器都将彩色图像捕获或显示为24位矩阵。 每个颜色通道有8位,因此每个通道的像素值在0-255范围内。 换句话说,普通的相机或显示器的动态范围是有限的。

但是,我们周围的世界有一个非常大的动态范围。 当灯光关闭时,它可以在车库内变黑,如果你直接看着太阳,它会变得非常亮。 即使没有考虑到这些极端情况,在日常情况下,8位勉强可以捕捉现场。 因此,相机会尝试估计光照并自动设置曝光,以使图像最有趣的方面具有良好的动态范围,而太暗和太亮的部分分别被裁剪为0和255。

iPhone如何拍摄HDR图像? 它实际上需要三个不同的曝光3张图像。 图像连续拍摄,三个镜头之间几乎没有移动。 三幅图像然后被组合以产生HDR图像。 我们将在下一节看到细节。

将在不同曝光设置下获取的相同场景的不同图像组合的过程称为高动态范围(HDR)成像。

高动态范围(HDR)成像如何工作?

在本节中,我们将通过使用OpenCV创建HDR图像的步骤。

要轻松学习本教程,请点击此处下载C ++和Python代码和图像。 如果您有兴趣了解更多关于人工智能,计算机视觉和机器学习的信息,请订阅我们的通讯。

第1步:用不同的曝光拍摄多张图像

当我们使用相机拍照时,每个通道只有8位来表示场景的动态范围(亮度范围)。 但是,通过改变快门速度,我们可以在不同的曝光条件下拍摄多个场景图像。 大多数单反相机有一个称为自动包围曝光(AEB)的功能,使我们可以在一次按下一个按钮的情况下以不同的曝光拍摄多张照片。 如果你正在使用iPhone,你可以使用这个AutoBracket HDR应用程序,如果你是一个Android用户,你可以尝试一个更好的相机应用程序。

在相机上使用自动包围曝光或在手机上使用自动包围应用程序,我们可以一个接一个地快速拍摄多张照片,所以场景不会改变。 当我们在iPhone中使用HDR模式时,需要三张照片。
1、曝光不足的图像:此图像比正确曝光的图像更暗。 目标是捕捉非常明亮的图像部分。
2、曝光正确的图像:这是相机根据估计的照度拍摄的常规图像。
3、曝光过度的图像:此图像比正确曝光的图像亮。 目标是拍摄非常黑暗的图像部分。

但是,如果场景的动态范围很大,我们可以拍摄三张以上的图片来组成HDR图像。 在本教程中,我们将使用曝光时间为1/30,0.25,2.5和15秒的4张图像。 缩略图如下所示。
使用OpenCV进行高动态范围(HDR)成像(C ++ / Python)_第1张图片

关于单反相机或手机使用的曝光时间和其他设置的信息通常存储在JPEG文件的EXIF元数据中。 查看此链接可查看Windows和Mac中存储在JPEG文件中的EXIF元数据。 或者,您可以使用我最喜欢的名为EXIFTOOL的EXIF命令行工具。

我们先从读取图像开始分配曝光时间


C++

void readImagesAndTimes(vector &images, vector ×)
{

  int numImages = 4;

  // List of exposure times
  static const float timesArray[] = {1/30.0f,0.25,2.5,15.0};
  //将timesArray里的值全部赋给times
  times.assign(timesArray, timesArray + numImages);

  // List of image filenames
  static const char* filenames[] = {"img_0.033.jpg", "img_0.25.jpg", "img_2.5.jpg", "img_15.jpg"};
  for(int i=0; i < numImages; i++)
  {
    Mat im = imread(filenames[i]);//默认读取BGR格式
    images.push_back(im);//全部存储在images
  }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

Python

def readImagesAndTimes():
  # List of exposure times
  times = np.array([ 1/30.0, 0.25, 2.5, 15.0 ], dtype=np.float32)

  # List of image filenames
  filenames = ["img_0.033.jpg", "img_0.25.jpg", "img_2.5.jpg", "img_15.jpg"]
  images = []
  for filename in filenames:
    im = cv2.imread(filename)
    images.append(im)

  return images, times
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

第2步:对齐图像

构成HDR图像时使用的图像未对齐可能会导致严重的伪影。 在下图中,左侧的图像是使用未对齐的图像组成的HDR图像,而右侧的图像是使用对齐的图像的图像。 通过放大图像的一部分(使用红色圆圈显示),我们会在左侧图像中看到严重的鬼影伪影。
使用OpenCV进行高动态范围(HDR)成像(C ++ / Python)_第2张图片

自然,在拍摄照片制作HDR图像时,专业摄影师将相机安装在三脚架上。 他们还使用称为镜像锁定功能来减少额外的振动。 即使如此,图像可能不完美对齐,因为没有办法保证无振动的环境。 当使用手持相机或手机拍摄图像时,对齐问题变得更糟。

幸运的是,OpenCV提供了一种使用AlignMTB对齐这些图像的简单方法。 该算法将所有图像转换为中值阈值位图(MTB)。 通过将值1分配给比中间亮度更亮的像素来计算图像的MTB,否则为0。 MTB对曝光时间不变。 因此,MTB可以调整,而不需要我们指定曝光时间。

基于MTB的对齐使用以下代码行来执行。

C++

// Align input images
Ptr alignMTB = createAlignMTB();
alignMTB->process(images, images);
  • 1
  • 2
  • 3

Python

# Align input images
alignMTB = cv2.createAlignMTB()
alignMTB.process(images, images)
  • 1
  • 2
  • 3

步骤3:恢复相机响应功能

典型相机的响应与场景亮度不成线性关系。 这意味着什么? 假设有两个物体由相机拍摄,其中一个物体是现实世界中其他物体的两倍。 当您测量照片中两个物体的像素强度时,较亮物体的像素值将不会是较暗物体的两倍。 在不估计相机响应函数(CRF)的情况下,我们将无法将图像合并到一个HDR图像中。

将多个曝光图像合并为HDR图像意味着什么?

在图像的某个位置(x,y)只考虑一个像素。 如果CRF是线性的,则像素值将直接与曝光时间成比例,除非像素在特定图像中太暗(即接近0)或太亮(即接近255)。 我们可以过滤出这些不好的像素(太暗或太亮),并通过将像素值除以曝光时间来估计像素的亮度,然后在像素不差的所有图像上平均亮度值(太暗或 太亮了 )。 我们可以对所有像素进行这样的处理,并通过对“好”像素进行平均来获得所有像素的单个图像。

但CRF不是线性的,我们需要通过首先估计CRF来使图像强度成为线性的,然后才能合并/平均它们。

好消息是,如果我们知道每个图像的曝光时间,则可以从图像估计CRF。 与计算机视觉中的许多问题一样,找到CRF的问题被设置为一个优化问题,其目标是使由数据项和平滑项组成的目标函数最小化。 这些问题通常会减少到线性最小二乘问题,这些问题可以使用奇异值分解(SVD)来解决,奇异值分解是所有线性代数包的一部分。 CRF恢复算法的细节在题为“从照片恢复高动态范围发光图”的论文中。

使用CalibrateDebevec或CalibrateRobertson在OpenCV中使用两行代码来完成CRF。 在本教程中,我们将使用CalibrateDebevec

C++

// Obtain Camera Response Function (CRF)
Mat responseDebevec;
Ptr calibrateDebevec = createCalibrateDebevec();
calibrateDebevec->process(images, responseDebevec, times);
  • 1
  • 2
  • 3
  • 4

Python

# Obtain Camera Response Function (CRF)
calibrateDebevec = cv2.createCalibrateDebevec()
responseDebevec = calibrateDebevec.process(images, times)
  • 1
  • 2
  • 3

第4步:合并图像

一旦CRF被估计,我们可以使用MergeDebevec将曝光图像合并成一个HDR图像。 C ++和Python代码如下所示。

C++

// Merge images into an HDR linear image
Mat hdrDebevec;
Ptr mergeDebevec = createMergeDebevec();
mergeDebevec->process(images, hdrDebevec, times, responseDebevec);
// Save HDR image.
imwrite("hdrDebevec.hdr", hdrDebevec);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Python

# Merge images into an HDR linear image
mergeDebevec = cv2.createMergeDebevec()
hdrDebevec = mergeDebevec.process(images, times, responseDebevec)
# Save HDR image.
cv2.imwrite("hdrDebevec.hdr", hdrDebevec)
  • 1
  • 2
  • 3
  • 4
  • 5

上面保存的HDR图像可以在Photoshop中加载并进行色调映射。 一个例子如下所示。

第5步:色调映射

现在我们已经将我们的曝光图像合并到一个HDR图像中。 你能猜出这个图像的最小和最大像素值吗? 对于黑色条件,最小值显然为0。 什么是理论最大值? 无穷! 在实践中,不同情况下的最大值是不同的。 如果场景包含非常明亮的光源,我们将看到一个非常大的最大值。

尽管我们已经使用多个图像恢复了相对亮度信息,但是我们现在面临将这些信息保存为24位图像用于显示的挑战。

将高动态范围(HDR)图像转换为8位每通道图像的过程同时保留尽可能多的细节称为色调映射。

有几种色调映射算法。 OpenCV实现了其中的四个。 要记住的是,没有正确的方法来做色调映射。 通常,我们希望在色调映射图像中看到比任何一个曝光图像更多的细节。 有时色调映射的目标是产生逼真的图像,而且往往是产生超现实图像的目标。 在OpenCV中实现的算法倾向于产生现实的,因此较不显着的结果。

我们来看看各种选项。 以下列出了不同色调映射算法的一些常见参数。
1、gamma:该参数通过应用gamma 校正来压缩动态范围。 当gamma等于1时,不应用修正。 小于1的gamma会使图像变暗,而大于1的gamma会使图像变亮。
2、饱和度:该参数用于增加或减少饱和度。 饱和度高时,色彩更丰富,更浓。 饱和度值接近零,使颜色逐渐消失为灰度。
3、对比度:控制输出图像的对比度(即log(maxPixelValue / minPixelValue))。

让我们来探索OpenCV中可用的四种色调映射算法。


Drago Tonemap

Drago Tonemap的参数如下所示

createTonemapDrago
(
float   gamma = 1.0f,
float   saturation = 1.0f,
float   bias = 0.85f 
)   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这里,bias 是[0,1]范围内偏差函数的值。 从0.7到0.9的值通常会给出最好的结果。 默认值是0.85。 有关更多技术细节,请参阅本文。

C ++和Python代码如下所示。 参数是通过试验和错误获得的。 最后的结果乘以3只是因为它给出了最令人满意的结果。

C++

// Tonemap using Drago's method to obtain 24-bit color image
Mat ldrDrago;
Ptr tonemapDrago = createTonemapDrago(1.0, 0.7);
tonemapDrago->process(hdrDebevec, ldrDrago);
ldrDrago = 3 * ldrDrago;
imwrite("ldr-Drago.jpg", ldrDrago * 255);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Python

# Tonemap using Drago's method to obtain 24-bit color image
tonemapDrago = cv2.createTonemapDrago(1.0, 0.7)
ldrDrago = tonemapDrago.process(hdrDebevec)
ldrDrago = 3 * ldrDrago
cv2.imwrite("ldr-Drago.jpg", ldrDrago * 255)
  • 1
  • 2
  • 3
  • 4
  • 5

使用OpenCV进行高动态范围(HDR)成像(C ++ / Python)_第3张图片
HDR tone mapping using Drago’s algorithm


Durand Tonemap

createTonemapDurand 
(   
  float     gamma = 1.0f, 
  float     contrast = 4.0f,
  float     saturation = 1.0f,
  float     sigma_space = 2.0f,
  float     sigma_color = 2.0f 
); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

该算法基于将图像分解为基础层和细节层。 使用称为双边滤波器的边缘保留滤波器来获得基本层。 sigma_space和sigma_color是双边滤波器的参数,分别控制空间域和彩色域中的平滑量。

有关更多详细信息,请查看本文。

C++

// Tonemap using Durand's method obtain 24-bit color image
Mat ldrDurand;
Ptr tonemapDurand = createTonemapDurand(1.5,4,1.0,1,1);
tonemapDurand->process(hdrDebevec, ldrDurand);
ldrDurand = 3 * ldrDurand;
imwrite("ldr-Durand.jpg", ldrDurand * 255);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Python

# Tonemap using Durand's method obtain 24-bit color image
 tonemapDurand = cv2.createTonemapDurand(1.5,4,1.0,1,1)
 ldrDurand = tonemapDurand.process(hdrDebevec)
 ldrDurand = 3 * ldrDurand
 cv2.imwrite("ldr-Durand.jpg", ldrDurand * 255)
  • 1
  • 2
  • 3
  • 4
  • 5

Reinhard Tonemap

createTonemapReinhard
(
float   gamma = 1.0f,
float   intensity = 0.0f,
float   light_adapt = 1.0f,
float   color_adapt = 0.0f 
)   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

参数强度应在[-8,8]范围内。 更高的强度值会产生更明亮的结果。 light_adapt控制灯光适应并处于[0,1]范围内。 值1表示仅基于像素值的自适应,而值0表示全局自适应。 中间值可以用于两者的加权组合。 参数color_adapt控制色适应,并在[0,1]范围内。 如果值被设置为1,则通道被独立处理,如果该值被设置为0,则每个通道的适应级别相同。中间值可以用于两者的加权组合。

C++

// Tonemap using Reinhard's method to obtain 24-bit color image
Mat ldrReinhard;
Ptr tonemapReinhard = createTonemapReinhard(1.5, 0,0,0);
tonemapReinhard->process(hdrDebevec, ldrReinhard);
imwrite("ldr-Reinhard.jpg", ldrReinhard * 255);
  • 1
  • 2
  • 3
  • 4
  • 5

Python

# Tonemap using Reinhard's method to obtain 24-bit color image
tonemapReinhard = cv2.createTonemapReinhard(1.5, 0,0,0)
ldrReinhard = tonemapReinhard.process(hdrDebevec)
cv2.imwrite("ldr-Reinhard.jpg", ldrReinhard * 255)
  • 1
  • 2
  • 3
  • 4

Mantiuk Tonemap

createTonemapMantiuk
(   
float   gamma = 1.0f,
float   scale = 0.7f,
float   saturation = 1.0f 
)   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

参数scale 是对比度比例因子。 从0.6到0.9的值产生最好的结果。

C++

// Tonemap using Mantiuk's method to obtain 24-bit color image
Mat ldrMantiuk;
Ptr tonemapMantiuk = createTonemapMantiuk(2.2,0.85, 1.2);
tonemapMantiuk->process(hdrDebevec, ldrMantiuk);
ldrMantiuk = 3 * ldrMantiuk;
imwrite("ldr-Mantiuk.jpg", ldrMantiuk * 255);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Python

# Tonemap using Mantiuk's method to obtain 24-bit color image
tonemapMantiuk = cv2.createTonemapMantiuk(2.2,0.85, 1.2)
ldrMantiuk = tonemapMantiuk.process(hdrDebevec)
ldrMantiuk = 3 * ldrMantiuk
cv2.imwrite("ldr-Mantiuk.jpg", ldrMantiuk * 255)

你可能感兴趣的:(视觉算法代码库)