文档下载链接
Mean shift作为一种跟踪算法经常被用到。它是一种无参数密度估计寻找局部极值的迭代逼近算法。
Mean shift直观描述
其中红点为特征点,蓝色为检测区域,黑点为检测区域中心,黑色虚线箭头为中心点到特征点向量,黄色箭头为检测区域内中心点到所有特征点向量和,是一个向量,这里称其为Mean shift向量(漂移向量)。
经过一次迭代,中心点向最优区域移动,移动量为上一漂移向量。
经过多次迭代,最终可以获取最优相似区域,即漂移向量值小于某阈值。
Mean shift算法原理
Mean shift算法基本思想
Mean shift算法模型实例(这里直接以视频目标跟踪为例)
目标模型:
候选模型:
首先候选模型与目标模型具有相同窗口大小和相同的核函数,同时用同样的特征来表示。
相似性判断:
目标定位:
Mean shift算法步骤(以二维图像跟踪为例,其特征为颜色直方图)
创建目标模型:
创建候选模型:
代码:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
#define u_char unsigned char
#define DIST 0.5 // 偏移向量阈值
#define NUM 20 // 迭代次数阈值
//全局变量
bool leftButtonDownFlag=false; //左键单击后的标志位
bool leftButtonUpFlag=false; //左键单击后松开的标志位
Point Point_s; //矩形框起点
Point Point_e; //矩形框鼠标左键弹起来的终点
Point processPoint; //矩形框移动的终点
bool tracking = false;
double *m_hist, *hist, *sim; // 模板直方图、当前直方图、直方图相似度
Mat w, hist_ind; // 目标模板权值矩阵,候选模板直方图索引
double C = 0.0; // 权重和
void onMouse( int event, int x, int y, int flags, void *param )
{
if(event==CV_EVENT_LBUTTONDOWN)
{
tracking = false;
leftButtonDownFlag = true; //标志位
leftButtonUpFlag = false;
processPoint=Point(x,y); //设置左键按下点的矩形起点
Point_s=processPoint;
}
else if(event == CV_EVENT_MOUSEMOVE && leftButtonDownFlag)
{
processPoint=Point(x,y);
}
else if(event==CV_EVENT_LBUTTONUP && leftButtonDownFlag)
{
leftButtonDownFlag=false;
processPoint=Point(x,y);
Point_e=processPoint;
leftButtonUpFlag = true;
tracking = true;
}
}
void init_target(Mat w, Mat mould)
{
double h, dist;
int q_r, q_g, q_b, q_temp;
h = pow(((double)mould.cols)/2,2) + pow(((double)mould.rows)/2,2); //带宽
//初始化权值矩阵和目标直方图
for (int i=0;i<4096;i++)
{
m_hist[i] = 0.0;
}
for (int i = 0;i < mould.rows; i++)
{
for (int j = 0;j < mould.cols; j++)
{
dist = pow(i - (double)mould.rows/2,2) + pow(j - (double)mould.cols/2,2);
w.at<double>(i, j) = 1 - dist / h; // 目标点权重
C += w.at<double>(i, j); // 权重和,用于归一化
}
}
//计算目标权值直方
for (int i = 0;i < mould.rows; i++)
{
for (int j = 0;j < mould.cols; j++)
{
//rgb颜色空间量化为16*16*16 bins
q_r = mould.at<Vec3b>(i, j)[2]/16;
q_g = mould.at<Vec3b>(i, j)[1]/16;
q_b = mould.at<Vec3b>(i, j)[0]/16;
q_temp = q_r * 256 + q_g * 16 + q_b;
m_hist[q_temp] = m_hist[q_temp] + w.at<double>(i, j); // 颜色权重直方图
}
}
//归一化直方图
for (int i=0;i<4096;i++)
{
m_hist[i] = m_hist[i] / C;
}
}
void MeanShift_Tracking(Mat img, Rect &rect)
{
int num = 0;
double sum_sim = 0, y_temp = 0, x_temp = 0, y = 2.0, x = 2.0;
int q_r, q_g, q_b;
while ((pow(x,2) + pow(y,2) > DIST)&& (num < NUM)) // 当移动距离大于0.5 或 循环NUM次结束循环
{
num++;
hist_ind = Mat::zeros(rect.height, rect.width, CV_32SC1); // 候选窗口直方图索引初始化
for (int i = 0;i<4096;i++)
{
sim[i] = 0.0;
hist[i] = 0.0;
}
for (int i = rect.y;i < rect.y + rect.height;i++)
{
for (int j = rect.x;j < rect.x + rect.width;j++)
{
//rgb颜色空间量化为16*16*16 bins
q_r = img.at<Vec3b>(i, j)[2]/16;
q_g = img.at<Vec3b>(i, j)[1]/16;
q_b = img.at<Vec3b>(i, j)[0]/16;
hist_ind.at<int>(i - rect.y, j - rect.x) = q_r * 256 + q_g * 16 + q_b; //获取点对应直方图索引
hist[hist_ind.at<int>(i - rect.y, j - rect.x)] = hist[hist_ind.at<int>(i - rect.y, j - rect.x)] + w.at<double>(i - rect.y, j - rect.x); // 获取候选窗口直方图
}
}
//归一化直方图
for (int i=0;i<4096;i++)
{
hist[i] = hist[i] / C;
}
for (int i = 0;i < 4096;i++)
{
if (hist[i] != 0)
{
sim[i] = sqrt(m_hist[i]/hist[i]); // 计算特征相似度
}else
{
sim[i] = 0;
}
}
sum_sim = 0.0;
y_temp = 0.0;
x_temp = 0.0;
// Mean shift向量
for (int i = 0;i < rect.height; i++)
{
for (int j = 0;j < rect.width; j++)
{
sum_sim = sum_sim + sim[hist_ind.at<int>(i, j)];
y_temp = y_temp + sim[hist_ind.at<int>(i, j)] * (i - rect.height/2); // 每个点相对中心点的偏移向量
x_temp = x_temp + sim[hist_ind.at<int>(i, j)] * (j - rect.width/2);
}
}
y = y_temp / sum_sim;
x = x_temp / sum_sim;
//中心点偏移两,即位置更新
rect.x += x;
rect.y += y;
}
circle(img, Point(rect.x + rect.width/2, rect.y + rect.height/2), 5, Scalar(0, 0, 255), -1); // 绘制更新点
rectangle(img, rect, Scalar(0, 255, 0), 3, 8, 0); //显示跟踪结果,框出
}
int main()
{
Mat img_mould, frame, mould;
Rect rect;
m_hist = (double *)malloc(sizeof(double)*16*16*16); // 目标直方图
hist = (double *)malloc(sizeof(double)*4096); // 候选直方图
sim = (double *)malloc(sizeof(double)*4096); // 直方图相似度
//打开摄像头或者特定视频
VideoCapture cap;
cap.open(0);//或cap.open("文件名")
//读入视频是否为空
if (!cap.isOpened())
{
return -1;
}
namedWindow("输出视频", 1);
setMouseCallback("输出视频", onMouse, 0);//鼠标回调函数,响应鼠标以选择跟踪区域
while (1)
{
cap >> frame;
if (frame.empty())
{
return -1;
}
if(tracking && leftButtonUpFlag)
{
leftButtonUpFlag = false;
rect.x = Point_s.x;
rect.y = Point_s.y;
rect.width = Point_e.x - Point_s.x;
rect.height = Point_e.y - Point_s.y;
img_mould = frame.clone();
mould = Mat(img_mould, rect);//目标图像
//目标初始化
w = Mat::zeros(rect.height, rect.width, CV_64FC1); // 初始化目标模板权重
init_target(w, mould); // 获取目标图像直方图
}
if(leftButtonDownFlag) // 绘制截取目标窗口
{
rect.x = Point_s.x;
rect.y = Point_s.y;
rect.width = processPoint.x - Point_s.x;
rect.height = processPoint.y - Point_s.y;
rectangle(frame, rect, Scalar(0, 255, 0), 3, 8, 0);
}
if(tracking)
{
MeanShift_Tracking(frame, rect); // 目标跟踪
}
imshow("输出视频", frame);
waitKey(10);
}
return 0;
}
Mean shift优缺点:
优点:
1、 算法复杂度小
2、 是无参数算法,易于与其他算法集成
3、 采用加权直方图建模,对目标小角度旋转、轻微变形和部分遮挡不敏感
缺点:
1、 搜索窗的核函数带宽保持不变
2、 缺乏必要的模板更新算法
3、 目标的运动不能过快
缺点解决方案:
1、对应带宽窗口不变可以用基于边界力计算带宽变化的方法、camshift算法来解决
2、模板更新和用实时模板更新、双系数模板更新来解决
3、目标移动速度可以用卡尔曼滤波来改善