# 動体検知についてpyhonで色々やってみた ## 環境 Windows10 Visual Studio Code Python CV2 (4.2.0)←重要 ### 死ぬほどお世話になったリンク オブジェクト輪郭検出|農業情報 https://axa.biopapyrus.jp/ia/opencv/detect-contours.html OpenCVで手っ取り早く動体検知してみた https://qiita.com/K_M95/items/4eed79a7da6b3dafa96d 動体検知とトラッキングを組み合わせて動いてた物体を見失わないようにする話 https://ensekitt.hatenablog.com/entry/2018/06/13/200000 ※CV2 ver.3だったので苦労した ## 画像でcv2を使って輪郭判断 ```python import cv2 import numpy as np # load image, change color spaces, and smoothing img = cv2.imread('tulip.jpg') img_HSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) img_HSV = cv2.GaussianBlur(img_HSV, (9, 9), 3) # detect tulips img_H, img_S, img_V = cv2.split(img_HSV) _thre, img_flowers = cv2.threshold(img_H, 140, 255, cv2.THRESH_BINARY) cv2.imwrite('tulips_mask.jpg', img_flowers) # find tulips contours, hierarchy = cv2.findContours(img_flowers, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE) for i in range(0, len(contours)): if len(contours[i]) > 0: # remove small objects if cv2.contourArea(contours[i]) < 500: continue rect = contours[i] x, y, w, h = cv2.boundingRect(rect) cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 0), 10) # save cv2.imwrite('tulips_boundingbox.jpg', img) cv2.imshow('test',img) ``` ### 結果 ![](https://i.imgur.com/ZGSjjSf.jpg) ![](https://i.imgur.com/fMX6tH3.jpg) まぁなんとなくできた(一部VSCodeでimshowが動かなかったりしたけど気にしない) ## PCのWebカメラの動画をcv2で動体検知 ```python import cv2 filepath = "vtest.avi" #cap = cv2.VideoCapture(filepath) # Webカメラを使うときはこちら cap = cv2.VideoCapture(0) avg = None while True: # 1フレームずつ取得する。 ret, frame = cap.read() if not ret: break # グレースケールに変換 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, 3, 255, cv2.THRESH_BINARY)[1] # cv2.imshow('test',thresh) # 画像の閾値に輪郭線を入れる contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) frame = cv2.drawContours(frame, contours, -1, (0, 255, 0), 3) # 結果を出力 cv2.imshow("Frame", frame) key = cv2.waitKey(30) if key == 27: break cap.release() cv2.destroyAllWindows() ``` {%youtube oXh75xdqLqE %} 前のフレームと画像を比べて動いているものを緑色にするので、人間はほぼ反応するってこと ## もうちょっと動体検知っぽくやってみる ```python import cv2 cap = cv2.VideoCapture(0) ok = False before = None detected_frame = None bbox = (0,0,0,0) while True: # OpenCVでWebカメラの画像を取り込む ret, frame = cap.read() # スクリーンショットを撮りたい関係で1/2サイズに縮小 frame = cv2.resize(frame, (int(frame.shape[1]/2), int(frame.shape[0]/2))) # 加工なし画像を表示する cv2.imshow('Raw Frame', frame) # # 取り込んだフレームに対して差分をとって動いているところが明るい画像を作る gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) if before is None: before = gray.copy().astype('float') continue cv2.accumulateWeighted(gray, before, 0.7) mdframe = cv2.absdiff(gray, cv2.convertScaleAbs(before)) # # 動いているところが明るい画像を表示する # cv2.imshow('MotionDetected Frame', mdframe) # # 動いているエリアの面積を計算してちょうどいい検知結果を抽出する thresh = cv2.threshold(mdframe, 3, 255, cv2.THRESH_BINARY)[1] cv2.imshow('test',thresh) contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) #contours, hierarchy= cv2.findContours(thresh.copy(),cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) #image, contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) max_area = 0 target = None for cnt in contours: area = cv2.contourArea(cnt) if max_area < area and area < 40000 and area > 4000: max_area = area target = cnt # 動いているエリアのうちそこそこの大きさのものがあればそれを矩形で表示する # ちょうどいいエリアがなかったら最後の動いているエリアがあるフレームとエリア情報を用いてトラッキングをする # どうしようもない時はどうしようもない旨を表示する if max_area <= 4000: track = False if detected_frame is not None: # インスタンスを作り直さなきゃいけないっぽい tracker = cv2.TrackerKCF_create() ok = tracker.init(detected_frame, bbox) detected_frame = None if ok: track, bbox = tracker.update(frame) if track: p1 = (int(bbox[0]), int(bbox[1])) p2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3])) cv2.rectangle(frame, p1, p2, (0,255,0), 2, 1) cv2.putText(frame, "tracking", (10,50), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA) else: ok = False cv2.putText(frame, "(^q^)", (10,50), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA) else: #areaframe = cv2.drawContours(frame, [target], 0, (0,255,0), 3) x,y,w,h = cv2.boundingRect(target) bbox = (x,y,w,h) detected_frame = frame.copy() frame = cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,0),2) cv2.putText(frame, "motion detected", (10,50), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1, cv2.LINE_AA) cv2.imshow('MotionDetected Area Frame', frame) # キー入力を1ms待って、k が27(ESC)だったらBreakする k = cv2.waitKey(30) if k == 27: break # キャプチャをリリースして、ウィンドウをすべて閉じる cap.release() cv2.destroyAllWindows() ``` ### 結果 {%youtube XhD5DP3ae3Q %} まぁこれもなんとなくできた感じ。 ## 結論 動体自体の検知はopen cvでもある程度できる。 最初のチューリップの例にあったようにスレッショルドで色を指定していけば、手の動きだけを見ることも可能かも(色の付いたバンドとかすれば) あとは、これをAIで学習させる必要があるけど、そこまでは今回はムリ!! ## その他 ### 普通のカメラコード pythonで普通に動くやつ。基本中の基本なのであげておく ```python import cv2 cap = cv2.VideoCapture(0) while True: # 1フレームずつ取得する。 ret, frame = cap.read() #フレームが取得できなかった場合は、画面を閉じる if not ret: break # ウィンドウに出力 cv2.imshow("Frame", frame) key = cv2.waitKey(1) # Escキーを入力されたら画面を閉じる if key == 27: break cap.release() cv2.destroyAllWindows() ``` ### google colabでwebアプリを動かす方法 colabを画像や動画を学習させるのには最高だけど、推論やリアルタイムで~っていうのにあまり向いていない(Driveなどの連携もあるし、詳細はJupyterが~~みたいなことが沢山書いてあったけどよく分からんかった)。無理やりwebカメラを動かすコードが下記。長…というか途中からJavaScriptになってる。 ```python import IPython from google.colab import output import cv2 import numpy as np from PIL import Image from io import BytesIO import base64 def run(img_str): #decode to image decimg = base64.b64decode(img_str.split(',')[1], validate=True) decimg = Image.open(BytesIO(decimg)) decimg = np.array(decimg, dtype=np.uint8); decimg = cv2.cvtColor(decimg, cv2.COLOR_BGR2RGB) #############your process############### out_img = cv2.Canny(decimg,100,200) #out_img = decimg #############your process############### #encode to string _, encimg = cv2.imencode(".jpg", out_img, [int(cv2.IMWRITE_JPEG_QUALITY), 80]) img_str = encimg.tostring() img_str = "data:image/jpeg;base64," + base64.b64encode(img_str).decode('utf-8') return IPython.display.JSON({'img_str': img_str}) output.register_callback('notebook.run', run) ``` ```python from IPython.display import display, Javascript from google.colab.output import eval_js def use_cam(quality=0.8): js = Javascript(''' async function useCam(quality) { const div = document.createElement('div'); document.body.appendChild(div); //video element const video = document.createElement('video'); video.style.display = 'None'; const stream = await navigator.mediaDevices.getUserMedia({video: true}); div.appendChild(video); video.srcObject = stream; await video.play(); //canvas for display. frame rate is depending on display size and jpeg quality. display_size = 500 const src_canvas = document.createElement('canvas'); src_canvas.width = display_size; src_canvas.height = display_size * video.videoHeight / video.videoWidth; const src_canvasCtx = src_canvas.getContext('2d'); src_canvasCtx.translate(src_canvas.width, 0); src_canvasCtx.scale(-1, 1); div.appendChild(src_canvas); const dst_canvas = document.createElement('canvas'); dst_canvas.width = src_canvas.width; dst_canvas.height = src_canvas.height; const dst_canvasCtx = dst_canvas.getContext('2d'); div.appendChild(dst_canvas); //exit button const btn_div = document.createElement('div'); document.body.appendChild(btn_div); const exit_btn = document.createElement('button'); exit_btn.textContent = 'Exit'; var exit_flg = true exit_btn.onclick = function() {exit_flg = false}; btn_div.appendChild(exit_btn); // Resize the output to fit the video element. google.colab.output.setIframeHeight(document.documentElement.scrollHeight, true); var send_num = 0 // loop _canvasUpdate(); async function _canvasUpdate() { src_canvasCtx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, src_canvas.width, src_canvas.height); if (send_num<1){ send_num += 1 const img = src_canvas.toDataURL('image/jpeg', quality); const result = google.colab.kernel.invokeFunction('notebook.run', [img], {}); result.then(function(value) { parse = JSON.parse(JSON.stringify(value))["data"] parse = JSON.parse(JSON.stringify(parse))["application/json"] parse = JSON.parse(JSON.stringify(parse))["img_str"] var image = new Image() image.src = parse; image.onload = function(){dst_canvasCtx.drawImage(image, 0, 0)} send_num -= 1 }) } if (exit_flg){ requestAnimationFrame(_canvasUpdate); }else{ stream.getVideoTracks()[0].stop(); } }; } ''') display(js) data = eval_js('useCam({})'.format(quality)) ``` ```python use_cam() ```