iPhone 12のイメージセンサーを解析|Sony IMX503は未確認

ImageSensor

関連記事

iPhone 12(無印)のイメージセンサーは、RAW画像からどこまで性能を読み取れるのでしょうか。この記事では筆者のiPhone 12を暗室で撮影し、信号・ノイズ・SNRの測定手順と結果を紹介します。Sony製IMX503という型番が挙げられることがありますが、Appleの公式仕様はセンサー型番を明記していません。以下は使用した実機の解析であり、型番そのものを証明するものではありません。

iPhoneのイメージセンサを解析する理由

スマホが世に誕生してからしばらく経ちました。
しかし、カメラ機能は驚くほど進化しています。
一方で、暗い場所の撮影ではまだ一眼レフとの性能差が残ることも事実です。
とはいえ、十分に明るい場面では一眼が不要と思うほど綺麗に撮影できます。
さらに、日常的に使うスマホだからこそ、その中核であるiPhone イメージセンサ 解析には大きな意義があります。


iPhone12に使われているイメージセンサ

筆者の手持ちのスマホはiPhone 12(無印)です。解析対象はこの実機で取得したRAW画像です。当時の非公式情報ではSony製「IMX503」という型番が挙がりましたが、どのカメラに採用されたかを含め、Appleの公開仕様だけでは確認できません。

公開データシートも確認できなかったため、以下の測定値をIMX503の公式仕様として扱うことはできません。一方、機種と撮影条件を明示した実機RAWの分析からは、信号やノイズの傾向を確かめられます。

イメージセンサの測定方法

今回は、イメージセンサの出力応答とノイズ性能を確認します。
まず、測定で準備するものは以下の通りです。

  1. iPhone (Raw取得できるカメラアプリ)
  2. NDフィルタ15段分
  3. フラットな光源
  4. 暗い空間
  5. PC(Python)

測定でのポイント

暗室での測定
ダーク付近のリニアリティを確保するため、光源以外の余分な光が入らないようにするのが重要です。

Raw画像取得
通常のiPhoneカメラアプリではRawを取得できません。
しかし、シンプルRawカメラなどを使うとDNGファイル形式でRaw撮影できます。

フラットな光源
なるべく均一な明るさが得られるものを使いましょう。
積分球を持っている方はそのまま活用できます。
ない場合は、PCモニタを白一色にして最大輝度にするのも一手です。

NDフィルタ
光量を落とすために必須です。
フィルタがなければシャッタースピードで調整しても構いませんが、ノイズ特性が変わる可能性があります。

 測定の手順

  1. 光源とカメラの位置を固定します。
    (ここで、NDフィルタをすべて入れられるだけの作業スペースを確保)
  2. NDフィルタなしでカメラが飽和するくらい、光源を明るくしてください。
    光源で飽和までいかない場合は、シャッタースピードを長くするなど工夫してみましょう。
  3. 光源を撮影します。
    そして、一番明るい状態から光量を1/2ずつ落としていき、繰り返し撮影してください。
    光量の落とし方は、NDフィルタを1枚ずつ足すかシャッタースピードを調整する方法があります。
  4. 飽和から十分暗い状態まで撮影できたら、データ取得完了です。

注意: 光源とカメラの距離は変えないでください。
距離が変わると光量まで変わるため、正確な結果が得られなくなります。


実際の配置イメージ

暗室、あるいは暗い空間を用意し、
カメラ(iPhone)と光源の間にNDフィルタを挟みます。
このとき、フィルタやカメラを動かすと測定条件が変わる可能性があるため、
しっかりと固定する必要があります。

Rawデータの解析

ここからは、撮影したRawデータを解析します。
しかし、iPhoneのDNGや一眼レフのRAWはメーカー独自形式が多いです。
したがって、汎用Rawへ変換してからPythonで扱うと便利でしょう。

