Picamera2を使用したRAW撮影と現像処理の方法

2024-01-11

Picamera2を用いてRAWデータの撮影を行い、DNG形式を経由して、PNG形式への現像します。

撮影環境

撮影時の具体的な設定は以下の通りです。

# picam2.create_still_configuration()['raw']
config = {
    'format': 'SRGGB10_CSI2P',
    'size': (4656, 3496),
    'stride': 5824,
    'framesize': 20360704}

capture_buffersメソッドを用いて撮影します。 (撮影のための関数に関しては過去記事でまとめています2。)

raw撮影

RAW形式での撮影は、names=[]パラメータに'raw'を追加することで簡単に行えます。

import time

from picamera2 import Picamera2


picam2 = Picamera2()

config_capture = picam2.create_still_configuration()
picam2.configure(config_capture)

picam2.start()

time.sleep(2)

(buffer, ), metadata = picam2.capture_buffers(names=['raw'])

bufferはカメラが捉えた光をデジタル情報として記録したもの、metadataは画像データとは別に、カメラセンサーが撮影時に記録した追加情報です。

これらの情報を用いて、現像を行います。

(metadataの中身は長いので、最後に添付しておきます。metadata)

rawからPNG/JPEGへ変換

現像にはPiDNG3rawpy4を使用します。

from pidng.camdefs import Picamera2Camera
from pidng.core import PICAM2DNG
import rawpy
from PIL import Image


# 撮影設定から幅と高さを取得
w, h = config_capture['raw']['size']
stride = config_capture['raw']['stride']

# バッファを画像データに変形
raw = buffer.reshape((h, stride))

# RAWデータからDNGへの変換
camera = Picamera2Camera(config, metadata)
r = PICAM2DNG(camera)
r.options(compress=0)
r.convert(raw, "sample.dng")

# DNGファイルの読み込みとPNGへの変換
dng = rawpy.imread("sample.dng")
data = dng.postprocess(use_camera_wb=True)
img = Image.fromarray(data)
img.save("sample.png")

このプロセスでは、PiDNGを用いてRAW形式のデータをDNG形式に変換し、その後rawpyでDNGファイルを読み込んでPNG画像として保存しています。

RAW画像の読み込みには、ファイルパスまたはファイルオブジェクトが必要なため、中間でDNGファイルを保存しています。




以下はおまけです。

RAWからDNGへの変換

PiDNGにより行われている処理を簡単に確認します。

撮影データのbufferは1次元配列ですが、撮影設定を利用して2次元配列へreshapeすることができます。

print(buffer.shape)
# Prints: (20360704,)

w, h = config_capture['raw']['size']
stride = config_capture['raw']['stride']
raw = buffer.reshape((h, stride))

print(raw.shape)
# Prints: (3496, 5824)

print(raw.min(), raw.max())
# Prints: 0 255

このデータは単なる数値の配列ではありますが、1ピクセルあたり8ビットのグレースケール画像として解釈することも可能です。

seabornのヒートマップを使用してデータの概観を確認します5

import seaborn as sns

sns.heatmap(raw, square=True, cmap='RdBu')
plt.show()

rawのヒートマップ

Fig1の(a)は全体像を示しています。 ぱっと見、風景が見えますが、shapeからも分かるように横に伸び、縦線が確認できます。

(b)は10×10ピクセルに拡大したものです。 4ピクセルごとに1ピクセルの縦線が入っていることが見て取れます。

これは、フォーマット('SRGGB10_CSI2P')により、本来1ピクセルあたり10ビットであることを示しています。つまり、1つの縦線には他の4ピクセル分の情報が含まれています。

以下は実際の処理の一部です。

if s_bpp == 10:
    data = data.astype(np.uint16) << 2
    for byte in range(4):
        data[:, byte::5] |= ((data[:, 4::5] >> ((byte+1) * 2)) & 0b11)
    data = np.delete(data, np.s_[4::5], 1)

