Skip to content

Instantly share code, notes, and snippets.

@iorionda
Last active March 20, 2019 19:28
Show Gist options
  • Save iorionda/9536e665f98c224e6b5d6aedc4556e05 to your computer and use it in GitHub Desktop.
Save iorionda/9536e665f98c224e6b5d6aedc4556e05 to your computer and use it in GitHub Desktop.
OpenCVを用いた動体検知

作業環境

  • macOS Sierra 10.12.6
  • Python 3.7.1

必要でなければ仮想環境を作成せずに直接 pip から opencv をインストールして問題ない。 今回は自分の環境に複数のバージョンのPythonが混在している為、仮想環境を用いて動作確認を行った。

仮想環境を作成

 ~/s/g/i/opencv_sample  pipenv install --python 3.7.1
Creating a virtualenv for this project…
Pipfile: /Users/iorionda/src/github.com/iorionda/opencv_sample/Pipfile
Using /Users/iorionda/.pyenv/versions/3.7.1/bin/python (3.7.1) to create virtualenv…
⠏ Creating virtual environment...Using base prefix '/Users/iorionda/.pyenv/versions/3.7.1'
New python executable in /Users/iorionda/.local/share/virtualenvs/opencv_sample-M5HMqQ7v/bin/python
Installing setuptools, pip, wheel...
done.
Running virtualenv with interpreter /Users/iorionda/.pyenv/versions/3.7.1/bin/python

✔ Successfully created virtual environment!
Virtualenv location: /Users/iorionda/.local/share/virtualenvs/opencv_sample-M5HMqQ7v
Creating a Pipfile for this project…
Pipfile.lock not found, creating…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Updated Pipfile.lock (a65489)!
Installing dependencies from Pipfile.lock (a65489)…
  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 0/0 — 00:00:00
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run.

 ~/s/g/i/opencv_sample  pipenv shell
Launching subshell in virtual environment…
Welcome to fish, the friendly interactive shell
Type help for instructions on how to use fish
 ~/s/g/i/opencv_sample   source /Users/iorionda/.local/share/virtualenvs/opencv_sample-M5HMqQ7v/bin/activate.fish

 (opencv_sample)  opencv_sample-M5HMqQ7v  ~/s/g/i/opencv_sample  python -V
Python 3.7.1

OpenCVのインストール

(opencv_sample)  opencv_sample-M5HMqQ7v  ~/s/g/i/opencv_sample  pipenv install opencv-python
Installing opencv-python…
Adding opencv-python to Pipfile's [packages]…
✔ Installation Succeeded
Pipfile.lock (4f8333) out of date, updating to (a65489)…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
✔ Success!
Updated Pipfile.lock (4f8333)!
Installing dependencies from Pipfile.lock (4f8333)…
  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 2/2 — 00:00:01

opencv-pythonの動作確認

