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; }
