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