ラズパイ4 動体検知する(PYTHON+OPENCV)

ラズパイで動体検知

 ラズパイで動体検知っぽいことをしてみます。
 よく理解できていませんが、“動体検知” 等のキーワードでネット検索すると、現在画像と1フレーム前の画像の差分から、動体の輪郭を表示する方法が紹介されています。
 輪郭は表示されるのですが、移動方向まで分析しているものがなく、検討してみました。
 
 次の動画はオリオンのラムネケースを振り子の重りにして、左右の移動方向を画面に表示するプログラムです。振り子の必要性はないのですが、手振り等は手の振り方でうまく反応しない場合もあり、ホームページで紹介する方法としては、安定して反応する方法にしました。

 振り子が左から右に移動する時は “To Right” 、振り子が右から左に移動する時は “To Left” という表示をします。
 動画が連続的に動いている時は、自分でも気づいていませんでしたが、本投稿のタイトル用画像をみると、動体を示す黄緑色の矩形表示がズレています。これは、実際の分析画像と表示画像が異なっていて、1フレーム遅れたが画像に矩形表示している為です。動画の撮影は結構面倒なので修正していません。

プログラム

 自分で検討した部分は、行番55~107あたりです。
 ここでは、検出した輪郭成分(動体)の数と面積を求め、連続して検出した輪郭成分が1つで、一定の値を超える場合に重心座標を配列に追加していきます。これは、複数の輪郭成分(動体)を同時に検出した場合に連続するフレーム画像間の対応付け方法が判らなかった為です。1つに限定することで、確実ではありませんが、同じ輪郭成分である可能性が高まると考えました。
 この様にして配列に一時的に蓄えたX座標の差分が、全て増加方向の場合に “To Right” 、減少時に “To Left” と判定しています。

import cv2
import numpy as np

# フレーム差分の計算
def frame_sub(img1, img2, img3, th):
    # フレームの絶対差分
    diff1 = cv2.absdiff(img1, img2)
    diff2 = cv2.absdiff(img2, img3)

    # 2つの差分画像の論理積
    diff = cv2.bitwise_and(diff1, diff2)

    # 二値化処理
    diff[diff < th] = 0
    diff[diff >= th] = 255

    # メディアンフィルタ処理(ゴマ塩ノイズ除去)
    mask = cv2.medianBlur(diff, 3)

    return  mask


def main():
    # カメラのキャプチャ
    cap = cv2.VideoCapture(0)
    kernel = np.ones((50,50),np.float32)/2500

    # フレームを3枚取得してグレースケール変換
    frame1 = cv2.cvtColor(cap.read()[1], cv2.COLOR_RGB2GRAY)
    frame2 = cv2.cvtColor(cap.read()[1], cv2.COLOR_RGB2GRAY)
    frame3 = cv2.cvtColor(cap.read()[1], cv2.COLOR_RGB2GRAY)

    flg_detect = 0
    cx_ary = []
    cy_ary = []
    temp_text=""
    dsp_pos=120
    dsp_col_r=0
    dsp_col_b=0

    while(cap.isOpened()):
        frame0 = cap.read()[1]

        # フレーム間差分を計算
        mask = frame_sub(frame1, frame2, frame3, th=30)
        closing=cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
        opening=cv2.morphologyEx(closing, cv2.MORPH_CLOSE, kernel)
        ret,thresh = cv2.threshold(closing,127,125,0)

        contours,hierarchy = cv2.findContours(thresh.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

        cnt_contour_selected = 0
        idx_contour = -1
        
        for i in range(0,len(contours)):
            area_contours = cv2.contourArea(contours[i])
            if area_contours > 9000 :
                cnt_contour_selected += 1
                idx_contour = i

        
        if cnt_contour_selected == 1:
            M = cv2.moments(contours[idx_contour])
            cx = int(M['m10']/M['m00'])
            cy = int(M['m01']/M['m00'])
                 
            x,y,w,h = cv2.boundingRect(contours[idx_contour])       
            cv2.rectangle(frame0,(x,y),(x+w,y+h),(0,255,0),2)

            cx_ary.append(cx)
            cy_ary.append(cy)
            
            flg_detect=1

        else:
            
            if (flg_detect == 1) & (len(cx_ary) > 0) :
                ck_dir = 0    

                for i in range(0,len(cx_ary)):
                    if i > 0 :
                        dif_cx = cx_ary[i] - cx_ary[i - 1]
                        if dif_cx > 0 :
                            ck_dir += 1
                            
                        if dif_cx < 0 :
                            ck_dir -= 1

                if (len(cx_ary) - 1) == abs(ck_dir) :
                    if ck_dir > 0 :
                        temp_text="To Right"
                        dsp_pos=120
                        dsp_col_r=255
                        dsp_col_b=0
                    else :
                        temp_text="To Left"
                        dsp_pos=420
                        dsp_col_r=0
                        dsp_col_b=255

            cx_ary = []
            cy_ary = []

            flg_detect = 0


        cv2.putText(frame0, temp_text , (30, dsp_pos), cv2.FONT_HERSHEY_SIMPLEX, 4, (dsp_col_b, 0, dsp_col_r), 15, cv2.LINE_AA)
        
        # 結果を表示
        cv2.imshow("Frame0", frame0)
        # cv2.imshow("Mask", closing)

        # 3枚のフレームを更新
        frame1 = frame2
        frame2 = frame3
        frame3 = cv2.cvtColor(cap.read()[1], cv2.COLOR_RGB2GRAY)


        # qキー, ESC が押されたら途中終了
        key = cv2.waitKey(1)
        if (key & 0xFF == ord('q')) or key == 27 :
            break
        
    cap.release()
    cv2.destroyAllWindows()
    
cv2.namedWindow("Frame0", cv2.WINDOW_NORMAL)
# cv2.namedWindow("Mask", cv2.WINDOW_NORMAL)


if __name__ == '__main__':
    main()

まとめ

 動体検知は予てより、使ってみたかった機能です。
  プログラム操作にジェスチャーを使いたいと思っていたの ですが、対象の動体を正確に検知する難しさを感じました。

コメントを残す

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