airpocketのアイコン画像
airpocket 2022年07月01日作成 (2022年07月01日更新) © MIT
製作品 製作品 閲覧数 956
airpocket 2022年07月01日作成 (2022年07月01日更新) © MIT 製作品 製作品 閲覧数 956

100年カメラは旧世紀の夢を見るか

100年カメラは旧世紀の夢を見るか

はじめに

前回作成した8mmフィルムカメラのデジタル化では70年前のカメラを使用していました。キリの良い100年前という言葉を使いたかったので100年前のカメラもデジタル化してみました。
キャプションを入力できます

使用したカメラ

使用したのは、"Kodak No. 2 Autographic Brownie"と言うカメラです。使用するフィルムは60mm幅のいわゆる中判カメラです。当時、自社のフィルムの拡販を狙ってKodak社が販売していた廉価なカメラで、程度にこだわらなければ今でも数千円程度で手に入ります。
キャプションを入力できます

改造方法

このカメラのイメージプレーンは60×40mmと非常に大きいため、同等のフォーマットのイメージセンサが出に入りません。そこで、レンズから入った光を一度スクリーンに投影し、その像をデジタルイメージセンサで撮影します。この方式はからあげさんの例のカメラを参考にさせていただきました。
キャプションを入力できます

スクリーン投影のテスト

スクリーンにはAliexpress で購入した透過型プロジェクタ用のスクリーンサンプルを使用しています。A4サイズのスクリーンが5種類入っていたので開発が捗りました。

ここに動画が表示されます

晴れた日の屋外であれば十分な明るさの像を得ることができます。

筐体の制作

本来のカメラは、蛇腹レンズ部とフィルムを収納するボディの2分割構造となっています。今回はボディ部を3Dプリンタで作り直し、RaspiCameraを設置する暗箱部を制作しました。カメラを制御するRaspberryPiZero2Wを載せるブラケットも3Dプリンタで制作しています。
キャプションを入力できます

撮影できたもの

このカメラで撮影するとこのような写真が撮れました。周囲が暗くなるのは周辺減光とスクリーン投影時の光の拡散による影響です。
キャプションを入力できます

減光による影響を測定して補正するプログラムも検討してみましたが、あまり面白くなかったのでこのカメラの味として許容することにしました。
周辺減光による光量変化の可視化

補正前写真

補正後写真

※補正後写真で黒ブチが出ているのは光軸ずれも補正したため。

プログラム

今回作成したプログラムです。開発中のため、PCと接続した状態でキーボード入力によりシャッター、シャッタースピード変更を行います。

OSはRaspbery pi os bullseye 32bit liteを使用しています。
事前にpython3やopencv-pythonのインストールが必要です。

import cv2
import numpy as np
import os
import termios, tty, sys
import datetime

# Set the number of pixels based on a 4:3 image size.
WIDTH  = 320 
HEIGHT = 240
magnification = 4 

WIDTH  *= magnification
HEIGHT *= magnification

# The coefficient for adjusting vignetting. The optimum value varies dependig on the brightness of the image and other factors
gainFactor      = 1    #default = 0.8
distanceFactor  = 0.7  #default = 0.71
cosIndex        = 4    #default = 4

# Parameters for correcting the optical axis of the lens and image sensor of a film camera
offsetRight      = 100 * magnification
offsetDown       = 100 * magnification

# The number of exposures for multiple exposures.
overlay = 1 

# Exposure time
exposureTime = 100 

# Correct for vignetting?
gainFlag = False 

# Correct optical axis?
opticalAxisOffset = False

capture = cv2.VideoCapture(0)
capture.set(cv2.CAP_PROP_FRAME_WIDTH, WIDTH)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, HEIGHT)
capture.set(cv2.CAP_PROP_FPS,1.0)
capture.set(cv2.CAP_PROP_BUFFERSIZE, 1)
os.system('v4l2-ctl -d /dev/video0 -c auto_exposure=1')                  # 0:auto 1:manual
os.system('v4l2-ctl -d /dev/video0 -c exposure_time_absolute=' + str(exposureTime))     # 1to10000
os.system('v4l2-ctl -d /dev/video0 -c exposure_metering_mode=0')

