当前位置:网站首页>Opencv学习笔记 高动态范围 (HDR) 成像
Opencv学习笔记 高动态范围 (HDR) 成像
2022-07-07 18:03:00 【坐望云起】
一、什么是高动态范围 (HDR) 成像
组合在不同曝光设置下获取的同一场景的不同图像的过程称为高动态范围 (HDR)成像。
大多数数码相机和显示器将彩色图像捕获或显示为 24 位矩阵。每个颜色通道有 8 位,因此每个通道的像素值在 0 – 255 范围内。换句话说,普通相机或显示器具有有限的动态范围。
然而,我们周围的世界有着非常大的动态范围。车库里关灯时它会变得漆黑一片,如果你直视太阳,它会变得非常亮。即使不考虑这些极端情况,在日常情况下,8 位也勉强足以捕捉场景。因此,相机会尝试估计光照并自动设置曝光,以使图像中最有趣的部分具有良好的动态范围,并且将太暗和太亮的部分分别裁剪为 0 和 255。
在下图中,左边的图像是正常曝光的图像。请注意,背景中的天空完全被洗掉了,因为相机决定使用正确拍摄对象的设置,但明亮的天空被洗掉了。右图是 iPhone 制作的 HDR 图像。
iPhone 如何捕捉 HDR 图像?它实际上在三种不同的曝光下拍摄了 3 张图像。图像是连续快速拍摄的,因此三张照片之间几乎没有移动。然后将这三个图像组合起来以生成 HDR 图像。我们将在下一节中看到详细信息。
二、高动态范围 (HDR) 成像如何工作?
在这里将介绍使用 OpenCV 创建 HDR 图像的步骤。
第 1 步:捕获具有不同曝光度的多张图像
当我们使用相机拍照时,我们每个通道只有 8 位来表示场景的动态范围(亮度范围)。但是我们可以通过改变快门速度在不同曝光下拍摄多张场景图像。大多数单反相机都有一个称为自动包围曝光 (AEB) 的功能,只需按一下按钮,我们就可以在不同的曝光下拍摄多张照片。如果您使用的是 iPhone,则可以使用此AutoBracket HDR 应用程序,如果您是 android 用户,则可以尝试A Better Camera 应用程序。
使用相机上的 AEB 或手机上的自动包围式应用程序,我们可以一张一张地快速拍摄多张照片,这样场景就不会改变。当我们在 iPhone 中使用 HDR 模式时,它会拍摄三张照片。
1、曝光不足的图像:此图像比正确曝光的图像更暗。目标是捕获图像中非常明亮的部分。
2、正确曝光的图像:这是相机根据其估计的照度拍摄的常规图像。
3、曝光过度的图像:此图像比正确曝光的图像更亮。目标是捕捉图像中非常暗的部分。
但是,如果场景的动态范围非常大,我们可以拍摄三张以上的图片来合成 HDR 图像。在本教程中,我们将使用 4 张曝光时间分别为 1/30、0.25、2.5 和 15 秒的图像。缩略图如下所示。
单反相机或手机使用的有关曝光时间和其他设置的信息通常存储在 JPEG 文件的 EXIF 元数据中。查看此链接以查看存储在 Windows 和 Mac 中的 JPEG 文件中的 EXIF 元数据。或者,您可以使用我最喜欢的 EXIF 命令行实用程序EXIFTOOL。
让我们从读取图像开始,分配曝光时间
C++
void readImagesAndTimes(vector<Mat> &images, vector<float> ×)
{
int numImages = 4;
// List of exposure times
static const float timesArray[] = {1/30.0f,0.25,2.5,15.0};
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]);
images.push_back(im);
}
}
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
第 2 步:对齐图像
用于组成 HDR 图像的图像未对齐会导致严重的伪影。在下图中,左边的图像是使用未对齐的图像合成的 HDR 图像,右边的图像是使用对齐的图像合成的图像。通过放大图像的一部分,使用红色圆圈显示,我们在左侧图像中看到了严重的重影伪影。
当然,在拍摄用于创建 HDR 图像的照片时,专业摄影师会将相机安装在三脚架上。他们还使用称为镜子锁定的功能来减少额外的振动。即使这样,图像也可能无法完美对齐,因为无法保证无振动的环境。当使用手持相机或手机拍摄图像时,对齐问题会变得更糟。
幸运的是,OpenCV 提供了一种使用AlignMTB. 该算法将所有图像转换为中值阈值位图 (MTB)。图像的 MTB 是通过将值 1 分配给比中值亮度更亮的像素,否则为 0 来计算的。MTB 对曝光时间是不变的。因此,MTB 可以对齐,而无需我们指定曝光时间。
使用以下代码行执行基于 MTB 的对齐。
C++
// Align input images
Ptr<AlignMTB> alignMTB = createAlignMTB();
alignMTB->process(images, images);
Python
# Align input images
alignMTB = cv2.createAlignMTB()
alignMTB.process(images, images)
第三步:恢复相机响应函数
典型相机的响应对场景亮度不是线性的。这意味着什么?假设,两个物体被相机拍摄,其中一个在现实世界中的亮度是另一个的两倍。当您测量照片中两个物体的像素强度时,较亮物体的像素值不会是较暗物体的两倍!如果不估计相机响应函数 (CRF),我们将无法将图像合并为一张 HDR 图像。
将多张曝光图像合并为 HDR 图像是什么意思?
只考虑图像某个位置 (x,y) 的一个像素。如果 CRF 是线性的,则像素值将与曝光时间成正比,除非像素在特定图像中太暗(即接近 0)或太亮(即接近 255)。我们可以过滤掉这些坏像素(太暗或太亮),并通过将像素值除以曝光时间
// Obtain Camera Response Function (CRF)
Mat responseDebevec;
Ptr<CalibrateDebevec> calibrateDebevec = createCalibrateDebevec();
calibrateDebevec->process(images, responseDebevec, times);
来估计像素处的亮度,然后在像素不错(太暗或太暗)的所有图像中平均该亮度值太亮了 )。我们可以对所有像素执行此操作并获得单个图像,其中所有像素都是通过平均“好”像素获得的。
但是 CRF 不是线性的,我们需要先使图像强度线性化,然后才能通过首先估计 CRF 来合并/平均它们。
好消息是,如果我们知道每张图像的曝光时间,就可以从图像中估计 CRF。与计算机视觉中的许多问题一样,寻找 CRF 的问题被设置为一个优化问题,其目标是最小化由数据项和平滑项组成的目标函数。这些问题通常简化为线性最小二乘问题,使用奇异值分解 (SVD) 来解决,SVD 是所有线性代数包的一部分。CRF 恢复算法的详细信息在题为Recovering High Dynamic Range Radiance Maps from Photos的论文中。
只需使用 OpenCV 中的两行代码,使用CalibrateDebevec或即可找到 CRF CalibrateRobertson。在本教程中,我们将使用CalibrateDebevec。
C++
// Obtain Camera Response Function (CRF)
Mat responseDebevec;
Ptr<CalibrateDebevec> calibrateDebevec = createCalibrateDebevec();
calibrateDebevec->process(images, responseDebevec, times);
Python
# Obtain Camera Response Function (CRF)
calibrateDebevec = cv2.createCalibrateDebevec()
responseDebevec = calibrateDebevec.process(images, times)
下图显示了使用红色、绿色和蓝色通道的图像恢复的 CRF。
第 4 步:合并图像
估计 CRF 后,我们可以使用MergeDebevec
. C++ 和 Python 代码如下所示。
C++
// Merge images into an HDR linear image
Mat hdrDebevec;
Ptr<MergeDebevec> mergeDebevec = createMergeDebevec();
mergeDebevec->process(images, hdrDebevec, times, responseDebevec);
// Save HDR image.
imwrite("hdrDebevec.hdr", hdrDebevec);
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)
上面保存的 HDR 图像可以在 Photoshop 中加载并进行色调映射。一个例子如下所示。
第 5 步:色调映射
将高动态范围 (HDR) 图像转换为每通道 8 位图像同时保留尽可能多的细节的过程称为色调映射。
现在我们已将曝光图像合并为一张 HDR 图像。你能猜出这张图片的最小和最大像素值吗?对于漆黑的情况,最小值显然是 0。理论最大值是多少?无穷!在实践中,不同情况下的最大值是不同的。如果场景包含非常亮的光源,我们将看到一个非常大的最大值。
尽管我们已经使用多张图像恢复了相对亮度信息,但我们现在面临着将这些信息保存为 24 位图像以供显示的挑战。
有几种色调映射算法。OpenCV 实现了其中的四个。要记住的是,没有正确的方法来进行色调映射。通常,我们希望在色调映射图像中看到比在任何一张曝光图像中更多的细节。有时色调映射的目标是产生逼真的图像,而通常目标是产生超现实的图像。OpenCV 中实现的算法倾向于产生真实的结果,因此不太引人注目。
让我们看看各种选项。下面列出了不同色调映射算法的一些常用参数。
1、gamma:此参数通过应用 gamma 校正来压缩动态范围。当 gamma 等于 1 时,不应用校正。小于 1 的 gamma 会使图像变暗,而大于 1 的 gamma 会使图像变亮。
2、饱和度:此参数用于增加或减少饱和度。饱和度高时,颜色更丰富、更强烈。饱和度值更接近于零,使颜色逐渐消失为灰度。
3、contrast:控制输出图像的对比度(即 log (maxPixelValue/minPixelValue) )。
让我们探索 OpenCV 中可用的四种色调映射算法。
Drago Tonemap
Drago Tonemap 的参数如下所示
createTonemapDrago
(
float gamma = 1.0f,
float saturation = 1.0f,
float bias = 0.85f
)
这里,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> tonemapDrago = createTonemapDrago(1.0, 0.7);
tonemapDrago->process(hdrDebevec, ldrDrago);
ldrDrago = 3 * ldrDrago;
imwrite("ldr-Drago.jpg", ldrDrago * 255);
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)
结果
Durand Tonemap
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
);
该算法基于将图像分解为基础层和细节层。基础层是使用称为双边滤波器的边缘保持滤波器获得的。sigma_space和sigma_color是双边滤波器的参数,分别控制空间和颜色域中的平滑量。
C++
// Tonemap using Durand's method obtain 24-bit color image
Mat ldrDurand;
Ptr<TonemapDurand> tonemapDurand = createTonemapDurand(1.5,4,1.0,1,1);
tonemapDurand->process(hdrDebevec, ldrDurand);
ldrDurand = 3 * ldrDurand;
imwrite("ldr-Durand.jpg", ldrDurand * 255);
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)
结果
莱因哈德色调图
createTonemapReinhard
(
float gamma = 1.0f,
float intensity = 0.0f,
float light_adapt = 1.0f,
float color_adapt = 0.0f
)
参数强度应在 [-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> tonemapReinhard = createTonemapReinhard(1.5, 0,0,0);
tonemapReinhard->process(hdrDebevec, ldrReinhard);
imwrite("ldr-Reinhard.jpg", ldrReinhard * 255);
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)
结果
曼蒂克色调图
createTonemapMantiuk
(
float gamma = 1.0f,
float scale = 0.7f,
float saturation = 1.0f
)
参数scale是对比度比例因子。从 0.6 到 0.9 的值产生最佳结果。
C++
// Tonemap using Mantiuk's method to obtain 24-bit color image
Mat ldrMantiuk;
Ptr<TonemapMantiuk> tonemapMantiuk = createTonemapMantiuk(2.2,0.85, 1.2);
tonemapMantiuk->process(hdrDebevec, ldrMantiuk);
ldrMantiuk = 3 * ldrMantiuk;
imwrite("ldr-Mantiuk.jpg", ldrMantiuk * 255);
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)
结果
三、图片来源
来源这篇文章中使用的四张曝光图像是在CC BY-SA 3.0下获得许可的,并且是从Wikipedia 的 HDR 页面下载的。他们是由凯文·麦考伊拍摄的。
边栏推荐
- 剑指 Offer II 013. 二维子矩阵的和
- Training IX basic configuration of network services
- Force buckle 674 Longest continuous increasing sequence
- 开源OA开发平台:合同管理使用手册
- gorilla官方:golang开websocket client的示例代码
- tp6 实现佣金排行榜
- 力扣599. 两个列表的最小索引总和
- Some arrangements about oneself
- Redis——基本使用(key、String、List、Set 、Zset 、Hash、Geo、Bitmap、Hyperloglog、事务 )
- PMP對工作有益嗎?怎麼選擇靠譜平臺讓備考更省心省力!!!
猜你喜欢
Nunjuks template engine
Classification automatique des cellules de modules photovoltaïques par défaut dans les images de lecture électronique - notes de lecture de thèse
力扣 599. 两个列表的最小索引总和
数据孤岛是企业数字化转型遇到的第一道险关
PMP对工作有益吗?怎么选择靠谱平台让备考更省心省力!!!
CSDN语法说明
Compiler optimization (4): inductive variables
How to cooperate among multiple threads
el-upload上传组件的动态添加;el-upload动态上传文件;el-upload区分文件是哪个组件上传的。
YoloV6:YoloV6+Win10---训练自己得数据集
随机推荐
Equals method
el-upload上传组件的动态添加;el-upload动态上传文件;el-upload区分文件是哪个组件上传的。
Browse the purpose of point setting
gorilla官方:golang开websocket client的示例代码
Kubernetes——kubectl命令行工具用法详解
J ü rgen schmidhub reviews the 25th anniversary of LSTM papers: long short term memory All computable metaverses. Hierarchical reinforcement learning (RL). Meta-RL. Abstractions in generative adversar
PMP对工作有益吗?怎么选择靠谱平台让备考更省心省力!!!
Some arrangements about oneself
MSE API学习
vulnhub之Funfox2
[confluence] JVM memory adjustment
LeetCode_ 7_ five
Mysql, sqlserver Oracle database connection mode
sql 常用优化
R语言ggplot2可视化:使用ggpubr包的ggviolin函数可视化小提琴图、设置palette参数自定义不同水平小提琴图的填充色、add参数在小提琴图添加箱图
Force buckle 674 Longest continuous increasing sequence
模拟实现string类
PMP對工作有益嗎?怎麼選擇靠譜平臺讓備考更省心省力!!!
一锅乱炖,npm、yarn cnpm常用命令合集
UCloud是基础云计算服务提供商