Open12

Raspberry Pi Camera Module 3を使って両眼立体視のステレオ撮影をしよう

astkastk

Raspberry Piは久しぶりですが、ふとステレオ撮影をしたくなったので試していきます。

Paspberry Pi関連機材

  • Raspberry Pi 5 8GBモデル
    • 動作が不安定だったので専用5V/5A電源と専用アクティブクーラーを使用
    • カメラ端子が2個あるのでステレオ撮影向き
    • ただしディスプレイと共用なのでディスプレイの選択肢は基本HDMIになる
  • Raspberry Pi Camera Module 3 x2
  • その他持っているもの
    • Raspberry Pi Camera Module 2 x2
    • Meta Quest 3

撮影機材

  • 三脚
  • アルカスイスの2台まとめてマウントできる長いマウンタ
  • 適当に購入した1/4インチネジマウンタ
  • 小型雲台x2 (Camera Moduleのセンサー取り付け角度に個体差があるので微調整用)

開発環境

  • macOS (Intel)
  • コード編集はmacOSのVSCodeのSSH機能を使用
  • 画像確認用にmacOSの上にParallels DesktopでUbuntuを立ててVNCクライアントのRemminaを使用
    • なので仮想モニターの中の仮想モニターを見ている感じ
    • macOSデフォルトのVNCクライアントは動作しなかったので
astkastk

カメラを使うだけなら簡単で rpicam-* コマンドで確認や撮影を行える。
動作確認のみであれば rpicam-hello を使うと実に簡単に行える。

--list-cameras で認識中のカメラ一覧を取得できる。

astk@pi5a:~$ rpicam-hello --list-cameras
Available cameras
-----------------
0 : imx708 [4608x2592 10-bit RGGB] (/base/axi/pcie@120000/rp1/i2c@88000/imx708@1a)
    Modes: 'SRGGB10_CSI2P' : 1536x864 [30.00 fps - (65535, 65535)/65535x65535 crop]
                             2304x1296 [30.00 fps - (65535, 65535)/65535x65535 crop]
                             4608x2592 [30.00 fps - (65535, 65535)/65535x65535 crop]

1 : imx708 [4608x2592 10-bit RGGB] (/base/axi/pcie@120000/rp1/i2c@80000/imx708@1a)
    Modes: 'SRGGB10_CSI2P' : 1536x864 [120.13 fps - (768, 432)/3072x1728 crop]
                             2304x1296 [56.03 fps - (0, 0)/4608x2592 crop]
                             4608x2592 [14.35 fps - (0, 0)/4608x2592 crop]

このcamera numberにより各種コマンドで任意のカメラを指定できる。

rpicam-hello --camera 0 -t 300s

とすると300秒間、カメラ0のプレビューウィンドウが表示される。実に簡単。

astkastk

というわけでステレオ撮影を行うコマンドはこれらを組み合わせれば、簡易的にであるができてしまう。

# vid <camera_number> <file_name> <duration>
vid() { rpicam-vid --camera ${1} --width 1920 --height 1080 -t ${3} -p 0,0,480,270 -o ${2}-${1}.mp4; }
# vid2 <file_name> <duration>
vid2() { vid 0 ${1} ${2}& vid 1 ${1} ${2}& }

vid2 test 300s

あとはこれをffmpegで左右に配置した動画を生成する。

ffmpeg -i test-1.mp4 -i test-0.mp4 -filter_complex hstack output.mp4

ここまでで実際にヘッドマウントディスプレイで見れるものが撮影できた。

あくまで簡易的なものなので、フレーム同期もなければ露光やピントも左右で差が出る。

astkastk
  • 動画撮影時はフレームのタイミングはカメラ任せでホスト側はデータを受け取るだけ、という何となくの想像があるので、フレーム同期は難しそう
    • 開始タイミングを合わせるくらい
    • 動きのゆったりした被写体の、数十分の撮影なら問題はないだろうと想像
  • 露光やフォーカスは画像処理を噛ませれば何とかなるかもしれない
    • 最悪、センシング用のカメラをもう1台追加するとか
astkastk

PythonのPicamera2を使う。Pythonはたまにしか使わないので慣れていない。

pipによるパッケージのインストールにvenvが必須になったみたいなのでvenvを使う。
aptでインストールするパッケージにはvenvは必須ではない。

sudo apt install python3-venv
# picamera2やOpenCVはaptで入れるつもりなので、venv環境でそれらを使うには
# --system-site-packagesが必要
python3 -m venv ~/venv/picam  --system-site-packages

