ラズパイOpenCVタイムラプス|超長時間撮影対応

2021/07/07

IoT Python ガジェット らずキャン△ ラズパイ 開発

OpenCVタイムラプス

こんにちはイチケンです。

OpenCVを使ってラズパイ+Webカメラでタイムラプス動画撮影をしていきますよ。

OpenCVを使わず定期的に撮影したJPEG画像をあとでMP4タイムラプスに変換する方法はこちら。

ラズパイで植物タイムラプス撮影【こどもちゃれんじ】

この記事でわかること

ラズパイ、Webカメラ、OpenCVの組み合わせでタイムラプス動画を作る方法がわかります。自動削除は実装していませんが、それ以外はストレージが生きている限り撮り続けることができる作りになっています。

  • OpenCVでWebカメラ動画取り込み
  • 時刻を埋め込んで表示
  • 1秒おきに動画として保存
  • 一時間ごとに新しい動画ファイルに切り替え
  • 一日ごとに新しい日付フォルダに切り替え

OpenCVでMP4を作る場合は、最後にきちんとファイルの終了処理をしないと動画が見れなくなります。なので不意のシャットダウンなどに備え、一時間に一回くらいファイルを切り替えるようにします。

前提条件

まず前提条件となる私の環境です。

Raspberry Pi 4 Model B 8GB
OS Raspbian GNU/Linux 10 (buster)
VSCode 1.55.2
Python 3.7.3
OpenCV 4.5.1

機器の準備はこちらから

【IoT×キャンプ|機器準備編】意地でもラズパイとキャンプへ!らずキャン△プロジェクト|第1回 【IoT×キャンプ|OpenCVインストール編】意地でもラズパイとキャンプへ!らずキャン△プロジェクト第10回

USB-Webカメラ

今回使用しているカメラはこちら。オートフォーカスや自動HD光補正など、高品質で撮影だけでなくリモート会議にも最適。三脚対応などタイムラプスに必要な機能もそろっており、マルチな一台としてオススメです。

ロジクールC920 PRO HDウェブカメラ、1080pビデオとステレオオーディオ

サンプルコード

ではさっそくサンプルコードです。2つのpyファイルで構成しています。

  • webcam.py
  • log.py

ではまず「webcam.py」から。名前は何でもかまいません。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import datetime
import cv2
import os
import time
import log #自作モジュール
from logging import getLogger


# Log設定
LOG_PATH = "./Log/" #Logファイル保存先
logger = getLogger(__name__)
log.set_log_config(logger, LOG_PATH, 'timelapse.log')


class WebCamTimeLapse:

    def __init__(self, mp4_fps, resize_ratio):
        
        # ファイルパス
        self.VIDEO_PATH = "./Timelapse/" #Videoデータ保存先

        # 時間ごとに処理するための比較用変数
        dt_now = datetime.datetime.now()
        self.previous_sec = dt_now.second
        self.previous_min = dt_now.minute
        self.previous_hour = dt_now.hour

        # Fourcc (MP4)
        self.fourcc = cv2.VideoWriter_fourcc("m","p","4","v") 

        # Video保存設定
        self.mp4_fps = mp4_fps #FPS for timelapse
        self.resize_ratio = resize_ratio # 縮小比率
        

    def timelapse(self):

        logger.info('Start timelapse')

        try:

            # MP4保存先
            filename = self.__get_video_dir()

            # Webカメラ用インスタンス     
            cap = cv2.VideoCapture(0) # 引数でカメラ指定

            # Webカメラ情報取得    
            fps = int(cap.get(cv2.CAP_PROP_FPS)) # FPS    
            w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) # Width   
            h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # Height
            logger.info(f'FPS={fps} Width={w} Hight={h}')

            # 設定にしたがってサイズ等分 1ならそのまま 2なら半分
            mp4_w = int(w/self.resize_ratio)
            mp4_h = int(h/self.resize_ratio)

            # Video保存用インスタンス
            video = cv2.VideoWriter(filename, self.fourcc, self.mp4_fps, (mp4_w,mp4_h))

            # Webカメラ安定タイマー
            time.sleep(2) #s

            while True:
                #1フレーム読み込み
                ret, img = cap.read()

                # Movie表示
                dt_now = datetime.datetime.now()
                dt_str = dt_now.strftime('%Y/%m/%d %H:%M:%S')
                cv2.putText(img, dt_str,(0,30), cv2.FONT_HERSHEY_PLAIN, 2, (255,255,255), 2, cv2.LINE_AA)
                cv2.imshow("image", img)

                # 1秒ごとにムービー保存
                if self.__compare_sec():
                    
                    # 1時間ごとに保存ファイル切り替え
                    if self.__compare_hour():
                        
                        # 日付変わったとき用にファイルパス更新
                        filename = self.__get_video_dir()

                        # Video保存用インスタンス更新
                        video.release()
                        video = cv2.VideoWriter(filename, self.fourcc, self.mp4_fps, (mp4_w,mp4_h))
                    
                    video.write(cv2.resize(img,(mp4_w,mp4_h)))

                # キー入力受付 & ループディレイ
                k = cv2.waitKey(50) #ms
                if k == 27: # ESCで終了
                    break

        except KeyboardInterrupt: # ターミナルCtrl+C強制終了
            logger.info(f"KeyboardInterrupt")

        except Exception as e:
            logger.exception(e)

        finally:
            #終了処理
            video.release()
            cap.release()
            cv2.destroyAllWindows()
            logger.info('Finished')


    def __get_video_dir(self):

        dt_now = datetime.datetime.now()

        day_folder = dt_now.strftime("%Y%m%d")
        video_dir = self.VIDEO_PATH + day_folder
        os.makedirs(video_dir, exist_ok=True)
        filename = video_dir +"/"+ dt_now.strftime("%Y%m%d_%H%M%S") + ".mp4"

        logger.info(filename)

        return filename


    def __compare_sec(self):

        dt_now = datetime.datetime.now()
        now = dt_now.second

        if self.previous_sec == now:
            return False
        else:
            self.previous_sec = now
            return True


    def __compare_min(self):

        dt_now = datetime.datetime.now()
        now = dt_now.minute

        if self.previous_min == now:
            return False
        else:
            self.previous_min = now
            return True


    def __compare_hour(self):

        dt_now = datetime.datetime.now()
        now = dt_now.hour

        if self.previous_hour == now:
            return False
        else:
            self.previous_hour = now
            return True


