高动态范围成像 OpenCV-Python v4.7.0

本教程介绍了如何使用OpenCV的StitchingAPI和高级HDR处理技术,包括从曝光序列创建HDR图像和应用曝光融合来生成低动态范围图像。通过加载曝光序列、校准响应函数、合并图像以及应用色调映射,展示了HDR成像的步骤。
摘要由CSDN通过智能技术生成

下一个教程: 高级别的Stitching API(Stitcher类)

Original authorFedor Morozov
CompatibilityOpenCV >= 3.0

简介

今天,大多数数字图像和成像设备使用每通道8比特,从而将设备的动态范围限制在两个数量级(实际上是256级),而人眼可以适应10个数量级的照明条件变化。当我们拍摄现实世界场景的照片时,明亮的区域可能曝光过度,而黑暗的区域可能曝光不足,所以我们不能用一次曝光来捕捉所有细节。HDR成像的工作原理是每个通道使用8位以上的图像(通常是32位浮动值),允许更广泛的动态范围。

有不同的方法来获得HDR图像,但最常见的方法是使用不同曝光值拍摄的场景的照片。为了结合这些曝光,了解你的相机的响应函数是很有用的,有一些算法可以估计它。在HDR图像被混合后,它必须被转换回8位,以便在通常的显示器上观看。这个过程被称为色调映射。当场景中的物体或相机在拍摄之间移动时,会产生额外的复杂性,因为不同曝光的图像应该被登记和对齐。

在本教程中,我们展示了如何从曝光序列中生成和显示HDR图像。在我们的案例中,图像已经对齐,没有移动的物体。我们还展示了另一种方法,称为曝光融合,产生低动态范围图像。HDR管道的每一步都可以用不同的算法来实现,所以请看参考手册来了解它们。

曝光序列

在这里插入图片描述

源代码

本教程的代码如下所示。你也可以从这里下载:

  • C++
#include "opencv2/photo.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <vector>
#include <iostream>
#include <fstream>
using namespace cv;
using namespace std;
void loadExposureSeq(String, vector<Mat>&, vector<float>&);
int main(int argc, char**argv)
{
    CommandLineParser parser( argc, argv, "{@input | | Input directory that contains images and exposure times. }" );
    vector<Mat> images;
    vector<float> times;
    loadExposureSeq(parser.get<String>( "@input" ), images, times);
    Mat response;
    Ptr<CalibrateDebevec> calibrate = createCalibrateDebevec();
    calibrate->process(images, response, times);
    Mat hdr;
    Ptr<MergeDebevec> merge_debevec = createMergeDebevec();
    merge_debevec->process(images, hdr, times, response);
    Mat ldr;
    Ptr<Tonemap> tonemap = createTonemap(2.2f);
    tonemap->process(hdr, ldr);
    Mat fusion;
    Ptr<MergeMertens> merge_mertens = createMergeMertens();
    merge_mertens->process(images, fusion);
    imwrite("fusion.png", fusion * 255);
    imwrite("ldr.png", ldr * 255);
    imwrite("hdr.hdr", hdr);
    return 0;
}
void loadExposureSeq(String path, vector<Mat>& images, vector<float>& times)
{
    path = path + "/";
    ifstream list_file((path + "list.txt").c_str());
    string name;
    float val;
    while(list_file >> name >> val) {
        Mat img = imread(path + name);
        images.push_back(img);
        times.push_back(1 / val);
    }
    list_file.close();
}
  • Java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Scalar;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.photo.CalibrateDebevec;
