目標跟蹤--CamShift
轉載請註明出處!
!!
http://blog.csdn.net/zhonghuan1992
目標跟蹤--CamShift
CamShift全稱是ContinuouslyAdaptive Mean Shift,即連續自適應的MeanShift算法。而MeanShift算法,首先得對MeanShift算法有個初步的了解,可以參考這裏。而CamShift是在MeanShift的基礎上,依據上一幀的結果。來調整下一幀的中心位置和窗體大小,所以。當跟蹤的目標在視頻中發生變化時,可以對這個變化有一定的調整。
OpenCV自帶樣例中的camShift算法,能夠分為三個部分:(引用自這裏 http://blog.csdn.net/carson2005/article/details/7439125)
一、計算色彩投影圖(反向投影):
(1)為了降低光照變化對目標跟蹤的影響,首先將圖像從RGB顏色空間轉換到HSV顏色空間。
(2)對H分量進行直方圖統計,直方圖代表了不同H分量取值出現的概率。或者說能夠據此查找出H分量的大小為x時的概率或像素個數。即,得到顏色概率查找表;
(3)將圖像中每一個像素的值用其顏色出現的概率進行替換,由此得到顏色概率分布圖;
以上三個步驟稱之為反向投影,須要提醒的是。顏色概率分布圖是一個灰度圖像。
二、meanShift尋優
前面提到過meanShift算法(http://blog.csdn.net/carson2005/article/details/7337432)是一種非參數概率密度預計方法,它通過不斷叠代計算得到最優搜索窗體的位置和大小。
三、camShift跟蹤算法
前面提到,camShift事實上就是在視頻序列的每一幀其中都運用meanShift,並將上一幀的meanShift結果作為下一幀的初始值,如此不斷循環叠代,就能夠實現目標的跟蹤了。
在openCV中自帶有camShift函數。老看一下實現,代碼中有部分解釋。(凝視功勞來自http://www.cnblogs.com/tornadomeet/archive/2012/03/15/2398769.html)
#include "opencv2/video/tracking.hpp" #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <iostream> #include <ctype.h> using namespace cv; using namespace std; Mat image; bool backprojMode = false; //表示是否要進入反向投影模式。ture表示準備進入反向投影模式 bool selectObject = false;//代表是否在選要跟蹤的初始目標,true表示正在用鼠標選擇 int trackObject = 0; //代表跟蹤目標數目 bool showHist = true;//是否顯示直方圖 Point origin;//用於保存鼠標選擇第一次單擊時點的位置 Rect selection;//用於保存鼠標選擇的矩形框 int vmin = 10, vmax =256, smin = 30; void onMouse(int event, int x, int y, int, void*) { if (selectObject)//僅僅有當鼠標左鍵按下去時才有效,然後通過if裏面代碼就能夠確定所選擇的矩形區域selection了 { 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);//矩形高 selection &= Rect(0, 0, image.cols,image.rows);//用於確保所選的矩形區域在圖片範圍內 } switch (event) { case CV_EVENT_LBUTTONDOWN: origin = Point(x, y); selection = Rect(x, y, 0, 0);//鼠標剛按下去時初始化了一個矩形區域 selectObject = true; break; case CV_EVENT_LBUTTONUP: selectObject = false; if (selection.width> 0 && selection.height > 0) trackObject = -1; break; } } void help() { cout << "\nThis is ademo that shows mean-shift based tracking\n" "You select acolor objects such as your face and it tracks it.\n" "This readsfrom video camera (0 by default, or the camera number the user enters\n" "Usage:\n" " ./camshiftdemo [camera number]\n"; cout << "\n\nHot keys:\n" "\tESC - quitthe program\n" "\tc - stop thetracking\n" "\tb - switchto/from backprojection view\n" "\th -show/hide object histogram\n" "\tp - pausevideo\n" "To initializetracking, select the object with mouse\n"; } const char* keys = { "{1| | 0 |camera number}" }; int main(int argc, const char** argv) { help(); VideoCapture cap; //定義一個攝像頭捕捉的類對象 Rect trackWindow; RotatedRect trackBox;//定義一個旋轉的矩陣類對象 int hsize = 16; float hranges[] = { 0, 180 };//hranges在後面的計算直方圖函數中要用到 const float* phranges = hranges; CommandLineParser parser(argc, argv, keys);//命令解析器函數 int camNum = parser.get<int>("0"); cap.open(camNum);//直接調用成員函數打開攝像頭 if (!cap.isOpened()) { help(); cout << "***Could notinitialize capturing...***\n"; cout << "Currentparameter's value: \n"; parser.printParams(); return -1; } namedWindow("Histogram", 0); namedWindow("CamShiftDemo",0); setMouseCallback("CamShiftDemo",onMouse, 0);//消息響應機制 createTrackbar("Vmin", "CamShiftDemo",&vmin, 256, 0);//createTrackbar函數的功能是在相應的窗體創建滑動條,滑動條Vmin,vmin表示滑動條的值,最大為256 createTrackbar("Vmax", "CamShiftDemo",&vmax, 256, 0);//最後一個參數為0代表沒有調用滑動拖動的響應函數 createTrackbar("Smin", "CamShift Demo", &smin, 256,0);//vmin,vmax,smin初始值分別為10,256,30 Mat frame, hsv, hue, mask, hist, histimg = Mat::zeros(200, 320, CV_8UC3), backproj; bool paused = false; for (;;) { if (!paused)//沒有暫停 { cap >> frame;//從攝像頭抓取一幀圖像並輸出到frame中 if (frame.empty()) break; } frame.copyTo(image); if (!paused)//沒有按暫停鍵 { cvtColor(image, hsv, CV_BGR2HSV);//將rgb攝像頭幀轉化成hsv空間的 if (trackObject)//trackObject初始化為0,或者按完鍵盤的'c'鍵後也為0,當鼠標單擊松開後為-1 { int _vmin = vmin, _vmax= vmax; //inRange函數的功能是檢查輸入數組每個元素大小是否在2個給定數值之間。能夠有多通道,mask保存0通道的最小值,也就是h分量 //這裏利用了hsv的3個通道,比較h,0~180,s,smin~256,v,min(vmin,vmax),max(vmin,vmax)。假設3個通道都在相應的範圍內,則 //mask相應的那個點的值全為1(0xff),否則為0(0x00). inRange(hsv, Scalar(0, smin, MIN(_vmin, _vmax)), Scalar(180, 256, MAX(_vmin, _vmax)),mask); int ch[] = { 0, 0 }; hue.create(hsv.size(),hsv.depth());//hue初始化為與hsv大小深度一樣的矩陣,色調的度量是用角度表示的,紅綠藍之間相差120度,反色相差180度 mixChannels(&hsv, 1,&hue, 1, ch, 1);//將hsv第一個通道(也就是色調)的數拷貝到hue中,0索引數組 if (trackObject <0)//鼠標選擇區域松開後,該函數內部又將其賦值1 { //此處的構造函數roi用的是Mat hue的矩陣頭。且roi的數據指針指向hue,即共用同樣的數據,select為其感興趣的區域 Mat roi(hue,selection), maskroi(mask, selection);//mask保存的hsv的最小值 //calcHist()函數第一個參數為輸入矩陣序列,第2個參數表示輸入的矩陣數目。第3個參數表示將被計算直方圖維數通道的列表,第4個參數表示可選的掩碼函數 //第5個參數表示輸出直方圖,第6個參數表示直方圖的維數,第7個參數為每一維直方圖數組的大小。第8個參數為每一維直方圖bin的邊界 calcHist(&roi, 1, 0,maskroi, hist, 1, &hsize, &phranges);//將roi的0通道計算直方圖並通過mask放入hist中。hsize為每一維直方圖的大小 normalize(hist, hist, 0,255, CV_MINMAX);//將hist矩陣進行數組範圍歸一化,都歸一化到0~255 trackWindow = selection; trackObject = 1;//僅僅要鼠標選完區域松開後。且沒有按鍵盤清0鍵'c',則trackObject一直保持為1。因此該if函數僅僅能運行一次,除非又一次選擇跟蹤區域 histimg = Scalar::all(0);//與按下'c'鍵是一樣的,這裏的all(0)表示的是標量所有清0 int binW = histimg.cols/ hsize; //histing是一個200*300的矩陣。hsize應該是每個bin的寬度。也就是histing矩陣能分出幾個bin出來 Mat buf(1, hsize, CV_8UC3);//定義一個緩沖單bin矩陣 for (int i = 0; i <hsize; i++)//saturate_case函數為從一個初始類型準確變換到還有一個初始類型 buf.at<Vec3b>(i) = Vec3b(saturate_cast<uchar>(i*180. /hsize), 255, 255);//Vec3b為3個char值的向量 cvtColor(buf, buf, CV_HSV2BGR);//將hsv又轉換成bgr for (int i = 0; i <hsize; i++) { int val =saturate_cast<int>(hist.at<float>(i)*histimg.rows/ 255);//at函數為返回一個指定數組元素的參考值 rectangle(histimg, Point(i*binW,histimg.rows), //在一幅輸入圖像上畫一個簡單抽的矩形,指定左上角和右下角。並定義顏色,大小,線型等 Point((i + 1)*binW,histimg.rows - val), Scalar(buf.at<Vec3b>(i)), -1, 8); } } calcBackProject(&hue, 1, 0,hist, backproj, &phranges);//計算直方圖的反向投影。計算hue圖像0通道直方圖hist的反向投影,並讓入backproj中 backproj &= mask; //opencv2.0以後的版本號函數命名前沒有cv兩字了,而且假設函數名是由2個意思的單詞片段組成的話。且前面那個片段不夠成單詞,則第一個字母要 //大寫,比方Camshift。假設第一個字母是個單詞,則小寫。比方meanShift,可是第二個字母一定要大寫 RotatedRect trackBox =CamShift(backproj, trackWindow, //trackWindow為鼠標選擇的區域。TermCriteria為確定叠代終止的準則 TermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1));//CV_TERMCRIT_EPS是通過forest_accuracy,CV_TERMCRIT_ITER if (trackWindow.area()<= 1) //是通過max_num_of_trees_in_the_forest { int cols =backproj.cols, rows = backproj.rows, r = (MIN(cols, rows) + 5) /6; trackWindow = Rect(trackWindow.x - r,trackWindow.y - r, trackWindow.x + r,trackWindow.y + r) & Rect(0, 0, cols, rows);//Rect函數為矩陣的偏移和大小,即第一二個參數為矩陣的左上角點坐標。第三四個參數為矩陣的寬和高 } if (backprojMode) cvtColor(backproj, image, CV_GRAY2BGR);//因此投影模式下顯示的也是rgb圖? ellipse(image, trackBox, Scalar(0, 0, 255), 3, CV_AA);//跟蹤的時候以橢圓為代表目標 } } //後面的代碼是無論pause為真還是為假都要運行的 else if (trackObject <0)//同一時候也是在按了暫停字母以後 paused = false; if (selectObject&& selection.width > 0 && selection.height > 0) { Mat roi(image,selection); bitwise_not(roi, roi);//bitwise_not為將每個bit位取反 } imshow("CamShiftDemo",image); imshow("Histogram", histimg); char c = (char)waitKey(10); if (c == 27) //退出鍵 break; switch (c) { case 'b': //反向投影模型交替 backprojMode = !backprojMode; break; case 'c': //清零跟蹤目標對象 trackObject = 0; histimg = Scalar::all(0); break; case 'h': //顯示直方圖交替 showHist = !showHist; if (!showHist) destroyWindow("Histogram"); else namedWindow("Histogram", 1); break; case 'p': //暫停跟蹤交替 paused = !paused; break; default: ; } } cap.release(); return 0; }
實驗結果:
以攝像頭中我的臉為目標,當他移動時,可以跟蹤到他。
能夠看到我的臉的各色調密度圖。
目標跟蹤--CamShift