# 動体検知について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)
```
### 結果


まぁなんとなくできた(一部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()
```