一、概述
目标跟踪是指对输入视频文件或实时摄像头图像中的运动物体进行跟踪,常见的目标跟踪算法有Kalman滤波、粒子滤波、Goturn、Unicorn Traker 以及KCF等。本实验基于粒子滤波算法(Particle Filter)对通过摄像头输入的实时视频进行目标跟踪。
基于PF的目标跟踪,每次通过当前的跟踪结果重采样粒子的分布,然后根据粒子的分布对粒子进行扩散,再通过扩散的结果来重新观察目标的状态,最后归一化更新目标的状态。此算法的特点是跟踪速度特别快,而且能解决目标的部分遮挡问题,在实际工程应用过程中越来越多的被使用。
其主要步骤如下:
二、效果展示
首先框选目标
选中目标后,移动目标:
可见,随着目标移动,蓝框和绿框时钟框住目标并跟随其移动。其中绿色框为跟踪过程中粒子所在位置的矩形框,是对目标区域的预测。蓝色框为最终得到的跟踪目标区域。
代码如下:
#####################################################################
# Example : Condensation based cam shift object track processing
# from a video file specified on the command line (e.g. python FILE.py video_file)
# or from an attached web camera
# N.B. use mouse to select region
# Author : Toby Breckon, toby.breckon@durham.ac.uk
# Copyright (c) 2017 Toby Breckon
# Durham University, UK
# License : LGPL - http://www.gnu.org/licenses/lgpl.html
#####################################################################
import cv2
import math
import numpy as np
import Condensation as cons
#####################################################################
keep_processing = True;
selection_in_progress = False; # support interactive region selection
fullscreen = False; # run in fullscreen mode
# select a region using the mouse
boxes = [];
current_mouse_position = np.ones(2, dtype=np.int32);
def on_mouse(event, x, y, flags, params):
global boxes;
global selection_in_progress;
current_mouse_position[0] = x;
current_mouse_position[1] = y;
if event == cv2.EVENT_LBUTTONDOWN:
boxes = [];
sbox = [x, y];
selection_in_progress = True;
boxes.append(sbox);
elif event == cv2.EVENT_LBUTTONUP:
ebox = [x, y];
selection_in_progress = False;
boxes.append(ebox);
# return centre of a set of points representing a rectangle
def center(points):
x = np.float32((points[0][0] + points[1][0] + points[2][0] + points[3][0]) / 4.0)
y = np.float32((points[0][1] + points[1][1] + points[2][1] + points[3][1]) / 4.0)
return np.array([np.float32(x), np.float32(y)], np.float32)
def nothing(x):
pass
# draw a cross on the specified image at location, colour, size (d)
# specified
def drawCross(img, center, color, d):
#On error change cv2.CV_AA for cv2.LINE_AA
cv2.line(img, (center[0] - d, center[1] - d), \
(center[0] + d, center[1] + d), color, 2, cv2.LINE_AA, 0)
cv2.line(img, (center[0] + d, center[1] - d), \
(center[0]- d, center[1] + d), color, 2, cv2.LINE_AA, 0)
# define video capture object
cap = cv2.VideoCapture(0,cv2.CAP_DSHOW) #or ("filename.avi")
if cap.isOpened():
print("video is OK!")
else:
print("video fail!")
# define display window name
windowName = "Condensation Tracking"; # window name
#windowName2 = "Hue histogram back projection"; # window name
#windowNameSelection = "initial selected region";
# init Condensation object
dimensions = 2 # number of parameters for tracking
nParticles = 100 # number of particles to use
xRange = 640.0 # image width
yRange = 480.0 # image hieght
LB = [0.0, 0.0] # lower bounds on sampling
UB = [xRange, yRange] # upper bounds on sampling
tracker = cons.Condensation(dimensions, dimensions, nParticles)
tracker.cvConDensInitSampleSet(LB, UB)
tracker.DynamMatr = [[1.0, 0.0],[0.0, 1.0]]
# if command line arguments are provided try to read video_name
# otherwise default to capture from attached H/W camera
'''
if (((args.video_file) and (cap.open(str(args.video_file))))
or (cap.open(args.camera_to_use))):
'''
state=1
if(state==1):
# cap.open(0)
# create window by name (note flags for resizable or not)
cv2.namedWindow(windowName, cv2.WINDOW_NORMAL);
# set a mouse callback
cv2.setMouseCallback(windowName, on_mouse, 0);
cropped = False;
print("\nObservation in image: BLUE");
print("Prediction from Condensation Tracker: GREEN\n");
# Setup the termination criteria for search, either 10 iteration or
# move by at least 1 pixel pos. difference
term_crit = ( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1 )
frame_count = 0
while (keep_processing):
# replaying video
frame_count = frame_count + 1
if frame_count == int(cap.get(cv2.CAP_PROP_FRAME_COUNT)):
frame_count = 0
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
if (cap.isOpened):
ret, frame = cap.read();
# set parameters from track bars
s_lower = 60
s_upper = 255
v_lower = 30
v_upper = 255
# select region using the mouse and display it
if (len(boxes) > 1) and (boxes[0][1] < boxes[1][1]) and (boxes[0][0] < boxes[1][0]):
crop = frame[boxes[0][1]:boxes[1][1],boxes[0][0]:boxes[1][0]].copy()
h, w, c = crop.shape; # size of template
if (h > 0) and (w > 0):
cropped = True;
# convert region to HSV
hsv_crop = cv2.cvtColor(crop, cv2.COLOR_BGR2HSV);
# select all Hue and Sat. values (0-> 180) but eliminate values with very low
# saturation or value (due to lack of useful colour information)
mask = cv2.inRange(hsv_crop, np.array((0., float(s_lower),float(v_lower))), np.array((180.,float(s_upper),float(v_upper))));
# construct a histogram of hue and saturation values and normalize it
crop_hist = cv2.calcHist([hsv_crop],[0, 1],mask,[180, 255],[0,180, 0, 255]);
cv2.normalize(crop_hist,crop_hist,0,255,cv2.NORM_MINMAX);
# set intial position of object
track_window = (boxes[0][0],boxes[0][1],boxes[1][0] - boxes[0][0],boxes[1][1] - boxes[0][1]);
# reset list of boxes
boxes = [];
# interactive display of selection box
if (selection_in_progress):
top_left = (boxes[0][0], boxes[0][1]);
bottom_right = (current_mouse_position[0], current_mouse_position[1]);
cv2.rectangle(frame,top_left, bottom_right, (0,255,0), 2);
# if we have a selected region
if (cropped):
# convert incoming image to HSV
img_hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV);
img_bproject = cv2.calcBackProject([img_hsv],[0,1],crop_hist,[0,180,0,255],1);
# apply camshift to predict new location (observation)
ret, track_window = cv2.CamShift(img_bproject, track_window, term_crit);
# draw observation on image
x,y,w,h = track_window;
frame = cv2.rectangle(frame, (x,y), (x+w,y+h), (255,0,0),2);
# extract centre of this observation as points
pts = cv2.boxPoints(ret)
pts = np.int0(pts)
pts = center(pts)
# use to update Condensation tracker
for hypothesis in range(tracker.SamplesNum):
# calculate the confidence based on the observations
diffX = (pts[0] - tracker.flSamples[hypothesis][0]) / xRange;
diffY = (pts[1] - tracker.flSamples[hypothesis][1]) / yRange;
tracker.flConfidence[hypothesis] = 1.0/(np.sqrt(np.power(diffX,2) + \
np.power(diffY,2)))
tracker.cvConDensUpdateByTime();
# get new Condensation tracker prediction
predictionF = tracker.State;
prediction = [int(s) for s in predictionF]
# draw predicton on image
frame = cv2.rectangle(frame, (prediction[0]-int(0.5*w),prediction[1]-int(0.5*h)), (prediction[0]+int(0.5*w),prediction[1]+int(0.5*h)), (0,255,0),2);
# draw all the tracker hypothesis samples on the image
for j in range(len(tracker.flSamples)):
posNew = [int(s) for s in tracker.flSamples[j]]
drawCross(frame, posNew, (255, 255, 0), 2)
else:
img_hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV);
mask = cv2.inRange(img_hsv, np.array((0., float(s_lower),float(v_lower))), np.array((180.,float(s_upper),float(v_upper))));
# display image
cv2.imshow(windowName,frame);
cv2.setWindowProperty(windowName, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN & fullscreen);
#key = cv2.waitKey(max(2, 40 - int(math.ceil(stop_t)))) & 0xFF;
key = cv2.waitKey(30)
# It can also be set to detect specific key strokes by recording which key is pressed
# e.g. if user presses "x" then exit / press "f" for fullscreen display
if (key == ord('x')):
keep_processing = False;
elif (key == ord('f')):
fullscreen = not(fullscreen);
# close all windows
cv2.destroyAllWindows()
else:
print("No video file specified or camera connected.");
Condensation.py
"""
Based on the OpenCV Version of the Condensation Algorithm and the
Android Version available here:
https://android.googlesource.com/platform/external/opencv/+/android-5.1.1_r6/cv/src/cvcondens.cpp
Read before:
http://www.anuncommonlab.com/articles/how-kalman-filters-work/
/*M///
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// Intel License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2000, Intel Corporation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of Intel Corporation may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
"""
import numpy as np
# Condensation Algorithm.
# Python version based on the original OpenCv Version and the
# Android version
# Requires the initialization of DynamMatr
# Example: DynamMatr = [[1.0, 0.0], [0.0, 1.0]]
# The confidence must be defined
# Example:
# a = (PositionX - sample[i][0])/650
# b = (PositionY - sample[i][1])/650
# flConfidence[i]=1.0/(sqrt(a*a + b*b))
class Condensation():
# Name: __init__
# Creating variables
# Parameters:
# DP - dimension of the state vector
# MP - dimension of the measurement vector
# SamplesNum - number of samples in sample set used in algorithm
def __init__(self, dimDP, dimMP, samplesNum):
if(dimDP < 0 or dimMP < 0 or samplesNum < 0):
raise ValueError("Parameters out of range")
self.SamplesNum = samplesNum
self.DP = dimDP
self.MP = dimMP
self.flSamples =[]
self.flNewSamples = np.empty([self.SamplesNum, self.DP], dtype=float)
self.flConfidence = []
self.flCumulative = np.zeros(self.SamplesNum, dtype=float)
self.DynamMatr = []
self.State = np.zeros(self.DP, dtype=float)
self.lowBound=np.empty(self.DP, dtype=float)
self.uppBound=np.empty(self.DP, dtype=float)
# Name: cvConDensInitSampleSet
# Initializing for the Condensation algorithm
# Parameters:
# conDens - pointer to CvConDensation structure
# lowerBound - vector of lower bounds used to random update
# of sample set
# upperBound - vector of upper bounds used to random update
# of sample set
#
def cvConDensInitSampleSet(self, lowerBound, upperBound):
prob = 1.0/self.SamplesNum
if((lowerBound is None) or (upperBound is None)):
raise ValueError("Lower/Upper Bound")
self.lowBound = lowerBound
self.uppBound = upperBound
# Generating the samples
for j in range(self.SamplesNum):
valTmp = np.zeros(self.DP, dtype=float)
for i in range(self.DP):
valTmp[i] = np.random \
.uniform(lowerBound[i],upperBound[i])
self.flSamples.append(valTmp)
self.flConfidence.append(prob)
# Name: cvConDensUpdateByTime
# Performing Time Update routine for ConDensation algorithm
# Parameters:
def cvConDensUpdateByTime(self):
valSum = 0
#Sets Temp To Zero
self.Temp = np.zeros(self.DP, dtype=float)
#Calculating the Mean
for i in range(self.SamplesNum):
self.State = np.multiply(self.flSamples[i], self.flConfidence[i])
self.Temp = np.add(self.Temp, self.State)
valSum += self.flConfidence[i]
self.flCumulative[i] = valSum
#Taking the new vector from transformation of mean by dynamics matrix
self.Temp = np.multiply(self.Temp, (1.0/valSum))
for i in range(self.DP):
self.State[i] = np.sum(np.multiply(self.DynamMatr[i],
self.Temp[i]))
valSum = valSum / float(self.SamplesNum)
#Updating the set of random samples
for i in range(self.SamplesNum):
j = 0
while (self.flCumulative[j] <= float(i)*valSum and
j < self.SamplesNum -1):
j += 1
self.flNewSamples[i] = self.flSamples[j]
lowerBound = np.empty(self.DP, dtype=float)
upperBound = np.empty(self.DP, dtype=float)
for j in range(self.DP):
lowerBound[j] = (self.lowBound[j] - self.uppBound[j])/5.0
upperBound[j] = (self.uppBound[j] - self.lowBound[j])/5.0
#Adding the random-generated vector to every vector in sample set
# which assumes a moiton model of equal likely motion in all directions
RandomSample = np.empty(self.DP, dtype=float)
for i in range(self.SamplesNum):
for j in range(self.DP):
RandomSample[j] = np.random.uniform(lowerBound[j],
upperBound[j])
self.flSamples[i][j] = np.sum(np.multiply(self.DynamMatr[j],
self.flNewSamples[i][j]))
self.flSamples[i] = np.add(self.flSamples[i], RandomSample)