agneto
最近在做无人机的目标追踪,于是了解了下camshift算法。找了好多资料,发现好多论文中的概念没有解释,就在这里整理下,顺便翻译了部分camshift论文内容。
1.公式全部LaTeX手打,写了简单的meanshift算法的matlab实现。
2.原论文的部分翻译在最下面,点目录就可以了。
3.opencv的camshift整个解析我也贴出来,整理了好久。
Meanshift算法是camshift算法的核心,处理方法可以说几乎相同,camshift仅仅是针对meanshift在连续帧情况下的优化版本罢了。meanshift是在一幅图上通过概率密度不断迭代找到最优解,而camshift是在连续帧情况下,一副一副图采用meanshift时优化了,继承上一帧图像最优解的窗口为起点继续meanshift迭代下一帧而不是新一帧又从头开始搜索。
这是opencv中meanshift的简要流程图,moment的概念在下面论文那里。
在下面写了代码的解读。
反向投影算法简要讲就是在计算图像的直方图后将直方图各分区再投影回去作为像素的值。一般讲rgb图像转换为hsv色域再取h(hue)分量进行反向投影。
opencv实现:在histogram.cpp里面,先调用cv::calcBackProject,在里面再根据输入参数格式调用原函数模板calcBackProj_,源码就不在这里贴了。
上面是opencv版本的meanshift算法,但按照1995年论文(链接)的优化版本,选定roi区域的概率密度计算增加了一个权重函数,根据距离不同贡献也不同。
meanshift算法属于非参数估计,需要的先验知识少,从目前数据进行估计。这里有篇博客(链接,这个部分不翻了网上好多解释)解释得很清楚,里面详细讲解了三种非参数估计方法。要了解meanshift原理需要了解推荐这篇博客里的核密度估计。
看完上面那篇博客的介绍,就可以简单了解到,核密度估计与直方图十分相似。先拿公式吓唬一下:
K(x)一般采用高斯核函数和Epanechnikov核函数。同时顶一个profile函数满足 K:[0,∞)→R ,即 K(x)=k(||x||) ,原式子变成:
计算权重有点特别
clear all;
close all;
%读入追踪图像
imglocation='/Users/Desktop/img.jpg' %图像储存位置
I=imread(imglocation);
[temp,rect]=imcrop(I); %imcrop用于选取ROI区域
[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));%归一化系数
%计算颜色概率密度
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; %计算rgb三通道权重累加
hist1(q_temp+1)= hist1(q_temp+1)+m_wei(i,j); %计算各bin中每个像素点占的权重
end
end
hist1=hist1*C;
rect(3)=ceil(rect(3));
rect(4)=ceil(rect(4));
%读入要跟踪的图像序列
myfile=dir('/Users/Desktop/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
camshift是对于meanshift算法的改进,称为连续自适应的MeanShift算法,CamShift算法的全称是”Continuously Adaptive Mean-SHIFT”,它的基本思想是视频图像的所有帧作MeanShift运算,并将上一帧的结果(即Search Window的中心和大小)作为下一帧MeanShift算法的Search Window的初始值,如此迭代下去。
因为论文第1,2部分是介绍和发展历史,因此不翻了。括号里面是原文section。
论文里面有加上一些自己的解释。
概率分布图(pdf:probability distribution image)的每一个像素对应这个像素在图中的分布概率。最常用的就是反向投影算法(backprojection,上面介绍过了)。
(论文里面讲得比较严谨)反向投影计算的直方图假设分为m个bin,并且有n个像素。 {qu^}u=1...m 表示 直方图标号, {xi^}i=1...n 表示像素的位置,并且定义一个函数 c:R→{1...m} 将 xi 像素转换为直方图对应bin的索引:
论文内容:
1.普通矩的计算(离散):
就是上面的核密度估计,在直方图统计基础上加了一个权重。
CV_IMPL int
cvCamShift( const void* imgProb, CvRect windowIn,
CvTermCriteria criteria,
CvConnectedComp* _comp,
CvBox2D* box )
{
const int TOLERANCE = 10;
CvMoments moments;
double m00 = 0, m10, m01, mu20, mu11, mu02, inv_m00;
double a, b, c, xc, yc;
double rotate_a, rotate_c;
double theta = 0, square;
double cs, sn;
double length = 0, width = 0;
int itersUsed = 0;
CvConnectedComp comp;
CvMat cur_win, stub, *mat = (CvMat*)imgProb;
comp.rect = windowIn;
mat = cvGetMat( mat, &stub );
itersUsed = cvMeanShift( mat, windowIn, criteria, &comp );
windowIn = comp.rect;
windowIn.x -= TOLERANCE;
if( windowIn.x < 0 )
windowIn.x = 0;
windowIn.y -= TOLERANCE;
if( windowIn.y < 0 )
windowIn.y = 0;
windowIn.width += 2 * TOLERANCE;
if( windowIn.x + windowIn.width > mat->width )
windowIn.width = mat->width - windowIn.x;
windowIn.height += 2 * TOLERANCE;
if( windowIn.y + windowIn.height > mat->height )
windowIn.height = mat->height - windowIn.y;
cvGetSubRect( mat, &cur_win, windowIn );
/* 计算moment,mu是中心矩 */
cvMoments( &cur_win, &moments );
m00 = moments.m00;
m10 = moments.m10;
m01 = moments.m01;
mu11 = moments.mu11;
mu20 = moments.mu20;
mu02 = moments.mu02;
if( fabs(m00) < DBL_EPSILON )
return -1;
inv_m00 = 1. / m00;
xc = cvRound( m10 * inv_m00 + windowIn.x );
yc = cvRound( m01 * inv_m00 + windowIn.y );
a = mu20 * inv_m00;
b = mu11 * inv_m00;
c = mu02 * inv_m00;
/* 计算长宽,公式在上面论文 */
square = sqrt( 4 * b * b + (a - c) * (a - c) );
/* 计算方向 */
theta = atan2( 2 * b, a - c + square );
cs = cos( theta );
sn = sin( theta );
rotate_a = cs * cs * mu20 + 2 * cs * sn * mu11 + sn * sn * mu02;
rotate_c = sn * sn * mu20 - 2 * cs * sn * mu11 + cs * cs * mu02;
length = sqrt( rotate_a * inv_m00 ) * 4;
width = sqrt( rotate_c * inv_m00 ) * 4;
if( length < width )
{
double t;
CV_SWAP( length, width, t );
CV_SWAP( cs, sn, t );
theta = CV_PI*0.5 - theta;
}
/* Saving results */
if( _comp || box )
{
int t0, t1;
int _xc = cvRound( xc );
int _yc = cvRound( yc );
t0 = cvRound( fabs( length * cs ));
t1 = cvRound( fabs( width * sn ));
t0 = MAX( t0, t1 ) + 2;
comp.rect.width = MIN( t0, (mat->width - _xc) * 2 );
t0 = cvRound( fabs( length * sn ));
t1 = cvRound( fabs( width * cs ));
t0 = MAX( t0, t1 ) + 2;
comp.rect.height = MIN( t0, (mat->height - _yc) * 2 );
comp.rect.x = MAX( 0, _xc - comp.rect.width / 2 );
comp.rect.y = MAX( 0, _yc - comp.rect.height / 2 );
comp.rect.width = MIN( mat->width - comp.rect.x, comp.rect.width );
comp.rect.height = MIN( mat->height - comp.rect.y, comp.rect.height );
comp.area = (float) m00;
}
if( _comp )
*_comp = comp;
if( box )
{
box->size.height = (float)length;
box->size.width = (float)width;
box->angle = (float)((CV_PI*0.5+theta)*180./CV_PI);
while(box->angle < 0)
box->angle += 360;
while(box->angle >= 360)
box->angle -= 360;
if(box->angle >= 180)
box->angle -= 180;
box->center = cvPoint2D32f( comp.rect.x + comp.rect.width*0.5f,
comp.rect.y + comp.rect.height*0.5f);
}
return itersUsed;
}