編集履歴一覧に戻る
airpocketのアイコン画像

airpocket が 2024年09月25日14時28分49秒 に編集

コメント無し

本文の変更

# はじめに このプロジェクトは、[ゼンマイ仕掛けのデジタルムービーカメラ(Ver.1 完成)](https://elchika.com/article/a97ac314-0eea-4290-b326-9a53864b3521/)の改良したVer.2を改題したものです。 このプロジェクトではDouble 8規格のゼンマイ駆動式8mmフィルムシネカメラをデジタルカメラに改造します。 目的は、ゼンマイ駆動式の8mmフィルムカメラによる撮影を手軽に体験し、その価値の再認識をうながし失われつつある8mmフィルムカメラを保護するきっかけにすることです。 改造を行うことで、8mmフィルムカメラとしては機能しなくなるため、使用可能なカメラを改造に供することは可能な限りお控え下さい。 記事作成時現在、市場には付属レンズを失ったカメラボディが多数販売されています。シネカメラ用Dマウントレンズは多くが他市場に流れて、シネカメラとして使用できないボディが入手できます。これらのカメラの多くはレンズ交換式のため、撮影可能なカメラからレンズを「借りて」使用する等を想定しています。 # 完成品紹介

+

このカメラでこのような動画が撮影できました。 @[youtube](https://www.youtube.com/watch?v=_FUOV39-nVU) ## Yashica 8

このカメラは、1958年に発売されたYashica 8というカメラをデジタルカメラに改造したものです。 Yashica 8 はDouble8という規格のフィルムを用いたシネカメラです。設計はスイス製のBolex C-8を参考に進められていた様で、駆動部などの基本設計はほぼそのまま流用されています。駆動力はぜんまい式、撮影用とファインダー用の2系統の光学系を持ち、撮影用レンズはターレット式の交換機構が組み込まれています。

+

## 改造の方針

デジタル化改造を行うにあたり、カメラ本体のゼンマイ駆動部は残置しつつフィルム送り部を取り除いてデジタル撮影モジュールを設置するスペースを確保しました。 Ver.1ではカメラ本体への改造は難しいと判断して、フィルムマガジン式カメラの改造を行いましたが、当時のカメラ複数台を分解、構造を確認してカメラ本体への改造も可能となりました。 これにより当時のゼンマイ式カメラの多くが改造可能となり、順次改造を進めています。 また、Ver.1ではシャッタ駆動のとシンクロを行っていましたが、今プロジェクトではシャッター膜を取り外してシャッタ駆動とのシンクロはオミットしました。これには種々の理由があるのですが、この改造方針の変更により撮影機能が向上し、以下の様なカメラ機能のスペックアップを実現しました。

-

また、最も重要な部分である、「オリジナルカメラと同じ操作性」確保しています。デジタル撮影ユニットをセットした後は、当時の全く同じ手順での撮影が可能なため、フィルムカメラ撮影の難しさや手間、そして撮影の手ごたえや快感はそのままにデジタルカメラの気楽さで撮影が可能となりました。

+

## 操作性 最も重要な部分である、「オリジナルカメラと同じ操作性」確保しています。 ==・シャッター動作はぜんまい駆動 ・光学系はオリジナル。フォーカス、露出はマニュアル設定== デジタル撮影ユニットをセットした後は、当時の全く同じ手順での撮影が可能なため、フィルムカメラ撮影の難しさや手間、そして撮影の手ごたえや快感はそのままにデジタルカメラの気楽さで撮影が可能となりました。

+

## デジタル化のメリット このカメラでは8mmフィルムカメラの特長を残しつつ以下の様なデジタルカメラのメリットを得られます。 ==・WiFi接続、もしくはUSB接続を用いたデータ出力が可能 ・ストリーミングカメラ機能で、Webカメラとしても使用可能== ネットワークに接続し、ファイルサーバとして動画ファイルの出力が可能なため、手軽に動画確認ができます。ストリーミング出力機能も実装したため、以下の様にWebカメラがわりに使用することもできます。 @[youtube](https://www.youtube.com/live/1yKNKbhAfV8?t=5016s)

## 主な仕様 |項目|Ver.1|Hi-Vision(Ver.2)|オリジナル| |:-:|:-:|:-:|:-:| |コントローラ|Zero 2W|Zero 2W|-| |イメージセンサ|IMX219|IMX708|-| |解像度|640x480|1920x1080|-| |視野(Double8 比|31%|110%|4.5x3.3mm| |連続撮影時間|6sec|45sec以上|45sec| |フレームレート|最大32|最大64|最大64| ## 外観 ![キャプションを入力できます](https://camo.elchika.com/1a338c46edf1957590f2e575a727bce0938e7d6d/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f61616139396432312d386535662d343734332d383566352d323064623231626462636136/) http://yashicatlr.com/Yashica8.html ## 撮影ユニット ![キャプションを入力できます](https://camo.elchika.com/d9aec176a9995524ef047ac354e0b44007083699/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f38353236396564342d633938612d343331362d386666632d373533363665626265363436/)

-

## 動画 @[youtube](https://www.youtube.com/watch?v=_FUOV39-nVU)

+

# 部品 ## 部品表 3D部品以外の部品表 |No|品名|単価|数量|小計|購入先| |-|-|-|-|-|-| |1|フロアプレート固定ネジM2x4||5||[AliExpress](https://ja.aliexpress.com/item/32810872544.html)| |2|ケーストップ固定ネジM2.5x4||6||[AliExpress](https://ja.aliexpress.com/item/32810872544.html)| |3|基板固定ネジ M2x4||4||[AliExpress](https://ja.aliexpress.com/item/32810872544.html)| |4|RasPi固定ネジ M2x4||3||[AliExpress](https://ja.aliexpress.com/item/32810872544.html)| |5|RasPiカメラ基板固定ネジ M2x4||4||[AliExpress](https://ja.aliexpress.com/item/32810872544.html)| |6|リフレクタ固定用ねじM1.6x6||1||[AliExpress](https://ja.aliexpress.com/item/32810872544.html)| |7|006P バッテリー|2358|1||[AMAZON](https://www.amazon.co.jp/Type-C%E3%80%919V-800mAh-006P%E5%9E%8B-%E3%83%AA%E3%83%81%E3%82%A6%E3%83%A0%E3%82%A4%E3%82%AA%E3%83%B3%E3%80%90%E3%83%AD%E3%83%AF%E3%82%B8%E3%83%A3%E3%83%91%E3%83%B3%E3%80%91%E9%9B%BB%E6%B1%A0%E3%82%B1%E3%83%BC%E3%82%B9-USB%E3%82%B1%E3%83%BC%E3%83%96%E3%83%AB/dp/B0C9LKYDHS/)| |8|オリジナル基板||1||[JLCPCB](https://jlcpcb.com/)| |9|9V電池端子||1||[AliExpress](https://ja.aliexpress.com/item/1005001860538968.html)| |10|dipswitch 6way||1||[AliExpress](https://ja.aliexpress.com/item/1005001777097565.html)| |11|tactswitch 6x6x8mm||1||[AliExpress](https://ja.aliexpress.com/item/1005004192734344.html)| |12|超小型スライドスイッチ 2回路2接点 IS-2235||1||[AliExpress](https://ja.aliexpress.com/item/1005004007743651.html)| |13|φ3 砲弾LED RED||1||[AliExpress](https://ja.aliexpress.com/item/1005003199457519.html)| |14|φ3 砲弾LED GREEN||1||[AliExpress](https://ja.aliexpress.com/item/1005003199457519.html)| |15|ステップダウンモジュール MP2307||1||[AliExpress](https://ja.aliexpress.com/item/32833506384.html)| |16|ロープロファイルピンヘッダー (低オス) 2×40 (80P) 7.7mm|40|1||[秋月電子](https://akizukidenshi.com/catalog/g/g102901/)| |17|ダブルピンソケット (低メス) 2×13 (26P) |80|2||[秋月電子](https://akizukidenshi.com/catalog/g/g103139/)| |18|LM397 SOT23-5||1||[AliExpress](https://ja.aliexpress.com/item/1005005372187306.html)| |19|SMD 0805抵抗 R1 36K||1||[AliExpress](https://ja.aliexpress.com/item/1005005474148914.html)| |20|SMD 0805抵抗 R2 1K||1||[AliExpress](https://ja.aliexpress.com/item/1005005474148914.html)| |21|SMD 0805抵抗 R3 12K||1||[AliExpress](https://ja.aliexpress.com/item/1005005474148914.html)| |22|SMD 0805抵抗 R4 220||1||[AliExpress](https://ja.aliexpress.com/item/1005005474148914.html)| |23|SMD 0805抵抗 R5 2K||1||[AliExpress](https://ja.aliexpress.com/item/1005005474148914.html)| |24|SMD 0805抵抗 R6 2K2||1||[AliExpress](https://ja.aliexpress.com/item/1005005474148914.html)| |25|SMD 0805抵抗 R7 220||1||[AliExpress](https://ja.aliexpress.com/item/1005005474148914.html)| |26|SMD 0805抵抗 R8 2K4||1||[AliExpress](https://ja.aliexpress.com/item/1005005474148914.html)| |27|SMD 0805抵抗 R9 1K||1||[AliExpress](https://ja.aliexpress.com/item/1005005474148914.html)| |29|Raspberry Pi Zero2 W|2915|1||[SWITCH SCIENCE](https://www.switch-science.com/products/7600)| |30|arducam IMX708 fix focus|4147|1||[SWITCH SCIENCE](https://www.switch-science.com/collections/arducam/products/9036)| |31|arducam camera extension cable 200mm||1||[UCTRONICS](https://www.uctronics.com/200mm-sensor-extension-cable-raspberry-pi-v2-v3.html)| |32|0.5mm pitch 22pin FPC cable Reverse 100mm||1||[AliExpress](https://ja.aliexpress.com/item/1005006103624605.html)|

-

※表面実装抵抗はinch表示

+

※表面実装抵抗はinchスケール

## 3Dデータ カメラ本体の改造及びデジタルカメラモジュールの筐体に必要な3Dプリント部品は[GitHub](https://github.com/airpocket-soundman/yashica8)で公開しています。 # 回路および基板 ## 回路 回路図は以下の通りです。 ![キャプションを入力できます](https://camo.elchika.com/8cb3b5db7841d6dba18df2115823e641f638355c/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f31356636633031622d626530392d346238622d626330332d393563326464666632653265/) ## PCB

-

基板を起こしています。[GitHub](https://github.com/airpocket-soundman/yashica8)にJLCPCB用の発注データを含むKiCAD8のデータを公開しています

+

開発初期はユニバーサル基板を用いていましたが、今はPCBを起こしています。 ==[GitHub](https://github.com/airpocket-soundman/yashica8)にJLCPCB用の発注データを含むKiCAD8のデータを公開しています==

![キャプションを入力できます](https://camo.elchika.com/a9b81d1b59f88849b47a017259c49c76fb70a55b/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666662353561372d373433312d346234382d383832642d3565323665663534343465652f63663530663232652d623337342d346237612d613938612d316432633635616137663932/)

+

# コード

+

## カメラ撮影用 ``` # GPIOをトリガーにビデオモードで撮影する。 # シャッター除去モデル使用、シャッターシンクロはしない。 # IMX708をビデオモードで撮影 # IMX708 ビデオモード仕様 1080P 50FPS, 720p 100FPS, 480p 120FPS import cv2 import os import time import RPi.GPIO as GPIO import sys from picamera2 import Picamera2 from picamera2.encoders import H264Encoder from picamera2.outputs import FfmpegOutput import libcamera import numpy as np import re import shutil number_cut = 0 # 動画ファイルの連番を格納 number_still = 0 # 静止画ファイルの連番を格納 # Bolex/yashica pin_shutter = 25 # shutter timing pickup # GPIO設定 GPIO.setmode(GPIO.BCM) GPIO.setup(pin_shutter, GPIO.IN, pull_up_down = GPIO.PUD_UP) is_recording = False #録画中フラグ max_record_sec = 45 # 最大撮影時間 record_fps = 16 # MP4へ変換する際のFPS設定値 temp_folder_path = "/tmp/" share_folder_path = "/home/cinecamera/share/" device_name = "yashica" codec = cv2.VideoWriter_fourcc(*'avc1') last_shutter_time = 0 shutter_release_threshold_time = 400 #msec シャッター開放判定の閾値 recording_start_time = 0 # shareフォルダの動画と静止画のファイル番号を読み取る def find_max_number_in_share_folder(): global number_still, number_cut # ファイル名の数字部分を抽出する正規表現パターン pattern_mp4 = re.compile(r'{}(\d{{3}})\.mp4$'.format(device_name)) pattern_jpg = re.compile(r'{}(\d{{3}})\.jpg$'.format(device_name)) number_cut = -1 # 存在する数字の中で最も大きいものを保持する変数 number_still = -1 # 指定されたフォルダ内のすべてのファイルに対してループ for filename in os.listdir(share_folder_path): match_mp4 = pattern_mp4.match(filename) match_jpg = pattern_jpg.match(filename) if match_mp4: # ファイル名から抽出した数字を整数として取得 number = int(match_mp4.group(1)) # 現在の最大値と比較して、必要に応じて更新 if number > number_cut: number_cut = number if match_jpg: number = int(match_jpg.group(1)) if number > number_still: number_still = number number_still += 1 number_cut += 1 def init_camera(): global camera, encoder camera = Picamera2() encoder = H264Encoder(bitrate=10000000) """ video_config = camera.create_video_configuration( main={"size": (1920, 1080), "format": "YUV420"}, controls={ #"ExposureTime": 20000, #"AnalogueGain": 1.0, #"AwbMode": "auto", "Brightness": 50, "Contrast": 15, "Saturation": 20, "Sharpness": 10, #"NoiseReductionMode": "high", "FrameRate": 16 } ) """ video_config = camera.create_video_configuration( main={"size": (1920, 1080)}, #controls={ # "ExposureTime" :50000, # "AnalogueGain" :1.0, # "AwbMode" :False, # "Brightness" :0.0, # "Contrast" :1.0, # "Saturation" :1.0, # "Sharpness" :1.0, # "NoiseReductionMode":1, # #"ColourGains" :(0.0, 0.0, 0), # "FrameRate" :16 #}, transform = libcamera.Transform(hflip=1, vflip=1), buffer_count = 8 ) camera.configure(video_config) camera.start() def shutter_detected(channel): global last_shutter_time, recording_start_time, is_recording current_time = int(time.perf_counter() * 1000) #print("detect") if not is_recording: print("start recording") movie_file_path = temp_folder_path + device_name + "{:03}".format(number_cut) + ".mp4" output = FfmpegOutput(movie_file_path) camera.start_recording(encoder, output) is_recording = True recording_start_time = current_time last_shutter_time = current_time def check_recording_status(): global is_recording, number_cut if is_recording: current_time = int(time.perf_counter() * 1000) print(str(current_time-last_shutter_time)) if current_time - last_shutter_time > shutter_release_threshold_time or current_time - recording_start_time > max_record_sec * 1000: camera.stop_recording() #camera.stop() print("stop recording") print("start:" + str(recording_start_time) + " diff: " + str(current_time-last_shutter_time) + " video: " + str(current_time - recording_start_time)) temp_file_path = temp_folder_path + device_name + "{:03}".format(number_cut) + ".mp4" save_file_path = share_folder_path + device_name + "{:03}".format(number_cut) + ".mp4" #cut_first_8_frames(temp_file_path, save_file_path) #os.remove(temp_file_path) shutil.move(temp_file_path, save_file_path) is_recording = False number_cut += 1 #camera.start() def cut_first_8_frames(input_path, output_path): # ビデオキャプチャを開始 cap = cv2.VideoCapture(input_path) # 出力ビデオの設定 fourcc = cv2.VideoWriter_fourcc(*'mp4v') fps = cap.get(cv2.CAP_PROP_FPS) width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) # 最初の8フレームをスキップ for _ in range(8): cap.read() # 残りのフレームを読み込み、出力ビデオに書き込む while True: ret, frame = cap.read() if not ret: break out.write(frame) # リソースの解放 cap.release() out.release() print("finish cut") if __name__ == "__main__": # shareフォルダ内の動画、静止画ファイルの最大番号を取得 find_max_number_in_share_folder() init_camera() GPIO.setmode(GPIO.BCM) GPIO.setup(25, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.add_event_detect(pin_shutter, GPIO.FALLING, callback=shutter_detected, bouncetime=200) try: while True: check_recording_status() time.sleep(0.1) finally: camera.stop() GPIO.cleanup() ``` ## Flaskによるストリーミング用コード ```python from flask import render_template, Flask, Response import cv2 from picamera2 import Picamera2 import time import numpy as np from typing import List app = Flask(__name__, template_folder='templates') #templates_folderはデフォルトで'templates'なので本来定義は不要 CAP_WIDTH = 640 #出力動画の幅 CAP_HEIGHT = 480 #出力動画の高さ LAW_WIDTH = 1640 #カメラ内のraw画像の幅 LAW_HEIGHT = 1232 #カメラ内のraw画像の高さ image_sensor = "IMX708" #IMX219/IMX708 folder_path ="/tmp/img" movie_length = 100 #撮影するフレーム数 time_list = [] exposure_time = 5000 #イメージセンサの露出時間 analog_gain = 20.0 #イメージセンサのgain def gen_frames(): print("gen_frames") count = 0 # init camera cap = Picamera2() config = cap.create_still_configuration(main={"size":(CAP_WIDTH, CAP_HEIGHT)},raw={"size":(LAW_WIDTH,LAW_HEIGHT)}) cap.configure(config) cap.set_controls({"ExposureTime":exposure_time, "AnalogueGain": analog_gain}) cap.start() while True: print("count = ",count) start_time_frame = time.perf_counter() frame = cap.capture_array() frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) frame = cv2.flip(frame,-1) #フレームデータをjpgに圧縮 ret, buffer = cv2.imencode('.jpg',frame) # bytesデータ化 frame = buffer.tobytes() yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') elapsed_time_frame = time.perf_counter() - start_time_frame print("frame_number = " + str(count) + " / time = " + str(elapsed_time_frame)) count +=1 @app.route('/video_feed') def video_feed(): #imgタグに埋め込まれるResponseオブジェクトを返す return Response(gen_frames(), mimetype='multipart/x-mixed-replace; boundary=frame') @app.route('/') @app.route('/index') def index(): user = {'username' : 'Raspberry Pi zero2 W', 'image sensor' : image_sensor, 'lens' : ""} return render_template('index.html', title='home', user=user) ``` ## flask用index.html ```html <html> <head> <title>{{ title }} CAMERA STREAMING TEST</title> </head> <body> <h3>from {{ user.username }}.</h3> <h3>CAMERA STREAMING TEST.</h3> <img src="{{ url_for('video_feed') }}"> <h3>lens:{{ user.lens }}</h3> </body> </html> ``` # Raspberry Pi Zero 2Wのセットアップ |項目|要件| |-|-| |SBC|Raspberry Pi Zero2 W| |microSD|32GB以上| |OS|Bullseye 64bit lite| ## OS version ==OSはRaspberry Pi OS Bullseye 64bit liteのみ対応しています。 現在GPIOライブラリの関係で、Bookwormには対応していません。== ``` $ lsb_release -a No LSB modules are available. Distributor ID: Debian Description: Debian GNU/Linux 11 (bullseye) Release: 11 Codename: bullseye $ getconf LONG_BIT 64 ``` ## USB SSHの有効化 起動用SDカードを書き込み後、config.txtに以下の行を追記 ``` dtoverlay=dwc2 ``` commandline.txtのrootwait とquietの間に[]の中を追記 ``` ootwait [modules-load=dwc2,g_ether] quiet ``` ==USB OTGするときは左側のmicro USBコネクタ windowsにもUSB Ethernet/RNDIS Gadgetのドライバインストールが必要です== ## wifi setting ``` sudo raspi-config ``` 1 System Options -> S1 Wireless LAN SSIDとPassphraseを入力してリブート ## 必要なライブラリ等のインストール ``` sudo apt update && sudo apt -y upgrade sudo apt -y install python3-dev python3-pip # sudo pip install picamera2 sudo pip install opencv-python sudo apt -y install libgl1-mesa-dev ``` ## IMX708を使用するための設定 ``` sudo nano /boot/config.txt ``` 最後に以下の一行を追記 ``` dtoverlay=imx708 ``` ## swap無効化とtempのRAMディスク化 ``` sudo systemctl stop dphys-swapfile sudo systemctl disable dphys-swapfile ``` ファイルシステムの設定を書き換えて/tmpをRAM上にマウントする ``` sudo nano /etc/fstab ``` 以下の行を追加 ``` tmpfs /tmp tmpfs defaults,size=64m,noatime,mode=1777 0 0 ``` microSD上の/tmpを削除する ``` sudo rm -rf /tmp ``` ここで一度リブート RAMの状況を確認するとこんな感じ ``` $ free -m total used free shared buff/cache available Mem: 419 73 193 0 151 292 Swap: 0 0 0 $ df -h Filesystem Size Used Avail Use% Mounted on /dev/root 29G 1.9G 26G 7% / devtmpfs 80M 0 80M 0% /dev tmpfs 210M 0 210M 0% /dev/shm tmpfs 84M 928K 83M 2% /run tmpfs 5.0M 4.0K 5.0M 1% /run/lock tmpfs 128M 0 128M 0% /tmp /dev/mmcblk0p1 255M 31M 225M 13% /boot tmpfs 42M 0 42M 0% /run/user/1000 ``` ## Flaskのインスト―ル ==ストリーミングにはFlask使っています== ``` sudo pip install flask ``` flaskでapp.pyを実行するには ``` flask run --host=0.0.0.0 ``` ## sambaサーバー設定 インストールと共有フォルダの作成など [user]にはraspberry pi zer2w ログイン時のuser名を入れる ``` sudo apt -y install samba mkdir /home/[user]/share sudo chmod 777 /home/[user]/share sudo nano /etc/samba/smb.conf ``` smb.conf の最後に以下を追記 ``` [share] comment = user file space path = /home/[user]/share force user = [user] guest ok = yes create mask = 0777 directory mask = 0777 read only = no ``` ``` sudo systemctl restart smbd ``` ## 開発環境について ==ソフト開発等する場合は、Sambaで共有したフォルダにGitHubのリポジトリをクローンして開発用PC上のVSCodeからSSH接続するのが楽。== vimer は直接開発してください。 開発用PCからVSCodeでSSH接続して、接続先となったzero2のフォルダを開こうとすると叱られが発生するので以下の様に回避。 共有フォルダのGitを利用する際。。。 git: UNC host '****' access is not allowedの回避方法 GitがUNCホスト表記を禁止しているため。 「設定」から、Allowed UNC Hostsを検索し、「項目の追加」から使用したいホスト名を登録、VSCodeを再起動すると使用できるようになる。 ## 自動起動 ==zero2起動時に自動的にカメラ制御ソフトを実行するにはcrontabが良い== ``` crontab -e ``` 以下の一行を追記 ``` @reboot python [/path/of/file.py] ``` [/path/of/file.py]にはカメラ制御コードをの絶対パスを記入 自動起動したpythonコードを停止するには ``` ps aux | grep pytnon ``` でプロセスナンバー確認して ``` kill [number] ``` ## ffmpegのインストール ==mpegのメタデータ埋め込みにffmpegを使用しています== python用のラッパーも入れておく ``` sudo apt -y install ffmpeg sudo pip install ffmpeg-python ``` ## vimもあった方が玄人っぽいので。 ``` sudo apt install vim ``` ==vimでも行番号表示、シンタックスハイライト、タブを4文字スペースに変更すると書ける== ``` vim ~/.vimrc ``` ``` set number syntax enable set expandtab set tabstop=4 set shiftwidth=4 ```