【Python】一眼、スマホで取得したRaw画像を汎用Rawに変換する方法
関連記事RAWビューアCZPチャートノイズ イメージセンサを扱う皆さんは、開発用のカメラなどでは汎用Rawを扱うことが多いと思います。そのRaw画像から画像の評価や画像処理を行うと思います。しかし、世の中に市販されているカメラで取得できるR…

以下が実際の解析コードです。
PythonとNumpy、Matplotlib、Mathライブラリを利用します。

import numpy as np
import matplotlib.pyplot as plt
import math

#***********************************
#1.画像設定
#***********************************
width=4032
height=3024
width_roi = 100
height_roi = 100
image_num = 15
bit = 12
dark = 528 #

half_width = int(width/2)
half_height = int(height/2)
half_width_roi = int(width_roi/2)
half_height_roi = int(height_roi/2)

#***********************************
#2.各種配列作成
#***********************************
img_roi=np.empty((height_roi, width_roi),dtype = np.uint16)
img_s = np.empty((4,image_num),dtype = np.float32)
img_n = np.empty((4,image_num),dtype = np.float32)
img_sn = np.empty((4,image_num),dtype = np.float32)
rgb = np.array(range(4)) 
rgb_det = rgb.reshape((2,2))
img_roi_r=np.empty((half_height_roi, half_width_roi),dtype = np.uint16)
img_roi_gr=np.empty((half_height_roi, half_width_roi),dtype = np.uint16)
img_roi_gb=np.empty((half_height_roi, half_width_roi),dtype = np.uint16)
img_roi_b=np.empty((half_height_roi, half_width_roi),dtype = np.uint16)
x_axis = np.empty((1,image_num),dtype = np.uint16)

#***********************************
#3.複数枚のRaw処理
#***********************************
for i in range(image_num):
    x_axis[0][i] = 2**i
    rawpath = "DNG_4032x3024_dark=528_12bit_lin"+str(i)+".raw"
    #***********************************
    #raw画像を開く
    #***********************************
    fd = open(rawpath, 'rb')
    f = np.fromfile(fd, dtype=np.uint16, count=height*width)
    img = f.reshape((height,width))
    fd.close()

    #***********************************
    #ROI切り出し
    #***********************************
    width_roi_start = int((width-width_roi)/2)
    height_roi_start = int((height-height_roi)/2)
    for x in range(width_roi_start,width_roi_start+width_roi):
        for y in range(height_roi_start,height_roi_start+height_roi):
            img_roi[y-height_roi_start][x-width_roi_start] = img[y][x]

    #***********************************
    #ダーク除去
    #***********************************
    for x in range(width_roi):
        for y in range(height_roi):
            if img_roi[y][x] > dark:
                img_roi[y][x] = img_roi[y][x]-dark
            else:
                img_roi[y][x] = 0

    #***********************************
    #SN計算
    #***********************************
    for x in range(width_roi):
        for y in range(height_roi):
            color = rgb_det[y % 2][x % 2]
            if color == 0: #R
                y_r = int(y/2)
                x_r = int(x/2)
                img_roi_r[y_r][x_r] = img_roi[y][x]
            elif color == 1: #Gr
                y_gr = int(y/2)
                x_gr = int((x-1)/2)
                img_roi_gr[y_gr][x_gr] = img_roi[y][x]
            elif color == 2: #Gb
                y_gb = int((y-1)/2)
                x_gb = int(x/2)
                img_roi_gb[y_gb][x_gb] = img_roi[y][x]
            else: #B
                y_gb = int((y-1)/2)
                x_gb = int((x-1)/2)
                img_roi_b[y_gb][x_gb] = img_roi[y][x]

    img_s[0][i] = np.mean(img_roi_r)
    img_s[1][i] = np.mean(img_roi_gr)
    img_s[2][i] = np.mean(img_roi_gb)
    img_s[3][i] = np.mean(img_roi_b)

    img_n[0][i] = np.std(img_roi_r)
    img_n[1][i] = np.std(img_roi_gr)
    img_n[2][i] = np.std(img_roi_gb)
    img_n[3][i] = np.std(img_roi_b)

    img_sn[0][i] = 20*(math.log10(img_s[0][i]/img_n[0][i]))
    img_sn[1][i] = 20*(math.log10(img_s[1][i]/img_n[1][i]))
    img_sn[2][i] = 20*(math.log10(img_s[2][i]/img_n[2][i]))
    img_sn[3][i] = 20*(math.log10(img_s[3][i]/img_n[3][i]))

