4 Star 31 Fork 9

OpenDocCN / apachecn-cv-zh

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
6.md 18.42 KB
一键复制 编辑 原始数据 按行查看 历史
布客飞龙 提交于 2021-02-12 00:55 . 2021-02-12 00:55:45

六、计算摄影

计算摄影是指使您能够扩展数字摄影的典型功能的技术。 这可能包括硬件附加组件或修改,但主要指基于软件的技术。 这些技术可能会产生“传统”数码相机无法获得的输出图像。 本章介绍了 OpenCV 中用于计算摄影的一些鲜为人知的技术:高动态范围成像,无缝克隆,脱色和非照片级渲染。 这三个位于库的photo模块中。 注意,在前面的章节中已经考虑了该模块内部的其他技术(修复和去噪)。

高动态范围图像

我们处理的典型图像每像素有 8 位(BPP)。 彩色图像还使用 8 位表示每个通道的值,即红色,绿色和蓝色。 这意味着仅使用 256 个不同的强度值。 在数字成像的整个历史中,这个 8 BPP 的限制一直盛行。 但是,很明显,自然界中的光并不只有 256 个不同的水平。 因此,我们应该考虑这种离散化是理想的还是足够的。 例如,已知人眼可以捕获更高的动态范围(最暗和最亮之间的亮度级别数),估计在 1 亿到 1 亿个亮度级别之间。 在只有 256 个光照级别的情况下,有些情况下明亮的光线看起来过度曝光或饱和,而黑暗的场景只是被捕获为黑色。

有些相机可以捕获超过 8 BPP 的图像。 但是,创建高动态范围图像的最常见方法是使用 8 BPP 相机并拍摄具有不同曝光值的图像。 当我们这样做时,动态范围有限的问题显而易见。 例如,考虑下图:

High-dynamic-range images

用六个不同的曝光值拍摄的场景

注意

左上方的图像大部分为黑色,但窗口详细信息可见。 相反,右下角的图像显示了房间的细节,但窗口的细节几乎看不见。

我们可以使用现代智能手机相机以不同的曝光水平拍摄照片。 例如,对于 iPhone 和 iPad,从 iOS 8 开始,使用本机相机应用更改曝光非常容易。 触摸屏幕,将出现一个黄色框,侧面带有一个小太阳。 向上或向下滑动可以更改曝光(请参见以下屏幕截图)。

注意

曝光级别的范围非常大,因此我们可能不得不重复多次滑动手势。

如果您使用的是 iOS 的早期版本,则可以下载相机应用,例如 Camera+,这些应用可让您专注于特定点并更改曝光。

对于 Android,可以在 Google Play 上使用大量相机应用来调整曝光度。 一个例子是 Camera FV-5,它具有免费和付费版本。

提示

如果使用手持设备捕获图像,请确保该设备是静态的。 实际上,您可能会使用三脚架。 否则,具有不同曝光度的图像将无法对齐。 同样,移动的被摄体将不可避免地产生鬼影。 在大多数情况下,低,中和高曝光量的三张图像就足够了。

High-dynamic-range images

使用 iPhone 5S 中的本机摄像头应用进行曝光控制

智能手机和桌子很方便,可以拍摄许多曝光不同的图像。 要创建 HDR 图像,我们需要知道每个捕获图像的曝光(或快门)时间(原因请参见以下部分)。 并非所有应用都允许您手动控制(甚至查看)此功能(iOS 8 本机应用则不允许)。 在撰写本文时,至少有两个免费的应用允许 iOS 版使用:ManuallyManualShot。 在 Android 中,免费的 Camera FV-5 可让您控制和查看曝光时间。 请注意,F/Stop 和 ISO 是控制曝光的其他两个参数。

捕获的图像可以传输到开发计算机,并用于创建 HDR 图像。

注意

从 iOS 7 开始,本机相机应用具有 HDR 模式,可自动快速捕获三幅图像,每幅图像具有不同的曝光度。 这些图像也会自动组合为单个(有时更好)的图像。

创建 HDR 图像

我们如何将多张(例如三张)曝光图像合成为 HDR 图像? 如果我们仅考虑一个通道和一个给定的像素,则必须在较大的输出范围(例如 16 bpp)中将这三个像素值(每个曝光级别一个)映射到单个值。 这种映射并不容易。 首先,我们必须考虑像素强度是传感器辐照度(入射在相机传感器上的光量)的(粗略)度量。 数码相机以非线性方式测量辐照度。 相机具有非线性响应函数,可以将辐照度转换为像素强度值,范围为 0 到 255。 为了将这些值映射到更大的离散值集,我们必须估计摄像机的响应函数(即,响应范围在 0 到 255 之间)。

