コンピュータサイエンス系勉強ノート

計算機科学に限らず日々学んだことを色々まとめていきます

平均値シフトでトラッキングしてみた(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()

f:id:clientver2:20151122134834p:plain

こういった重み関数をカーネル関数とよぶらしく,収束を保証するためのものらしいです.さらにこの重みにトラッキング対象の特徴量を表す重みを掛けてあげる必要があります.今回は初期ウインドウ内で正規化ヒストグラムを使って,どの画素値をもっていればトラッキング対象らしいかを表す確率のようなものを計算してあげます.画像処理の世界ではバックプロジェクションとよぶそうです.

最終的に重心計算の際には,ウインドウ内の各画素を x_i (i=0...N)カーネル関数の重みを g_i,特徴量の重みを w_iとすると重心 Pは,

 P = \frac{\sum_{i=0}^{N}x_iw_ig_i}{\sum_{i=0}^{N}w_ig_i}
みたいな感じになるそうです.求めた重心を中心にまたウインドウを作成し,また重心を求めるを繰り返して物体を追跡します.

プログラムを組む

プログラムは以下の様になります.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()

結果

一応トラッキングはできました.物体を早く動かすとダメでした.適当に組んでるので改良の余地は大いにあります.気が向いたらちゃんと組みます.