fig1 = plt.figure()
ax1 = fig1.add_subplot(111)
ax1.plot(x_axis[0,:], img_sn[0,:],label = "R",color = "r")
ax1.plot(x_axis[0,:], img_sn[1,:],label = "Gr",color = "lightgreen")
ax1.plot(x_axis[0,:], img_sn[2,:],label = "Gb",color = "forestgreen")
ax1.plot(x_axis[0,:], img_sn[3,:],label = "B",color = "b")
ax1.set_xscale("log")
ax1.legend()

fig2 = plt.figure()
ax2 = fig2.add_subplot(111)
ax2.plot(x_axis[0,:], img_s[0,:],label = "R",color = "r")
ax2.plot(x_axis[0,:], img_s[1,:],label = "Gr",color = "lightgreen")
ax2.plot(x_axis[0,:], img_s[2,:],label = "Gb",color = "forestgreen")
ax2.plot(x_axis[0,:], img_s[3,:],label = "B",color = "b")
ax2.plot(x_axis[0,:], img_n[0,:],linestyle="--",label = "R_Noise",color = "r")
ax2.plot(x_axis[0,:], img_n[1,:],linestyle="--",label = "Gr_Noise",color = "lightgreen")
ax2.plot(x_axis[0,:], img_n[2,:],linestyle="--",label = "Gb_Noise",color = "forestgreen")
ax2.plot(x_axis[0,:], img_n[3,:],linestyle="--",label = "B_Noise",color = "b")
ax2.set_xscale("log")
ax2.set_yscale("log")
ax2.legend()

plt.show()

コードを実行すると、グラフが表示されます。
一つは信号・ノイズの応答特性、もう一つはSN比の特性です。

解析結果と考察

  • 信号は光量に比例
    当たり前ですが、光量が増えるほどセンサ出力は上昇します。
  • ノイズは信号の平方根にほぼ比例
    これはショットノイズ(フォトンノイズ)の典型的な特性です。
  • SN比は最大約35dB
    さらに、ダイナミックレンジは約70dBとなります。

ダイナミックレンジの定義については、
外部リンク: ImageSensor-info.comの記事
を参照してください。

つまり、このiPhone 12実機から得たRAW画像の信号・ノイズ特性は、これらの結果から読み取れます。型番をIMX503と確定する測定ではありません。
また、この方法はiPhoneに限らず、すべてのカメラで応用可能です。
ぜひ、身近なカメラセンサで挑戦してみてください。


まとめ: iPhone イメージセンサ 解析の魅力

この記事では、iPhone イメージセンサ 解析の具体的な手法をご紹介しました。
実際に暗室での撮影やNDフィルタの活用、PythonでのRaw解析を通して、使用したiPhone 12実機の信号・ノイズ特性を調べられます。
さらに、スマホカメラの性能がどこまで向上しているかを数値的に確認できるのは、とても興味深いです。

日常使いのスマホカメラの実力を深堀りしたい方は、同じ要領で自前のデータ取得と解析をしてみてください。
一眼レフやミラーレスとも比較すると、性能の違いが一層際立って面白いでしょう。


注意: このコードを使用した際に発生した現象・損害には責任を負いかねます。
あくまで参考情報としてご利用ください。



同一手法は他のスマホカメラでも応用できるため、ぜひ試してみてください。

【図解】HDRとは?ダイナミックレンジを解説
関連記事積層CMOSローリングシャッターセンササイズ HDR(ハイダイナミックレンジ)とは、カメラやイメージセンサが捉えることのできる明るさの範囲を指します。この技術により、非常に暗い部分から非常に明るい部分まで、同時に詳細に表現することが…

コメント

タイトルとURLをコピーしました