豪鬼メモ

一瞬千撃

写真の統計値と自動補正アルゴリズムの改善

raw現像と自動補正のサービスgenzo.cgiをちょっと改良した。以前の記事で述べた、「平均輝度が35%以上になるまで20%を変曲点としたシグモイド補正をかけ続ける」という方法だと、7割くらいはうまくいくが、3割くらいイマイチなのが残っていた。輝度とコントラストが足りなくて暗い画像や、それらを上げすぎて飛び気味になった画像が出てきてしまうことがある。それを改善するにはどうすればいいか考えてみた。
f:id:fridaynight:20181126230301j:plain


そもそも、「平均輝度が35%」ってのは根拠が薄い。たまたま手元にあった何枚かのraw画像を処理した時にそれでうまくいったからというだけなのだ。それじゃよくないということで、ちゃんと統計を取ってみた。自分が過去1年に撮って現像してキープ写真になった9125枚を対象にして、それぞれの写真の露出値、最低輝度、最高輝度、平均輝度、輝度の標準偏差、輝度の歪度、輝度の尖度に対して、平均値と標準偏差と歪度と尖度と中央値を出してみた。

EV min max mean stddev skewness kurtosis
mean 9.43 0.00 0.97 0.22 0.21 1.72 1.21
stddev 2.96 0.00 0.07 0.09 0.06 9.38 0.91
skewness -0.09 13.76 -4.24 0.93 0.29 67.01 1.87
kurtosis -0.80 277.69 22.86 1.08 0.03 5542.57 34.30
median 9.293 0.000 0.999 0.208 0.207 0.642 1.206

ここで言う露出値(Exposure Value)は 2 * log2(Aperture) - log2(ShutterSpeed) - log2(ISO/100) の結果だ。つまり、カメラの設定にかかわらず、被写体がどれだけ明るかったかという尺度を対数で示している。その値の平均が9.43だ。この9.43という値は、実例を見る限り、日中の日陰で背景に眩しい反射が入らない場合とか、外光がうっすら差し込む室内とか、夕方に日が傾いて直射日光が完全に遮られた状態の屋外とか、雨の日の屋外とかがそれにあたる。こんな感じ。
f:id:fridaynight:20170330172512j:plain
f:id:fridaynight:20170223085312j:plain

露出値の平均が9.43で、標準偏差が2.96ということは、7割方は6.5EVから11.5EVの間の被写体を撮っていて、9割5分方は3.5EVから15.5EVの間の被写体を撮っているということになる。ちなみに最低値は-5.95の例で、星を撮っていたものだった。
f:id:fridaynight:20170727213912j:plain

最高値は16.07の例で、西表島で磯遊びをしているところだった。
f:id:fridaynight:20180727160118j:plain

平均輝度に話を戻そう。平均輝度の平均は、0.224であった。結構暗い画素が多いということだ。0.4くらいに平均があると思っていたので、意外だった。歪度の平均が1.72なので、分布のピークは平均よりさらに暗いところにあり、比較的少数のハイライトが平均輝度を上げているということになろうか。私が特別にローキーの画像が好きだと言うことはなく、むしろ明るめの現像を心がけているくらいなのに、平均輝度はそんなに高くないらしい。また、平均輝度の標準偏差が0.087なので、7割方の例で、平均輝度は0.15から0.32くらいになっていることがわかる。よって、「平均輝度が35%以上になるまで」などという経験則は全く間違っているということが分かった。

いちおう最低輝度と最高輝度もみておくと、ほとんどの写真で最低輝度は0で最高輝度は1になっている。つまりJPEGのダイナミックレンジを使い切っている。ノイズでそうなっているのかと疑って画像縮小とガウシアンぼかしをした結果でも測ってみたが、やはり同じ傾向だ。日常の風景を撮ることが多く、とりたててハイキーやローキーの味付けをしない私が現像しているので、妥当な結果だろう。現像時にダイナミックレンジを0から1まで使い切るようにブラックポイントとホワイトポイントを設定するというのは、多くの場合で合理的だ。

