今回バルブ撮影機能を持っていないスマホやコンデジを使って、疑似的な長秒露光画像を作ってみようと思います。Photoshopは使いません。
何のことはないシャッターを開けっぱなしにするのではなく、複数枚の写真を合成してそれっぽいことをしてみます。
世間的にはマルチショットノイズ除去とかそんな呼び方で、暗部撮影時のノイズ除去を目的に行われているようです。珍しい技術ではないです。これをPython使ってやってみます。
長秒露光
この長秒露光ですが、色々な作例があるように面白い写真が取れます。動いているものを消し去り、静止しているものだけの写真を作ってみたり、暗部ノイズを取り除いてみたり。光の流れを取ったり、星の軌跡を取るのは定番ですかね。
通常バルブ撮影とか呼ばれることが多いですね。シャッターボタンを押している間だけ露光され、シャッターボタンを放すとシャッターが閉じるのが一般的な動作だと思います。一眼レフには必ず備わっている機能ですね。
露光時間が長くなるので、S/Nにも有利になります。特に小さなセンサを使っているスマホでは有利になるはずです。以前考察したようなノイズ除去処理も不要になり、写真のディテールがキレイに残るはずです。
ただ残念ながら、そこいらの安コンデジやスマホではこのバルブ機能、ないのが一般的です。またデバイス的にも開きっぱなしができないものもあるそうです。
疑似長秒露光
ないのなら作ってしみましょう。長い時間シャッター開くことができないのであれば短い時間シャッター 開いた複数の写真をつなげて1つの写真にして、疑似的に長い時間シャッターを開けてた画像を作ってみます。
最近のスマホであれば、シャッタースピードは調節できないにしても、連写機能は備わっていることが多いです。これを利用してやります。
原理的には、シャッター速度1/1000の画像を10枚使えば1/100の画像が作れるはずです。複数枚の画像を単純に加算すればできる気がします。
試してみます。
やり方
Photoshopでもできそうです。本格的にやりたければRawで撮って、ノイズ除去処理をOFFにして現像した上で、Photoshop。というのがどうも定番ルートのようです。が、今回もPython使います。JPEG画像がスタートのカジュアルなものを作ってみます。
撮影画像の用意
固定したスマホないしコンデジを使って、連射する。以上。このカジュアルさが一番です。面倒だとやらなくなるので。
今回は効果を分かりやすくするために、コンデジ使って、ISO感度を上げて、1/2000で室内を12枚連写。という画像をソースにしました。
合成の方針
カメラのセンサが記録するものは、光の量です。光電変換というくらいなので、光の量を電気的な量に変換するのがカメラのセンサです。それをデジタル化して、記録するのがカメラの役割になります。
このデジタル化してJPEGに保存する過程で、各種の画像処理が入ります。色の変換からガンマ補正、ノイズ除去等々色々な画像処理が施され、最終的な画像として記録されます。
複数の画像を合成する方法は各種あります。乗算やスクリーン、覆い焼き等色々な名前がついてますが、それぞれ計算方法が違います。長秒露光をする。というのはセンサに届いた光の量を足すことになります。つまり撮った写真の分だけ「加算」するのが今回の合成の正解だと思います。
ただ、本来の光の量だけを複数の写真で加算したいのですが、色々な凝った画像処理が入ることで、純粋な光量だけを加算することができません。単純な足し算ではいまいちです。これを推定するために、異なる露光時間の複数の写真を使ってそのカメラ関数を求める手段もあるようですが、ちょっと小難しいのでパスします。
逆変換できないので基本的にはあきらめなのですが、ガンマ補正だけは簡単にキャンセルできるので、それだけはやっておこうと思います。
ガンマ補正
通常入力された光量に対して、ディスプレイで観察するにふさわしい特性に変換するために、以下のようなガンマ補正が入っています。厳密にはディスプレイで人間が信号強度に線形性を感じられるように変換する。というのが正しいのかもしれません。
$$out = in^{1/2.2}$$
この2.2という数字が、今回処理するJPEG画像に施されている正しい数字か否かはわかりません。が広く一般的には、1/2.2乗することが知られているので、今回もこれを仮定します。入力のJPEGに対してこの逆ガンマをあてることで、キャンセルして光量に変換します。「ガンマ1.0にする」とかそんな言い方もするようです。
こんな入出力特性の変換をさせます。処理としては簡単で、
lut = np.arange(0,256)
lut = 255*np.power(lut/255,2.2)
で作られるLUTを画素それぞれに対して処理してあげればよいです。255で正規化した、ガンマ2.2乗のLUTを作っているだけです。画像が多いと処理時間もかかるので、事前に計算結果をLUTに入れておいて、時間節約を図ります。
加算合成
あとはひたすら加算するだけです。最後平均化して、8bitにしてJPEG保存しているだけです。
exif情報付加
最後の写真のexif情報を保存する画像にくっつけてあげます。これでexif情報付きになります。凝った事をすれば露光時間だけ変更したいところですが。このためだけにPIL(Pillow)を使ってます。配列の中身がBGR順をRGB順にするのを忘れずに。
コード
import cv2
from PIL import Image
import glob
import numpy as np
if __name__ == '__main__':
flist = glob.glob("source/*.jpg") #ソースの写真群がある場所
lut = np.arange(0,256)
lut = 255*((lut/255)**2.2) #逆ガンマ作成(浮動小数点)
sums = lut[cv2.imread(flist.pop())]
for fname in flist:
sums += lut[cv2.imread(fname)]
out = (sums/(len(flist)+1)) #平均化
out = (255*((out/255)**(1/2.2))).astype(np.uint8)
image = Image.open(flist[-1]) #最終画像を再取得
exif = image.info["exif"] #exif情報取得
Image.fromarray(out[:,:,[2,1,0]]).save("source\marge_exif.jpg", "JPEG", exif=exif, quality=95)
結果
今回12枚の写真を加算合成しました。これをソース画像として12枚。
結果がこんな感じ、キレイにノイズが取れてます。まぁ処理も単純なのでお手軽かな。
画像は400x300pixelのそれを2倍に単純拡大して表示しています。もともとのJPEGにもノイズ除去の処理が入って気持ち悪いですが、それも気持ちよく取れています。
このコードでは最後平均化して8bitのレンジに丸めていますが、これをせずに加算後の階調を残した16bitのTIFFにでも保存すれば、トーン調整にも耐えられるのではなかろうかと思います。加算することで、疑似的に階調数が増えます。
out = (sums*257/(len(flist)+1)) #16bit化+平均(65535/255=257)
out = (65535*((out/65535)**(1/2.2))).astype(np.uint16)
cv2.imwrite("source\marge.tiff",out)
こんな感じ。残念ながらEXIFは付けられませんが。コードの中にトーン調整も含めればいいのかもしれません。HDRっぽいこともできるかもしれません。
まとめ
今回複数のJPEG画像から1枚の疑似長秒露光画像を作ってみました。処理としては簡単で、コードも単純。仰々しい工程も踏まず、ひとまず写真を用意すれば簡単に作れそうです。
ただ、画素にずれがないことを前提としているので、撮影している間中カメラが動いてはダメです。ちょっと動いてもぼけちゃいます。三脚固定が必須になります。コードとしても面白みがないし、何よりこれではカジュアル感がないので、今後は手持ちでできるようにコード側で位置合わせをしてみようと思います。
コメント