平均値シフトでトラッキングしてみた(Python)
平均値シフトでトラッキングをしてみました.理屈はよくわかりませんが,最初にトラッキング対象を囲むようなウインドウを作成し,ウインドウ内でトラッキング対象の特徴量を計算してあげます.フレームが更新されるとウインドウ内の各画素でどれくらい特徴量に近いかの重みを計算してあげて,重みを使って重心を計算し,その重心を中心としたウインドウを作成することを繰り返してトラッキング対象を追跡するそうです.
重み計算
ウインドウの中心で一番重みが大きく,中心から離れるにつれて重みが小さくなるような関数を使用するそうです.すぐ思いつくのはガウス関数だと思います.ガウス関数だと下図のような感じで重みが広がっています.import scipy.ndimage.filters as fi import matplotlib.pyplot as plt input = np.zeros((500, 500)) input[500/2, 500/2] = 1 output = fi.gaussian_filter(input, 100) plt.imshow(output) plt.show()
こういった重み関数をカーネル関数とよぶらしく,収束を保証するためのものらしいです.さらにこの重みにトラッキング対象の特徴量を表す重みを掛けてあげる必要があります.今回は初期ウインドウ内で正規化ヒストグラムを使って,どの画素値をもっていればトラッキング対象らしいかを表す確率のようなものを計算してあげます.画像処理の世界ではバックプロジェクションとよぶそうです.
最終的に重心計算の際には,ウインドウ内の各画素を,カーネル関数の重みを,特徴量の重みをとすると重心は,
みたいな感じになるそうです.求めた重心を中心にまたウインドウを作成し,また重心を求めるを繰り返して物体を追跡します.プログラムを組む
プログラムは以下の様になります.opencvのカメラ処理のところはどうでもいいです.import numpy as np import cv2 import scipy.stats as st class CameraGui(object): def __init__(self): self.pos1, self.pos2 = None, None self.image = None self.patch_height, self.patch_width = None, None self.kernel = None def start(self): cap = cv2.VideoCapture(0) cv2.namedWindow("frame") cv2.setMouseCallback("frame", self.mouseFunc) while(True): ret, frame = cap.read() self.image = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) draw = self.tracking(self.image) if draw != None: cv2.imshow("frame", draw) else: cv2.imshow("frame", self.image) if cv2.waitKey(20) & 0xFF == 27: break self.cap.release() cv2.destroyAllWindows() def mouseFunc(self, event, x, y, flags, param): if event == cv2.EVENT_LBUTTONDOWN: if self.pos1 == None: self.pos1 = [y, x] elif self.pos2 == None: self.pos2 = [y, x] self.hist = cv2.calcHist([self.image[self.pos1[0]:self.pos2[0], self.pos1[1]:self.pos2[1]]], [0], None, [256], [0, 256]).astype(np.float) self.hist /= np.sum(self.hist) self.patch_height = self.pos2[0] - self.pos1[0] self.patch_width = self.pos2[1] - self.pos1[1] self.kernel = self.calc_gkern(self.patch_height, self.patch_width) def tracking(self, image): if self.pos1 != None and self.pos2 != None: center = self.calcWeight(image) draw = cv2.cvtColor(np.copy(image), cv2.COLOR_GRAY2RGB) pt1 = (center[0] - self.patch_height / 2, center[1] - self.patch_width / 2) pt2 = (center[0] + self.patch_height / 2, center[1] + self.patch_width / 2) draw = cv2.circle(draw, (center[1], center[0]), 50, (0, 0, 255), -1) self.pos1 = pt1 self.pos2 = pt2 return draw return None def calcWeight(self, image): patch = image[self.pos1[0]:self.pos1[0]+self.patch_height,self.pos1[1]:self.pos1[1]+self.patch_width] height, width = patch.shape weight = np.copy(self.kernel) #back projection for i in range(height): for j in range(width): weight[i, j] *= self.hist[patch[i, j]] #calculate center of mass Y, X = np.mgrid[:height, :width] Y, X = Y * weight, X * weight W = np.sum(weight) centerY, centerX = np.sum(Y) / W, np.sum(X) / W return [int(self.pos1[0] + centerY), int(self.pos1[1] + centerX)] def calc_gkern(self, height, width): import scipy.ndimage.filters as fi inp = np.zeros((height, width)) inp[height//2, width//2] = 1 return fi.gaussian_filter(inp, 50) def main(): gui = CameraGui() gui.start() main()