こうして統計を取ってみると、露出値と平均輝度に相関があるのか気になってくる。明るい被写体の写真は明るい画像になっていて、暗い被写体の写真は暗い画像になっているのではないか。しかし、相関係数を計算してみたところ、露出値と平均輝度のそれは0.09しかない。つまりほとんど相関していないと言える。散布図を以下に示す。
f:id:fridaynight:20181126181536p:plain

5EV以下の暗さだとちょっぴり相関がありそうな感じもするけど、全体としては相関がないのだ。暗い室内の写真も見やすいように明るく現像しているし、明るい屋外の写真も見やすいように暗くして現像しているのだから、無理もない話だ。ちなみに、輝度の標準偏差と露出値の相関係数は0.168なので、ほとんど相関しないと言うべき範疇でありつつも、平均輝度よりは関係が深い感じになっている。このことは、明るい被写体ほどダイナミックレンジが広くて露出の調整に苦労するという経験則と一致している。

余談だが、キープ写真の中で最も平均輝度が低い例と高い例はどんな写真だろうか。最低は上述の星の写真で輝度0.004だ。ここでは最低から二番目と最高の例を示す。最高は意外にも室内の写真で、平均輝度0.64だ。明るい場所というより、白っぽいものが平均輝度を上げる。
f:id:fridaynight:20170728193052j:plain
f:id:fridaynight:20171227122135j:plain

では、標準偏差の最低例と最高例はどんな感じか。最低はまた例の星の写真なので、最低から二番目と最高の例を示す。それぞれ標準偏差0.11と0.41だ。写っているものが少ないと標準偏差は低まり、逆光などの露出設定に苦労するシーンでは標準偏差は高まる。
f:id:fridaynight:20170802095532j:plain
f:id:fridaynight:20170406132525j:plain

ついでに、歪度の最低例と最高例も見てみよう。最高はまた例の星の写真なので、最低と最高から二番目の例を示す。それぞれ歪度-1.84と149だ。白が基調のシーンに陰が差すと歪度は低まり、黒が基調のシーンに光が差すと歪度は高まる。
f:id:fridaynight:20180504103751j:plain
f:id:fridaynight:20180218184845j:plain

尖度に関してはちょっと面白い発見がある。尖度が低い写真は、たいてい白っぽい。紙などをドアップで撮ったものや、雪景色だ。明るいとは限らなく、白っぽいのだ。言い換えれば、光源や反射がフラットな場合には画面全体の陰影が薄くなり、尖度が低くなるということか。
f:id:fridaynight:20180404150957j:plain
f:id:fridaynight:20170618185922j:plain
f:id:fridaynight:20170109083448j:plain

逆に尖度が低い写真は、たいてい黒っぽい。黒っぽい写真は大抵夜景なので、暗いことが多い。暗いのに写真を撮っているということはどこかしらに光源や反射があるわけだが、それらは点光源に近く、画面内の限られた部分にハイライトを作るので、尖度が高くなるということだろう。
f:id:fridaynight:20180217195122j:plain
f:id:fridaynight:20180126211815j:plain

統計値を見ると、写真の特徴の一部を言い当てることができそうだということは分かった。しかし、問題は、そういった統計値を作り出す分布は、撮影者(カメラJPEG撮りの場合)や現像者(PC現像の場合)が意図をもって調整をした結果として出来上がるものであり、後で調整することを前提に作成したraw画像にはそれが当てはまらないということだ。raw撮りに慣れた多くの人は後で調整する時に潰しがきくように、暗く仕上げたいような被写体も白飛びしないギリギリまで明るく撮っておいて情報量を多くしたり、明るい被写体だと白飛びしないように暗く撮ったりする。そうなると輝度の分布から撮影意図を汲み取るというのはほとんど不可能だ。

ここまで検討すると、推定した作画意図から目標となる平均輝度を決めて輝度を調整するというのはあまり現実的でない気がしてきた。元々の分布を無視して平均のみを見て非線形な調整を施すと、分布の形が著しく変わってしまう。そして、目標となる平均輝度を適切に設定すること自体が難しい。


