1 Meanshift原理
meanshift算法,其本质还是一种梯度下降法求最值方法。我认为可以这样表述,我们在取一个点(比如区域的某个角)作为区域的代表,将区域与目标相似程度数值化(或者机器学习中,将此点一定大小范围内匹配点的数目),作为这点的值,这样在图像上就可以形成坐标的xy的标量场,这样再利用梯度沿着相似程度上升的方向移动,这大概是就是算法原理。
在目标追踪中描述这个算法,我在代码实现中选择的是HSV空间中的H分量直方图分布作为目标的的特征,将追踪到的区域的直方图与目标直方图进行比较,数值化两个直方图的相似程度。然后在追踪区域的附近区域计算相似程度,一旦发现邻域中某区域(A)的相似程度有提升,就将追踪区域更新为A,并在A邻域重复上述操作。直到追踪区域与目标的相似程度达到局部最大值停止迭代。
2 出现的问题
1 容易陷入局部最优值。
梯度下降法的通病,表现为追踪到颜色相近的物体。
想到的解决方法:设置相似度的阈值,假如某邻域低于某个阈值将从此邻域跳出。(未在代码中体现)
2 梯度下降收敛速度过慢。
假如在区域邻域计算相似度的过程花费时间,物体移动速度又较快,很容易就会失去对物体的跟随。
想到的优化:考虑到物体移动应该不是无规律的,那么梯度上升的方向一定程度上代表了物体的移动方向,物体保留原来速度的可能性比较大,那么在新区域也优先计算上一区域相似度上升方向的的相似度,这样一定程度上加快了收敛速率。
3 实现代码。
// meanshifttet.cpp: 定义控制台应用程序的入口点。 #include "stdafx.h" #include<opencv2/opencv.hpp> #include<math.h> using namespace cv; using namespace std; Point origin; Rect selection; Point track[36]; Mat frame, trackobject, trackhsv; int flag = 0; MatND dstlist; static void onMouse(int event, int x, int y, int, void*)//鼠标事件,框选要追踪的物体,并计算目标物体HSV空间中H的直方图 { switch (event) { case EVENT_LBUTTONDOWN: origin = Point(x, y); selection = Rect(x, y, 0, 0); break; case EVENT_LBUTTONUP: selection.x = MIN(x, origin.x); selection.y = MIN(y, origin.y); selection.width = std::abs(x - origin.x); selection.height = std::abs(y - origin.y); trackobject = frame(selection).clone(); cvtColor(trackobject, trackhsv, CV_RGB2HSV); int hsvnum = 30; float hrange[] = { 0,179 }; const float *range[] = { hrange }; int channels = 0; int size = 256; //? calcHist(&trackhsv, 1, &channels, Mat(), dstlist, 1, &size, range); flag = 1; break; } } int drawrect(Mat &img, Rect t) { //追踪框 rectangle(img, t.tl(), t.br(), Scalar(0)); return 1; } double calcmp(Rect a) { //计算与目标直方图的距离 Mat select = frame(a); Mat newtrack = select.clone(); Mat newtrackhsv; MatND newlist; cvtColor(newtrack, newtrackhsv, CV_RGB2HSV); int hsvnum = 30; float hrange[] = { 0,179 }; const float *range[] = { hrange }; int channels = 0; int size = 256; calcHist(&newtrackhsv, 1, &channels, Mat(), newlist, 1, &size, range); return compareHist(dstlist, newlist, CV_COMP_CORREL); } int main() { Rect newtrack; VideoCapture cap; namedWindow("meanshift", CV_WINDOW_NORMAL); cap.open(0); double step = 20; //追踪的步长,假如运动速度较快,就设置的大一点 for (int i = 0; i < 4; i++) { //邻域方向,为计算速率的提升,仅仅选取了上下左右四个方向 track[i].x = cos(i*1.0 / 2 * 3 .14 )*step; track[i].y = sin(i*1.0 / 2 * 3.14)*step; } if (!cap.isOpened()) { cout << "摄像头未能正常开启\n"; return -1; } while (1) { cap >> frame; cvSetMouseCallback("meanshift", onMouse); drawrect(frame, selection); imshow("meanshift", frame); if (flag == 1) { double last = calcmp(selection); for (int i = 0; i < 4; i++) { if ((selection.x + track[i].x) > 0&&(selection.y + track[i].y)>0&& (selection.br().y + track[i].y)<479&& (selection.br().x + track[i].x)<639) { //防止追踪框越界 newtrack = selection + track[i]; } while (last < calcmp(newtrack)) { if ((selection.x + track[i].x) > 0 && (selection.y + track[i].y)>0 && (selection.br().y + track[i].y)<479 && (selection.br().x + track[i].x)<639) {//防止追踪框越界 selection = selection + track[i]; //优先计算原梯度上升方向。 } break; } } } waitKey(30); } return 0; }