Napa Log

Blog

大家さんのお願いで、監視カメラ自動解析ツールをpythonで作った

サムネイル

なぱです。私がお借りしているアパートには、宅配ボックスがあります。amaz〇nのヘビーユーザーですので宅配ボックスは大変ありがたいです。
アパートの大家さんとは大変仲が良く、一緒にご飯を食べに行ったりもします。

宅配ボックスが何者かに破壊された。。。

私が荷物を宅配ボックスから出そうとすると、まず開かなかった。開かなかったというより、なんかもう半開きで開いてるんだけど、それ以上開けることはできませんでした。
大家さんに連絡をしてマスターキーで開けてもらいましたが、誰かが強引に開けようとしたようで、中のカギとなる金具がひん曲がっていました。

大家さんとしても「住人が間違えて強引に開けてしまった」とかであれば賃貸の必要経費かなあ、、、ということでしたが、悪意を持った外部犯であるとそうもいきません。
そこで監視カメラを見ることになりましたが、たまたま7日前に宅配ボックスを開けてる住人がいましたが、その時にはまだ壊れてなかったです。
つまり、7日前から本日までの間で壊れたのです。7 × 24 = 168時間も動画をずっと見ていくのはしんどいです。

後は私に託されました

幸いにも、中の金具が少しひん曲がっているだけで済んでるので、まだ直せる余地はあります。
なので大家さんも原因わかったらでいいよ~と軽いノリだったので、私も軽ーくだけ見ようと思いましたが、もう本当に動画長くて、、、

そこでpythonさんに頑張ってもらおうと思ったわけです。

開発をしていく前に、必要なこと

必要なことは下記でした。

  1. 動画を.h264という謎拡張子からmp4に変換する。
  2. mp4の動画をpythonに読み込ませて、解析を行う。
  3. 人がいる部分を検知したらその写真をファイルとして保存

h264という拡張子は、監視カメラのファイルとしてはよくあるようです。なかなか扱いが難しく専用のソフトが必要だったりするようです。この拡張子にするのも、監視カメラの会社の大人の事情があるようです。。。

開発していきます

作戦も立てれたので、作っていきます。

最近個人開発でGoやらTypescriptやらAWSやらで大忙しだったので、pythonはひっさしぶりです。サクッと環境開発をしていきました。

環境

環境

C:\Users\napa>python -V
Python 3.12.6

Windows11 Pro
CPU AMD Ryzen 7 7840HS w/ Radeon 780M Graphics 
メモリ 16GB
ストレージ 512GB SSD

普段はWSLラブですが、大量の動画ファイルを扱うので、ubuntuにファイル配置したりするのだるいので、そのままwindowsでやっていきます。

動画変換

convert.pyという名前で作成しておきました。変換するにあたり、ffmpegが必要ですので、別途インストールが必要です。

ffmpeg公式

参考サイト

import subprocess
import os
import glob


# 使用例
input_directory = 'raw'   # 元のh264ファイルが入っているディレクトリ
output_directory = 'converted'  # 変換後のファイルを保存するディレクトリ

# h264ファイルをMP4に変換する関数
def convert_h264_to_mp4(input_file, output_file):
    command = ['ffmpeg', '-i', input_file, '-c:v', 'libx264', '-preset' , 'veryfast' ,  '-loglevel', 'error', output_file]
    subprocess.run(command)

# raw_movieディレクトリのすべてのファイルを変換
def convert_all_h264_in_directory(input_dir, output_dir):
    # 出力ディレクトリが存在しない場合は作成
    os.makedirs(output_dir, exist_ok=True)

    # .h264ファイルを全て取得
    raw_files = glob.glob(os.path.join(input_dir, '*.h264'))

    for file in raw_files:
        # ファイル名だけを取得し、拡張子を.mp4に変更
        file_name = os.path.splitext(os.path.basename(file))[0]
        output_file = os.path.join(output_dir, f"{file_name}.mp4")

        # 変換を実行
        print(f"Converting {file} to {output_file}...")
        convert_h264_to_mp4(file, output_file)


def main():
    convert_all_h264_in_directory(input_directory, output_directory)

if __name__ == '__main__':
    main()

