opencv学习实例(一)---camshift 色块追踪(详细注释)

建议拷贝到本地看。借鉴了众多网站,才大概明白了这个程序的大概怎么运行的, 技术细节以及原理恕我无能为力.
ps: 我所知道的都写在注释里了,新的问题,问我我也极大概率不知道,抱歉。

/*		程序描述:来自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 
//ctype.h是C标准函数库中的头文件,定义了一批C语言字符分类函数--百度百科
#include 

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(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(i) = Vec3b(saturate_cast(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(hist.at(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(i))  buf是颜色
						计算机里,坐标在左上角,x轴朝右,y朝下
						val决定各个矩形的高度
						*/
						rectangle( histimg, Point(i*binW,histimg.rows),    
							Point((i+1)*binW,histimg.rows - val),  
							Scalar(buf.at(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;  
}  

你可能感兴趣的:(opencv相关,opencv,色块追踪,camshift,详细注释)