ラズパイで動体検知
ラズパイで動体検知っぽいことをしてみます。
よく理解できていませんが、“動体検知” 等のキーワードでネット検索すると、現在画像と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()
まとめ
動体検知は予てより、使ってみたかった機能です。
プログラム操作にジェスチャーを使いたいと思っていたの ですが、対象の動体を正確に検知する難しさを感じました。