【Sony】아이폰의 이미지 센서를 분석해 보자

ImageSensor

 여러분은 스마트폰으로 사진을 찍어본 적 있나요? 저는 가끔 찍습니다. 스마트폰이 세상에 등장한 지 꽤 시간이 지났지만, 카메라는 상당히 발전해 왔습니다. 이제는 DSLR 카메라가 필요 없을 정도로 스마트폰으로도 매우 선명한 사진을 찍을 수 있죠. (물론 어두운 곳에서는 아직 차이가 있긴 하지만요…)
이번에는 그런 스마트폰에 사용되는 이미지 센서의 성능을 분석해 보려고 합니다!

아이폰에 사용되는 이미지 센서

 제 손에 있는 스마트폰은 iPhone 12 기본 모델이기 때문에, 분석할 것은 iPhone 12에 사용된 이미지 센서입니다.
먼저, iPhone 12에 사용된 이미지 센서를 조사해 보았습니다. 여러 정보를 찾아본 결과, 이런 사이트에 도달했습니다. 이 사이트의 정보에 따르면, iPhone 12의 광각 카메라에 사용된 것은 Sony의 IMX503 이미지 센서라고 합니다.
IMX503이 어떤 센서인지 궁금해서 더 찾아보았지만, 아무리 찾아봐도 데이터 시트를 찾을 수 없었습니다. 아마도 일반적으로 판매되는 제품이 아니라 Apple 전용으로 개발된 센서인 것 같습니다.

이미지 센서의 측정 방법

 그럼, 본격적으로 이미지 센서의 분석 방법을 알아보겠습니다. 이번에는 센서의 출력 응답과 노이즈 성능을 확인해 보려고 합니다. 준비물은 다음과 같습니다.。

  • iPhone(Raw 촬영이 가능한 카메라)
  • ND 필터 15단
  • 균일한 광원
  • 어두운 공간
  • PC(Python)

 이미지 센서를 분석하기 위해서는 Raw 이미지를 얻는 것이 필요합니다. 아이폰 사용자는 기본 카메라 앱으로는 Raw 이미지를 얻을 수 없습니다. 아이폰에서 Raw 이미지를 얻고 싶다면, 이 앱(심플 Raw 카메라)을 사용해 보세요. Raw 형식은 DNG 파일로 저장됩니다.

광원은 가능한 한 균일한 것을 사용해 주세요. 만약 적분구를 가지고 있는 매니아라면 적분구를 사용해도 괜찮습니다. 플랫한 광원을 가지고 있지 않다면, PC 모니터의 밝기를 최대한 높이고 전체 화면을 흰색으로 설정하는 것도 방법입니다.

ND 필터는 말할 필요도 없이 광량을 줄이기 위해 사용합니다. ND 필터를 준비할 수 없는 경우, 셔터 스피드를 조절해 광량을 변경해 보세요. 다만, 이 경우 노이즈 특성이 바뀔 수 있습니다.

여기서 가장 중요한 점은 반드시 어두운 공간에서 측정을 해야 한다는 것입니다. 어두운 부분의 선형성을 확보하지 못할 수 있기 때문입니다.

 여기서부터는 실제 측정 방법입니다.

  1. 광원과 카메라의 위치를 고정합니다. (ND 필터가 모두 들어갈 수 있는 거리)
  2. ND 필터 없이 카메라가 포화 상태에 이를 정도로 광원을 밝게 설정합니다. 광원이 포화 상태에 이르지 않는 경우에는 셔터 속도를 길게 조정해 보세요.
  3. 광원을 촬영합니다. 가장 밝은 상태에서부터 광량을 1/2씩 줄이며 반복 촬영합니다. 광량을 줄이는 방법은 ND 필터를 끼우거나 셔터 속도를 변경하는 것입니다.
  4. 포화 상태에서 충분히 밝은 상태까지 촬영이 완료되면, 데이터 수집이 끝납니다.

 주의 사항은, 광원과 카메라의 거리를 변경하지 말라는 것입니다. 광원을 멀리하면 당연히 어두워지기 때문입니다. 배치의 이미지는 다음과 같습니다.

Raw 데이터의 분석

 먼저, 아이폰이나 DSLR로 촬영한 Raw 이미지는 각 회사마다 고유한 형식으로 저장되므로, 범용 Raw 형식으로 변환한 후에 분석을 시작해 주세요.

 얻은 데이터를 분석합니다. 이번에는 Python을 사용하여 분석을 진행하려고 합니다.
이번에 사용할 라이브러리는 다음과 같습니다.

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

전체 코드의 흐름입니다. 조금 길지만, 끝까지 함께 해주세요.

  1. Raw 이미지 불러오기 설정
  2. 이미지 처리를 위해 Numpy 배열 설정
  3. 여러 장의 Raw 이미지를 다루기 위한 설정
  4. 다크 값을 감산 (이번에는 528)
  5. 신호, 노이즈, 신호대잡음비(SN비) 계산
  6. 그래프 표시

입니다. 아래가 실제 코드입니다.

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 분배용
rgb_det = rgb.reshape((2,2)) # RGB 분배용
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 이미지 이름
    #***********************************
    # 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
            elif img_roi[y][x] <= dark:
                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)
    # SN비
    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",color = "r")
ax2.plot(x_axis[0,:], img_n[1,:],linestyle="--",label = "Gr",color = "lightgreen")
ax2.plot(x_axis[0,:], img_n[2,:],linestyle="--",label = "Gb",color = "forestgreen")
ax2.plot(x_axis[0,:], img_n[3,:],linestyle="--",label = "B",color = "b")
ax2.set_xscale("log") # 축 로그화
ax2.set_yscale("log") # 축 로그화
ax2.legend()

plt.show()

이 코드를 실행하면 그래프가 표시됩니다.

왼쪽 그래프는 신호와 노이즈의 광량 응답 특성입니다. 오른쪽은 SN비 특성입니다.
이 결과를 바탕으로 IMX503을 대략적으로 분석해 보면,

  • 당연한 결과이지만, 신호는 광량에 비례하고 있습니다.
  • 노이즈는 대략 신호의 제곱근에 비례합니다.
  • SN비는 최대 약 35dB입니다.
  • 다이내믹 레인지는 약 70dB입니다.

라는 사실을 알 수 있었습니다! 다이내믹 레인지의 정의는 아래의 기사에서 확인해 주세요.

이렇게 해서, 가까이에 있는 이미지 센서를 간단히 분석해 보았습니다. 아이폰에 국한되지 않고 모든 카메라에 적용할 수 있는 방법이니, 여러분도 한번 도전해 보세요!

コメント

제목과 URL을 복사했습니다