ProRawをRAW現像する

概要

  • 前エントリの続き irohalog.hatenablog.com

  • この記事を参考に、イメージングパイプラインを学ぶ目的でProRawデータ(DNGファイル)をC++でRAW現像する。

  • RAW現像結果から各処理の効果を確認するとともに、LibRaw及びMacPreviewでRAW現像した結果と自分のRAW現像結果とを比較する。

【目次】

コード

コードはこちら。

github.com

DNGファイルの読み込みには、LibRawを用いる。 データはNumPy風に多次元配列を扱える Xtensor で扱い、行列演算などの処理を行う。 処理した結果はOpenCV を用いて保存する。

Xtensor及びOpenCVを用いるのは、自分がすでに使える環境を持っていて慣れているからである。今回の処理だけを考えれば、もっと導入のしやすい選択があったかもしれない。

この記事に出てくるコードはGitHubに置いたコードから抜粋し、簡略化したものである。

ProRawデータをRAW現像する

今日はバレンタインデー🍫 ということで、今回は最終的にこんな感じになる美味しそうな(美味しかった)ケーキのProRawデータを処理していく。

ProRawの読み込み

LibRawを用いて、ProRaw(DNGファイル)を読み込む。

LibRaw raw;
int res = raw.open_file(input_filename.c_str());
res = raw.unpack();

ProRawの画像データをXtensorに格納する。

xt::xtensor<ushort, 2> image({4, (std::size_t)(raw.imgdata.sizes.iheight *
                                              raw.imgdata.sizes.iwidth)});
for (int i = 0; i < image.shape()[1]; i++) {
     xt::view(image, xt::all(), i) =
          xt::adapt(raw.imgdata.rawdata.color4_image[i], {4});
}
image = xt::view(image, xt::range(0, 3), xt::all());

今回はピクセル毎の処理しかしないため、画像の二次元構造を無視してChannel毎にデータを一列に並べて扱う。 この時点でimageのサイズは、(3 Channels, 全ピクセル数) であり、ChannelはRGB順に並ぶ。

下記は、処理前のProRawデータをそのまま8-bitに変換し、PNG形式で保存した画像である1。うっすらケーキが見えるかな。

“処理前のProRawデータ”

ブラックレベル補正

ProRawはブラックレベルが0であり、ブラックレベル補正は必要ない。ProRawとして保存する前にiPhoneで処理してくれていると思われる。

カラー補正

画像データをカメラ色空間からsRGB'へ変換する。この変換に用いる行列については、前回のエントリへ記載した。

// Conversion Matrix from Camera Native Color Spaxce to sRGB'
xt::linalg::dot(srgb_to_cam, image);

ここで、srgb_to_camはカメラ色空間からsRGB'へ変換する行列であり、LibRawのimgdata.color.rgb_camをXtensorに格納した行列である。

camera_to_sRGB()

輝度とコントラスト調整

シンプルなヒストグラムストレッチ (Histogram stretching) で画像全体の輝度とコントラストを調整する。 ピクセル全体から、輝度値の上位・下位から指定された割合(rate)を0あるいはMAX値へ移動させるようにRGB値を線形変換し、ヒストグラムを引き延ばす。

ヒストグラムストレッチによる輝度ヒストグラムの変化
([左]: ヒストグラムストレッチ未適用の場合、[右]: ヒストグラムストレッチ適用の場合 [rate=0.12] )

adjust_brightness()

なお、同様の処理はLibRawでno_auto_bright=falseで適用される(defaultで適用)。 raw.imgdata.params.no_auto_bright = 0;

ガンマ補正

sRGB'値にガンマ補正を適用し、非線形なsRGB値へと変換する。 次の式を線形なsRGB'値のそれぞれに適用し、非線形なsRGB値に変換する2

 \displaystyle
C_{sRGB} = \left\{
\begin{array}{ll}
12.92 C_{sRGB'} & (C_{sRGB'} \leq 0.0031308) \\
1.055 C_{sRGB'}^{1/2.4} - 0.055&  ( C_{sRGB'} > 0.0031308)
\end{array}
\right.

