はじめに
これは「ゼロから作るRAW現像 」という一連の記事の一つです。 これらの記事の内容を前提としていますので、まだお読みでない方はこちらの記事からお読みいただくことをおすすめします。
「ゼロから作るRAW現像 その3 - デモザイク処理基本編」
「ゼロから作るRAW現像 その4 - デモザイク処理応用編」
ラズベリーパイのRAW画像
これまでRAW現像の対象として、ソニーα7iiiで撮影した画像を使ってきました。 これは基本的な処理の説明をするにあたって、ノイズが少ない、歪がない、解像感が高い、など質の良いRAW画像のほうがやりやすかったからです。 しかし、実際のカメラ画像処理では、ノイズや意図しない不鮮明さなど好ましくない性質を修正するというのが大きな目的になります。そういった処理を行うにあたって、ミラーレスカメラの高画質データは逆に扱いにくいものになってしまいます。なにしろ直したいノイズもボケもあまりありませんから。
今回はラズベリーパイでRAW画像をキャプチャーして、これまで行ったRAW現像処理を行い、次回以降のテーマの準備を行います。
ラズベリーパイの準備とRAW画像のキャプチャー
今回紹介する内容について前提とする環境は以下のとおりです。
他のラズベリーパイやv1.3カメラでも実行は可能だとは思われますが、試してはいません。
以下の内容はラズベリーパイカメラv2.1がラズベリーパイに接続され正常に動作していることを前提としていますので、まず公式ドキュメント などに従ってカメラの動作を確認下さい1。
特に、GUIの左上のラズベリーパイアイコンから選択できる「設定」、「RaspberryPiの設定」、から「インターフェイス」タブで「カメラ」を有効にしておく必要がありますので、ご注意ください。
RAW画像のキャプチャー
ラズベリーパイにカメラをセッティングした状態で、以下のコマンドを実行します。
> raspistill -r -o raw_capture.jpg
-r
がRAWキャプチャーを行うことをしめしています。
うまくいけば、カレントディレクトリにraw_capture.jpgという名前のJPEGファイルができているはずです。 このファイルは一見通常のJPEGファイルに見えますが、8MPのJPG画像ファイルにしては15MB前後と巨大なファイルサイズです。これは、RAWデータが埋め込みデータとしてファイル中に組み込まれているからです。
キャプチャーが成功したら、このファイルをUSBメモリーで移す、ネットワークコピー、またはRaspbianの入ったSDCARDをホストPCに読み込ませる、などの方法で、前回までの作業を行ったPCに移動します2。
うまくキャプチャーできたか、JPEGファイルのJPEG画像部分を見てみましょう。通常の画像ビューワーや画像エディタで確認できます。
あまり良い画像ではないですが、キャプチャーできていることは確認できました。
ラズベリーパイのRAW画像の抽出
今回の画像はこれまでと違い、JPEGファイルの中に埋め込まれているので、rawpy以外の方法で取り出す必要があります。 なお、この部分は今回の本筋ではないので、簡単な説明ですまします。詳しい内容は picameraのドキュメント を参照ください。
これ以降は、Jupyterなどpython3のインタラクティブ環境で作業します。
BayerデータはJPEGファイルの最後の部分に位置するので、読み出してから末尾だけ取り出します。
with open("raw_capture.jpg", "rb") as input_file: data = input_file.read() data = data[-10237440:]
(未確認ですがカメラv1.3ではdata = data[-6371328:]
とするとよいようです。)
これでBayer部分が読み込めたはずです。データを見てみましょう。
import numpy as np with open("raw_capture.raw", "rb") as input_file: data = input_file.read() w = 3282 # for v1.3 w = 2592 h = 2480 # for v1.3 h = 1944 img = np.zeros((h, w)) stride = math.ceil(w * 10 / 8 / 32) * 32 for y in range(h): for x in range(w // 4): word = data[y * stride + x * 5: y * stride + x * 5 + 5] img[y, 4 * x ] = (word[0] << 2) | ((word[4] >> 6) & 3) img[y, 4 * x + 1] = (word[1] << 2) | ((word[4] >> 4) & 3) img[y, 4 * x + 2] = (word[2] << 2) | ((word[4] >> 2) & 3) img[y, 4 * x + 3] = (word[3] << 2) | ((word[4] ) & 3)
最後の部分ですが、picameraのドキュメントによると、ラズベリーパイのBayerデータは4画素毎に5バイトのデータにまとまっていて、最初の4バイトがそれぞれの画像の上位8ビット、最後のバイトの2ビットずつが、各画素の下位2ビットになっているとのことです。
さて、ちゃんとデータが読めたのか見てみましょう。
from matplotlib.pyplot import imshow outimg = img.copy() outimg[outimg < 0] = 0 outimg = outimg / outimg.max() imshow(outimg, cmap='gray')
どうやらそれらしい画像が取り出せたようです。
ラズベリーパイカメラのRAW画像の現像
だいぶ手間がかかりましたが、ここから本題のラズベリーパイのRAW画像の現像に入ります。
まず前回までのスクリプトを使いたいところですが、これまで使った関数は殆どがrawpyのインスタンスに依存していました。 今回扱うラズベリーパイのRAW画像はrawpyで読み込んだものではないのでそのままでは使えません。
そこで各関数を、rawデータと指定した任意のパラメータで実行できるように書き換えて、raw_process4.pyとしました。 それぞれの関数の変更点は以下の説明で触れます。
まずはモジュールを読み込みましょう。
import os import sys module_path = os.path.abspath(os.path.join('..')) if module_path not in sys.path: sys.path.append(module_path) import raw_process4 as raw_process
ブラックレベル補正
ブラックレベル補正は、引数としてブラックレベルの数値を与えるようにしました。 引数で与えるブラックレベルは、左上、右上、左下、右下、の順にブラックレベルを格納したリストです。
def black_level_correction(raw_array, black_level): blc_raw = raw_array.copy() blc_raw[0::2, 0::2] -= black_level[0] blc_raw[0::2, 1::2] -= black_level[1] blc_raw[1::2, 0::2] -= black_level[2] blc_raw[1::2, 1::2] -= black_level[3] return blc_raw
ブラックレベルを64と仮定して処理してみましょう。
blacklevel = [64] * 4 blc_raw = raw_process.black_level_correction(img, blacklevel)
ホワイトバランス補正
def white_balance_Bayer(raw_array, wbg, bayer_pattern): img_wb = raw_array.copy() img_wb[0::2, 0::2] *= wbg[bayer_pattern[0, 0]] img_wb[0::2, 1::2] *= wbg[bayer_pattern[0, 1]] img_wb[1::2, 0::2] *= wbg[bayer_pattern[1, 0]] img_wb[1::2, 1::2] *= wbg[bayer_pattern[1, 1]] return img_wb
ホワイトバランス補正には、R・G・Bのゲインを並べたリストのwbgと、ベイヤーのパターンを渡すようになりました。
今回ホワイトバランスははっきりしないので仮に赤のゲインx1.5、青のゲインx2.2を与えます。 今回のラズベリーパイの画像では左上が青色画素ですので、bayer_patternは[[2, 1], [1, 0]]になります。
wbg = np.array([1.5, 1, 2.2, 1]) bayer_pattern = np.array([[2, 1], [1, 0]]) wb_raw = raw_process.white_balance_Bayer(blc_raw, wbg, bayer_pattern)
デモザイク
前回のデモザイクは画像の左上隅が赤色画素であることを仮定していました。 今回のラズベリーパイの画像では左上が青色画素ですのでこのままでは実行できません。 ベイヤーパターンごとの違いは各チャンネルの位相を180度変える事になります。
def advanced_demosaic(dms_input, bayer_pattern): hlpf = np.array([[1, 2, 3, 4, 3, 2, 1]]) / 16 vlpf = np.transpose(hlpf) hhpf = np.array([[-1, 2, -3, 4, -3, 2, -1]]) / 16 vhpf = np.transpose(hhpf) identity_filter = np.zeros((7, 7)) identity_filter[3, 3] = 1 # generate FIR filters to extract necessary components FC1 = np.matmul(vhpf, hhpf) FC2H = np.matmul(vlpf, hhpf) FC2V = np.matmul(vhpf, hlpf) FL = identity_filter - FC1 - FC2V - FC2H # f_C1 at 4 corners c1_mod = signal.convolve2d(dms_input, FC1, boundary='symm', mode='same') # f_C1^1 at wy = 0, wx = +Pi/-Pi c2h_mod = signal.convolve2d(dms_input, FC2H, boundary='symm', mode='same') # f_C1^1 at wy = +Pi/-Pi, wx = 0 c2v_mod = signal.convolve2d(dms_input, FC2V, boundary='symm', mode='same') # f_L at center f_L = signal.convolve2d(dms_input, FL, boundary='symm', mode='same') # Move c1 to the center by shifting by Pi in both x and y direction # f_c1 = c1 * (-1)^x * (-1)^y f_c1 = c1_mod.copy() f_c1[:, 1::2] *= -1 f_c1[1::2, :] *= -1 if bayer_pattern[0, 0] == 1 or bayer_pattern[0, 0] == 3: f_c1 *= -1 # Move c2a to the center by shifting by Pi in x direction, same for c2b in y direction c2h = c2h_mod.copy() c2h[:, 1::2] *= -1 if bayer_pattern[0, 0] == 2 or bayer_pattern[1, 0] == 2: c2h *= -1 c2v = c2v_mod.copy() c2v[1::2, :] *= -1 if bayer_pattern[0, 0] == 2 or bayer_pattern[0, 1] == 2: c2v *= -1 # f_c2 = (c2v_mod * x_mod + c2h_mod * y_mod) / 2 f_c2 = (c2v + c2h) / 2 # generate RGB channel using # [R, G, B] = [[1, 1, 2], [1, -1, 0], [1, 1, - 2]] x [L, C1, C2] height, width = dms_input.shape dms_img = np.zeros((height, width, 3)) dms_img[:, :, 0] = f_L + f_c1 + 2 * f_c2 dms_img[:, :, 1] = f_L - f_c1 dms_img[:, :, 2] = f_L + f_c1 - 2 * f_c2 return dms_img
実行してみます。
dms_img = raw_process.advanced_demosaic(wb_raw, bayer_pattern)
カラーマトリクス補正
カラーマトリクスの値もはっきりしません。今回は色が多少強調されるように、以下のようなマトリクスをかけ合わせてみます。
[[1536, -256, -256], [-256, 1536, -256], [-256, -256, 1536]]
実行します。
img_ccm = raw_process.color_correction_matrix(dms_img, [1536, -256, -256, -256, 1536, -256, -256, -256, 1536])
ガンマ補正
最後にガンマ補正です。ガンマ値を引数でもたせるようにしました。
def gamma_correction(rgb_array, gamma): img_gamma = rgb_array.copy() img_gamma[img_gamma < 0] = 0 img_gamma = img_gamma / img_gamma.max() img_gamma = np.power(img_gamma, 1/gamma) return img_gamm
実行します。
img_gamma = raw_process.gamma_correction(img_ccm, 2.2)
画像を保存して確認してみましょう。
raw_process.write(img_gamma, "raspi_raw_out.png") import imageio from pylab import imshow, show imshow(imageio.imread('raspi_raw_out.png')) show()
お世辞にもきれいな画像とは言えませんがどうにかラズベリーパイでキャプチャーしたRAWデータを現像して画像ファイルにすることができました。
まとめ
Raspberry Pi 3BでキャプチャーしたRAW画像を現像して画像ファイルに変換しました。 今回はどうにか現像するところで終わりでしたが、次回以降画質に手をいれて行きたいと思います。
最後に
今回の内容はRAW画像データ及びraw_process4.py
と共にgithubにアップロードしてあります。
-
公式ドキュメント以外では以下の記事が参考になります。Raspberry PiカメラモジュールV2でデジカメを作ってみた↩
-
前回までの環境をRaspberry Pi上で構築できていれば同じことができるはずですが、私の環境ではうまくいきませんでしたので、別のPCにデータを運んでから以下の作業を行っています。↩