Meanshiftアルゴリズムのアイデアを直感的に理解することは簡単である.点の集合(ヒストグラムの逆投影法のような画素の分布など)があるとする.ここでの仕事は,与えられた小さなウィンドウを移動し,画素の分布密度(もしくは画素数)が最大になる領域にウィンドウの位置を合わせることである.この状況を以下の図に示す:
最初のウィンドウは “C1” と名付けられ、青い円で描かれている. “C1” の中心 “C1_o” は青い四角形でマークされている.window内に位置する点群の重心を “C1_r” (小さい青い円でマークされている)とする.この二点(“C1_o” と “C1_r”)は確実に異なる点である.そこで,ウィンドウが重心 “C1_r” に重なるように移動させる.再びウィンドウ内に位置する点群の重心を計算する.おそらく今回もwindow内の重心と円の中心は重ならないだろう.この重心の計算と円の移動を二点が重なるまで(もしくは二点の距離が許容誤差範囲に収まるまで)繰り返す.最終的に画素の分布密度が最大となる位置にウィンドウが移動する.この時の円 “C2” を緑色で描いている.画像を見れば分かるように,緑色の円の中に最も点が密集している.以下のアニメーションがmeanshiftアルゴリズムを使った物体追跡の全体的な流れを説明している:(肌の色モデル(HSVで32×32×32のヒストグラム)→ウィンドゥの初期化→平均値の計算→平均値の移動(1回目)→ウィンドゥの位置(1回目)→平均値の計算→平均値の移動(2回目)→...→ウィンドゥの位置(4回目)→収束→ウィンドゥの移動図)
普通はヒストグラムを逆投影した画像と対象物体の初期位置を指定する.物体が動くと,その動きはヒストグラムを逆投影した画像中にはっきりと反映される.結果として,meanshiftアルゴリズムはウィンドウを密度が最大となる場所へと移動させる.
OpenCVでmeanshiftを使うには,まず初めに追跡対象を設定し,対象のヒストグラムを計算する必要がある.そうすればmeanshiftの計算をする際に,追跡対象を各フレームに対して逆投影できる.また,ウィンドウの初期位置も設定する必要がある.ここで紹介する例ではHue成分のみを考慮する.暗い光源化での追跡失敗を防ぐために,cv2.inRange()関数を使って暗い画素値を無視するようにしている:使用している動画
import numpy as np
import cv2
cap = cv2.VideoCapture('slow.avi')
# take first frame of the video
ret,frame = cap.read()
# setup initial location of window
r,h,c,w = 100,50,500,80 # simply hardcoded the values
track_window = (c,r,w,h)
# set up the ROI for tracking
roi = frame[r:r+h, c:c+w]
hsv_roi = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.)))
roi_hist = cv2.calcHist([hsv_roi],[0],mask,[180],[0,180])
cv2.normalize(roi_hist,roi_hist,0,255,cv2.NORM_MINMAX)
# Setup the termination criteria, either 10 iteration or move by atleast 1 pt
term_crit = ( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1 )
# i=1
while(1):
ret ,frame = cap.read()
if ret == True:
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
dst = cv2.calcBackProject([hsv],[0],roi_hist,[0,180],1)
# apply meanshift to get the new location
ret, track_window = cv2.meanShift(dst, track_window, term_crit)
# Draw it on image
x,y,w,h = track_window
img2 = cv2.rectangle(frame, (x,y), (x+w,y+h), 255,2)
cv2.imshow('img2',img2)
# cv2.imwrite("stamp"+str(i)+".jpg",img2)
# i += 1
k = cv2.waitKey(60) & 0xff
if k == 27:
break
else:
cv2.imwrite(str(k)+".jpg",img2)
else:
break
cv2.destroyAllWindows()
for _ in range(5):
cv2.waitKey(1)
cap.release()
この結果をよく観察しただろうか?実は問題があった.追跡対象の車が近づいてきているのに対し,追跡に使用したウィンドウは常に同じサイズだった.これはうまくない.追跡対象の見え方によってウィンドウのサイズを調節する必要がある.Gary Bradskが1988年に発表した “Computer Vision Face Tracking for Use in a Perceptual User Interface” で提案されたCAMshift (Continuously Adaptive Meanshift) を使えばこの問題を解決できる.
まず初めにmeanshiftアルゴリズムを適用する.meanshiftアルゴリズムの計算が収束したら $\displaystyle s = 2 \times \sqrt{\frac{M_{00}}{256}}$ というようにウィンドウサイズを変更する.同時に対象物体に最もフィットする楕円の回転角を計算する.再びmeanshiftアルゴリズムを適用するが,サイズを変更したウィンドウと前回の収束場所から計算を始める.このように,meanshiftアルゴリズムとウィンドウサイズの更新を収束条件が満たされるまで繰り返すのがCAMshiftアルゴリズムである.
meanshiftアルゴリズムとほとんど同じであるが,戻り値が回転した矩形とそのパラメータという点が異なる.コードは以下のようになる:
import numpy as np
import cv2
cap = cv2.VideoCapture('slow.avi')
# take first frame of the video
ret,frame = cap.read()
# setup initial location of window
r,h,c,w =100,50,500,80 # simply hardcoded the values
track_window = (c,r,w,h)
# set up the ROI for tracking
roi = frame[r:r+h, c:c+w]
hsv_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.)))
roi_hist = cv2.calcHist([hsv_roi],[0],mask,[180],[0,180])
cv2.normalize(roi_hist,roi_hist,0,255,cv2.NORM_MINMAX)
# Setup the termination criteria, either 10 iteration or move by atleast 1 pt
term_crit = ( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1 )
while(1):
ret ,frame = cap.read()
if ret == True:
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
dst = cv2.calcBackProject([hsv],[0],roi_hist,[0,180],1)
# apply meanshift to get the new location
ret, track_window = cv2.CamShift(dst, track_window, term_crit)
# Draw it on image
pts = cv2.boxPoints(ret)
pts = np.int0(pts)
img2 = cv2.polylines(frame,[pts],True, 255,2)
cv2.imshow('img2',img2)
k = cv2.waitKey(60) & 0xff
if k == 27:
break
else:
cv2.imwrite(chr(k)+".jpg",img2)
else:
break
cv2.destroyAllWindows()
for _ in range(5):
cv2.waitKey(1)
cap.release()
import numpy as np
import cv2
cap = cv2.VideoCapture('videos/slow%03d.jpg')
fourcc = cv2.VideoWriter_fourcc(*'X264')
out = cv2.VideoWriter('slow.avi',fourcc, 20.0, (640,480))
while(cap.isOpened()):
ret, frame = cap.read()
if ret:
cv2.imshow('frame',frame)
out.write(frame)
else:
break
if cv2.waitKey(25) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
# Notebookの場合はこれが必要
for _ in range(5):
cv2.waitKey(1)
cap.release()
%matplotlib inline
import matplotlib.pyplot as plt
r,h,c,w = 100,50,500,80 # simply hardcoded the values
cap = cv2.imread('videos/slow001.jpg')
cv2.rectangle(cap,(c,r),(c+w,r+h),(0,0,0),5)
plt.imshow(cap)