一、簡介
首先扯扯無參密度估計理論,無參密度估計也叫做非參數估計,屬於數理統計的一個分支,和參數密度估計共同構成了概率密度估計方法。參數密度估計方法要求特徵空間服從一個已知的概率密度函數,在實際的應用中這個條件很難達到。而無參數密度估計方法對先驗知識要求最少,完全依靠訓練數據進行估計,並且可以用於任意形狀的密度估計。所以依靠無參密度估計方法,即不事先規定概率密度函數的結構形式,在某一連續點處的密度函數值可由該點鄰域中的若干樣本點估計得出。常用的無參密度估計方法有:直方圖法、最近鄰域法和核密度估計法。
MeanShift算法正是屬於核密度估計法,它不需要任何先驗知識而完全依靠特徵空間中樣本點的計算其密度函數值。對於一組採樣數據,直方圖法通常把數據的值域分成若干相等的區間,數據按區間分成若干組,每組數據的個數與總參數個數的比率就是每個單元的概率值;核密度估計法的原理相似於直方圖法,只是多了一個用於平滑數據的核函數。採用核函數估計法,在採樣充分的情況下,能夠漸進地收斂於任意的密度函數,即可以對服從任何分佈的數據進行密度估計。
然後談談MeanShift的基本思想及物理含義:
此外,從公式1中可以看到,只要是落入Sh的採樣點,無論其離中心x的遠近,對最終的Mh(x)計算的貢獻是一樣的。然而在現實跟蹤過程中,當跟蹤目標出現遮擋等影響時,由於外層的像素值容易受遮擋或背景的影響,所以目標模型中心附近的像素比靠外的像素更可靠。因此,對於所有采樣點,每個樣本點的重要性應該是不同的,離中心點越遠,其權值應該越小。故引入核函數和權重係數來提高跟蹤算法的魯棒性並增加搜索跟蹤能力。
接下來,談談核函數:
核函數也叫窗口函數,在覈估計中起到平滑的作用。常用的核函數有:Uniform,Epannechnikov,Gaussian等。本文算法只用到了Epannechnikov,它數序定義如下:
二、基於MeanShift的目標跟蹤算法
基於均值漂移的目標跟蹤算法通過分別計算目標區域和候選區域內像素的特徵值概率得到關於目標模型和候選模型的描述,然後利用相似函數度量初始幀目標模型和當前幀的候選模版的相似性,選擇使相似函數最大的候選模型並得到關於目標模型的Meanshift向量,這個向量正是目標由初始位置向正確位置移動的向量。由於均值漂移算法的快速收斂性,通過不斷迭代計算Meanshift向量,算法最終將收斂到目標的真實位置,達到跟蹤的目的。
下面通過圖示直觀的說明MeanShift跟蹤算法的基本原理。如下圖所示:目標跟蹤開始於數據點xi0(空心圓點xi0,xi1,…,xiN表示的是中心點,上標表示的是的迭代次數,周圍的黑色圓點表示不斷移動中的窗口樣本點,虛線圓圈代表的是密度估計窗口的大小)。箭頭表示樣本點相對於核函數中心點的漂移向量,平均的漂移向量會指向樣本點最密集的方向,也就是梯度方向。因爲 Meanshift 算法是收斂的,因此在當前幀中通過反覆迭代搜索特徵空間中樣本點最密集的區域,搜索點沿着樣本點密度增加的方向“漂移”到局部密度極大點點xiN,也就是被認爲的目標位置,從而達到跟蹤的目的,MeanShift 跟蹤過程結束。
運動目標的實現過程【具體算法】:
三、代碼實現
說明:
1. RGB顏色空間刨分,採用16*16*16的直方圖
2. 目標模型和候選模型的概率密度計算公式參照上文
3. opencv版本運行:按P停止,截取目標,再按P,進行單目標跟蹤
4. Matlab版本,將視頻改爲圖片序列,第一幀停止,手工標定目標,雙擊目標區域,進行單目標跟蹤。
MATLAB版本:
function [] = select()
close all;
clear all;
%%%%%%%%%%%%%%%%%%根據一幅目標全可見的圖像圈定跟蹤目標%%%%%%%%%%%%%%%%%%%%%%%
I=imread('result72.jpg');
figure(1);
imshow(I);
[temp,rect]=imcrop(I);
[a,b,c]=size(temp); %a:row,b:col
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%計算目標圖像的權值矩陣%%%%%%%%%%%%%%%%%%%%%%%
y(1)=a/2;
y(2)=b/2;
tic_x=rect(1)+rect(3)/2;
tic_y=rect(2)+rect(4)/2;
m_wei=zeros(a,b);%權值矩陣
h=y(1)^2+y(2)^2 ;%帶寬
for i=1:a
for j=1:b
dist=(i-y(1))^2+(j-y(2))^2;
m_wei(i,j)=1-dist/h; %epanechnikov profile
end
end
C=1/sum(sum(m_wei));%歸一化係數
%計算目標權值直方圖qu
%hist1=C*wei_hist(temp,m_wei,a,b);%target model
hist1=zeros(1,4096);
for i=1:a
for j=1:b
%rgb顏色空間量化爲16*16*16 bins
q_r=fix(double(temp(i,j,1))/16); %fix爲趨近0取整函數
q_g=fix(double(temp(i,j,2))/16);
q_b=fix(double(temp(i,j,3))/16);
q_temp=q_r*256+q_g*16+q_b; %設置每個像素點紅色、綠色、藍色分量所佔比重
hist1(q_temp+1)= hist1(q_temp+1)+m_wei(i,j); %計算直方圖統計中每個像素點佔的權重
end
end
hist1=hist1*C;
rect(3)=ceil(rect(3));
rect(4)=ceil(rect(4));
%%%%%%%%%%%%%%%%%%%%%%%%%讀取序列圖像
myfile=dir('D:\matlab7\work\mean shift\image\*.jpg');
lengthfile=length(myfile);
for l=1:lengthfile
Im=imread(myfile(l).name);
num=0;
Y=[2,2];
%%%%%%%mean shift迭代
while((Y(1)^2+Y(2)^2>0.5)&num<20) %迭代條件
num=num+1;
temp1=imcrop(Im,rect);
%計算侯選區域直方圖
%hist2=C*wei_hist(temp1,m_wei,a,b);%target candidates pu
hist2=zeros(1,4096);
for i=1:a
for j=1:b
q_r=fix(double(temp1(i,j,1))/16);
q_g=fix(double(temp1(i,j,2))/16);
q_b=fix(double(temp1(i,j,3))/16);
q_temp1(i,j)=q_r*256+q_g*16+q_b;
hist2(q_temp1(i,j)+1)= hist2(q_temp1(i,j)+1)+m_wei(i,j);
end
end
hist2=hist2*C;
figure(2);
subplot(1,2,1);
plot(hist2);
hold on;
w=zeros(1,4096);
for i=1:4096
if(hist2(i)~=0) %不等於
w(i)=sqrt(hist1(i)/hist2(i));
else
w(i)=0;
end
end
%變量初始化
sum_w=0;
xw=[0,0];
for i=1:a;
for j=1:b
sum_w=sum_w+w(uint32(q_temp1(i,j))+1);
xw=xw+w(uint32(q_temp1(i,j))+1)*[i-y(1)-0.5,j-y(2)-0.5];
end
end
Y=xw/sum_w;
%中心點位置更新
rect(1)=rect(1)+Y(2);
rect(2)=rect(2)+Y(1);
end
%%%跟蹤軌跡矩陣%%%
tic_x=[tic_x;rect(1)+rect(3)/2];
tic_y=[tic_y;rect(2)+rect(4)/2];
v1=rect(1);
v2=rect(2);
v3=rect(3);
v4=rect(4);
%%%顯示跟蹤結果%%%
subplot(1,2,2);
imshow(uint8(Im));
title('目標跟蹤結果及其運動軌跡');
hold on;
plot([v1,v1+v3],[v2,v2],[v1,v1],[v2,v2+v4],[v1,v1+v3],[v2+v4,v2+v4],[v1+v3,v1+v3],[v2,v2+v4],'LineWidth',2,'Color','r');
plot(tic_x,tic_y,'LineWidth',2,'Color','b');
end
運行結果:
opencv版本:
#include "stdafx.h"
#include "cv.h"
#include "highgui.h"
#define u_char unsigned char
#define DIST 0.5
#define NUM 20
//全局變量
bool pause = false;
bool is_tracking = false;
CvRect drawing_box;
IplImage *current;
double *hist1, *hist2;
double *m_wei; //權值矩陣
double C = 0.0; //歸一化係數
void init_target(double *hist1, double *m_wei, IplImage *current)
{
IplImage *pic_hist = 0;
int t_h, t_w, t_x, t_y;
double h, dist;
int i, j;
int q_r, q_g, q_b, q_temp;
t_h = drawing_box.height;
t_w = drawing_box.width;
t_x = drawing_box.x;
t_y = drawing_box.y;
h = pow(((double)t_w)/2,2) + pow(((double)t_h)/2,2); //帶寬
pic_hist = cvCreateImage(cvSize(300,200),IPL_DEPTH_8U,3); //生成直方圖圖像
//初始化權值矩陣和目標直方圖
for (i = 0;i < t_w*t_h;i++)
{
m_wei[i] = 0.0;
}
for (i=0;i<4096;i++)
{
hist1[i] = 0.0;
}
for (i = 0;i < t_h; i++)
{
for (j = 0;j < t_w; j++)
{
dist = pow(i - (double)t_h/2,2) + pow(j - (double)t_w/2,2);
m_wei[i * t_w + j] = 1 - dist / h;
//printf("%f\n",m_wei[i * t_w + j]);
C += m_wei[i * t_w + j] ;
}
}
//計算目標權值直方
for (i = t_y;i < t_y + t_h; i++)
{
for (j = t_x;j < t_x + t_w; j++)
{
//rgb顏色空間量化爲16*16*16 bins
q_r = ((u_char)current->imageData[i * current->widthStep + j * 3 + 2]) / 16;
q_g = ((u_char)current->imageData[i * current->widthStep + j * 3 + 1]) / 16;
q_b = ((u_char)current->imageData[i * current->widthStep + j * 3 + 0]) / 16;
q_temp = q_r * 256 + q_g * 16 + q_b;
hist1[q_temp] = hist1[q_temp] + m_wei[(i - t_y) * t_w + (j - t_x)] ;
}
}
//歸一化直方圖
for (i=0;i<4096;i++)
{
hist1[i] = hist1[i] / C;
//printf("%f\n",hist1[i]);
}
//生成目標直方圖
double temp_max=0.0;
for (i = 0;i < 4096;i++) //求直方圖最大值,爲了歸一化
{
//printf("%f\n",val_hist[i]);
if (temp_max < hist1[i])
{
temp_max = hist1[i];
}
}
//畫直方圖
CvPoint p1,p2;
double bin_width=(double)pic_hist->width/4096;
double bin_unith=(double)pic_hist->height/temp_max;
for (i = 0;i < 4096; i++)
{
p1.x = i * bin_width;
p1.y = pic_hist->height;
p2.x = (i + 1)*bin_width;
p2.y = pic_hist->height - hist1[i] * bin_unith;
//printf("%d,%d,%d,%d\n",p1.x,p1.y,p2.x,p2.y);
cvRectangle(pic_hist,p1,p2,cvScalar(0,255,0),-1,8,0);
}
cvSaveImage("hist1.jpg",pic_hist);
cvReleaseImage(&pic_hist);
}
void MeanShift_Tracking(IplImage *current)
{
int num = 0, i = 0, j = 0;
int t_w = 0, t_h = 0, t_x = 0, t_y = 0;
double *w = 0, *hist2 = 0;
double sum_w = 0, x1 = 0, x2 = 0,y1 = 2.0, y2 = 2.0;
int q_r, q_g, q_b;
int *q_temp;
IplImage *pic_hist = 0;
t_w = drawing_box.width;
t_h = drawing_box.height;
pic_hist = cvCreateImage(cvSize(300,200),IPL_DEPTH_8U,3); //生成直方圖圖像
hist2 = (double *)malloc(sizeof(double)*4096);
w = (double *)malloc(sizeof(double)*4096);
q_temp = (int *)malloc(sizeof(int)*t_w*t_h);
while ((pow(y2,2) + pow(y1,2) > 0.5)&& (num < NUM))
{
num++;
t_x = drawing_box.x;
t_y = drawing_box.y;
memset(q_temp,0,sizeof(int)*t_w*t_h);
for (i = 0;i<4096;i++)
{
w[i] = 0.0;
hist2[i] = 0.0;
}
for (i = t_y;i < t_h + t_y;i++)
{
for (j = t_x;j < t_w + t_x;j++)
{
//rgb顏色空間量化爲16*16*16 bins
q_r = ((u_char)current->imageData[i * current->widthStep + j * 3 + 2]) / 16;
q_g = ((u_char)current->imageData[i * current->widthStep + j * 3 + 1]) / 16;
q_b = ((u_char)current->imageData[i * current->widthStep + j * 3 + 0]) / 16;
q_temp[(i - t_y) *t_w + j - t_x] = q_r * 256 + q_g * 16 + q_b;
hist2[q_temp[(i - t_y) *t_w + j - t_x]] = hist2[q_temp[(i - t_y) *t_w + j - t_x]] + m_wei[(i - t_y) * t_w + j - t_x] ;
}
}
//歸一化直方圖
for (i=0;i<4096;i++)
{
hist2[i] = hist2[i] / C;
//printf("%f\n",hist2[i]);
}
//生成目標直方圖
double temp_max=0.0;
for (i=0;i<4096;i++) //求直方圖最大值,爲了歸一化
{
if (temp_max < hist2[i])
{
temp_max = hist2[i];
}
}
//畫直方圖
CvPoint p1,p2;
double bin_width=(double)pic_hist->width/(4368);
double bin_unith=(double)pic_hist->height/temp_max;
for (i = 0;i < 4096; i++)
{
p1.x = i * bin_width;
p1.y = pic_hist->height;
p2.x = (i + 1)*bin_width;
p2.y = pic_hist->height - hist2[i] * bin_unith;
cvRectangle(pic_hist,p1,p2,cvScalar(0,255,0),-1,8,0);
}
cvSaveImage("hist2.jpg",pic_hist);
for (i = 0;i < 4096;i++)
{
if (hist2[i] != 0)
{
w[i] = sqrt(hist1[i]/hist2[i]);
}else
{
w[i] = 0;
}
}
sum_w = 0.0;
x1 = 0.0;
x2 = 0.0;
for (i = 0;i < t_h; i++)
{
for (j = 0;j < t_w; j++)
{
//printf("%d\n",q_temp[i * t_w + j]);
sum_w = sum_w + w[q_temp[i * t_w + j]];
x1 = x1 + w[q_temp[i * t_w + j]] * (i - t_h/2);
x2 = x2 + w[q_temp[i * t_w + j]] * (j - t_w/2);
}
}
y1 = x1 / sum_w;
y2 = x2 / sum_w;
//中心點位置更新
drawing_box.x += y2;
drawing_box.y += y1;
//printf("%d,%d\n",drawing_box.x,drawing_box.y);
}
free(hist2);
free(w);
free(q_temp);
//顯示跟蹤結果
cvRectangle(current,cvPoint(drawing_box.x,drawing_box.y),cvPoint(drawing_box.x+drawing_box.width,drawing_box.y+drawing_box.height),CV_RGB(255,0,0),2);
cvShowImage("Meanshift",current);
//cvSaveImage("result.jpg",current);
cvReleaseImage(&pic_hist);
}
void onMouse( int event, int x, int y, int flags, void *param )
{
if (pause)
{
switch(event)
{
case CV_EVENT_LBUTTONDOWN:
//the left up point of the rect
drawing_box.x=x;
drawing_box.y=y;
break;
case CV_EVENT_LBUTTONUP:
//finish drawing the rect (use color green for finish)
drawing_box.width=x-drawing_box.x;
drawing_box.height=y-drawing_box.y;
cvRectangle(current,cvPoint(drawing_box.x,drawing_box.y),cvPoint(drawing_box.x+drawing_box.width,drawing_box.y+drawing_box.height),CV_RGB(255,0,0),2);
cvShowImage("Meanshift",current);
//目標初始化
hist1 = (double *)malloc(sizeof(double)*16*16*16);
m_wei = (double *)malloc(sizeof(double)*drawing_box.height*drawing_box.width);
init_target(hist1, m_wei, current);
is_tracking = true;
break;
}
return;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
CvCapture *capture=cvCreateFileCapture("test.avi");
current = cvQueryFrame(capture);
char res[20];
int nframe = 0;
while (1)
{
/* sprintf(res,"result%d.jpg",nframe);
cvSaveImage(res,current);
nframe++;*/
if(is_tracking)
{
MeanShift_Tracking(current);
}
int c=cvWaitKey(1);
//暫停
if(c == 'p')
{
pause = true;
cvSetMouseCallback( "Meanshift", onMouse, 0 );
}
while(pause){
if(cvWaitKey(0) == 'p')
pause = false;
}
cvShowImage("Meanshift",current);
current = cvQueryFrame(capture); //抓取一幀
}
cvNamedWindow("Meanshift",1);
cvReleaseCapture(&capture);
cvDestroyWindow("Meanshift");
return 0;
}
運行結果:
初始目標直方圖:
候選目標直方圖:
運行結果: