1. 程式人生 > >opencv學習例項(一)---camshift 色塊追蹤(詳細註釋)

opencv學習例項(一)---camshift 色塊追蹤(詳細註釋)

/*      程式描述:來自OpenCV安裝目錄下Samples資料夾中的官方示例程式-彩色目標跟蹤操作

        增加了"非常"詳細的註釋。----97年的頑石

------------------------------------------------------------------------------------------------*/

/*---------------------------------【標頭檔案、名稱空間包含部分】----------------------------------
                            描述:包含程式所使用的標頭檔案和名稱空間
-------------------------------------------------------------------------------------------------*/
#include "opencv2/video/tracking.hpp" #include "opencv2/imgproc/imgproc.hpp" /* highgui為跨平臺的gui/IO元件,支援平臺包括windows,linux,mac,IOS,android, 可支援影象/視訊/攝像頭的讀取顯示以及轉碼。 */ #include "opencv2/highgui/highgui.hpp" #include <iostream> //ctype.h是C標準函式庫中的標頭檔案,定義了一批C語言字元分類函式--百度百科 #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; //--------------------------------【onMouse( )回撥函式】------------------------------------ // 描述:滑鼠操作回撥 //------------------------------------------------------------------------------------------------- static void onMouse( int event, int x, int y, int, void* ) { /* 然後滑鼠在移動觸發if (selectObject) 這一行. 這時候新的座標點的x,y值都會傳過來, 不管是從哪個方向往哪個方向畫都可以得到矩形 (因為他是取絕對值的,從左下往。。。,等等等都行) http://blog.csdn.net/ddqqfree123/article/details/52173359 */ if( selectObject ) { 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: 這一行 case CV_EVENT_LBUTTONDOWN: origin = Point(x,y); selection = Rect(x,y,0,0); selectObject = true; break; //左鍵滑鼠擡起這個事件 傳到函式裡,觸發 case CV_EVENT_LBUTTONUP:這一行 case CV_EVENT_LBUTTONUP: selectObject = false; if( selection.width > 0 && selection.height > 0 ) //跟蹤目標數量為什麼要設定為-1? 後面有if(trackObject <0)就畫出直方圖 trackObject = -1; break; } } //--------------------------------【help( )函式】---------------------------------------------- // 描述:輸出幫助資訊 //------------------------------------------------------------------------------------------------- static void ShowHelpText() { cout << "\n\n\t此Demo顯示了基於均值漂移的追蹤(tracking)技術\n" "\t請用滑鼠框選一個有顏色的物體,對它進行追蹤操作\n"; cout << "\n\n\t操作說明: \n" "\t\t用滑鼠框選物件來初始化跟蹤\n" "\t\tESC - 退出程式\n" "\t\tc - 停止追蹤\n" "\t\tb - 開/關-投影檢視\n" "\t\th - 顯示/隱藏-物件直方圖\n" "\t\tp - 暫停視訊\n"; } const char* keys = { "{1| | 0 | camera number}" }; //-----------------------------------【main( )函式】-------------------------------------------- // 描述:控制檯應用程式的入口函式,我們的程式從這裡開始 //------------------------------------------------------------------------------------------------- int main( int argc, const char** argv ) { ShowHelpText(); VideoCapture cap; //定義一個攝像頭捕捉的類物件 Rect trackWindow; int hsize = 16; //---------------------------------------------------------------------------------------------精度 float hranges[] = {0,180};//直方圖的範圍//hranges在後面的計算直方圖函式中要用到 const float* phranges = hranges; cap.open(0); //直接呼叫成員函式開啟攝像頭 if( !cap.isOpened() ) { cout << "不能初始化攝像頭\n"; } namedWindow( "Histogram", 0 ); namedWindow( "CamShift Demo", 0 ); setMouseCallback( "CamShift Demo", onMouse, 0 );//訊息響應機制 //createTrackbar函式的功能是在對應的視窗建立滑動條, //滑動條Vmin,vmin表示滑動條的值,最大為256 createTrackbar( "Vmin", "CamShift Demo", &vmin, 256, 0 ); //最後一個引數為0代表沒有呼叫滑動拖動的響應函式 createTrackbar( "Vmax", "CamShift Demo", &vmax, 256, 0 ); //vmin,vmax,smin初始值分別為10,256,30 createTrackbar( "Smin", "CamShift Demo", &smin, 256, 0 ); /* CV_8UC1,CV_8UC2,CV_8UC3。最後的1、2、3表示通道數,譬如RGB3通道就用CV_8UC3。 CV_8UC3 表示使用8位的 unsigned char 型,每個畫素由三個元素組成三通道, 初始化為(0,0,255) http://blog.csdn.net/augusdi/article/details/8876459 Mat::zeros 返回指定的大小和型別的零陣列。 C++: static MatExpr Mat::zeros(int rows, int cols, int type) rows–行數。 cols –列數。type– 建立的矩陣的型別。 A = Mat::zeros (3,3,CV_32F); 在上面的示例中,只要A不是 3 x 3浮點矩陣它就會被分配新的矩陣。 否則為現有的矩陣 A填充零。 轉自:http://blog.csdn.net/sherrmoo/article/details/40951997 hist 直方圖數字矩陣 最後 -> histimg 直方圖影象 hsv ->(取出h) hue mask? 掩膜? --------------------------------------------------------------------------------------------------????? backproj 反向投影的矩陣 */ 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空間的 --------------------轉hsv //trackObject初始化為0,或者按完鍵盤的'c'鍵後也為0,當滑鼠單擊鬆開後為-1 if( trackObject ) { 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初始化為與hsv大小深度一樣的矩陣,色調的度量是用角度表示的, //紅綠藍之間相差120度,反色相差180度 hue.create(hsv.size(), hsv.depth());//HSV(Hue, Saturation, Value) /* int from_to[] = { 0,2, 1,1, 2,0, 3,3 }; mixChannels( &rgba, 1, out, 2, from_to, 4 ); from_to:通道交換對,陣列中每兩個元素為一對,表示對應的交換通道 pair_count:通道交換對個數(即*from_to陣列中行數) http://blog.163.com/jinlong_zhou_cool/blog/static/22511507320138932215239/ bgr 三原色RGB混合能形成其他的顏色,所以不能用一個值來表示顏色 hsv H是色彩 S是深淺,S = 0時,只有灰度 V是明暗 hsv -> hue 把色彩單獨分出來 http://blog.csdn.net/viewcode/article/details/8203728 */ mixChannels(&hsv, 1, &hue, 1, ch, 1); if( trackObject < 0 )//滑鼠選擇區域鬆開後,該函式內部又將其賦值-1 { //此處的建構函式roi用的是Mat hue的矩陣頭, //且roi的資料指標指向hue,即共用相同的資料,select為其感興趣的區域 Mat roi(hue, selection), maskroi(mask, selection);//mask儲存的hsv的最小值 /* 將roi的0通道計算直方圖並通過mask放入hist中,hsize為每一維直方圖的大小 calcHist函式來計算影象直方圖 ---calcHist函式呼叫形式 C++: void calcHist(const Mat* images, int nimages, const int* channels, InputArray mask, OutputArray hist, int dims, const int* histSize, const float** ranges, bool uniform=true, bool accumulate=false 引數詳解 onst Mat* images:輸入影象 int nimages:輸入影象的個數 const int* channels:需要統計直方圖的第幾通道 InputArray mask:掩膜,,計算掩膜內的直方圖 ...Mat() OutputArray hist:輸出的直方圖陣列 int dims:需要統計直方圖通道的個數 const int* histSize:指的是直方圖分成多少個區間,,,就是 bin的個數 const float** ranges: 統計畫素值得區間 bool uniform=true::是否對得到的直方圖陣列進行歸一化處理 bool accumulate=false:在多個影象時,是否累計計算畫素值得個數 http://blog.csdn.net/qq_18343569/article/details/48027639 */ //--------------------------------------calcHist原始碼要不要去找找看? calcHist(&roi, 1, 0, maskroi, hist, 1, &hsize, &phranges); /* 將hist矩陣進行陣列範圍歸一化,都歸一化到0~255 void normalize(const InputArray src, OutputArray dst, double alpha=1, double beta=0, int normType=NORM_L2, int rtype=-1, InputArray mask=noArray()) 當用于歸一化時,normType應該為cv::NORM_MINMAX,alpha為歸一化後的最大值, beta為歸一化後的最小值 http://www.cnblogs.com/mikewolf2002/archive/2012/10/24/2736504.html */ normalize(hist, hist, 0, 255, CV_MINMAX); trackWindow = selection; /* 只要滑鼠選完區域鬆開後,且沒有按鍵盤清0鍵'c',則trackObject一直保持為1, 因此該if函式 if( trackObject < 0 ) 只能執行一次,除非重新選擇跟蹤區域。 */ trackObject = 1; /* 與按下'c'鍵是一樣的,這裡的all(0)表示的是標量 全部清0 inline CvScalar cvScalarAll( double val0123 ); 同時設定VAL0,1,2,3的值; OpenCV裡的Scalar:all的意思: scalar所有元素設定為0,其實可以scalar::all(n),就是原來的CvScalarAll(n); */ histimg = Scalar::all(0); /* histing是一個200*300的矩陣,hsize應該是每一個bin的寬度, 也就是histing矩陣能分出幾個bin出來 opencv直方圖的bins中儲存的是什麼? https://zhidao.baidu.com/question/337997654.html 假設 有數值 0,0,1,2,3,10,12,13 。 你分的bins為 0-6 為第一個bin,7-13 為一個bins。 那麼bins[0] 即第一個bins 儲存的數就是 4, 原因是 0,0,1,2,3在第一個bin的範圍內, bins[1] 儲存的數為 3,原因是 10,12,13落在這個[7-13]這個bin內。 Line111 : hsize=16 */ int binW = histimg.cols / hsize; //算出寬 /* Mat::Mat(); //default Mat::Mat(int rows, int cols, int type); Mat::Mat(Size size, int type); Mat::Mat(int rows, int cols, int type, const Scalar& s); 引數說明: int rows:高 int cols:寬 int type:參見"Mat型別定義" Size size:矩陣尺寸,注意寬和高的順序:Size(cols, rows) const Scalar& s:用於初始化矩陣元素的數值 const Mat& m:拷貝m的矩陣頭給新的Mat物件, 但是不復制資料!相當於建立了m的一個引用物件 轉自:http://blog.csdn.net/holybin/article/details/17751063 定義一個緩衝單bin矩陣。這裡使用的是第二個 過載 函式。 過載函式:https://baike.baidu.com/item/%E9%87%8D%E8%BD%BD%E5%87%BD%E6%95%B0/3280477?fr=aladdin */ Mat buf(1, hsize, CV_8UC3); /* saturate_cast函式為從一個初始型別準確變換到另一個初始型別 saturate_cast<uchar>(int v)的作用 就是防止資料溢位, 具體的原理可以大致描述如下: if(data<0) data=0; if(data>255) data=255 轉自:http://blog.csdn.net/wenhao_ir/article/details/51545330?locationNum=10&fps=1 Vec3b為3個char值的向量 CV_8UC3 表示使用8位的 unsigned char 型,每個畫素由三個元素組成三通道, 初始化為(0,0,255) */ for( int i = 0; i < hsize; i++ ) /* 互補色相差180度 顏色->hsv->hue(0,255)->roi->hist(0,255) 所以這裡只是,以i為輸入,把直方圖本來各個矩形的顏色算出來,放在buf裡。 hsv三個值的取值範圍: h 0-180 s 0-255 v 0-255 http://blog.csdn.net/taily_duan/article/details/51506776 https://wenku.baidu.com/view/eb2d600dbb68a98271fefadc.html */ buf.at<Vec3b>(i) = Vec3b(saturate_cast<uchar>(i*180./hsize), 255, 255); cvtColor(buf, buf, CV_HSV2BGR); //將hsv又轉換成bgr,畫矩形顏色的用BGR格式 for( int i = 0; i < hsize; i++ ) //畫直方圖 { /* at函式為返回一個指定陣列元素的參考值 histimg.rows常量=200 val決定各個矩形的高度 */ int val = saturate_cast<int>(hist.at<float>(i)*histimg.rows/255); /* C++: void rectangle(Mat& img, Rect rec, const Scalar& color, int thickness=1, int lineType=8, int shift=0 ) 引數介紹: img 影象. pt1 矩形的一個頂點。 pt2 矩形對角線上的另一個頂點 color 線條顏色 (RGB) 或亮度(灰度影象 )(grayscale image)。 thickness 組成矩形的線條的粗細程度。取負值時(如 CV_FILLED) 函式繪製填充了色彩的矩形。 line_type 線條的型別。見cvLine的描述 https://zhidao.baidu.com/question/427970238676959132.html shift 座標點的小數點位數。 histimg.rows是一個常量。histmig.rows=200 Scalar(buf.at<Vec3b>(i)) buf是顏色 計算機裡,座標在左上角,x軸朝右,y朝下 val決定各個矩形的高度 */ rectangle( histimg, Point(i*binW,histimg.rows), Point((i+1)*binW,histimg.rows - val), Scalar(buf.at<Vec3b>(i)), -1, 8 ); } } /* 反向投影 = = 嚇蒙我 http://blog.csdn.net/qq_18343569/article/details/48028065 */ calcBackProject(&hue, 1, 0, hist, backproj, &phranges); //相與??????這一句註釋了也沒事 = = ------加上的話,整體來說,噪音要少很多 backproj &= mask; /* Camshift它是MeanShift演算法(Mean Shift演算法,又稱為均值漂移演算法)的改進, 稱為連續自適應的MeanShift演算法, CamShift演算法的全稱是"Continuously Adaptive Mean-SHIFT", 它的基本思想是視訊影象的所有幀作MeanShift運算,並將上一幀的結果 (即Search Window的中心和大小) 作為下一幀MeanShift演算法的Search Window的初始值,如此迭代下去。 對於OPENCV中的CAMSHIFT例子,是通過計算目標HSV空間下的HUE分量直方圖, 通過直方圖反向投影得到目標畫素的概率分佈, 然後通過呼叫CV庫中的CAMSHIFT演算法,自動跟蹤並調整目標視窗的中心位置與大小。 https://baike.baidu.com/item/Camshift/5302311?fr=aladdin cvCamShift(IplImage* imgprob, CvRect windowIn, CvTermCriteria criteria, CvConnectedComp* out, CvBox2D* box=0); imgprob:色彩概率分佈圖像。 windowIn:Search Window的初始值。 Criteria:用來判斷搜尋是否停止的一個標準。 out:儲存運算結果,包括新的Search Window的位置和麵積。 box:包含被跟蹤物體的最小矩形。 http://blog.csdn.net/houdy/article/details/191828 CV_INLINE CvTermCriteria cvTermCriteria( int type, int max_iter, double epsilon ) { CvTermCriteria t; t.type = type; t.max_iter = max_iter; t.epsilon = (float)epsilon; return t; } 該函式是行內函數,返回的值為CvTermCriteria結構體。 看得出該函式還是c介面想使用c語言來模擬面向物件的結構,其中的引數為: type: - CV_TERMCRIT_ITER 在當演算法迭代次數超過max_iter的時候終止。 - CV_TERMCRIT_EPS 在當演算法得到的精度低於epsolon時終止; -CV_TERMCRIT_ITER+CV_TERMCRIT_EPS 當演算法迭代超過max_iter或者當獲得的精度低於epsilon的時候,哪個先滿足就停止 max_iter:迭代的最大次數 epsilon:要求的精度 http://www.cnblogs.com/shouhuxianjian/p/4529174.html L231 trackWindow = selection; */ //trackWindow為滑鼠選擇的區域,TermCriteria為確定迭代終止的準則 RotatedRect trackBox = CamShift(backproj, trackWindow, //CV_TERMCRIT_EPS是通過forest_accuracy,CV_TERMCRIT_ITER 是通過max_num_of_trees_in_the_forest TermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 )); if( trackWindow.area() <= 1 ) //如果trackWindow 找到解了?-----??? { 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函式為矩陣的偏移和大小,即第一二個引數為矩陣的左上角點座標, //第三四個引數為矩陣的寬和高 Rect(0, 0, cols, rows); } if( backprojMode ) //因此投影模式下顯示的也是rgb圖? cvtColor( backproj, image, COLOR_GRAY2BGR ); /* void cvEllipse( CvArr* img, CvPoint center, CvSize axes, double angle, double start_angle, double end_angle, CvScalar color, int thickness=1, int line_type=8, int shift=0 ); img 影象。 center 橢圓圓心座標。 axes 軸的長度。 angle 偏轉的角度。 start_angle 圓弧起始角的角度。 end_angle 圓弧終結角的角度。 color 線條的顏色。 thickness 線條的粗細程度。 line_type 線條的型別,見CVLINE的描述。 shift 圓心座標點和數軸的精度。 lineType – 線型 Type of the line: 8 (or omitted) - 8-connected line. 4 - 4-connected line. CV_AA - antialiased line. 抗鋸齒線。 shift – 座標點小數點位數. 跟蹤的時候以橢圓為代表目標 */ ellipse( image, trackBox, Scalar(0,0,255), 3, CV_AA ); } } //後面的程式碼是不管pause為真還是為假都要執行的 else if( trackObject < 0 )//同時也是在按了暫停字母以後 paused = false; //---------------------???在這裡有什麼作用?只有L220 用到了roi--------------------???? if( selectObject && selection.width > 0 && selection.height > 0 ) { Mat roi(image, selection); bitwise_not(roi, roi); //bitwise_not為將每一個bit位取反 } imshow( "CamShift Demo", image ); imshow( "Histogram", histimg ); int i; /* waitKey(x); 第一個引數: 等待x ms,如果在此期間有按鍵按下,則立即結束並返回按下按鍵的 ASCII碼,否則返回-1 如果x=0,那麼無限等待下去,直到有按鍵按下 http://blog.sina.com.cn/s/blog_82a790120101jsp1.html */ char c = (char)waitKey(10); if( c == 27 ) //退出鍵 break; switch(c) { case 'b': //反向投影模型 img/mask交替 backprojMode = !backprojMode; break; case 'c': //清零跟蹤目標物件 trackObject = 0; histimg = Scalar::all(0); break; case 'h': //顯示直方圖交替 showHist = !showHist; if( !showHist ) destroyWindow( "Histogram" ); //好像並沒有destroy 還是看不出來 = = else namedWindow( "Histogram", 1 ); break; case 'p': //暫停跟蹤交替 paused = !paused; break; default: ; } } return 0; }