高ダイナミック・レンジ

目的

このチュートリアルでは

  • 露光シーケンスから高ダイナミック・レンジ(HDR)の画像を生成し表示する方法を学ぶ
  • 露光融合を用いて露光シーケンスを統合する

理論

高ダイナミック・レンジ・イメージング(HDRI または HDR) は、標準的なデジタルイメージングや写真技術よりも大きなダイナミックレンジを再現するためにイメージングや写真撮影で用いられている技術である。人間の目は広範囲の光条件に適応できるが、ほとんどのイメージング・デバイスはチャネルあたり8ビットを使用しており、つまるところ256レベルに制限されている。現実世界の情景を写真に撮ると明るい領域は露出オーバーになり、暗い領域は露出不足になる可能性がある。つまり、単一の露出で細部をすべて捉えることはできない。そこでHDRイメージングでは​​、チャネル当たり8ビット以上(普通は32ビットの浮動小数点数)を使い、より広いダイナミック・レンジを可能にしている。

HDR画像を作るにはいろいろな方法があるが、最も一般的なのは、いろいろな露出値で撮影した情景の写真を使用するという方法である。これらの露出を組み合わせるには、カメラの応答関数を知ることと、それを推定するアルゴリズムがあると便利である。HDR画像を統合した後、通常のディスプレイで表示するには、8ビットに変換しなければならない。この処理過程をトーンマッピング(tonemapping)と呼ぶ。異なる露光量の画像を登録して整列させなければならないため、情景中の物体やカメラが画像間で動いていたりすると処理の複雑さが増す。

このチュートリアルでは、露光シーケンスからHDRイメージを生成し表示する2つのアルゴリズム(Debvec、Robertson)を紹介する。そして低ダイナミックレンジの画像を生成し、露光時間データを必要としない、露光融合(exposure fusion) と呼ばれる代替アプローチ(Mertens)を紹介する。さらに、多くのコンピュータ・ビジョンのアルゴリズムにとり大きな価値があるカメラ応答関数(CRF) を推定する。HDRパイプラインの各ステップは、いろいろなアルゴリズムとパラメータを使って実装できるので、詳しくはリファレンス・マニュアルを参照のこと。

露光シーケンスHDR

このチュートリアルでは、次にあげる露出時間が15, 2.5, 1/4, 1/30秒の4つの露出による画像を見ていく。(この画像はウィキペディアからダウンロードできる)

1. 露光画像をリストに読み込む

第1段階は、すべての画像をリストに読み込むことである。また通常のHDRアルゴリズムでは露光時間が必要である。またデータの型に注意すること:画像は1チャンネルか3チャンネルの8ビット(np.uint8)で、露光時間は秒単位でfloat32型で表されなければならない。

In [1]:
%matplotlib inline
import matplotlib.pyplot as plt
import cv2
import numpy as np

# 露出画像をリストに取り込む
img_fn = ["img0.jpg", "img1.jpg", "img2.jpg", "img3.jpg"]
img_list = [cv2.imread(fn) for fn in img_fn]
exposure_times = np.array([15.0, 2.5, 0.25, 0.0333], dtype=np.float32)

2. 露出をHDR画像に統合する

この第2段階では、露出シーケンスを1つのHDR画像に統合する。OpenCVでは2通りの方法がある:方法の一つはDebvec、もう一つはRobertsonである。HDR画像はすべての露出画像の全部のダイナミックレンジを含んでいるため、uint8ではなくfloat32タイプであることに注意。

注意: Spizhevoy, A. & Rhbnikov, A. (2018) OpenCV 3 Computer Vision with Python Cookbook: Leverage the power of OpenCV (pp.226-228). Packt から、以下を動かすには OpenCV 3.3 以上が必要とのこと。現在の私の環境は OpenCV3.1なので、以下のようなエラーになる

In [11]:
calibrate = cv2.createCalibrateDebevec()
In [12]:
response = calibrate.process(img_list, exposure_times)
-------------------------------------------------------------------
error                             Traceback (most recent call last)
<ipython-input-12-8c7dd18fc28b> in <module>()
----> 1 response = calibrate.process(img_list, exposure_times)

error: /home/travis/miniconda/conda-bld/conda_1486587069159/work/opencv-3.1.0/modules/photo/src/hdr_common.cpp:57: error: (-215) images[i].cols == width && images[i].rows == height in function checkImageDimensions
In [10]:
# 露出をHDR画像に統合
merge_debvec = cv2.createMergeDebevec()
hdr_debvec = merge_debvec.process(img_list, exposure_times.copy(),response)
-------------------------------------------------------------------
error                             Traceback (most recent call last)
<ipython-input-10-91bde1a07e90> in <module>()
      1 # 露出をHDR画像に統合
      2 merge_debvec = cv2.createMergeDebevec()
----> 3 hdr_debvec = merge_debvec.process(img_list, exposure_times.copy(),True)
      4 merge_robertson = cv2.createMergeRobertson()
      5 hdr_robertson = merge_robertson.process(img_list, times=exposure_times.copy())