我们如何估计相机响应函数? 我们从像素本身做到这一点! 响应函数是每个颜色通道的 Sigmoid 曲线,可以根据像素进行估计(如果有 3 个像素曝光,则每个颜色通道的曲线上有 3 个点)。 由于这非常耗时,因此通常选择一组随机像素。

只剩下一件事了。 我们之前曾讨论过估计辐照度和像素强度之间的关系。 我们如何知道辐照度? 传感器的辐照度与曝光时间(或等效地,快门速度)成正比。 这就是我们需要曝光时间的原因!

最后,将 HDR 图像计算为从每次曝光的像素中恢复的辐照度值的加权和。 请注意,此图像无法在范围有限的常规屏幕上显示。

注意

关于高动态范围成像的一本好书是 Reinhard 等人的《高动态范围成像:获取,显示和基于图像的照明》,Morgan Kaufmann Pub。 该书随附 DVD,其中包含不同 HDR 格式的图像。

范例

OpenCV(仅从 3.0 版开始)提供了从一组以不同曝光拍摄的图像中创建 HDR 图像的函数。 甚至还有一个名为hdr_imaging的教程示例,该示例从图像文件中读取图像文件和曝光时间列表,并创建 HDR 图像。

注意

为了运行hdr_imaging教程,您将需要使用列表下载所需的图像文件和文本文件。 您可以从这个页面下载它们。

CalibrateDebevecMergeDebevec类实现 Debevec 的方法来估计相机响应函数并将曝光分别合并为 HDR 图像。createHDR示例之后的向您展示了如何使用这两个类:

#include <opencv2/photo.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main(int, char** argv)
{
    vector<Mat> images;
    vector<float> times;

    // Load images and exposures...
    Mat img1 = imread("1div66.jpg");
    if (img1.empty())
    {
        cout << "Error! Input image cannot be read...\n";
        return -1;
    }
    Mat img2 = imread("1div32.jpg");
    Mat img3 = imread("1div12.jpg");
    images.push_back(img1);
    images.push_back(img2);
    images.push_back(img3);
    times.push_back((float)1/66);
    times.push_back((float)1/32);
    times.push_back((float)1/12);

    // Estimate camera response...
    Mat response;
 Ptr<CalibrateDebevec> calibrate = createCalibrateDebevec();
 calibrate->process(images, response, times);

    // Show the estimated camera response function...
    cout << response;

    // Create and write the HDR image...
    Mat hdr;
 Ptr<MergeDebevec> merge_debevec = createMergeDebevec();
 merge_debevec->process(images, hdr, times, response);
    imwrite("hdr.hdr", hdr);

    cout << "\nDone. Press any key to exit...\n";
    waitKey(); // Wait for key press
    return 0;
}

该示例使用三个杯子的图像(这些图像以及本书随附的代码均可用)。 图像是使用 ManualShot 拍摄的前面提到的应用,使用的曝光时间为 1/66、1/32 和 1/12 秒; 请参考下图:

Example

示例中用作输入的三个图像

请注意,createCalibrateDebevec方法在 STL 向量中期望图像和曝光时间(STL 是一种有用的常用函数和标准 C++ 中可用的数据结构的库)。 相机响应函数以 256 个实值向量的形式给出。 这表示像素值和辐照度之间的映射。 实际上,它是一个256 x 3的矩阵(三个颜色通道中的每个颜色通道一列)。 下图显示了示例给出的响应:

Example

估计的 RGB 相机响应函数

提示

代码的cout部分以 MATLAB 和 Octave(两种用于数值计算的包)使用的格式显示矩阵。 复制输出中的矩阵并将其粘贴到 MATLAB/Octave 中以进行显示很简单。

生成的 HDR 图像以无损 RGBE 格式存储。 此图像格式使用每个颜色通道一个字节,再加上一个字节作为共享指数。 格式使用与浮点数表示法相同的原理:共享指数允许您表示更大范围的值。 RGBE 图像使用.hdr扩展名。 请注意,由于它是无损图像格式,因此.hdr文件相对较大。 在此示例中,RGB 输入图像分别为 1224 x 1632(每个 100 至 200 KB),而输出.hdr文件占用 5.9 MB。

该示例使用 Debevec 和 Malik 的方法,但是 OpenCV 还基于 Robertson 的方法提供了另一个校准函数。 校准和合并函数均可用,即createCalibrateRobertsonMergeRobertson

注意