import org.opencv.photo.MergeDebevec;
import org.opencv.photo.MergeMertens;
import org.opencv.photo.Photo;
import org.opencv.photo.Tonemap;
class HDRImaging {
    public void loadExposureSeq(String path, List<Mat> images, List<Float> times) {
        path += "/";
        List<String> lines;
        try {
            lines = Files.readAllLines(Paths.get(path + "list.txt"));
            for (String line : lines) {
                String[] splitStr = line.split("\\s+");
                if (splitStr.length == 2) {
                    String name = splitStr[0];
                    Mat img = Imgcodecs.imread(path + name);
                    images.add(img);
                    float val = Float.parseFloat(splitStr[1]);
                    times.add(1/ val);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void run(String[] args) {
        String path = args.length > 0 ? args[0] : "";
        if (path.isEmpty()) {
            System.out.println("Path is empty. Use the directory that contains images and exposure times.");
            System.exit(0);
        }
        List<Mat> images = new ArrayList<>();
        List<Float> times = new ArrayList<>();
        loadExposureSeq(path, images, times);
        Mat response = new Mat();
        CalibrateDebevec calibrate = Photo.createCalibrateDebevec();
        Mat matTimes = new Mat(times.size(), 1, CvType.CV_32F);
        float[] arrayTimes = new float[(int) (matTimes.total()*matTimes.channels())];
        for (int i = 0; i < times.size(); i++) {
            arrayTimes[i] = times.get(i);
        }
        matTimes.put(0, 0, arrayTimes);
        calibrate.process(images, response, matTimes);
        Mat hdr = new Mat();
        MergeDebevec mergeDebevec = Photo.createMergeDebevec();
        mergeDebevec.process(images, hdr, matTimes);
        Mat ldr = new Mat();
        Tonemap tonemap = Photo.createTonemap(2.2f);
        tonemap.process(hdr, ldr);
        Mat fusion = new Mat();
        MergeMertens mergeMertens = Photo.createMergeMertens();
        mergeMertens.process(images, fusion);
        Core.multiply(fusion, new Scalar(255,255,255), fusion);
        Core.multiply(ldr, new Scalar(255,255,255), ldr);
        Imgcodecs.imwrite("fusion.png", fusion);
        Imgcodecs.imwrite("ldr.png", ldr);
        Imgcodecs.imwrite("hdr.hdr", hdr);
        System.exit(0);
    }
}
public class HDRImagingDemo {
    public static void main(String[] args) {
        // Load the native OpenCV library
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        new HDRImaging().run(args);
    }
}
  • Python
from __future__ import print_function
from __future__ import division
import cv2 as cv
import numpy as np
import argparse
import os
def loadExposureSeq(path):
    images = []
    times = []
    with open(os.path.join(path, 'list.txt')) as f:
        content = f.readlines()
    for line in content:
        tokens = line.split()
        images.append(cv.imread(os.path.join(path, tokens[0])))
        times.append(1 / float(tokens[1]))
    return images, np.asarray(times, dtype=np.float32)
parser = argparse.ArgumentParser(description='Code for High Dynamic Range Imaging tutorial.')
parser.add_argument('--input', type=str, help='Path to the directory that contains images and exposure times.')
args = parser.parse_args()
if not args.input:
    parser.print_help()
    exit(0)
images, times = loadExposureSeq(args.input)
calibrate = cv.createCalibrateDebevec()
response = calibrate.process(images, times)
merge_debevec = cv.createMergeDebevec()
hdr = merge_debevec.process(images, times, response)
tonemap = cv.createTonemap(2.2)
ldr = tonemap.process(hdr)
merge_mertens = cv.createMergeMertens()
fusion = merge_mertens.process(images)
cv.imwrite('fusion.png', fusion * 255)
cv.imwrite('ldr.png', ldr * 255)
cv.imwrite('hdr.hdr', hdr)

图像样本

包含图像、曝光时间和list.txt文件的数据目录可以从这里下载。

解释

  • 加载图像和曝光时间
  • C++
 vector<Mat> images;
    vector<float> times;
    loadExposureSeq(parser.get<String>( "@input" ), images, times);
  • Java
 List<Mat> images = new ArrayList<>();
        List<Float> times = new ArrayList<>();
        loadExposureSeq(path, images, times);
  • Python
images, times = loadExposureSeq(args.input)

首先,我们从用户定义的文件夹中加载输入图像和曝光时间。该文件夹应包含图像和list.txt–包含文件名和反转曝光时间的文件。

对于我们的图像序列,列表如下:

memorial00.png 0.03125
memorial01.png 0.0625
...
memorial15.png 1024
  • 估算摄像机的响应
  • C++
    Mat response;
    Ptr<CalibrateDebevec> calibrate = createCalibrateDebevec();
    calibrate->process(images, response, times);
  • Java
        Mat response = new Mat();
        CalibrateDebevec calibrate = Photo.createCalibrateDebevec();
        Mat matTimes = new Mat(times.size(), 1, CvType.CV_32F);
        float[] arrayTimes = new float[(int) (matTimes.total()*matTimes.channels())];
        for (int i = 0; i < times.size(); i++) {
            arrayTimes[i] = times.get(i);
        }
        matTimes.put(0, 0, arrayTimes);
        calibrate.process(images, response, matTimes);
  • Python
calibrate = cv.createCalibrateDebevec()
response = calibrate.process(images, times)

对于很多HDR构建算法来说,知道相机响应函数(CRF)是必要的。我们使用其中一种校准算法来估计所有256个像素值的逆CRF。

  • 制作HDR图像
  • C++
    Mat hdr;
    Ptr<MergeDebevec> merge_debevec = createMergeDebevec();
    merge_debevec->process(images, hdr, times, response);
  • Java
        Mat hdr = new Mat();
        MergeDebevec mergeDebevec = Photo.createMergeDebevec();
        mergeDebevec.process(images, hdr, matTimes);
  • Python
merge_debevec = cv.createMergeDebevec()
hdr = merge_debevec.process(images, times, response)

我们使用Debevec的加权方案来构建HDR图像,使用前一项计算的响应。

  • 音频地图HDR图像
  • C++
    Mat ldr;
    Ptr<Tonemap> tonemap = createTonemap(2.2f);
    tonemap->process(hdr, ldr);
  • Java
        Mat ldr = new Mat();
        Tonemap tonemap = Photo.createTonemap(2.2f);
        tonemap.process(hdr, ldr);
  • Python
tonemap = cv.createTonemap(2.2)
ldr = tonemap.process(hdr)

由于我们想在普通的LDR显示器上看到我们的结果,我们必须将我们的HDR图像映射到8位范围,保留大部分细节。这也是色调映射方法的主要目标。我们使用带有双边过滤的色调映射器,并设置2.2作为伽玛校正值。

  • 进行曝光融合
  • C++
    Mat fusion;
    Ptr<MergeMertens> merge_mertens = createMergeMertens();
    merge_mertens->process(images, fusion);
  • Java
        Mat fusion = new Mat();
        MergeMertens mergeMertens = Photo.createMergeMertens();
        mergeMertens.process(images, fusion);
  • Python
merge_mertens = cv.createMergeMertens()
fusion = merge_mertens.process(images)

在我们不需要HDR图像的情况下,有一种替代方法来合并我们的曝光。这个过程被称为曝光融合,产生不需要伽马校正的LDR图像。它也不使用照片的曝光值。

  • 在我们不需要HDR图像的情况下,有一种替代方法来合并我们的曝光。这个过程被称为曝光融合,产生不需要伽马校正的LDR图像。它也不使用照片的曝光值。

  • 写下结果

  • C++

    imwrite("fusion.png", fusion * 255);
    imwrite("ldr.png", ldr * 255);
    imwrite("hdr.hdr", hdr);
  • Java
        Core.multiply(fusion, new Scalar(255,255,255), fusion);
        Core.multiply(ldr, new Scalar(255,255,255), ldr);
        Imgcodecs.imwrite("fusion.png", fusion);
        Imgcodecs.imwrite("ldr.png", ldr);
        Imgcodecs.imwrite("hdr.hdr", hdr);
  • Python
cv.imwrite('fusion.png', fusion * 255)
cv.imwrite('ldr.png', ldr * 255)
cv.imwrite('hdr.hdr', hdr)

现在是时候看一下结果了。请注意,HDR图像不能以常见的图像格式存储,所以我们把它保存为Radiance图像(.hdr)。另外,所有的HDR成像函数返回的结果都在[0, 1]范围内,所以我们应该把结果乘以255。

你可以尝试其他的色调图算法:cv::TonemapDrago, cv::TonemapMantiukcv::TonemapReinhard 你也可以为你自己的照片调整HDR校准和色调图方法中的参数。

结果

色调映射的图像

在这里插入图片描述

曝光融合

在这里插入图片描述

其他资源

  1. Paul E Debevec和Jitendra Malik. 从照片中恢复高动态范围辐射图。在ACM SIGGRAPH 2008班,第31页。ACM,2008年。(Paul E Debevec and Jitendra Malik. Recovering high dynamic range radiance maps from photographs. In ACM SIGGRAPH 2008 classes, page 31. ACM, 2008)[57]
  2. Mark A Robertson, Sean Borman, and Robert L Stevenson. 通过多次曝光改善动态范围。In Image Processing, 1999. ICIP 99. Proceedings. 1999年国际会议,第3卷,第159-163页。IEEE,1999年。(Mark A Robertson, Sean Borman, and Robert L Stevenson. Dynamic range improvement through multiple exposures. In Image Processing, 1999. ICIP 99. Proceedings. 1999 International Conference on, volume 3, pages 159–163. IEEE, 1999.)[209]
  3. Tom Mertens, Jan Kautz, and Frank Van Reeth. 曝光融合. In Computer Graphics and Applications, 2007. PG’07. 第15届太平洋会议,第382-390页。IEEE, 2007.(Tom Mertens, Jan Kautz, and Frank Van Reeth. Exposure fusion. In Computer Graphics and Applications, 2007. PG’07. 15th Pacific Conference on, pages 382–390. IEEE, 2007. ) [172]
  4. 维基百科-HDR
  5. 从照片中恢复高动态范围辐射图(网页)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值