python上でraw現像を行ってみます。これまでrawpyを使った現像に関していろいろ調べてきました。ただ、このライブラリのお手軽現像関数postprocessに少し足りない機能があり、それらを補填していこうと思います。
まずとっかかったのはトーン調整。一般的な現像ソフトではまず備わっている機能です。Pythonを使って、ハイライト、 シャドウ、それぞれ独立に階調補正ができるようにしてみようと思います。今回はscipyを使うのがよさそうなので、その環境も作りました。
rawpyの現像関数の詳細に関してはこちら。
トーン調整
一般的な現像ソフトにはまず備わっている機能です。PhotoshopやLightroomにはもちろん、カメラメーカー添付の現像ソフトにも備わっています。

これはCanonの現像ソフト。ハイライト、シャドウそれぞれのトーンをプラスマイナスで調整できます。

これはOlympus。同じ感じ。名称がハイライト&シャドーコントロールとなっています。

Panasonic(SILKYPIX)だとこんな感じ。これは任意点を補正できるタイプです。自由度は高いのですが、個人的には簡単なCanon / Olympusタイプが好みです。
ちなみに自分はソフトを買うお金がないので、メーカーの現像ソフトを、使うカメラ毎に切り替える、というとても残念な作業をしてます。使い勝手がそれぞれ違って不便です。が、Lightroomを買う気にはなれません。
この機能を真似ます。
曲線近似
このハイライト、シャドーだけをいじった非線形なトーンカーブですが、アンカーポイントを設定したうえで曲線近似するのがよさそうです。この曲線近似ですが、手法はいろいろあります。指定したアンカーポイントを確実に通ってほしい(その方が調整しやすい)のであまり考察もせず、世の中広く使われているスプライン曲線を採用することとします。
この補間は、制御点や区間に対し、その前後の数点だけから近似し、全体としてはいくつもの多項式による曲線をつなぎ合わるようにした曲線とのこと。Wikipedia抜粋
ほかにもベジエ曲線とかラグランジュとかキーワードだけは載せておきます。極端なトーンカーブにしない限り破綻はしないでしょう。
このスプライン曲線を学ぼうとすると、それだけで1日くらいかかりそうなので、ここはひとまずPython様の力を借りてチャチャっと済ませようと思います。
方針としては、最小値、中央値、最大値の3点は固定して、最小値と中央値の間、中央値と最大値の間の点をアンカーポイントにして、その出力を上下させることで実現させようと思います。最小値:0 最大値:1とした場合、
入力:0.00, 0.25, 0.50, 0.75, 1.00
出力:0.00, 0.xx, 0.50, 0.yy, 1.00
といった感じでxxとyyをパラメータにした曲線を描いて、そのトーンカーブを画像に適用することにします。常識的にはxxは0.5以下、yyは0.5以上となると思います。
例えば、コントラストを強めるために、シャドー部をより小さく、ハイライト部をより大きく、するようなことを考えると、設定としては
入力 = [0, 0.25, 0.5, 0.75, 1] 出力 = [0, 0.15, 0.5, 0.85, 1]
となり結果は、

なトーンカーブとなることを想定しています。
Scipy
これまで使う機会がなかったので、自分のPython環境には入っていなかったので、インストールをしました。
PyCharmだと、まずfileメニューからSettingsを選びます。Settingsが開き、左のProject Interpreterを選択すると、現状インストールされているライブラリがずらっと並びます。そこで右上の方にある「+」アイコン(Install)をクリックすると、AvailablePackagesの画面が出ます。

ここで”scipy”と検索すると、上記のように見つかってくれるので、左下のInstall Packageをクリックしてインストールです。

