OpenCV – 背景差分で物体を検出する方法について

目次

概要

背景差分(background subtraction)を利用した物体検出手法と OpenCV での実装方法について解説します。

背景差分

背景差分は、静止したカメラで撮影された動画から、前景で起こっている変化に基づいて前景と背景を分離する手法です。分離した結果は、背景の画素を0、前景の画素を255とした2値画像で表します。 背景差分の一つの方法として、背景のフレームを予め登録しておき、現在のフレームと背景のフレームとの差を計算し、前景と背景を分離することが考えられます。この方法は単純ですが、影や照明が変化したことにより、背景の画素値が変わり、前景として誤って検出されてしまいます。このため、連続的に変化するフレームの情報を利用して、背景をモデル化するより高度な手法が一般には使用されます。

OpenCV の背景差分

OpenCV では、以下の背景差分アルゴリズムが利用できます。

背景差分アルゴリズムの使い方

背景差分アルゴリズムは BackgroundSubtractor クラスを継承しているので、パラメータの設定以外は使い方はすべて同じです。 動画のフレームを BackgroundSubtractor.apply() に渡すと、背景の画素を0、前景の画素を255とした2値画像が返されます。

背景差分結果を表示する

サンプルの動画として vtest.avi を利用します。

GUI を使用するので、Jupyter Notebook では実行できません。.py ファイルに保存して Python スクリプトとして実行してください。スクリプトは Ctrl+C で終了できます。

In [ ]:
import cv2

cap = cv2.VideoCapture("vtest.avi")
wait_secs = int(1000 / cap.get(cv2.CAP_PROP_FPS))

model = cv2.bgsegm.createBackgroundSubtractorMOG()

while True:
    ret, frame = cap.read()
    if not ret:
        break

    mask = model.apply(frame)

    cv2.imshow("Mask", mask)
    cv2.waitKey(wait_secs)

cap.release()
cv2.destroyAllWindows()

実行中のウィンドウ

背景差分結果を元に前景の画素のみ表示する

背景差分結果の2値画像を元に、フレームの背景の画素に黒 (0, 0, 0) を代入し、前景の画素のみ表示します。

In [ ]:
import cv2

cap = cv2.VideoCapture("vtest.avi")
wait_secs = int(1000 / cap.get(cv2.CAP_PROP_FPS))

model = cv2.bgsegm.createBackgroundSubtractorMOG()

while True:
    ret, frame = cap.read()
    if not ret:
        break

    mask = model.apply(frame)
    # 背景の画素は黒 (0, 0, 0) にする。
    frame[mask == 0] = 0

    cv2.imshow("Frame (Only Forground)", frame)
    cv2.waitKey(wait_secs)

cap.release()
cv2.destroyAllWindows()

実行中のウィンドウ

背景差分結果を元に物体を検出する

背景差分結果の2値画像に対して、cv2.findContours() で輪郭抽出をすることで、前景の物体を検出します。

  1. cv2.findContours() で輪郭を抽出する
  2. cv2.contourArea() で輪郭を面積を計算し、filter() で小さい輪郭は除く
  3. 輪郭を囲む外接矩形を取得する cv2.boundingRect()map() ですべての輪郭に適用する
  4. cv2.rectangle() で矩形を描画する
In [ ]:
import cv2

cap = cv2.VideoCapture("vtest.avi")
wait_secs = int(1000 / cap.get(cv2.CAP_PROP_FPS))

model = cv2.bgsegm.createBackgroundSubtractorMOG()

while True:
    ret, frame = cap.read()
    if not ret:
        break

    mask = model.apply(frame)

    # 輪郭抽出する。
    contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]

    # 小さい輪郭は除く
    contours = list(filter(lambda x: cv2.contourArea(x) > 500, contours))

    # 輪郭を囲む外接矩形を取得する。
    bboxes = list(map(lambda x: cv2.boundingRect(x), contours))

    # 矩形を描画する。
    for x, y, w, h in bboxes:
        cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

    cv2.imshow("Frame", frame)
    cv2.waitKey(wait_secs)

cap.release()
cv2.destroyAllWindows()

コメント

コメントする

目次