error: /home/travis/miniconda/conda-bld/conda_1486587069159/work/opencv-3.1.0/modules/photo/src/hdr_common.cpp:57: error: (-215) images[i].cols == width && images[i].rows == height in function checkImageDimensions
In [15]:
merge_robertson = cv2.createMergeRobertson()
hdr_robertson = merge_robertson.process(img_list, exposure_times.copy(),response)
-------------------------------------------------------------------
NameError                         Traceback (most recent call last)
<ipython-input-15-4c5785a91c9e> in <module>()
      1 merge_robertson = cv2.createMergeRobertson()
----> 2 hdr_robertson = merge_robertson.process(img_list, exposure_times.copy(),response)

NameError: name 'response' is not defined

3. トーンマップHDR画像

この段階では、32ビット浮動小数点数のHDRデータを[0..1]の範囲に写像する。実際には、値が1より大きい場合も、0よりも小さい場合もある。そこでオーバーフローを避けるため後でデータをクリップしなければならなくなる可能性に注意する。(注: 以下でprocessメソッドは response引数が必要だが、ここでは与えられていないため、下のコードは動かない)

In [ ]:
# トーンマップHDR画像
tonemap1 = cv2.createTonemapDurand(gamma=2.2)
res_debvec = tonemap1.process(hdr_debvec.copy())
tonemap2 = cv2.createTonemapDurand(gamma=1.3)
res_robertson = tonemap2.process(hdr_robertson.copy())

4. Mertens融合を用いて露光を統合する

ここでは、露光画像の統合のための、露光時間を必要としない代替アルゴリズムを紹介する。Mertensアルゴリズムでは結果が[0..1]の範囲になるので、トーンマップ・アルゴリズムを使用する必要もない。

In [ ]:
# Mertensを用いて露光を統合
merge_mertens = cv2.createMergeMertens()
res_mertens = merge_mertens.process(img_list)

5. 8ビットに変換してデータを保存

結果を保存したり表示したりするには、データを8ビット(値の範囲が[0..255])に変換する必要がある。

In [ ]:
# データ型を8ビットに変換して保存
res_debvec_8bit = np.clip(res_debvec*255, 0, 255).astype('uint8')
res_robertson_8bit = np.clip(res_robertson*255, 0, 255).astype('uint8')
res_mertens_8bit = np.clip(res_mertens*255, 0, 255).astype('uint8')

cv2.imwrite("ldr_debvec.jpg", res_debvec_8bit)
cv2.imwrite("ldr_robertson.jpg", res_robertson_8bit)
cv2.imwrite("fusion_mertens.jpg", res_mertens_8bit)

結果

いろいろな結果が得られるが、それぞれのアルゴリズムには、望ましい結果を得るための追加パラメータがあることを忘れないように。一番よいのは、さまざまな方法を試して、どれが自分の望む画像として最も適しているか、確認することである。

Debvec:

Robertson:

Mertenes融合:

カメラ応答関数の推定

カメラ応答関数(CRF)は、情景の放射輝度とカメラによる測定値である輝度との関係を示すものである。CRFは、HDRアルゴリズムなどのコンピュータ・ビジョン・アルゴリズムにおいて非常に重要である。ここでは逆カメラ応答関数を推定し、HDR統合に使用する。

In [16]:
# カメラ応答関数(CRF)の推定
cal_debvec = cv2.createCalibrateDebevec()
crf_debvec = cal_debvec.process(img_list, times=exposure_times)
hdr_debvec = merge_debvec.process(img_list, times=exposure_times.copy(), response=crf_debvec.copy())
cal_robertson = cv2.createCalibrateRobertson()
crf_robertson = cal_robertson.process(img_list, times=exposure_times)
hdr_robertson = merge_robertson.process(img_list, times=exposure_times.copy(), response=crf_robertson.copy())
-------------------------------------------------------------------
error                             Traceback (most recent call last)
<ipython-input-16-8356d0ac40a0> in <module>()
      1 # カメラ応答関数(CRF)の推定
      2 cal_debvec = cv2.createCalibrateDebevec()
----> 3 crf_debvec = cal_debvec.process(img_list, times=exposure_times)
      4 hdr_debvec = merge_debvec.process(img_list, times=exposure_times.copy(), response=crf_debvec.copy())
      5 cal_robertson = cv2.createCalibrateRobertson()

error: /home/travis/miniconda/conda-bld/conda_1486587069159/work/opencv-3.1.0/modules/photo/src/hdr_common.cpp:57: error: (-215) images[i].cols == width && images[i].rows == height in function checkImageDimensions

カメラ応答関数は、それぞれのカラーのチャンネルに対し長さ256のベクトルで表される。このシーケンスでは次の推定値が得られる。

補足資料

  1. Paul E Debevec and Jitendra Malik.Recovering high dynamic range radiance maps from photographs. In ACM SIGGRAPH 2008 classes, page 31. ACM, 2008.
  2. 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.
  3. 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.
  4. Images from Wikipedia-HDR

課題

  1. OpenCV3.3をインストールし、上を試してみよう
  2. すべてのトーンマップ・アルゴリズムを試そう: Drago, Durand, Mantiuk, Reinhard
  3. HDRキャリブレーションとトーンマップ手法のパラメータをいろいろ変えてみよう