python上でraw現像を行ってみます。これまでrawpyを使った現像に関していろいろ調べてきました。ただ、このライブラリのお手軽現像関数postprocessに少し足りない機能があり、それらを補填していこうシリーズの第2弾です。
第1段ではトーン調整を作ってみました。今回取り掛かったのは、彩度調整とシャープネス調整。これも一般的な現像ソフトではまず備わっている機能です。Pythonを使って、これらを実装してみようと思います。
rawpyの現像関数の詳細に関してはこちら。
彩度調整
一般的な現像ソフトにはまず備わっている機能です。PhotoshopやLightroomにはもたぶん(使ったことないのでわかりませんが)、カメラメーカー添付の現像ソフトにも備わっています。
Canonの現像ソフト。これには「色の濃さ」という名前で彩度調整が備わっています。Olympusの現像ソフトにも「彩度」という名前で存在します。
この機能ですが、rawpyには備わっていません。シンプルな処理なのであってもよさそうなものですが、実装されていません。個人的には現像時に気持ちいじるかどうかのパラメータですが、あった方がうれしいです。でやるからには8bitに落とす前にやりたいです。疑似輪郭とか変なアーティファクトが出そうなので。
処理としてはRGBを色相・彩度系の色空間(HSV等)に変換して、その彩度(S)にゲインをかけることで実現できそうです。このHSVの色空間ですが、Wikipediaによると(抜粋、加筆)
HSVモデルは色相(Hue)、彩度(Saturation・Chroma)、明度(Value・Brightness)の三つの成分からなる色空間
色相 – 色の種類(赤、青、黄)を角度で表現、0 – 360の範囲
Wikipedia HSV色空間
彩度 – 色の鮮やかさ。0 – 100 % の範囲
明度 – 色の明るさ。0 – 100 % の範囲
とのこと。今回の場合このゲインが調整パラメータになります。
処理前後のイメージはこんな感じ。どぎつくかけてます。今回の実装で作った画像ですが、それっぽいです。ツーリング帰りの苫小牧港フェリー乗り場です。
シャープネス調整
いわゆる画像の鮮鋭性を整える処理です。 これもrawpyにはないです。先のCanonの現像ソフトでも調整が可能で、デフォルトでシャープネスの処理が施されています。
通常エッジ強調はRGBの色空間から、輝度・色差系(YCbCr、Lab等)の色空間に変換した後に、輝度面(YCbCrであればY)に対して処理するのが一般的です。RGB面に対して処理をしてしまうと、偽色が出る場合があるので。
この処理は、階調数が少ないことに起因する不具合は少なそうなので、16bit処理にそれほどこだわってないです。
とはいえ、彩度調整の際に色相・彩度系の空間に変換するので、その輝度面に対して行えばいいかなと思ったので、ついでといえばついでの処理です。色空間を行ったり来たりすることによるビット欠けも抑制したいので、いいタイミングといえばいいタイミングです。
処理としては、輝度に対してアンシャープマスクを使うことにします。詳細説明は割愛します。簡単に説明すると、オリジナルの画像に対してガウシアンフィルタを使ってぼかしたもの(アンシャープしたもの)を用意します。オリジナルからそのボケ画像を減算し、エッジ成分を取り出し、その結果をオリジナルに加算することでシャープネスを強めます。
エッジ成分の加算比率
ガウシアンのフィルタサイズ
ガウシアンの分散
の3つがシャープネス調整のパラメータになります。
アンシャープマスクなのにエッジが強調されるので、最初聞いた時は逆をイメージしてしまいましたが、エッジ強調の手法です。
処理前後のイメージはこんな感じ。もともと暗部ノイズが多い画像だったのでエッジ強調が悪さをしています。いいサンプルではないですね…。パラメータ次第ですが、しっかりデノイズしないと弊害が多いですね。エッジはしっかり強調されています。これも今回の実装での結果です。
実装
PILでのお手軽実装は失敗しましたが、なぜダメだったか載せておきます。結果としてOpenCVを使いました。
PIL
PIL(Pillow)に何ぞこのあたり便利関数がそろってそうです。こいつを使えばいいかなと思っていろいろ調べてみました。が後述するように失敗に終わりました。
from PIL import ImageEnhance
これでいろいろな画像補正系の処理が使えそうです。
img_pil =Image.open(fname) # 彩度調整 sat = ImageEnhance.Color(img_pil) img_up = sat.enhance(0.5) # 彩度ダウン img_dwn = sat.enhance(1.5) # 彩度アップ # シャープネス調整 shrp = ImageEnhance.Sharpness(img_pil) img_shrp = shrp.enhance(1.5) # シャープネスUP img_unshrp = shrp.enhance(0.5) # シャープネスdown
これだけで済んでしまいます。あら便利。もうこだわらずこれでいいかと思ったのですが、16bit系で処理ができないです。少なくとも試した範囲では…。(やり方まずいのかなぁ)
# rawpyでの読み込み raw = rawpy.imread(filename) img = raw.postprocess(use_camera_wb=True, output_bps=16) # PIL形式へ変換 imgp = Image.fromarray(img)
とすればnumpy arrayからPIL形式の画像になってくれるかなぁと思ったのですが、
File "\site-packages\PIL\Image.py", line 2651, in fromarray raise TypeError("Cannot handle this data type") TypeError: Cannot handle this data type
となってエラーになってしまいました。ちなみに8bitで現像すればそれっぽく動きました。というわけで、一番やりたかった8bitへの変換によるビット欠けが起こる前の処理は実現できなそうです。やり方がまずいのかもしれませんが、失敗です。
OpenCV
というわけで、あきらめてOpenCVにお願いすることにしました。この場合はある程度、自力でコーディングすることになりますが、難しい処理ではないので、簡単に済ませます。
処理のフローとしては、
0.16bit現像
1.RGBからHSVへの色空間変換
2.Sへのゲイン調整(彩度調整)
3.Vへのアンシャープマスク(シャープネス調整)
4.HSVからRGBへの色空間変換
5.8bit化して保存
な感じで行います。がそれぞれ数行で終わってしまいました。せいぜい引っかかったのは、OpenCVでの色空間変換はfloat32である必要がある点だけ。そこさえ直せば簡単に行きました。
コード
import rawpy import numpy as np import cv2 # 彩度調整パラメータ SAT_GAIN = 1.4 # 1より大で彩度UP、小で彩度DOWN # シャープネス調整パラメータ SHP_GAIN = 0.5 # 適用率、0で無変化 SHP_SIZE = 7 # フィルタサイズ SHP_SIGMA = 3 # ガウシアンの偏差 # 0.16bit現像 raw = rawpy.imread(filename) img = raw.postprocess(use_camera_wb=True, output_bps=16) # uint16 -> float32変換(0~1) img = (img/65535).astype(np.float32) # 1.RGBからHSVへの色空間変換 img_hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV) # 2.Sへのゲイン調整(彩度調整) img_hsv[:, :, 1] = np.clip(img_hsv[:, :, 1]*SAT_GAIN, 0, 1) # 3.Vへのアンシャープマスク(シャープネス調整) img_blr = cv2.GaussianBlur(img_hsv[:, :, 2], (SHP_SIZE, SHP_SIZE), SHP_SIGMA) img_hsv[:, :, 2] = np.clip(img_hsv[:, :, 2] + SHP_GAIN*(img_hsv[:, :, 2] - img_blr), 0, 1) # 4.HSVからBGRへの色空間変換 img = cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR) # 5.8bit化して保存 img = (img*255).astype(np.uint8) cv2.imwrite(outputfile, img)
これでうまく行ったように見えます。GaussianBlurへの引数にfloat32が使えるのかどうか怪しかったですが、動いてくれているので大丈夫だと思います。
冒頭に載せた画像はこのコードの結果です。まぁまぁそれっぽいし、階調飛びとかもないので、思惑通りと思います。
まとめ
rawpy postprocessでは処理できない、現像のプロセスではまぁあると便利な彩度調整、シャープネス調整のコードを、OpenCVの力を借りて書いてみました。結果も思った通り動いてそうです。短いコードで2つの処理が同時に動けるのでまぁよかったかな。
これらの処理は、最後の仕上げ的な処理だと思うので、現像後のそれこそJPEGにやってもよいのですが、階調数の高い現像直後にこだわってコードにしてみました。パラメータを外から指定できるような関数化して、rawpy現像の後処理にしてやろうと思います。
コメント