有关其他函数及其背后原理的更多信息,请参见这个页面

最后,请注意示例不会显示结果图像。 HDR 图像无法在常规屏幕中显示,因此我们需要执行另一步,称为色调映射。

色调映射

当要显示高动态范围图像时,信息可能会丢失。 这是由于,因为计算机屏幕的对比度也很有限,而且打印材料通常也限制为 256 色。 当我们具有高动态范围的图像时,有必要将强度映射到一组有限的值。 这称为色调映射。

为了提供逼真的输出,仅将 HDR 图像值缩放到显示设备的缩小范围是不够的。 缩放通常会产生缺乏细节(对比度)的图像,从而消除了原始场景内容。 最终,色调映射算法旨在提供视觉上看起来类似于原始场景的输出(即类似于人类在查看场景时所看到的输出)。 已经提出了各种音调映射算法,并且仍然是广泛研究的问题。 以下代码行可以将色调映射应用于上一个示例中获得的 HDR 图像:

Mat ldr;
Ptr<TonemapDurand> tonemap = createTonemapDurand(2.2f);
tonemap->process(hdr, ldr); // ldr is a floating point image with 
ldr=ldr*255;      //  values in interval [0..1]
imshow("LDR", ldr);

该方法由 Durand 和 Dorsey 于 2002 年提出。构造器实际上接受许多影响输出的参数。 下图显示了输出。 请注意,此图像不一定比三个原始图像中的任何一个都要好:

Tone mapping

音调输出

OpenCV 中提供了其他三种音调映射算法:createTonemapDragocreateTonemapReinhardcreateTonemapMantiuk

可以使用 MATLAB 显示 HDR 图像(RGBE 格式,即扩展名为.hdr的文件)。 它只需要三行代码:

hdr=hdrread('hdr.hdr');
rgb=tonemap(hdr); 
imshow(rgb);

注意

pfstools 是命令行工具的开源套件,用于读取,写入和渲染 HDR 图像。 该套件可以读取.hdr和其他格式,其中包括许多相机校准和色调映射算法。 Luminance HDR 是基于 pfstools 的免费 GUI 软件。

对齐

用多张曝光图像拍摄的场景必须是静态的。 摄像机也必须是静态的。 即使满足两个条件,也建议执行对齐过程。

OpenCV 提供了 G. Ward 在 2003 年提出的图像对齐算法。主要函数createAlignMTB采用定义最大位移的输入参数(实际上,每个尺寸的最大位移以 2 为底的对数)。 在上一示例中估计摄像机响应函数之前,应插入以下几行:

    vector<Mat> images_(images);
    Ptr<AlignMTB> align=createAlignMTB(4);// 4=max 16 pixel shift
    align->process(images_, images);

曝光融合

我们也可以使用相机响应校准(即曝光时间)或中间 HDR 图像,将具有多次曝光的图像组合在一起。 这称为曝光融合。 该方法由 Mertens 等人在 2007 年提出。以下几行执行曝光融合(images是输入图像的 STL 向量;请参见前面的示例):

    Mat fusion;
    Ptr<MergeMertens> merge_mertens = createMergeMertens();
    merge_mertens->process(images, fusion); // fusion is a 
 fusion=fusion*255; // float. point image w. values in [0..1]
 imwrite("fusion.png", fusion);

下图显示了结果:

Exposure fusion

曝光融合

无缝克隆

在无缝克隆中,我们通常要在源图像中剪切一个对象/人并将其插入目标图像。 当然,这可以通过简单地粘贴对象以简单的方式完成。 但是,这不会产生现实的效果。 参见下图,例如,我们想要将图像上半部分的船插入图像下半部分的海中:

Seamless cloning

克隆

从 OpenCV 3 开始,已有无缝克隆函数可用,其结果更为真实。 此函数称为seamlessClone,它使用 Perez 和 Gangnet 在 2003 年提出的方法。以下SeamlessCloning示例向您展示了如何使用它:

#include <opencv2/photo.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main(int, char** argv)
{
    // Load and show images...
    Mat source = imread("source1.png", IMREAD_COLOR);
    Mat destination = imread("destination1.png", IMREAD_COLOR);
    Mat mask = imread("mask.png", IMREAD_COLOR);
    imshow("source", source);
    imshow("mask", mask);
    imshow("destination", destination);

    Mat result;
    Point p;    // p will be near top right corner
    p.x = (float)2*destination.size().width/3;
    p.y = (float)destination.size().height/4;
    seamlessClone(source, destination, mask, p, result, NORMAL_CLONE);
    imshow("result", result);

    cout << "\nDone. Press any key to exit...\n";
    waitKey(); // Wait for key press
    return 0;
}

