Raspberry Pi Zero 2 WでOpenCVを使った静体検知テスト

# 1.はじめに
[前回記事](https://qiita.com/dokuhakoneko/items/d0156af2fe3e1111c8f2)の続きを行う。
今回は[参考サイト](https://qiita.com/hase-k0x01/items/acd0d9159a9001ebfbd3)のソースコードをベースに映像に動きがない時に赤枠を出す「静体検知」を実装する。


# 2.ソースコード
まずは最終コードから。
参考サイトでコメントが付いている部分は省いている。
```MotionStop.py
# モジュールインポート
import datetime
import cv2

# カメラ入力の解像度設定値(任意設定)
WIDTH = 640
HEIGHT = 480

# 表示するウィンドウのサイズ設定値(任意設定)
WIN_WIDTH = 400
WIN_HEIGHT = 300

motion_factor = 0       # motion_factor
max_motion_factor = 0   # motion_factor の最大値
DOT_TH = 10             # 各ドットの変化を検知する感度（小：感度高、大：感度低）
MOTHON_FACTOR_TH = 0.05 # 静動の閾値（値以下で静と判断）

DELTA_MAX = 255
avg = None

# カメラ入力先設定
cap = cv2.VideoCapture(0, CAP_V4L2)
# カメラ入力の解像度設定
cap.set(cv2.CAP_PROP_FRAME_WIDTH, WIDTH)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, HEIGHT)

# ウィンドウ作成　サイズ指定を変更可能に設定
cv2.namedWindow('camera', cv2.WINDOW_NORMAL)
cv2.resizeWindow('camera', WIN_WIDTH, WIN_HEIGHT)

while True:
    ret, frame = cap.read()
    dt_now = datetime.datetime.now()
    dt_format_string = dt_now.strftime('%Y-%m-%d %H:%M:%S')
    
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    if avg is None:
        avg = gray.copy().astype("float")
        continue

    cv2.accumulateWeighted(gray, avg, 0.6)
    frameDelta = cv2.absdiff(gray, cv2.convertScaleAbs(avg))
    thresh = cv2.threshold(frameDelta, DOT_TH, DELTA_MAX, cv2.THRESH_BINARY)[1]

    motion_factor = thresh.sum() * 1.0 / thresh.size / DELTA_MAX 
    motion_factor_str = '{:.08f}'.format(motion_factor)

    # motion_factor の最大値を更新
    if motion_factor > max_motion_factor:
        max_motion_factor = motion_factor

    cv2.putText(frame, dt_format_string, (25,50), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0,0,255), 2)
    cv2.putText(frame, motion_factor_str, (25,470), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0,0,255), 2)

    # カメラ入力の映像に変化がない時の動作
    if motion_factor <= MOTHON_FACTOR_TH:
        # 映像の外枠に赤枠を付ける
        frame = cv2.rectangle(frame, (0, 0), (frame.shape[1] - 1, frame.shape[0] - 1), (0, 0, 255), 5)

    cv2.imshow('camera', frame)

    k = cv2.waitKey(1000)

    if k == ord('r'):  # 「r」キーを押したら max_motion_factor をリセット
        print("Max motion_factor reset!")
        max_motion_factor = 0

    if k == 27:  # ESCキーが押されたら終了
        break

# 終了時に motion_factor の最大値を表示
print(f"Final motion_factor: {motion_factor:.08f}")
print(f"Max motion_factor: {max_motion_factor:.08f}")

print("Bye!\n")
cap.release()
cv2.destroyAllWindows()
```
追加した機能（メイン）
・motion_factorがMOTHON_FACTOR_TH以下となった時、画面に赤枠を出す

追加した機能（サブ）
・スタート時のウインドウサイズを小さくする
・ウインドウサイズをマウスで変更可能
・motion_factorの最大値を取得（デバック用 DOT_TH調整のため）
・上記を動作終了時にターミナルに出力


# 3.ソースコード解説（備忘）
### 1.``import time``削除
ソースコード上で使用されていないため。datetimeのモジュールで取得できる値しか使われていない。

### 2.``cv2.VideoCapture(0, CAP_V4L2)``のCAP_V4L2
``cv2.VideoCapture(0)``で実行したとき以下の警告が出る。
```
[ WARN: @4.510] global ./modules/videoio/src/cap_gstreamer.cpp (1405) open OpenC
V | GStreamer warning: Cannot query video position: status=0, value=-1, duration=-1
```
この警告は、GStreamerを使用してビデオ入力を取得しようとした時、ビデオの現在の位置情報（再生時間やフレーム位置）を取得できなかった場合に出るらしい。
これに対しCAP_V4L2を追加すると、上記を解決できる。
V4L2 (Video for Linux 2) は、Linux カーネルに組み込まれているビデオキャプチャAPIで、USBカメラやWebカメラ、キャプチャカードなどのデバイスを扱うための標準的なフレームワークとのこと。

### 3.``cv2.namedWindow``と``cv2.resizeWindow``
``cv2.namedWindow``の引数に``cv2.WINDOW_NORMAL``を入れるとウインドウサイズをマウスで変更できるようになる。ウインドウサイズを固定しておきたい時は引数を消せば良い。
``cv2.resizeWindow``は既存のウィンドウのサイズを指定する。cv2.namedWindow() で cv2.WINDOW_NORMAL を指定している場合のみ動作することに注意が必要。


# 4.動作確認
2節のソースコードを適当なディレクトリに保存して、実行する。
ホーム画面で操作をしていないとき。
![20250202_110159488_iOS.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992520/3c8d70df-8794-4cda-4588-3d6a4ea3b95b.jpeg)

操作をしたとき。
![20250202_110206759_iOS.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992520/f676d290-48c2-d9a8-134c-de22a0b08be6.jpeg)

左下数値がMOTHON_FACTOR_THの設定以下で赤枠が出ており、正常に動作していることがわかる。
また表示ウインドウサイズも小さくできている。

# 5.おわりに
「静体検知」を実装した。
ウインドウサイズが小さくなったことが要因か不明だが、CPU使用率が大幅に下がった。
・「動体検知」では15～25％程度
・「静体検知」では2～5％程度

次回は一定時間停止していた場合に静止判定を取り、ラズパイの外側にアウトプットを出せるようにする。