opencv學習例項(一)---camshift 色塊追蹤(詳細註釋)
阿新 • • 發佈:2018-12-31
/* 程式描述:來自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;
}