これでいい感じに変換をしてくれますが、びっくりするぐらい時間がかかります。

veryfastに設定していますが、初期設定だとびっくりするぐらい遅かったのでご自身のスペックと相談しながら設定してみてください。

mp4になりました。

動画をPythonで読み込んで、人の動きを検知

cv2というライブラリを使用することにより、動画の指定した範囲である一定の動きがあることを検知することができるようです。

import cv2
import os
import glob

# 動画ファイルの読み込み
video_dir = "converted"
output_dir = "capture"

frame_skip = 20  

os.makedirs(output_dir, exist_ok=True)


video_files = glob.glob(os.path.join(video_dir, "*.mp4"))

frame_count = 0
saved_image_count = 0
for video_path in video_files:
    cap = cv2.VideoCapture(video_path)

    # 背景差分を使った動きの検出
    fgbg = cv2.createBackgroundSubtractorMOG2()


    while cap.isOpened():
        if not cap.isOpened():
            print(f"Error: Could not open video {video_path}")
            continue
        
        ret, frame = cap.read()
        
        if not ret:
            break


        frame_count += 1
    # 指定したフレーム間隔でのみ処理を行う
        if frame_count % frame_skip != 0:
            continue

        # 背景差分法で動きを検出
        fgmask = fgbg.apply(frame)

        # 特定のエリア(宅配ボックス周辺)にROIを設定する (例: 左上が (x, y), 幅w, 高さh)
        x, y, w, h = 700, 300, 400, 400  # 例: 宅配ボックスの位置に合わせて調整
        roi = fgmask[y:y+h, x:x+w]
        
        # フレームにROIの矩形を描画する
        cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)  # 緑色で矩形を描画

        # 動きが一定以上あった場合にフレームを保存
        if cv2.countNonZero(roi) > 20000:  # しきい値を調整
            saved_image_path = os.path.join(output_dir, f"frame_{frame_count}.jpg")
            cv2.imwrite(saved_image_path, frame)
            saved_image_count += 1

        # 描画されたフレームを表示
        cv2.imshow('Frame with ROI', frame)

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

    cap.release()
    cv2.destroyAllWindows()

    print(f"Saved {saved_image_count} images with detected motion.")

大事なところは二つです。

まずは、読み取る範囲です。
下記のように指定しますが、私はざっくり、動画の解像度で、ちょうど動画の真ん中にあるから、720 × 1/2 みたいな感じで計算をしていったら大体合いました。

        # 特定のエリア(宅配ボックス周辺)にROIを設定する (例: 左上が (x, y), 幅w, 高さh)
        x, y, w, h = 700, 300, 400, 400  # 例: 宅配ボックスの位置に合わせて調整
        roi = fgmask[y:y+h, x:x+w]

設定された場所は下記のように緑枠で表示されるのでトライ&エラーです

※画像はおうちがばれちゃうので、バイクのツーリングの時の動画に置き換えてます

下記はどのぐらいの動きを検知するかを判断します。
夜になると、ノイズにより動きがあったと判定されるため、低い値にしていると大量の画像が作成されてしまい、結局動画見るのと変わらなくなるので、思い切って大きい値にしましょう。それでちょうどいいです。

        # 動きが一定以上あった場合にフレームを保存
        if cv2.countNonZero(roi) > 20000:  # しきい値を調整
            saved_image_path = os.path.join(output_dir, f"frame_{frame_count}.jpg")
            cv2.imwrite(saved_image_path, frame)
            saved_image_count += 1

これもとっても時間かかりましたが、バイクで3時間ぐらいぶらぶらして帰ってくると終わってました。

こんな感じでファイルに吐き出してくれます

意外と簡単にできてしまった

今回はここまでですが、こういったお助けアイテムを作る場合、pythonのライブラリは本当に素晴らしいと思ってしまいます。
私は今AIのお仕事をしていますが、それはそれで楽しいのですが、ゴリゴリにアプリを作ったりするほうが私には合ってるようです。
ですが、一度アプリ開発の手を止めて、pythonのライブラリに目を向けることで、アプリにも使えるものがあるのではという気づきが得られました。

皆さんも、pythonを試してみてください。