# If you need to set more conditions, you may use the following commdands.
#os.system('v4l2-ctl -d /dev/video0 -c exposure_dynamic_framerate=1')
#os.system('v4l2-ctl -d /dev/video0 -c iso_sensitivity_auto=0')          # 0:manual 1:auto
#os.system('v4l2-ctl -d /dev/video0 -c iso_sensitivity=0')               # 0,1,2,3,4
#os.system('v4l2-ctl -d /dev/video0 -c auto_exposure_bias=0')            #-24 to 24
#os.system('v4l2-ctl -d /dev/video0 -c scene_mode=8')
#os.system('v4l2-ctl --set-ctrl auto_exposure=1,exposure_time_absolute=10000,auto_exposure_bias=12,iso_sensitivity=4')

def timeNow():
    now = datetime.datetime.now()
    now = (str(now.year) + str(now.month).zfill(2) + str(now.day).zfill(2) + 
           str(now.hour).zfill(2) + str(now.minute).zfill(2)  + str(now.second).zfill(2))
    print(now)

    return now

def getch():
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)
    tty.setcbreak(fd)
    ch = sys.stdin.read(1)
    termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
    return ch

def takePicture():

    global overlay, gainFlag, opticalAxisOffset, offsetRight, offsetDown, pictureNum

    for i in range(overlay):
        print("number of shot = ",i)
        ret, frame = capture.read()
        ret, frame = capture.read()
        if ret is False:
            print("cap error")

        if i == 0:
            frameO = frame/overlay
        else:
            frameO = frameO + frame/overlay

    if opticalAxisOffset == False:
        offsetRight = 0
        offsetDown  = 0

    if gainFlag == True:
        h, w = frameO.shape[:2]
        size = max([h, w])

        x = np.linspace((-w + offsetRight)/size, (w + offsetRight)/size, w)
        y = np.linspace((-h + offsetDown )/size, (h + offsetDown )/size, h)

        xx, yy = np.meshgrid(x, y)
        r = np.sqrt(xx**2 + yy**2)

        gain = 1 / np.power(np.cos(r * distanceFactor), cosIndex) * gainFactor
        gainmap = np.dstack([gain, gain, gain])

#        frame = frame**2.2
        frameO = np.clip(frameO * gainmap, 0., 255.0)
#        frame = frame**(1/2.2)

    now = timeNow()
    cv2.imwrite(now + ".jpg", frameO)
    print("push [s] to take pic / [q] to quit")

    if capture.isOpened() is False:
        raise IOError

print("ready")
print("push [s] to take pic / [q] to quit")
while True:
    key = getch()

    if key == "q":
        break
    elif key == "s":
        print("shutter!")
        takePicture()
    

capture.release()

stlファイル

筐体の3Dデータです。

githubにて公開しています。

※ちょっと整理してからアップします。

まとめ

フィルムカメラのデジタル化はこれで2台目になりますが、スクリーン投影方式はかなり気楽に制作できるのがメリットです。今回はRaspberry Pi Zero2を使用しましたが、静止画だとRaspberry Pi Zeroでも十分な処理能力があり、かつ省電力なので電池駆動化も容易です。
最近はフィルムカメラのブームと言われつつも、中古市場には安価なフィルムカメラが山ほど滞留しています。絶好のハック対象ではないでしょうか。

1
1
airpocketのアイコン画像
電子工作、プログラミング、AI、DIY、XR、IoT M5Stack / Raspberry Pi / Arduino / spresense / K210 / ESP32 / Maix / maicro:bit / oculus / Jetson Nano / minipupper etc
ログインしてコメントを投稿する