iPhone ProRawのカメラ色空間→sRGB' 変換行列を求める
概要
RAW画像処理に興味のある今日この頃。
下記の記事を参考にRAW現像をしてみたいが、まずはiPhoneで撮影したRAW(ProRaw)を、sRGB'(ガンマ補正前のsRGB)に変換する方法についてまとめておく。 uzusayuu.hatenadiary.jp
2023.2.6追記
ProRawを触ってみたい(プログラムで扱ってみたい)と思っているなら、下記の順にみておくことが最短ルートと思われる。 ネットで情報を漁っても自分はうまくこの情報に辿り着けなかったので、ここに記載しておく。
Capture and process ProRAW images
Apple が提供するProRawの概要を説明したDeveloper向け動画。英語字幕もTranscriptもあるので、倍速でみても概要はつかめると思う。DNG標準仕様書 v1.6
DNGの標準仕様書に各メタデータの詳細が記載されていた。v1.6ではProRaw用に追加されたタグ(Semantic Masks)があり、説明されている。また、「Mapping Camera Color Space to CIE XYZ Space」節に、DNGのどの値をいかに使ってカメラ色空間→sRGBへの変換を実現するか、知りたいことは全て書いてあった。 helpx.adobe.com
【目次】
iPhoneでRAW画像を取得する方法
Appleのサイトを参考にiPhoneの標準カメラでRAW画像を撮影し、DNGファイルを取得することができた。
しかし、データはBayer配列ではなかった。
iPhone標準カメラ経由で取得されるProRawはBayer配列ではなく、すでにデモザイク後のデータになっているようだ1。
ProRawファイルついては、下記の2つのブログが大変参考になった。
iPhoneでBayer配列を取得するなら、3rd Partyのアプリを使うしかないようだ2。
デモザイキングも奥深いようだが3、今回はデモザイキング後のProRawファイルを使うことにする。
カメラ色空間からsRGB'への変換
LibRawではimgdata.color.rgb_cam
にsRGBへと変換する行列が格納されており、同じ値がrawpyではcolor_matrix
に格納されている。
rgb_cam
(ないしはcolor_matrix
) とProRawのメタデータ (Exif) に格納されているColor Matrix 2
(0xc622) との関係性が知りたかったので、LibRaw内部の処理などを探ってみた。
まず、Exif Tag の解説4によれば、Color Matrix 2
はXYZ表色系からカメラ色空間へと変換する行列のようだ。
ExifのCalibration Illuminant 2
がD65となっていることから、D65光源でのXYZ表色系からカメラ色空間への変換行列である。
XYZ表色系→カメラ色空間へと変換する行列cam_xyz
にExifのColor Matrix 2
を入れる(命名規則についてはこちら5)。
sRGB'(ガンマ補正していないsRGB)からXYZ表色系への変換行列を、XYZ表色系→カメラ色空間へと変換するColor Matrix 2
にかけることで、sRGB'→カメラ色空間へと変換する行列cam_rgb
が算出される。
この擬似逆行列がrgb_cam
であり、カメラ色空間→sRGB'へと変換する行列となる。
rawpyで確認してみる。
まず、iPhoneで撮影したProRaw/DNGファイルから、rawpyのcolor_matrix
の値を求める。
raw = rawpy.imread("proraw_sample.DNG") print("Color Matrix:") print(raw.color_matrix)
Color Matrix: [[ 1.3842709 -0.3266568 -0.05761418 0. ] [-0.18470636 1.3894675 -0.20476116 0. ] [ 0.03309519 -0.6075885 1.5744933 0. ]]
これがLibRawのcam_rgb
の値と同じであることは確認した。
次に、Exifデータからcam_rgb
, color_matrix
と同じ値を算出する。
XYZ表色系→カメラ色空間の変換行列をcam_xyz
として定義する。これとsRGB'→XYZ表色系への変換行列xyz_srgb
の内積をとりsRGB'→カメラ色空間の変換行列cam_srgb
を求める。
# XYZ to Camera Native Color Space Matrix (D65) in Exif cam_xyz = np.array([[0.9145434499, -0.3222275078, -0.1262248605], [-0.4288679957, 1.309540987, 0.09467574954], [-0.1062918678, 0.2350454628, 0.4307328463]]) # sRGB' to XYZ xyz_srgb = np.array([[0.4124564, 0.3575761, 0.1804375], [0.2126729, 0.7151522, 0.0721750], [0.0193339, 0.1191920, 0.9503041]]) cam_srgb = np.dot(cam_xyz, xyz_srgb) print("sRGB' to Camera-Native-Color-Space Matrix:") print(cam_srgb)
cam_srgb
の値
sRGB' to Camera-Native-Color-Space Matrix: [[0.56836791 0.1513202 0.04047686] [0.10344498 0.79445276 0.107103 ] [0.03339166 0.41852832 0.93916176]]
cam_srgb
を正規化して擬似逆行列を求めることでrgb_cam
, color_matrix
と同じものが求められた。
# Normalize norm_cam_srgb = np.empty_like(cam_srgb) for r in range(0, 3): sum = np.sum(cam_srgb[r, :]) if 0.00001 < sum: norm_cam_srgb[r, :] = cam_srgb[r, :] / sum else: norm_cam_srgb[r, :] = 0 print("Normalized sRGB to Camera-Color-Space Matrix:") print(norm_cam_srgb) # Camera Native Color Space to sRGB' srgb_cam = np.linalg.inv(norm_cam_srgb) print("Camera-Native-Color-Space to sRGB matrix:") print(srgb_cam)
Camera-Native-Color-Space to sRGB matrix: [[ 1.38427096 -0.32665678 -0.05761418] [-0.18470636 1.38946752 -0.20476116] [ 0.03309519 -0.60758851 1.57449332]]
ふむ、LibRawの値と同じである。
なお、LibRawの内部処理を追っていくと、ExifのAnalog Balance
をColor Matrix 2
に掛けてcam_xyz
を定めていた。
しかしながら、少なくとも今回のProRawの場合にはAnalog Balance
を掛けても掛けなくても正規化後には同じ値になる。
また、Analog Balance
はExif 2.3 standardでは定義されいないが、ここを読む限り、(理想的には)RAW画像として保存される前に適用されたアナログゲインのようだ。
いずれにせよ、今回は無視してよさそうである。
- Appleのサイトを見てもProRawとしか言ってなくて、RAWがとれるとは言ってない。↩
- iPhoneのAPIにはBayer配列でRAW画像を取得したり、ProRawを取得したりするAPIが用意されている。ChatGPTにBayer配列のRAWが撮れるiPhoneアプリを聞いたところ、Lightroom、ProCamera、VSCO、Halideとのこと。↩
- Adobe Lightroomのデモザイク処理ではCNNを使っているようだ。この記事は興味深い。https://business.adobe.com/blog/the-latest/enhance-details↩
- https://exiv2.org/tags.html↩
- 変数をout_inの形で書くことにする。LibRawでもそのように命名している。これは最初混乱したが、行列の計算を考えると、左辺に出力、右辺に入力がくるからだと思われる。行列の計算順になっているのだと思えばLibRawの変数も理解しやすくなった。↩