if __name__ == "__main__":

    logger.info('App start')
    mp4_fps = 60
    resize_ratio = 1 # 2にするとサイズ半分

    # タイムラプスのインスタンス
    tl = WebCamTimeLapse(mp4_fps, resize_ratio)       
    tl.timelapse()

追記:ラズパイに上記ソフトを入れて屋外でcrontab自動立ち上げ時しようとするとimshowでエラーになります。エラーハンドリングするかコメントアウトしましょう。

次にログ用の「log.py」です。webcam.pyと同じフォルダに配置するのがラクです。こちらは名前を変えた場合はwebcam.pyの import log もあわせて変えてください。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from logging import StreamHandler, DEBUG, INFO, Formatter
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
import os

def set_log_config(logger, dir, filename):

    # ---- Prepare log folder ----
    os.makedirs(dir, exist_ok=True) 
    filepath = dir + filename

    # ---- handler1: For terminal ----
    handler1 = StreamHandler()
    handler1.setLevel(DEBUG)
    handler1.setFormatter(Formatter("[%(asctime)s][%(levelname)8s][%(name)s][%(funcName)s] %(message)s"))

    # ---- handler2: For log file ----
    #handler2 = TimedRotatingFileHandler(filename = filepath, when='midnight', backupCount=30, encoding='utf-8') # If you want to rotate file by time, use this
    handler2 = RotatingFileHandler(filename = filepath, maxBytes = 1048576, backupCount = 10, encoding='utf-8', ) # 1MB, keep 10 files
    handler2.setLevel(INFO)
    handler2.setFormatter(Formatter("[%(asctime)s][%(levelname)8s][%(name)s][%(funcName)s] %(message)s"))

    # ---- Set log config ----
    logger.setLevel(DEBUG)
    logger.addHandler(handler1)
    logger.addHandler(handler2)
    logger.propagate = False

あとはwebcam.pyを実行すればタイムラプス撮影開始します。終わる時は動画ウィンドウにフォーカスをあわせてESCキーです。

サクサク解説

では手短にサクサク解説いきます。ログについてはこちら。

結局どうする?ラクして無難にPython loggingを使うためのサンプルコードと使用例

Webカメラ起動

VideoCaptureのインスタンス作った時点でWebカメラが起動します。大抵のWebカメラは起動後数フレームはピントや明るさ調整するので、取り込みをスキップします。

# Webカメラ用インスタンス     
cap = cv2.VideoCapture(0) # 引数でカメラ指定

フレーム取り込み

readで1フレームを画像配列として取得できます。

#1フレーム読み込み
ret, img = cap.read()

現在時刻埋め込みと動画表示

そのままだと何時かよくわからないので時刻を画像として埋め込みます。

# Movie表示
dt_now = datetime.datetime.now()
dt_str = dt_now.strftime('%Y/%m/%d %H:%M:%S')
cv2.putText(img, dt_str,(0,30), cv2.FONT_HERSHEY_PLAIN, 2, (255,255,255), 2, cv2.LINE_AA)
cv2.imshow("image", img)

時間ごとに処理

表示は毎サイクル実施しますが、動画ファイルへの保存は1回/秒に間引いてます。

あと1ファイル/時間で切り替えていき、日付が変わると新しい日付フォルダに切り替えます。

それぞれ前回処理した秒や時間を覚えておき、比較して不一致なら実行するようにしています。

# 1秒ごとにムービー保存
if self.__compare_sec():
    
    # 1時間ごとに保存ファイル切り替え
    if self.__compare_hour():
        
        # 日付変わったとき用にファイルパス更新
        filename = self.__get_video_dir()

        # Video保存用インスタンス更新
        video.release()
        video = cv2.VideoWriter(filename, self.fourcc, self.mp4_fps, (mp4_w,mp4_h))
    
    video.write(cv2.resize(img,(mp4_w,mp4_h)))

解説は以上です。

最後に

いかがでしたか?

この記事が皆さんのお役に立てれば幸いです。別の手法でのタイムラプスはこちら。JPEG画像保存のほうがシャットダウンに対し堅牢ですね。

ラズパイで植物タイムラプス撮影【こどもちゃれんじ】

スポンサーリンク

フォロワー

Labels

Amazon (3) Apache (3) Apple (9) AppleSilicon (7) Bloggerカスタマイズ (12) EchoShow15 (1) IoT (25) Jetson (1) MySQL (1) PHP (3) Python (20) Web (3) アウトドア (11) アメリカ生活 (19) ガジェット (35) キャンプ (9) ディープラーニング (1) らずキャン△ (11) ラズパイ (24) 暗号資産 (5) 開発 (31) 旅行 (8)

QooQ