今回はいよいよ購入した Raspberry Pi カメラモジュール V3(NoIR/広角)をハードウェア的に接続し、テストの上、Pysthon スクリプトからの撮影(静止画・動画)を行います。
今回もサンプルコードを参考にしたのでプログラミング自体はそれほど悩みませんでしたが、準備した venv 仮想環境をカスタマイズして作り直してが必要だったので、その辺りの話も盛り込みます。
- 目次 -
カメラ接続
まずは購入したカメラモジュールに付けられているカメラケーブルを外し、購入した Raspberry Pi 用のカメラケーブルと付け替えます。テープ状のケーブルの裏表に注意して接続しましょう。
次に、カメラケーブルを電源を抜いてある Raspberry Pi 5 本体のカメラスロットに接続します。二つあるどちらに接続してもかまいませんが、スロットのストッパーを引き上げるとき、力を加えすぎるとストッパーが外れてしまいました(笑)。ストッパーをはめ直せば大丈夫でしたが、力加減には注意しましょう。また、このときもケーブルの裏表に注意しましょう。
カメラテスト(bash コマンドから)
Raspberry Pi 5 を起動したら、LXTerminal から
rpicam-hello
を実行して見ます。
カメラプレビュー画面が立ち上がって、カメラからの映像が表示されていればOKです。
次に静止画を撮影してみます。
rpicam-still -o picture.jpg
ホームディレクトリ直下に「picture.jpg」ファイルが生成されます。ダブルクリックすると画像ビューアーが立ち上がり、画像を確認できます。
次に動画を撮影してみます。
rpicam-vid -t 10000 -o video.mp4
ホームディレクトリ直下に「video.mp4」ファイルが生成されます。ダブルクリックすると VLC プレーヤーが立ち上がり、10秒間の動画が確認できます。
簡単ですね(笑)。
この調子で Python スクリプトから撮影を……とおもったら、ここで小さな落とし穴がありました。。
Python venv 仮想環境作り直し
ライブラリが足りないなら、追加すれば良いじゃ無い
さっそく、書籍『ラズパイ5完全ガイド』付録のサンプルコードを、CAM ディレクトリに配置し、前回作成した venv 仮想環境の python で実行して見ました。
/home/pi/V_CAM/bin/python ./takepic.py
Traceback (most recent call last):
File "/home/pi/CAM/./takepic.py", line 1, in <module>
import picamera2
ModuleNotFoundError: No module named 'picamera2'
ふむふむ。サンプルコードの冒頭で
import picamera2
import libcamera
import time
3つのライブラリを読み込むようになっていますが、このうち「picamera2」と「libcamera」は初見ですので、venv 環境に入っていないと考えられます。そこで、まず、
source V_CAM/bin/activate
pip3 install picamera2
と picamera2 ライブラリをインストールしてみました。が、途中でエラーメッセージが表示され、インストールに失敗しているようです。
pip list
で見ても、一覧に picamera2 は表示されないので使用できない、ということでしょう。
念のため、venv 環境の python ではない、素の python でも試してみます。
deactivate
python ./takepic.py
こちらは正常に撮影が行われ、CAM ディレクトリ内に「picture.jpg」が生成されました。
うーん。どうやら pip で提供される picamera2 ライブラリ(と念のため試したところ libcamera ライブラリも)と Raspberry Pi OS で提供されているライブラリとで何らかの違いがありそうです。
OLED ディスプレイを使用するには、venv 環境でインストールしたライブラリが必要、しかし肝心のカメラは、venv 環境では動かない。。
venv 仮想環境を「–system-site-packages」オプションを付けて作り直し
色々調べて、venv の公式ページをよく読んだ結果、venv には「–system-site-packages」というオプションがあり、これを使うと「Give the virtual environment access to the system site-packages dir.」(仮想環境にシステムのsite-packagesディレクトリへのアクセス権を与える。)機能があるらしいです。素晴らしい! これで venv 仮想環境から、システム側の picamera2 ライブラリが利用できそうです。
このオプションは、仮想環境作成時に指定するものなので、いったん仮想環境を作り直します。
rm -rf V_CAM
python -m venv --system-site-packages V_CAM
source V_CAM/bin/activate
pip3 install luma.core
pip3 install luma.oled
ちなみに、後から追加でインストールした「gpiozero」ライブラリなどは、システム側には元から入っていましたので、追加の必要はありませんでした。
「deactivate」で仮想環境を抜け、恐る恐る venv の python で「takepic.py」を動かしてみると……撮影されました!
また、念のため前回までに作成していた cam_02.py が正常に動作するかも確認します。こちらも問題ありませんでした。
Python スクリプトでカメラ撮影(1ファイルずつ)
と、環境が整ったところで、例によって「cam_02.py」を「cam_03.py」にコピーして、今回の開発を行います。
成果物はこちら。
#!/home/pi/V_CAM/bin/python3
"""
###########################################################################
カメラで静止画、動画を撮影する。
#Filename :cam.py
#Update :2024/07/21
2024/07/21 Ver. α 0.03 カメラ撮影 単ファイル
2024/07/15 Ver. α 0.02 OLED に文字、プログレスバー表示
2024/07/06 Ver. α 0.01 SW ブッシュ で LED 点灯
############################################################################
"""
# タイマー用時刻取得拡張
import time
# ボタンスイッチ用 gpio 拡張
from gpiozero import Button
# ボタンスイッチ初期設定
SW_1 = Button(5,pull_up=False)
SW_2 = Button(6,pull_up=False)
# LED 用拡張
from gpiozero import LED
# LED 初期設定
LEDPIN1 = LED(17)
LEDPIN2 = LED(27)
# OLED ディスプレイ用拡張
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306
# OLED ディスプレイ用初期設定
# Raspberry Pi 4以降の場合、port=1を指定
serial = i2c(port=1, address=0x3C)
# その他の初期化パラメータを設定(必要に応じて)
device = ssd1306(serial, width=128, height=32) # 必要に応じ64
from PIL import ImageFont
def oled_text(text,size):
# フォントを指定
font = ImageFont.truetype("fonts-japanese-gothic.ttf", size)
with canvas(device) as draw:
draw.text((0, 0), text, font=font, fill="white")
def oled_square(x1,y1,x2,y2,color):
if color == 0:
#矩形の描画:
with canvas(device) as draw:
draw.rectangle((x1,y1,x2,y2), outline="white",fill="black")
if color == 1:
#矩形の描画:
with canvas(device) as draw:
draw.rectangle((x1,y1,x2,y2), outline="white",fill="white")
# カメラ用拡張
import picamera2
import libcamera
# カメラ用初期設定
cam_no = 0
width = 1920
height = 1080
flip_hor = False
flip_ver = False
wait_time = 0
rec_time = 10
# カメラ初期化処理
picam2 = picamera2.Picamera2( camera_num = cam_no )
preview_config = picam2.create_preview_configuration(main={"size":( width, height )})
preview_config["transform"] = libcamera.Transform( vflip = flip_ver, hflip = flip_hor )
video_config = picam2.create_video_configuration(main={"size":( width, height )})
video_config["transform"] = libcamera.Transform( vflip = flip_ver, hflip = flip_hor )
#print message at the begining ---custom function
def print_message():
print ('|********************************|')
print ('| カメラ撮影(Still & Movie) |')
print ('|********************************|\n')
print ('Program is running...')
print ('Press Green Switch = Still')
print ('Press Red Switch = Movie')
print ('Please press Ctrl+C to end the program...')
# Switch 状態把握
#read SW_PI_1's level
def ReadSW_1():
if SW_1.is_pressed:
sw_ = 'on'
else:
sw_ = 'off'
return sw_
#read SW_PI_2's level
def ReadSW_2():
if SW_2.is_pressed:
sw_ = 'on'
else:
sw_ = 'off'
return sw_
#main function
def main():
loop_n = 0
# Switch状態初期化
sw_ = 'off'
# 初期メッセージ表示
print_message()
# ループ処理開始
while True:
# スイッチ1状態取得
sw_ = ReadSW_1()
# スイッチ1プッシュ時
if sw_ =='on':
# LED1点灯
LEDPIN1.on()
# OLED ディスプレイ文字出力
text = "静止画撮影!"
oled_text(text,22)
# カメラ起動、撮影(静止画)
picam2.start()
picam2.capture_file( 'picture.jpg' )
# カメラ停止
picam2.close()
# LED1消灯
LEDPIN1.off()
# 1秒待って OLED ディスプレイクリア
time.sleep(1)
device.clear()
break
# スイッチ2状態取得
sw_ = ReadSW_2()
# スイッチ2プッシュ時
if sw_ =='on':
# LED2点灯
LEDPIN2.on()
# カメラ起動、撮影(動画)
picam2.start()
encoder = picamera2.encoders.H264Encoder( bitrate=10000000 )
output = picamera2.outputs.FfmpegOutput( 'video.mpeg' )
picam2.start_recording( encoder, output )
# プログレスバー描画
for x in range(0,127,1):
oled_square(0, 10, x, 21,1)
time.sleep(0.1)
# OLED ディスプレイクリア
device.clear()
# LED2消灯
LEDPIN2.off()
break
pass
# ループ処理ここまで
#
# if run this script directly ,do:
if __name__ == '__main__':
try:
main()
#when 'Ctrl+C' is pressed,child program destroy() will be executed.
except KeyboardInterrupt:
pass
except ValueError as e:
print(e)
本当は単一撮影ではなく、複数撮影出来るようにしたいのですが、このままではスクリプトと同階層に決め打ちのファイル名で静止画や動画が生成されるので、連写しようにも最後の撮影分しか残りません。
また、スクリプトと同階層に生成されるのも使い勝手がいまいちですね。
そこで、次回はファイル名を「年月日時分秒」にし、保存場所をホームディレクトリの「画像」や「ビデオ」フォルダーに変更したいと思います。