こんな感じでインストールが進んで、使えるようになります。そうすれば、
1 2 3 | import scipy tck = scipy.interpolate.splrep(xs, ys) |
な感じで使えるようになるかと思いきや、interpolate splrep / splrevの関数を使おうとすると、何ぞ
1 | module 'scipy' has no attribute 'interpolate' |
こんなエラーが出て使えませんでした。なので、
1 | from scipy.interpolate import splrep, splev |
こんな感じでimportしてあげると使えました。この2つの書式における意味の違いがよくわかってませんが、使えるのでひとまずよしとします。
コード
上記で示したようなトーンカーブを作るためのコードは以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from matplotlib import pyplot as plt from scipy.interpolate import splrep, splev xs = [ 0 , 0.25 , 0.5 , 0.75 , 1 ] # 入力 ys = [ 0 , 0.15 , 0.5 , 0.85 , 1 ] # 出力 tck = splrep(xs, ys) # スプライン xx = np.arange( 0 , 1 , 0.01 ) yy = splev(xx, tck) plt.scatter(xs, ys, label = 'anchor point' ) plt.plot(xx, yy, label = "spline" ) plt.axis(( 0 , 1 , 0 , 1 )) plt.legend() plt.show() |
このコードで先ほどのグラフが書けます。
このようにして作ったトーンカーブを現像済みrawデータに適用していきます。今回は16bitで処理させています。階調数が少ないと疑似輪郭やトーンジャンプしてしまうので。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import rawpy import numpy as np import cv2 from scipy.interpolate import splrep, splev xs = [ 0 , 0.25 , 0.5 , 0.75 , 1 ] ys = [ 0 , 0.15 , 0.5 , 0.85 , 1 ] tck = splrep(xs, ys) # スプライン raw = rawpy.imread(filename) # 16bitデコード+BGR化 img = raw.postprocess(use_camera_wb = True , output_bps = 16 )[:,:,[ 2 , 1 , 0 ]] # そのまま保存(uint 8bit化) cv2.imwrite( "before.tif" ,(img / 256 ).astype(np.uint8)) img = img / 65535 # 0~1へ正規化 img = splev(img, tck) # スプライン適用 # トーン調整後保存(uint 8bit化) cv2.imwrite( "after.tif" ,(img * 255 ).astype(np.uint8)) |
このようにして保存された画像が以下。これがトーン調整前。

トーン調整後がこれ

見た目でもコントラストが強くついている様子がわかります。調整前後がわかるようにどぎつくやっているので、イマイチ画質ではありますが。
前後のヒストグラムを見ると、(上が調整前、下が調整後)

中心位置ではそれほど変化なく、左右に延ばされていることがわかります。ひとまず成功だと思います。
ただこのコードだと、スプラインが3次スプラインになっているようで、シャドー部、ハイライト部で階調がつぶれています。グラフを拡大すると、

な感じで、入力0.025くらいまで出力0になってそうです。まだギリギリセーフな気がしますが、もう少しコントラストをつけようとすると、ダメそうです。その場合、スプライン補間の関数に与える次数を下げるともう少し粘ってくれます。
1 | tck = splrep(xs, ys, k=2) # スプライン |
とすると、グラフは

こんな感じになり、少しマシになります。この次数kの値は1~4まで設定できるようで、1は単なるアンカーポイント間の線形補間になるので、2か3のどちらかを適宜選んで使う感じかなと思います。(デフォルトは3のようです)
今回のようにトーン調整が目的のスプライン補間であればk=2を使ってもよいかもしれないです。
まとめ
rawpy postprocessでは処理できない、現像のプロセスでは必須だと思われるハイライト、シャドーの独立トーン調整のコードを、scipyを使ったスプライン曲線へフィッティングさせる形で書いてみました。結果も思った通り動いてそうです。コードとしてもとても短く済みました。関数化しておいとくとよいかな。
とはいえ処理順としてはガンマをOFFで現像(gamma = 1.0)して、このトーン調整したのちに、自前でガンマを適用させるのが正解な気がします。今後のためにも自前のガンマ補正関数も作った方がよさそうです。
コメント