这个例子很简单。 seamlessClone函数获取源图像,目标图像和遮罩图像以及目标图像中将插入裁剪对象的点(可以从这个页面)。 请参见下图的结果:

Seamless cloning

无缝克隆

seamlessClone的最后一个参数表示要使用的确切方法(可以使用三种方法产生不同的最终效果)。 另一方面,库提供以下相关函数:

函数 效果
colorChange 将源图像的三个颜色通道中的每个乘以一个因子,仅在遮罩给定的区域中应用乘法
illuminationChange 仅在遮罩指定的区域内更改源图像的照度
textureFlattening 仅在遮罩指定的区域中洗掉源图像中的纹理

seamlessClone相反,这三个函数仅接受源图像和遮罩图像。

脱色

脱色是将彩色图像转换为灰度的过程。 有了这个定义,读者可能会问,我们是否已经有了灰度转换? 是的,灰度转换是 OpenCV 和任何图像处理库中的基本例程。 标准转换基于 R,G 和 B 通道的线性组合。 问题在于这种转换可能会产生原始图像中的对比度丢失的图像。 原因是两种不同的颜色(在原始图像中被视为对比度)可能最终被映射到相同的灰度值。 考虑将 A 和 B 这两种颜色转换为灰度。 假设 B 是 R 和 G 通道中 A 的变体:

A = (R, G, B) => G = (R + G + B) / 3
B = (R-x, G + x, B) => G = (R-x + G + x + B) / 3 = (R + G + B) / 3

即使它们被认为是截然不同的,两种颜色 A 和 B 也被映射为相同的灰度值! 以下脱色示例的图像显示如下:

#include <opencv2/photo.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main(int, char** argv)
{
    // Load and show images...
    Mat source = imread("color_image_3.png", IMREAD_COLOR);
    imshow("source", source);

    // first compute and show standard grayscale conversion...
    Mat grayscale = Mat(source.size(),CV_8UC1);
    cvtColor(source, grayscale, COLOR_BGR2GRAY);
    imshow("grayscale",grayscale);

    // now compute and show decolorization...
    Mat decolorized = Mat(source.size(),CV_8UC1);
    Mat dummy = Mat(source.size(),CV_8UC3);
    decolor(source,decolorized,dummy);
    imshow("decolorized",decolorized);

    cout << "\nDone. Press any key to exit...\n";
    waitKey(); // Wait for key press
    return 0;
}

Decolorization

脱色示例输出

这个例子很简单。 读取图像并显示标准灰度转换的结果后,它使用decolor函数执行脱色。 所使用的图像(color_image_3.png文件)包含在 opencv_extra 存储库中,位于这个页面

注意

该示例中使用的图像实际上是一个极端的情况。 选择其颜色是为了使标准灰度输出相当均匀。

非真实感渲染

作为photo模块的的一部分,提供了四个函数,这些函数可以转换输入图像,从而产生不真实但仍具有艺术感的输出。 这些函数非常易于使用,OpenCV(npr_demo)中包含一个很好的示例。 为了说明的目的,在这里我们为您提供一个表格,让您掌握每个函数的效果。 看一下下面的fruits.jpg输入图像,包含在 OpenCV 中:

Non-photorealistic rendering

输入参考图像

效果是:

函数 影响
edgePreservingFilter 平滑是一种方便且经常使用的过滤器。 此函数在保留对象边缘细节的同时执行平滑处理。
Non-photorealistic rendering
detailEnhance 增强图像中的细节
Non-photorealistic rendering
pencilSketch 输入图像的铅笔状线条图版本
Non-photorealistic rendering
stylization 水彩效果
Non-photorealistic rendering

总结

在本章中,您学习了什么是计算摄影以及 OpenCV 3 中可用的相关功能。我们解释了photo模块中最重要的函数,但请注意,在此模块中还考虑了该模块的其他功能(修复和降噪) 前几章。 计算摄影是一个快速发展的领域,与计算机图形学有着紧密的联系。 因此,预计 OpenCV 的此模块将在将来的版本中增加。

下一章将讨论我们尚未考虑的重要方面:时间。 解释的许多功能都需要花费大量时间来计算结果。 下一章将向您展示如何使用现代硬件进行处理。

1
https://gitee.com/OpenDocCN/apachecn-cv-zh.git
git@gitee.com:OpenDocCN/apachecn-cv-zh.git
OpenDocCN
apachecn-cv-zh
apachecn-cv-zh
master

搜索帮助