iPhone ProRawのカメラ色空間→sRGB' 変換行列を求める

概要

RAW画像処理に興味のある今日この頃。

下記の記事を参考にRAW現像をしてみたいが、まずはiPhoneで撮影したRAW(ProRaw)を、sRGB'(ガンマ補正前のsRGB)に変換する方法についてまとめておく。 uzusayuu.hatenadiary.jp


2023.2.6追記

ProRawを触ってみたい(プログラムで扱ってみたい)と思っているなら、下記の順にみておくことが最短ルートと思われる。 ネットで情報を漁っても自分はうまくこの情報に辿り着けなかったので、ここに記載しておく。

  1. Capture and process ProRAW images
    Apple が提供するProRawの概要を説明したDeveloper向け動画。英語字幕もTranscriptもあるので、倍速でみても概要はつかめると思う。

  2. 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表色系からカメラ色空間へと変換する行列のようだ。 ExifCalibration Illuminant 2がD65となっていることから、D65光源でのXYZ表色系からカメラ色空間への変換行列である。

Exif Toolの値(一部)
Exif Toolの値(一部)

XYZ表色系→カメラ色空間へと変換する行列cam_xyzExifColor 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の内部処理を追っていくと、ExifAnalog BalanceColor Matrix 2に掛けてcam_xyzを定めていた。 しかしながら、少なくとも今回のProRawの場合にはAnalog Balanceを掛けても掛けなくても正規化後には同じ値になる。

また、Analog BalanceExif 2.3 standardでは定義されいないが、ここを読む限り、(理想的には)RAW画像として保存される前に適用されたアナログゲインのようだ。

いずれにせよ、今回は無視してよさそうである。


  1. Appleのサイトを見てもProRawとしか言ってなくて、RAWがとれるとは言ってない。
  2. iPhoneのAPIにはBayer配列でRAW画像を取得したり、ProRawを取得したりするAPIが用意されている。ChatGPTにBayer配列のRAWが撮れるiPhoneアプリを聞いたところ、Lightroom、ProCamera、VSCO、Halideとのこと。
  3. Adobe Lightroomのデモザイク処理ではCNNを使っているようだ。この記事は興味深い。https://business.adobe.com/blog/the-latest/enhance-details
  4. https://exiv2.org/tags.html
  5. 変数をout_inの形で書くことにする。LibRawでもそのように命名している。これは最初混乱したが、行列の計算を考えると、左辺に出力、右辺に入力がくるからだと思われる。行列の計算順になっているのだと思えばLibRawの変数も理解しやすくなった。