Raspberry Pi 5 で遊ぶ(日時ファイル名で連続撮影)

さて、5回目となる「カメラ編」ですが、カメラ編は今回で最終回となります。

前回終了時の課題は「ファイル名が決め打ち、かつスクリプトと同じディレクトリに生成される」ことでした。

今回はこの課題を克服するわけですが、サンプルスクリプトの切り貼りでなんとか乗り切ってきたこれまでと違い、「ファイル名を撮影日時にする」という改修を行わなければなりません。

Python で「現在の日時ッてどうやって取得して、変数に格納するの?」という事さえ分からない私(笑)。上手くいくでしょうか。。

フルパス付きファイル名を変数に格納する

分からなければ、AI に聞いちゃえ!

……ということで、Google 検索とかもしたのですが、あまりに基本的過ぎるらしく、詳しい説明が見つからず、ここは AI(Windows 付属の Copilot)に聞いちゃいました。

質問文:python で日付時刻を含むファイル名のファイルを作成するにはどうしますか?

回答文のサンプルコード:

import datetime

# 現在の日付と時刻を取得
now = datetime.datetime.now()

# 日付と時刻をフォーマットして文字列に変換
timestamp = now.strftime("%Y%m%d_%H%M%S")

# ファイル名を作成
filename = f"file_{timestamp}.txt"

# ファイルを作成して書き込み
with open(filename, "w") as file:
    file.write("これはテストファイルです。")

print(f"ファイル '{filename}' が作成されました。")

ふむふむ、なるほど。「datetime」ライブラリというものが用意されている訳なんですね。

じゃぁ、これを例によって「cam_04.py」に前回の成果物をコピーしたものに切り貼りして埋め込みで見ます。

とその前に、静止画はホームディレクトリ直下の「画像」ディレクトリ、動画は「ビデオ」ディレクトリに入れ込みたいので、「フルパス付きファイル名」を何らかの変数に入れなきゃならないので、それを考えてみます。これくらいなら、AI に聞かなくてもなんとなく想像つきますので、コーディング。

import datetime
save_paths = "/home/pi/画像/"

# 現在日時取得、ファイル保存パス + ファイル名生成
now = datetime.datetime.now()
save_files = save_paths + now.strftime('%Y%m%d_%H%M%S.jpg')
  

こんな感じでしょうか。では「cam_04.py」に入れ込んでみます。

#!/home/pi/V_CAM/bin/python3

"""
###########################################################################
カメラで静止画、動画を撮影する。

#Filename      :cam.py

#Update        :2024/07/28
2024/07/21  Ver. β 0.05 カメラ撮影 複数ファイル(日時ファイル名)
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
import datetime

# カメラ用初期設定
cam_no = 0
width = 1920
height = 1080
flip_hor = False
flip_ver = False
#wait_time = 0
#rec_time = 10

save_paths = "/home/pi/画像/"
save_pathm = "/home/pi/ビデオ/"


# カメラ初期化処理
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()
            # 現在日時取得、ファイル保存パス + ファイル名生成
            now = datetime.datetime.now()
            save_files = save_paths + now.strftime('%Y%m%d_%H%M%S.jpg')
            picam2.capture_file( save_files )
            # カメラ停止
            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()
            now = datetime.datetime.now()
            save_filem = save_pathm + now.strftime('%Y%m%d_%H%M%S.mpeg')

            encoder = picamera2.encoders.H264Encoder( bitrate=10000000 )
            output = picamera2.outputs.FfmpegOutput( save_filem )

            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)

動かしてみますと、見事 /home/pi/画像/20240728_1744502.jpg ファイルが生成されました!

が、詳しく動作検証してみると、期待したように複数回の静止画・動画撮影が出来ていないッぽい。。

一回撮影したら、次はカメラを初期化し直す

よくよくコードを見直すと、main 関数に入る前に

# カメラ初期化処理
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 )

を行っています。で、main 関数内で撮影を行っています。

で、VS Code 付属ターミナルに表示されるエラーメッセージの最後は

RuntimeError: Camera not opened

でした。

つまり、一回使ってカメラを閉じたら、その次の撮影に入る前にもう一度カメラ初期化処理が必要そうです。

そこで、下記のように改修しました。

#!/home/pi/V_CAM/bin/python3

"""
###########################################################################
カメラで静止画、動画を撮影する。