mkdir -p ~/src/hello-picam
cd ~/src/hello-picam
. ~/venv/picam/bin/activate

sudo apt install python3-{opencv,picamera2}

よく考えたらまだpipパッケージは使っていないが、多分そのうち使うのでよしとする。

astkastk

ChatGPTに聞きながら、2台のカメラの画角調整のためのプログラムを書いた。これだけで2台のカメラの映像を重ね合わせたリアルタイムプレビューを行える。

import cv2
from picamera2 import Picamera2

c1 = Picamera2(0)
c2 = Picamera2(1)

c1.configure(c1.create_preview_configuration())
c2.configure(c2.create_preview_configuration())

c1.start()
c2.start()

def get_frame(cam: Picamera2) -> any:
    return cv2.cvtColor(cam.capture_array(), cv2.COLOR_BGR2RGB)

while True:
    f1 = get_frame(c1)
    f2 = get_frame(c2)

    blended_frame = cv2.addWeighted(f1, 0.5, f2, 0.5, 0)
    cv2.imshow("Combined Camera Feed", blended_frame)

    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

c1.close()
c2.close()
cv2.destroyAllWindows()
astkastk

Picamera2 インスタンスの create_****_configuration で設定を生成して使う想定の設計らしい。確認のためそれぞれ貼っておく。

>>> pprint.pp(cam.create_still_configuration())
{'use_case': 'still',
 'transform': <libcamera.Transform 'identity'>,
 'colour_space': <libcamera.ColorSpace 'sYCC'>,
 'buffer_count': 1,
 'queue': True,
 'main': {'format': 'BGR888', 'size': (4608, 2592)},
 'lores': None,
 'raw': {'format': 'SRGGB10_CSI2P', 'size': (4608, 2592)},
 'controls': {'NoiseReductionMode': <NoiseReductionModeEnum.HighQuality: 2>,
              'FrameDurationLimits': (100, 1000000000)},
 'sensor': {},
 'display': None,
 'encode': None}
>>> pprint.pp(cam.create_video_configuration())
{'use_case': 'video',
 'transform': <libcamera.Transform 'identity'>,
 'colour_space': <libcamera.ColorSpace 'Rec709'>,
 'buffer_count': 6,
 'queue': True,
 'main': {'format': 'XBGR8888', 'size': (1280, 720)},
 'lores': None,
 'raw': {'format': 'SRGGB10_CSI2P', 'size': (1280, 720)},
 'controls': {'NoiseReductionMode': <NoiseReductionModeEnum.Fast: 1>,
              'FrameDurationLimits': (33333, 33333)},
 'sensor': {},
 'display': 'main',
 'encode': 'main'}
>>> pprint.pp(cam.create_preview_configuration())
{'use_case': 'preview',
 'transform': <libcamera.Transform 'identity'>,
 'colour_space': <libcamera.ColorSpace 'sYCC'>,
 'buffer_count': 4,
 'queue': True,
 'main': {'format': 'XBGR8888', 'size': (640, 480)},
 'lores': None,
 'raw': {'format': 'SRGGB10_CSI2P', 'size': (640, 480)},
 'controls': {'NoiseReductionMode': <NoiseReductionModeEnum.Minimal: 3>,
              'FrameDurationLimits': (100, 83333)},
 'sensor': {},
 'display': 'main',
 'encode': 'main'}
astkastk

ステレオグラムを作るのに使ったコマンドをメモしていきます。

astkastk

動画を左右に並べる。

ffmpeg -i test-1.mp4 -i test-0.mp4 -filter_complex hstack output.mp4

画像を左右に並べる。

magick convert +apend img1.png img2.png output.png
astkastk

アクセスポイントを新しくしたので、更新のためカメラを外して代わりにディスプレイを繋ぎ、wpa_supplicantで設定しようとしたらFAILになり、Pi 5からNetworkManagerに移行していたらしくそちらで繋ごうとnmcliで変更しようとしたら、起動しなくなったのでクリーンインストールしたがそれでも改善しなかったのでカメラを全て外してからHDMIでディスプレイを繋いだらようやく起動して画面が見えた。

次回のために、インストール手順をメモしておく。

astkastk
sudo -i
apt update
apt install python3-picamera2
exit

python -m venv ~/venv/default --system-site-packages
. ~/venv/default/bin/activate
pip install opencv-python numpy