発想を変えてみた。 理想的な分布の形に後で簡単に修正できるような、「ニュートラル」な画像を先に作ってから、それに対して主要被写体の輝度付近のコントラストを強調した画像を作って仕上げるのだ。ニュートラルな画像を作るにあたっては、被写体の種類や作画意図を無視して、単にスケール対数補正をかけて平均輝度を0.5にするパラメータを模索すればいい。スケール対数補正のパラメータは二分探索で探せばいいので、最低値0、最大値128から10回もやれば十分な精度になる。スケール対数補正はどの部分の階調を強調するわけでもないので、このような「ニュートラル」な補正を行うのに適している。
f:id:fridaynight:20181127083703j:plain

平均輝度0.5を目指す代わりに標準偏差の最大化や尖度の最低化を目指すという作戦もある。標準偏差や尖度はスケールログ補正に対して凸関数なので、三分探索で最適解が得られる。画面の一部に強烈なハイライトが入って他の多くの部分が暗くなる逆光のシーンなどでは、近いトーンの突出を嫌う尖度最低化の方が良い結果になることもある。しかし、どんなraw画像でも安定した結果が得られるという点では平均輝度を使う方法が最善だろう。

コントラストを強調する段階では、主要被写体の輝度付近を変曲点にして線形RGB空間でシグモイド補正をかけることになる。シグモイド補正の強度は、スケール対数補正のパラメータをCとした場合に、(log(C) + 1) * 1.35 くらいにすると丁度良いという経験則を編み出した。変曲点をどうするかは悩ましいのだが、とりあえず0.48くらいにしておくと望ましい結果が出ることが多い。あとは、同じアルゴリズムで、スケール対数補正を+0.8した暗いバージョンと、スケール対数補正を-0.8した明るいバージョンを作って、ユーザに好きなものを選んでもらう。ついでに、Dcrawの現像結果はLightroom等よりも彩度が低い傾向にあるので、前回述べた彩度に対するスケール対数補正をかけて、ちょっとだけ彩度を高めるようにする。
f:id:fridaynight:20181127205704j:plain

てことで、新しくなったgenzo.cgiのUIはこんな感じだ。輝度のヒストグラムが表示されるのだが、ニュートラルな画像の輝度は平均が0.5になるように調整されているので、分布の山のピークは真ん中付近にくる。ここで、手動調整した画像の歪度の平均が1.72だったのを思い出されたい。多くの場合、分布のピークは平均よりちょっと左にくる。したがって、線形RGB空間での0.48を変曲点としてシグモイド補正をかけた結果として、分布のピークは中央より左に寄る。そうすると多くの場合でいい塩梅の画像が出てくる。一方で、作画意図によっては、より暗い画像や、より明るい画像の方が望ましい場合もあるだろうから、別バージョンも自動的に作って選べるようにしている。(追記:さらに変更して、最新版では仕上げのパラメータを手動で選べるようにした)。
f:id:fridaynight:20181126215133p:plain

再加工用のTIFF画像がダウンロードできるが、それは輝度のシグモイド補正と彩度の対数補正とシャープネス処理をかける前の状態の画像データである。再加工するのであればトーンカーブを自分でいじることになるだろうから、できるだけニュートラルな状態の方が望ましい。

ここまで述べたアルゴリズムコマンドラインで実行するスクリプトはこちらである。ご参考まで。


新実装での現像例をいくつかあげてみる。まずはうまく行った例。大抵の場合でうまくいく。以前の実装だと暗すぎた例も、だいたい良い感じになっている。
f:id:fridaynight:20181126220102j:plain
f:id:fridaynight:20181126215841j:plain
f:id:fridaynight:20181127005529j:plain
f:id:fridaynight:20181126215940j:plain
f:id:fridaynight:20181126220015j:plain
f:id:fridaynight:20181127005339j:plain

イマイチな例。ローキーの作画意図がある場合はやはりダメで、著しく飛び気味の画像を救うのもやはり手動調整には適わない。まあそういうのは手動でやれって話なんだけども。
f:id:fridaynight:20181126220230j:plain
f:id:fridaynight:20181126220325j:plain


まとめ。統計値を見て画像の分類をするのは楽しいが、それを自動現像の調整に生かすのは難しい。なので、割り切って、スケール対数補正だけでニュートラルな画像を作ってから、それに対するコントラストアップ処理を複数のバージョンで分けて提示することにした。これによって、98%くらいのケースで受容できる出力が得られるようになったと思う。