#Filename      :cam.py

#Update        :2024/07/28
2024/07/21  Ver. β 0.05 カメラ撮影 複数ファイル(日時ファイル名)
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
import datetime

# カメラ用初期設定
cam_no = 0
width = 1920
height = 1080
flip_hor = False
flip_ver = False
#wait_time = 0
#rec_time = 10

save_paths = "/home/pi/画像/"
save_pathm = "/home/pi/ビデオ/"


# カメラ初期化処理
#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 = 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 )
            picam2.configure(preview_config)
            # カメラ起動、撮影(静止画)
            picam2.start()
            # 現在日時取得、ファイル保存パス + ファイル名生成
            now = datetime.datetime.now()
            save_files = save_paths + now.strftime('%Y%m%d_%H%M%S.jpg')
            picam2.capture_file( save_files )
            # カメラ停止
            picam2.close()
            # LED1消灯
            LEDPIN1.off()
            # 1秒待って OLED ディスプレイクリア
            time.sleep(1)
            device.clear()
#            break

        # スイッチ2状態取得
        sw_ = ReadSW_2()
        # スイッチ2プッシュ時
        if sw_ =='on':
            # カメラ初期化
            picam2 = picamera2.Picamera2( camera_num = cam_no )
            video_config = picam2.create_video_configuration(main={"size":( width, height )})
            video_config["transform"] = libcamera.Transform( vflip = flip_ver, hflip = flip_hor )
            picam2.configure( video_config )
            # LED2点灯
            LEDPIN2.on()
            # カメラ起動、撮影(動画)
            picam2.start()
            now = datetime.datetime.now()
            save_filem = save_pathm + now.strftime('%Y%m%d_%H%M%S.mpeg')

            encoder = picamera2.encoders.H264Encoder( bitrate=10000000 )
            output = picamera2.outputs.FfmpegOutput( save_filem )

            picam2.start_recording( encoder, output )
            # プログレスバー描画
            for x in range(0,127,1):
                oled_square(0, 10, x, 21,1)
                time.sleep(0.1)
            # 動画撮影終了
            picam2.stop_recording()
            # カメラ停止
            picam2.close()
            # 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)

動画撮影の方には撮影後にカメラを閉じる処理が入っていませんでしたので、それも追加しています。

これで意図通り、スクリプト起動中は静止画、動画とも複数回の撮影が可能となりました。いや~長かった(笑)。

最後に

感想

プログラミングはほぼ経験が無く、特に Python は読み下すのも無経験だったのですが、電子パーツ付属サンプルコードの切り貼り(+AI のサンプルコード提供)だけでここまで出来るのは、正直驚きと感動を覚えました。

皆さんも Raspberry Pi を入手する機会があれば、是非色々試してみて下さい。この Blog 連載が、万分の一でもそのきっかけ、助けになれれば嬉しいです。

コード管理について

今回、Blog 上では「cam_○○.py」にコピーしていくことで、コードの世代管理を行っていますが、密かに GitHub でも世代管理しています。VS Code とGitHub の連携、思ったより便利ですね。次回以降、その辺も軽く触れられればと思います。

ちなみに、この「カメラ編」の GitHub はまだ Private 設定ですが、もう少しコードを整理したら https://github.com/Sa-Q/CAM にて公開予定です。まぁ、誰も見ないかも知れませんが、一応(笑)。

次回以降の予告

実は「SunFounder Raspberry pi 用のスターター電子工作キット」というものを購入しました。「スターターキット」ながら電子パーツが 40 くらい付いてました(^_^;)。

こちらでしばらく電子パーツ制御の練習課題(テキスト、ビデオを含めてかなり充実しています)をこなした後、温度・湿度・気圧センサーを使って、温度湿度気圧のグラフを Web 公開する、ことを最終目標に、またちまちま Blog 化していきたいと思います。

ターミナル上に温度・湿度・気圧センサーで得られたそれぞれの値を表示するサンプルプログラムがあるのは分かっていますが、グラフ化するには、当然定時測定してデータベースに値を保存し、グラフ画像を生成して、Web ページにそれを公開させる、というかなり大がかりなプロジェクトになります。気長にお待ちいただければと思います。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください