LoginSignup
7
3

はじめに

  • 家に眠っていたRaspberry Piを使って何か開発したい.
  • 一人暮らしに向けて家に侵入者がいないか監視したい.

こんなことを思い,Raspberry Piとwebカメラを使って監視システムを開発しました.
他のセンサーを使わずに,部屋に侵入者がいた際にカメラで撮影し,Lineに通知することができました.

概要

基準となる通常時の画像を撮影し,画像のフレームに変化があった際に撮影を行い,LINEに通知します.

20240608_125443.jpg
通常時の画像

20240608_125614.jpg
 何者かが侵入しようとした画像

IMG_3429.PNG
Lineに通知

目次

  1. 開発手順
    1. カメラを有効にする
    2. OpenCVをインストール
    3. Lineトークンを取得
    4. Pythonでコーディング
  2. 終わりに
  3. 参考文献

開発手順

  1. カメラを有効にする.
  2. OpenCVをインストールする.
  3. Lineトークンを取得する.
  4. プログラミング!(Python)

カメラを有効にする.

まずはカメラを使えるようにする必要があります.
USBカメラの場合は特に有効にする必要はなく,ラズペリーパイ用のカメラモジュールを利用する方のみ設定が必要です.
以下のコマンドを実行し,OSを調べます.

lsb_release -a

スクリーンショット 2024-06-10 18.51.26.png

OSを調べ,Busterであることがわかったため,
Raspberry piの設定から,インターフェイスを開き,カメラを有効にします.

スクリーンショット 2024-06-10 19.00.25.png

OpenCVをインストールする.

python3が必要なため,以下のコマンドを実行してください.

python3 --version

Python3が入っていない場合はインストールしてください.

次に以下のコマンドを実行し,pipを最新版にアップグレードします.
pipとは,Pythonのパッケージインストーラのことです.

sudo python3 -m pip install --upgrade pip

pipは,Pythonに標準で含まれていますが,万が一うまくいかない時は以下のコマンドでpipの有無を確認してください.

python3 -m pip --version

スクリーンショット 2024-06-11 0.38.51.png

OpenCVのバージョンを指定して,インストールします.

sudo pip3 install opencv-python==4.5.1.48

今回は特定のバージョン(4.5.1.48)をインストールしていますが,最新の安定版を利用する場合はバージョン指定を省略することも可能です.

Lineトークンを取得する.

以下の記事を参考にLINEからトークンを取得しました.
この後のコーディングにおいて必要なので,適宜コピペして保存してください.

Pythonでコードを書く

ここまでで動作可能な環境を構築できたと思います.
あとはPythonでコードを書き,システムを構築していきたいと思います.

最初に,最終的なコードを記します.

import cv2  # OpenCVライブラリのインポート
import datetime  # 日付と時間の操作のためのライブラリのインポート
import os  # ファイルパス操作のためのライブラリのインポート
import requests  # HTTPリクエストを行うためのライブラリのインポート
import time  # 時間操作のためのライブラリのインポート

save_directory = '/home/pi/Pictures'  # 保存するディレクトリのパス
os.makedirs(save_directory, exist_ok=True)  # 保存ディレクトリがない場合は作成する

camera = cv2.VideoCapture(0)  # カメラデバイスの初期化(0はデフォルトのカメラ)
reference_frame = None  # 基準となるフレームを保持するための変数
last_notification_time = datetime.datetime.now() - datetime.timedelta(seconds=30)  # 前回通知した時刻
notification_interval = datetime.timedelta(seconds=30)  # 通知の間隔を30秒に設定
take_reference_time = None  # 基準フレームを撮影する時刻

def send_line(file_path):
    url = "https://notify-api.line.me/api/notify"  # LINE NotifyのAPIエンドポイント
    token = "LINEトークンを入力"
    headers = {"Authorization": "Bearer " + token}  # 認証用ヘッダー
    message = '大きな動きを検出しました: ' + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")  # 通知メッセージ
    payload = {"message": message}  # 送信するデータ
    files = {'imageFile': open(file_path, "rb")}  # 画像ファイル
    response = requests.post(url, headers=headers, files=files, data=payload)  # POSTリクエスト
    print("LINE通知結果:", response.status_code)  # 結果の出力

