高ダイナミック・レンジ・イメージング(HDRI または HDR) は、標準的なデジタルイメージングや写真技術よりも大きなダイナミックレンジを再現するためにイメージングや写真撮影で用いられている技術である。人間の目は広範囲の光条件に適応できるが、ほとんどのイメージング・デバイスはチャネルあたり8ビットを使用しており、つまるところ256レベルに制限されている。現実世界の情景を写真に撮ると明るい領域は露出オーバーになり、暗い領域は露出不足になる可能性がある。つまり、単一の露出で細部をすべて捉えることはできない。そこでHDRイメージングでは、チャネル当たり8ビット以上(普通は32ビットの浮動小数点数)を使い、より広いダイナミック・レンジを可能にしている。
HDR画像を作るにはいろいろな方法があるが、最も一般的なのは、いろいろな露出値で撮影した情景の写真を使用するという方法である。これらの露出を組み合わせるには、カメラの応答関数を知ることと、それを推定するアルゴリズムがあると便利である。HDR画像を統合した後、通常のディスプレイで表示するには、8ビットに変換しなければならない。この処理過程をトーンマッピング(tonemapping)と呼ぶ。異なる露光量の画像を登録して整列させなければならないため、情景中の物体やカメラが画像間で動いていたりすると処理の複雑さが増す。
このチュートリアルでは、露光シーケンスからHDRイメージを生成し表示する2つのアルゴリズム(Debvec、Robertson)を紹介する。そして低ダイナミックレンジの画像を生成し、露光時間データを必要としない、露光融合(exposure fusion) と呼ばれる代替アプローチ(Mertens)を紹介する。さらに、多くのコンピュータ・ビジョンのアルゴリズムにとり大きな価値があるカメラ応答関数(CRF) を推定する。HDRパイプラインの各ステップは、いろいろなアルゴリズムとパラメータを使って実装できるので、詳しくはリファレンス・マニュアルを参照のこと。
第1段階は、すべての画像をリストに読み込むことである。また通常のHDRアルゴリズムでは露光時間が必要である。またデータの型に注意すること:画像は1チャンネルか3チャンネルの8ビット(np.uint8)で、露光時間は秒単位でfloat32型で表されなければならない。
%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段階では、露出シーケンスを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なので、以下のようなエラーになる
calibrate = cv2.createCalibrateDebevec()
response = calibrate.process(img_list, exposure_times)
# 露出をHDR画像に統合
merge_debvec = cv2.createMergeDebevec()
hdr_debvec = merge_debvec.process(img_list, exposure_times.copy(),response)
merge_robertson = cv2.createMergeRobertson()
hdr_robertson = merge_robertson.process(img_list, exposure_times.copy(),response)
この段階では、32ビット浮動小数点数のHDRデータを[0..1]の範囲に写像する。実際には、値が1より大きい場合も、0よりも小さい場合もある。そこでオーバーフローを避けるため後でデータをクリップしなければならなくなる可能性に注意する。(注: 以下でprocessメソッドは response引数が必要だが、ここでは与えられていないため、下のコードは動かない)
# トーンマップ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())
ここでは、露光画像の統合のための、露光時間を必要としない代替アルゴリズムを紹介する。Mertensアルゴリズムでは結果が[0..1]の範囲になるので、トーンマップ・アルゴリズムを使用する必要もない。
# Mertensを用いて露光を統合
merge_mertens = cv2.createMergeMertens()
res_mertens = merge_mertens.process(img_list)
結果を保存したり表示したりするには、データを8ビット(値の範囲が[0..255])に変換する必要がある。
# データ型を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)
いろいろな結果が得られるが、それぞれのアルゴリズムには、望ましい結果を得るための追加パラメータがあることを忘れないように。一番よいのは、さまざまな方法を試して、どれが自分の望む画像として最も適しているか、確認することである。
カメラ応答関数(CRF)は、情景の放射輝度とカメラによる測定値である輝度との関係を示すものである。CRFは、HDRアルゴリズムなどのコンピュータ・ビジョン・アルゴリズムにおいて非常に重要である。ここでは逆カメラ応答関数を推定し、HDR統合に使用する。
# カメラ応答関数(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())
カメラ応答関数は、それぞれのカラーのチャンネルに対し長さ256のベクトルで表される。このシーケンスでは次の推定値が得られる。