概要
背景差分(background subtraction)を利用した物体検出手法と OpenCV での実装方法について解説します。
背景差分
背景差分は、静止したカメラで撮影された動画から、前景で起こっている変化に基づいて前景と背景を分離する手法です。分離した結果は、背景の画素を 0、前景の画素を 255 とした 2 値画像で表されます。
背景差分の一つの方法として、背景のフレームを予め登録しておき、現在のフレームと背景のフレームとの差を計算することで前景と背景を分離する方法があります。この方法は単純ですが、影や照明の変化により背景の画素値が変化し、前景として誤って検出されることがあります。そのため、連続的に変化するフレームの情報を利用して背景をモデル化するより高度な手法が一般には使用されます。
OpenCV の背景差分
OpenCV では、以下の背景差分アルゴリズムが利用できます。
背景差分アルゴリズムの使い方
背景差分アルゴリズムは BackgroundSubtractor クラスを継承しているため、パラメータの設定以外は使い方はすべて共通です。 動画のフレームを BackgroundSubtractor.apply() に渡すと、背景の画素を 0、前景の画素を 255 とした 2 値画像が返されます。
背景差分結果を表示する
サンプルの動画として vtest.avi を利用します。(クリックするとダウンロード可能です)
GUI を使用するため、Jupyter Notebook では実行できません。.py
ファイルに保存し、Python スクリプトとして実行してください。スクリプトは Ctrl+C
で終了できます。
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)
を代入し、前景の画素のみを表示します。
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() で輪郭を抽出し、前景の物体を検出します。
- cv2.findContours() で輪郭を抽出する
- cv2.contourArea() で輪郭の面積を計算し、小さい輪郭を除く
- cv2.boundingRect() を
map()
で 輪郭を囲む外接矩形を取得する - cv2.rectangle() で矩形を描画する
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()
コメント