while True:
    _, frame = camera.read()  # カメラからフレームを読み込み
    show_frame = frame.copy()  # 表示用フレームの作成

    if take_reference_time is not None and datetime.datetime.now() >= take_reference_time:
        reference_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # グレースケール変換
        reference_frame = cv2.GaussianBlur(reference_frame, (21, 21), 0)  # ガウシアンブラーで平滑化
        take_reference_time = None  # 基準フレーム撮影時刻をリセット
        print("基準フレームを撮影しました.動きの検出を開始します.")

        cv2.imshow("WebCamera", show_frame)  # ウィンドウに現在のフレームを表示
        key = cv2.waitKey(1) & 0xFF  # キー入力の取得
    if key == ord('s'):  # 's'キーが押されたら
        take_reference_time = datetime.datetime.now() + datetime.timedelta(minutes=1)  # 1分後に基準フレームを設定
        print("1分後に基準フレームを撮影します.")
    elif key == ord('q'):  # 'q'キーが押されたら
        break  # ループを抜ける

    if reference_frame is None:
        continue  # 基準フレームが設定されていなければ何もしない

    # 動きの検出
    gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # 現在のフレームをグレースケール変換
    gray_frame = cv2.GaussianBlur(gray_frame, (21, 21), 0)  # 平滑化
    frame_diff = cv2.absdiff(reference_frame, gray_frame)  # フレーム差分の計算
    _, thresh = cv2.threshold(frame_diff, 25, 255, cv2.THRESH_BINARY)  # 二値化
    thresh = cv2.dilate(thresh, None, iterations=2)  # 膨張処理
    contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  # 輪郭検出
    for contour in contours:
        if cv2.contourArea(contour) < 5000:  # 輪郭の面積が小さい場合は無視
            continue
        current_time = datetime.datetime.now()
        if current_time - last_notification_time > notification_interval:  # 通知間隔を満たしているか確認
            (x, y, w, h) = cv2.boundingRect(contour)  # 輪郭の外接矩形
            cv2.rectangle(show_frame, (x, y), (x+w, y+h), (0, 255, 0), 2)  # 矩形で表示
            file_name = current_time.strftime('%Y%m%d_%H%M%S') + '.jpg'  # ファイル名
            full_path = os.path.join(save_directory, file_name)  # ファイルパス
            cv2.imwrite(full_path, frame)  # ファイルに保存
            send_line(full_path)  # LINEに通知
            last_notification_time = current_time  # 最後の通知時刻を更新
            print("動きが検出され,画像が保存されました.")

            cv2.imshow("WebCamera", show_frame)  # 更新されたフレームを表示

camera.release()  # カメラリリース
cv2.destroyAllWindows()  # すべてのウィンドウを閉じる

このスクリプトでは,'Sキー'を押してから,1分後に基準となるフレームを撮影します.
その後,面積の5000ピクセル以上の変化があった場合は,画像を撮影し,LINEに通知するように設定しています.
検出された輪郭の面積が5000ピクセル未満の場合は無視するように閾値を設定しました.
また,LINEへの通知は多くても30秒に1回の頻度でくるように設定しています.

このコードが何を行なっているのかは,コメントアウト形式でChatGPTに書いてもらいました.
何かご不明点等ございましたら,コメントで問い合わせてもらえると幸いです.

終わりに

今回のシステムでは常にカメラを実行していなければならない点が懸念点ですが,
家を出るまでに基準画像を撮影し,鍵を閉め忘れて何者かが(ひょっとしたら親が勝手に部屋に入っている可能性も・・・?)侵入してきた際に即時LINEに画像を送ることができます.

ラズパイの消費電力は,以下の方の記事によると,1ヶ月で60円とのこと.

今回はUSBカメラも使っているため,これよりは高くなる予想ですが,それにしてもエコなシステムだと思います.

ぜひ皆さんも,ラズパイを使ってエコな監視システムを作ってみてはいかがでしょうか.

以上です.最後までお読みいただき誠にありがとうございます.
いいねとコメントお待ちしております.

参考文献

7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3