この処理からも分かるように、単に縦線を取り除いても画像として成立します。

縦線分のデータを使用した場合と使用しない場合の比較を以下に示します。

10bppと8bppの比較

図2の(a)は1ピクセルあたり10ビットの画像、(b)は8ビットの画像を示しています。

目だけでは差をはっきり認識するのは難しいですが、(c)に示される2つの画像の差分から、2ビット分の違いが確認できます。

PiDNGを使用することで、フォーマットを意識せずにRAWからDNGへの変換が可能です。

from pidng.camdefs import Picamera2Camera
from pidng.core import PICAM2DNG

camera = Picamera2Camera(config, metadata)
r = PICAM2DNG(camera)
r.options(compress=0)
r.convert(raw, "sample.dng")

この手順でRAWデータをDNGフォーマットに変換し、保存します。

このDNGフォーマットは、多くのソフトウェアでRAW画像として認識されます。

DNGからPNGへの変換

rawpyを使用することで非常に簡単に変換できます。 今回は完全にオートで変換を行います。

rawpyライブラリを使用することで、DNGからPNGへの変換を非常に簡単に行うことができます。 今回は、変換は完全にオートで行い、マニュアルでの調整等は行いません。

変換後は、Pillowライブラリを用いて画像ファイルを保存します。

import rawpy
from PIL import Image

dng = rawpy.imread("sample.dng")
data = dng.postprocess(use_camera_wb=True)
img = Image.fromarray(data)
img.save("sample.png")

変換後の写真を以下に示します。

現像後の写真

この例では、空の部分が完全に飛んでしまっています。

そのため、今後はDNGからの変換処理に手を加え、改善していく予定です。

Apple Silicon Mac でのrawpyのインストール

Appleシリコン搭載のMacでは、rawpyを通常の方法でインストールすることができません。

このため、GitHubのissueにあるコメント6を参考にしてインストールを行いました。

以下にインストール手順を示します。

brew install libraw
pip install cython
git clone https://github.com/letmaik/rawpy
cd rawpy
env RAWPY_USE_SYSTEM_LIBRAW=1 python setup.py install

この手順により、Appleシリコン搭載のMacでもrawpyを使用することが可能になります。

(私の環境は "Apple M1 Pro" です。)

metadata

カメラセンサーからの撮影時の詳細な情報や撮影条件を含んだデータです。

この情報には、カラーゲイン、オートフォーカスの状態、デジタルゲイン、色補正行列、露出時間、レンズの位置、光の強さ(Lux)など、画像の品質や特性を理解するのに役立つ多くの要素が含まれています。

print(json.dumps(metadata, indent=4))
# Prints:
# {
#     "ColourGains": [
#         2.175602436065674,
#         1.464006781578064
#     ],
#     "AfState": 2,
#     "DigitalGain": 1.0,
#     "ColourCorrectionMatrix": [
#         1.4823477268218994,
#         -0.35153281688690186,
#         -0.13082416355609894,
#         -0.21499906480312347,
#         1.426438570022583,
#         -0.2114395648241043,
#         -0.040877100080251694,
#         -0.3976461887359619,
#         1.4385325908660889
#     ],
#     "FocusFoM": 25623,
#     "ColourTemperature": 5850,
#     "SensorTimestamp": 42583836000,
#     "AfPauseState": 0,
#     "ScalerCrop": [
#         0,
#         0,
#         4656,
#         3496
#     ],
#     "ExposureTime": 592,
#     "FrameDuration": 111092,
#     "LensPosition": 1.7826648950576782,
#     "AnalogueGain": 1.0,
#     "Lux": 112766.6953125,
#     "SensorBlackLevels": [
#         4096,
#         4096,
#         4096,
#         4096
#     ],
#     "AeLocked": false
# }

参考文献

5

画像の出力の設定等の本質的に関係ない部分は省略しています。