$pipenv run python
Python 3.7.1 (default, Dec 26 2018, 22:14:57)
[Clang 9.0.0 (clang-900.0.39.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
>>> cv2.__version__
'4.0.0'
>>>

Canny法によるエッジ検出

>>> import cv2
>>> import numpy as np
>>> import matplotlib.pyplot as plt

>>> img = cv2.imread(<image_file_path>)
>>> edges = cv2.Canny(img, 150, 200)
>>> cv2.imwrite('/Users/iorionda/Desktop/P8270171_edges.jpg', edges)

ヒステリシス閾値化の小さい方の閾値を調整すると検出できるエッジが増えたり減ったりする。

ビデオカメラからキャプチャ

動画を扱う

import cv2

capture = cv2.VideoCapture(0) # カメラの場号を指定する、カメラが1台しか接続されていない場合は"0"を指定する。

while True: # カメラから連続的に画像を取得するために無限ループさせる
    ret,  frame = capture.read() # 1コマ分のキャプチャ画像を読み込む、取得した画像データはframeに代入される
    cv2.imshow('frame', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break;

capture.release()
cv2.destroyAllWindows()

ビデオカメラから検出した動態を矩形エリアで表示する

import cv2

capture = cv2.VideoCapture(0)
before = None

# エリアの最大値と最小値は任意の値に変更して
MAX_AREA_LIMIT = 50000
MIN_AREA_LIMIT = 1000

while capture.isOpened():
    #  OpenCVでWebカメラの画像を取り込む
    ret, frame = capture.read()
    # frameのサイズを変更
    # スクリーンショットを撮りたい関係で1/4サイズに縮小
    frame = cv2.resize(frame, (int(frame.shape[1]/2), int(frame.shape[0]/2)))

    # 加工なし画像を表示する
    cv2.imshow('frame', frame)

    # 取り込んだフレームに対して差分をとって動いているところが明るい画像を作る
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    gray = cv2.bitwise_not(gray, gray)
    if before is None:
        before = gray.copy().astype('float')
        continue

    # 現フレームと前フレームの加重平均を使うと良いらしい
    cv2.accumulateWeighted(gray, before, 0.2)
    mdframe = cv2.absdiff(gray, cv2.convertScaleAbs(before))
    # 動いているところが明るい画像を表示する
    cv2.imshow('MotionDetected Frame', mdframe)

    # 動いているエリアの面積を計算してちょうどいい検出結果を抽出する

    # 閾値を指定して2値化する
    _, thresh = cv2.threshold(mdframe, 3, 255, cv2.THRESH_BINARY)

    # 輪郭データに変換しくれるfindContours
    contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    max_area = 0
    target = contours[0]

    for cnt in contours:
         #輪郭の面積を求めてくれるcontourArea
        area = cv2.contourArea(cnt)

        if max_area < area and area < MAX_AREA_LIMIT and area > MIN_AREA_LIMIT:
            max_area = area;
            target = cnt

    # 動いているエリアのうちそこそこの大きさのものがあればそれを矩形で表示する
    if max_area <= MIN_AREA_LIMIT:
        areaframe = frame
        # 動体検知できていないことを表示しているだけ
        cv2.putText(areaframe, 'Not Detected', (0,50), cv2.FONT_HERSHEY_PLAIN, 3, (0, 255,0), 3, cv2.LINE_AA)
    else:
        # 矩形検出
        x, y, width, height = cv2.boundingRect(target)
        areaframe = cv2.rectangle(frame, (x, y),(x + width, y + height), (0,255,0), 2)

    cv2.imshow('MotionDetected Area Frame', areaframe)

    # q が押されたら終了させる、ラズパイだと何で終了させたらいいのかは自分で調べて
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break;

# キャプチャをリリースして、ウィンドウをすべて閉じる
capture.release()
cv2.destroyAllWindows()

カメラ内で指定した座標をトラッキングさせたい場合

OpenCVの場合はCamShiftが実装されているので、それを利用してトラッキングを行うことができる。

Meanshift and Camshift Mean Shiftの理論

でも、サンプルの説明をまだ書いてる途中なので待ってて。2019/03/19追記する予定。

@iorionda
Copy link
Author

import numpy as np
import cv2

frame = None
roi_points = []
input_mode = False

def selectROI(event, x, y, flags, params):
    global frame, roi_points, input_mode

    if input_mode and event == cv2.EVENT_LBUTTONDOWN and len(roi_points) < 4:
        roi_points.append((x, y))
        cv2.circle(frame, (x, y), 4 ,(0, 255, 0), 2)
        cv2.imshow("frame", frame)


def main():
    global frame, roi_points, input_mode

    capture = cv2.VideoCapture(0)

    cv2.namedWindow('frame')
    cv2.setMouseCallback('frame', selectROI)

    termination = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1)
    roi_box = None

    while capture.isOpened():
        (grabbed, frame) = capture.read()

        if not grabbed:
            break

        if roi_box is not None:
            hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
            back_project = cv2.calcBackProject([hsv], [0], roi_hist, [0, 180], 1)
            (r, roi_box) = cv2.CamShift(back_project, roi_box, termination)
            points = np.int0(cv2.boxPoints(r))
            cv2.polylines(frame, [points], True, (0, 255, 0), 2)

        cv2.imshow('frame', frame)
        key = cv2.waitKey(1) & 0xFF

        if key == ord('i') and len(roi_points) < 4:
            input_mode = True
            orig = frame.copy()

            while len(roi_points) < 4:
                cv2.imshow('frame', frame)
                cv2.waitKey(0)

            roi_points = np.array(roi_points)
            s = roi_points.sum(axis = 1)
            tl = roi_points[np.argmin(s)]
            br = roi_points[np.argmax(s)]

            roi = orig[tl[1]: br[1], tl[0]: br[0]]
            roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)

            roi_hist = cv2.calcHist([roi], [0], None, [16], [0, 180])
            roi_hist = cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX)
            roi_box = (tl[0], tl[1], br[0], br[1])

        elif key == ord('q'):
            break
    capture.release()
    cv2.destroyAllWindows()

if __name__ == '__main__':
    main()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment