OpenCVで動画から動いている物体を検出してみる(MOGやKNNで簡単に) 背景差分(Background_Subtraction)など
前回は静止画像でクラゲを検出してみましたが、今度は動画読み込みでクラゲを追跡検出してみました。学習とか関係なく単純に輪郭を検出する方法です。
参考にしたのはこのあたりです。
https://docs.opencv.org/4.1.1/d1/dc5/tutorial_background_subtraction.html
素材はPixabayから拝借した以下のクラゲ動画です。これにチュートリアルサイトを参考にクラゲ追跡して(背景とクラゲの領域を切り分けて)輪郭と四角で囲んでみたいと思います。
動いている物体と背景をうまく分離してくれるのがMOG2とかKNNとかのアルゴリズムでOpenCVに実装されていて簡単にだれでも背景差分が取れるようになっているんですね。素晴らしいですね。。
MOGはMixture of Gaussian Distributionのことで(よくわかりませんね勉強が必要です。)で背景差分で使われる方法みたいですね。
KNNはk-nearest neighbor algorithmでk近傍法、よく聞く単語ですが、私にはまだ勉強が必要です。
こちらサンプルコードです。チュートリアルのページのソースに動画保存のための書き込み処理、輪郭の書き込み処理が追加されてます。
import numpy as np
import cv2
#動画読み込み
cap = cv2.VideoCapture('jf.mp4')
#背景差分のアルゴリズム?を取得。KNNやMOG2
fgbg = cv2.createBackgroundSubtractorKNN()
#fgbg = cv2.createBackgroundSubtractorMOG2()
# 動画書き込み変数設定
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS)
fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
writer = cv2.VideoWriter('output.mp4', fourcc, fps, (width, height))
while(1):
ret, frame = cap.read()
if frame is None:
break
#背景を抽出してくれる。
fgmask = fgbg.apply(frame)
thresh = cv2.threshold(fgmask, 127, 255, cv2.THRESH_BINARY)[1]
contours,hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#輪郭をそのまま書き込み
areaframe=cv2.drawContours(frame,contours,-1,(0,255,0),2)
#輪郭を四角で囲む。あまり小さい四角は外す。
for cnt in contours:
area=cv2.contourArea(cnt)
if area >100:
x,y,w,h = cv2.boundingRect(cnt)
areaframe=cv2.rectangle(areaframe,(x,y),(x+w,y+h),(0,0,255),2)
cv2.imshow('frame',areaframe)
writer.write(areaframe)
k = cv2.waitKey(30) & 0xff
if k == 27:
break
cap.release()
writer.release()
cv2.destroyAllWindows()
ちなみに出来上がったMP4ファイルはサイズがもとのファイルより大きいですので、ffmpegでサイズ小さくしてあります。参考コマンドです。
##ffmpegインストール
$ sudo apt-get install ffmpeg
##適用したffmpegコマンド
$ ffmpeg -i outputKNN.mp4 -c:v libx264 -b:v 300k out.mp4
結果MOG2
結果KNN
差分なし
ただこの動画程度背景の切り分けができるのであれば背景差分を使わなくてもそれなりに抽出はできましたね。こちらはKNNもMOG2も使っていない場合の結果です。
まとめ
実際の問題に適用できるまではいろいろ調整が必要そうでしたが、たったこれだけのコードでここまでできることは検証できました。次は学習を取り入れて検出やってみたいと思います。