ここで、  C_{sRGB'} はsRGB'のR, G, Bを範囲[0, 1]で表した値であり、  C_{sRGB} はsRGBへ変換後のR, G, B値である。

コードはこんな感じ。

if ( image(ch, i) < 0.0031308 * USHRT_MAX) {
     image(ch, i) *= 12.92;
} else {
     float value = static_cast<float>(image(ch, i)) / USHRT_MAX;
     value = (std::pow(value, 1. / 2.4) * 1.055) - 0.055;
     image(ch, i) = value * USHRT_MAX;
}

gamma_correction()

RAW現像結果

様々なパタンでRAW現像した結果を比較し、各処理の効果を確認する。

C: カラー補正適用
A: 輝度とコントラスト調整適用 (特に指定がなければrate=0.12)
G: ガンマ補正適用

カラー補正の効果確認

カラー補正の有無で結果を比較する。

C+A+G A+G
“Result “Result

カラー補正を適用することで、色味(彩度)が本物に近づいている3

輝度とコントラスト調整の効果確認

輝度とコントラスト調整のrateを変化させた結果は下記の通り。

C+G
rate=0.00
C+A+G
rate=0.04
C+A+G
rate=0.08
C+A+G
rate=0.10
C+A+G
rate=0.12
Result image (threshold=0.00) Result image (threshold=0.04) Result image (threshold=0.08) Result image (threshold=0.10) Result image (threshold=0.12)

輝度とコントラスト調整を入れない場合には画像は全体的に暗いが、適用することで画像が明るくなっている。 また、輝度とコントラスト調整で引き伸ばしを大きくする(よりストレッチさせてヒストグラムを平坦にする)ことで、コントラストが高まっている。

ガンマ補正の効果確認

ガンマ補正の効果を確認する。

C+A+G C C+A C+G
“Result “Result “Result “Result

ガンマ補正も輝度とコントラスト調整も未適用の場合には画像が暗くなる(左から2番目の図)。

ガンマ補正なしで輝度とコントラスト調整を行なった場合には、コントラストが強調されすぎて不自然な印象である(左から3番目の図)。輝度とコントラスト調整のrateを下げることで不自然なコントラスト強調は抑制されるだろうが、その場合にはおそらく画像全体が暗くなっていくだろう(左から2番目の図に近づく)。

ややズレるが、カラー補正とガンマ補正だけの画像もここに載せておく(一番右の図)。左から2番目の図に比べて明るくなっているが、これだけでは何が写っているのか分かりにくい。

LibRaw及びMac Previewで現像した結果との比較

今回はProRawのイメージングパイプラインを学ぶことが目的であり、いい感じにRAW現像することには注力していないが4、LibRaw及びMacPreviewで現像した結果との比較をのせておく。

自分の結果は、全部入り「C+A+G」パタンで現像した結果である(rate=12%)。

LibRawは、raw.imgdata.params.use_camera_wb = 1;のみ指定し、他はデフォルトパラメータを用いて変換した結果である。これは、dcraw -c -wで変換した結果に相当する。LibRawはTIFF形式でしか保存できないため、LibRawの変換結果をOpenCVのcv::Matに入れてPNG形式で保存した5。 コードはこちら

Mac Previewは、DNGファイルをPreviewで開いて、8-bit PNG形式にエクスポートした画像である。

自分の結果 LibRaw Mac Preview
“Result “Result “Result
“Result “Result “Result
“Result “Result “Result
“Result “Result “Result

全体的に、Previewの変換結果はさすがだな...と思う。一行目、自分の結果はケーキのクリームが白飛びしているのに対し、Previewのクリームは質感がしっかり残っている。二行目、Previewの結果は黒つぶれを防ぎ、全体がわかるように調整されている。三行目、四行目、やはり自分の結果は白飛びが目立つのに対し、Previewの結果は色味(なんというか、彩度なんだけどそれだけじゃない)が絶妙にいいと思う。LibRawの結果も、白飛び・黒つぶれはあるものの、デフォルト設定の割に悪くないね。


RAWは8-bit画像になってしまっては中々取り戻せない鮮明な世界だ。いかにセンサーが捉えてくれた鮮明さをその後残し続けられるか、RAWの12-bit/16-bitの良さを活かすImage Processingに今後も期待したい。


  1. DNGファイルの画像データは12-bitで、これを16-bit(ushort)で格納する。PNG形式で保存する際に、一列に並べていたピクセル値を縦横の位置に戻し、16-bit(ushort)から8-bit(uchar)に変換して保存する。OpenCVはchannelの並びがBGRなので、channelの並びも変更することに注意する。
  2. この数式はパラメータも含めてIEC 61966-2-1:1999 で定めされている。
  3. 近づけるべき「本物」とは何か、あるところまでいくと難しい問題である。実物を見た記憶よりもむしろ、画像をディスプレイで表示し、それを目で見たときに素敵に見えるかどうかが重要かもしれない。実際のイチゴよりも赤いほうが好まれるのだ、多分。
  4. 言い訳です!
  5. 念のため同じ結果をLibRawでTIFF形式でも保存しており、OpenCVPNG形式で保存した画像と見た感じ同じであることを確認した。GitHubのdata内に双方の画像を保存している。