OpenCV视频篇——颜色跟踪


在这里插入图片描述

一、黑白图片

来自:轻舞飞扬

在了解色彩空间前,先了解一下黑白图片的形成。

用光线对着传感器,从传感器的立场上来看的话,一部分接收到了光线,那么这一部分就是明亮的,一部分没有接收到光线,那么这一部分将会是黑暗的,我们可以视为传感器存在两个通道。

如果传感器存在多个通道的话,意思就是说我们将光线进行分类,高能量的,中等能量的,低能量的等等多种分类,传感器对这多种能力进行区分显示出不同的颜色

不同的颜色有不同的能量分布,一般来说红色(R)在600-700nm波长处能量最高,绿色在534-555nm处能量最高,蓝色(B)在420-440nm处能力最高。

在这里插入图片描述

二、HSV颜色空间

✨色调H
用角度度量,取值范围为0°~360°,从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°。它们的补色是:黄色为60°,青色为180°,品红为300°;

✨饱和度S
饱和度S表示颜色接近光谱色的程度。一种颜色,可以看成是某种光谱色与白色混合的结果。其中光谱色所占的比例愈大,颜色接近光谱色的程度就愈高,颜色的饱和度也就愈高。饱和度高,颜色则深而艳。光谱色的白光成分为0,饱和度达到最高。通常取值范围为0%~100%,值越大,颜色越饱和。

✨明度V
明度表示颜色明亮的程度,对于光源色,明度值与发光体的光亮度有关;对于物体色,此值和物体的透射比或反射比有关。通常取值范围为0%(黑)到100%(白)。

HSV模型的三维表示从RGB立方体演化而来。设想从RGB沿立方体对角线的白色顶点向黑色顶点观察,就可以看到立方体的六边形外形。六边形边界表示色彩,水平轴表示纯度,明度沿垂直轴测量。


在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

三、OpenCV中的HSV

HSV颜色空间与人眼较为接近,一般以HSV为颜色检测和识别。
opencv H范围(0-180) S(0-255) V(0-255)。

1. HSV二值化处理的函数:
  • 说明
    检查数组元素是否位于其他两个数组的元素之间。
    该功能检查范围如下:

    🎈 对于单通道输入数组的每个元素:
    dst ( I ) = lowerb ( I ) 0 ≤ src ( I ) 0 ≤ upperb ( I ) 0 \texttt{dst} (I)= \texttt{lowerb} (I)_0 \leq \texttt{src} (I)_0 \leq \texttt{upperb} (I)_0 dst(I)=lowerb(I)0src(I)0upperb(I)0
    🎈 对于两通道阵列:
    dst ( I ) = lowerb ( I ) 0 ≤ src ( I ) 0 ≤ upperb ( I ) 0 ∧ lowerb ( I ) 1 ≤ src ( I ) 1 ≤ upperb ( I ) 1 \texttt{dst} (I)= \texttt{lowerb} (I)_0 \leq \texttt{src} (I)_0 \leq \texttt{upperb} (I)_0 \land \texttt{lowerb} (I)_1 \leq \texttt{src} (I)_1 \leq \texttt{upperb} (I)_1 dst(I)=lowerb(I)0src(I)0upperb(I)0lowerb(I)1src(I)1upperb(I)1

    也就是说,如果src(I)在指定的1D,2D,3D等区域内,则dst(I)设置为255(全1位),否则设置为0。

    高低阈值指的是hsv的高低阈值,当图像的hsv在高低阈值之间那么输出图像为255(白),否则为0(黑色)。

    当下边界参数和/或上边界参数为标量时,应忽略上式中在lowerb和upperb处的索引(I)。

  • 声明

    void inRange(InputArray src, InputArray lowerb,InputArray upperb, OutputArray dst);
    
  • 参数

    src输入图像
    lowerb低阈值
    upperb高阈值。
    dst输出图像(大小与输入图像一样,类型是8U)相当于:Mat dstImage = Mat::zeros(srcImage.size(),CV_8U);
2. HSV颜色范围的选取:

HSV色环

内容来自: 阿卡蒂奥

比如我们选择某个偏红色的范围:
💢 1. 色环图中这个区间即BGR(0,128,255)到BGR(255,0,213);
💢 2. B、G、R这三个通道的范围分别为0-255,0-128,213-255。
💢 3. 因此阈值下限lowerb=Scalar(0,0,213),阈值上限upperb=Scalar(255,128,255)。
在这里插入图片描述

四、颜色直方图的获取与目标跟踪

1. 颜色直方图的获取

颜色直方图是对运动目标表面颜色分布的统计,不受目标的形状、姿态等变化的影响。所以用直方图作为目标的特征,依据颜色分布进行匹配,具有稳定性好、抗部分遮挡、计算方法简单和计算量小的特点,是比较理想的目标颜色特征。为了抵抗光照亮暗带来的影响,一般的颜色直方图均在HSV色系下提取。

对HSV3个分量按照对颜色变化的敏感程度不同分别进行量化。设量化后,3个分量的取值范围分别为 { 0 , 1 , . . . L H − 1 } \{0,1,...L_H-1\} {0,1,...LH1} { 0 , 1 , . . . , L S − 1 } \{0,1,...,L_S-1\} {0,1,...,LS1} { 0 , 1 , . . . , L V − 1 } \{0,1,...,L_V-1\} {0,1,...,LV1}按照 [ H , S , V ] [H,S,V] [HSV]的形式排列成一个矢量,则其范围为: { 0 , 1 , . . . , L H − 1 , . . . , L H + L S − 1 , . . . , L H + L S + L V − 1 } \{0,1,...,L_H-1,...,L_H+L_S-1,...,L_H+L_S+L_V-1\} {0,1,...,LH1,...,LH+LS1,...,LH+LS+LV1}设颜色i的像素点个数为 m i m_i mi,图像的像素点的总数为:

在这里插入图片描述

则颜色i的出现概率 P i P_i Pi,即被定义为颜色直方图,即
在这里插入图片描述

2.基于颜色直方图的目标跟踪

因为颜色直方图是矢量,以此作为特征进行目标跟踪时,即基于颜色直方图的目标跟踪时,可采用Bhattacharyya距离作为两直方图相似度的度量。计算公式为:

在这里插入图片描述 在这里插入图片描述

其中: ρ ρ ρ为两直方图的Bhattacharyya系数;p为候选目标直方图分布; q q q为模板直方图分布; d d d为两直方图的Bhattacharyya距离,其值越小,表明两直方图的相似度越高;反之,两直方图相似度越低。

五、camshift算法原理

camshift就是利用目标的颜色直方图模型将图像转换为颜色概率分布图,初始化一个搜索窗的大小和位置,并根据上一帧得到的结果自适应调整搜索窗口的位置和大小,从而定位出当前图像中目标的中心位置。

分为三个部分:

1. 色彩投影图(反向投影):

(1) RGB颜色空间对光照亮度变化较为敏感,为了减少此变化对跟踪效果的影响,首先将图像从RGB空间转换到HSV空间

(2) 然后对其中的H分量作直方图,在直方图中代表了不同H分量值出现的概率或者像素个数,就是说可以查找出H分量大小为h的概率或者像素个数,即得到了颜色概率查找表。

(3) 将图像中每个像素的值用其颜色出现的概率对替换,就得到了颜色概率分布图。这个过程就叫反向投影,颜色概率分布图是一个灰度图像。

2. meanshift

meanshift算法是一种密度函数梯度估计的非参数方法,通过迭代寻优找到概率分布的极值来定位目标。
算法过程:
💞1). 在颜色概率分布图中选取搜索窗W
💞2). 计算阶距:

  •        计算零阶矩
    M 00 = ∑ x ∑ y I ( x , y ) M_{00}=\sum_x\sum_yI(x,y) M00=xyI(x,y)
  •        计算一阶矩:
    M 10 = ∑ x ∑ y x I ( x , y ) M_{10}=\sum_x\sum_y{xI(x,y) } M10=xyxI(x,y)
    M 01 = ∑ x ∑ y y I ( x , y ) M_{01}=\sum_x\sum_y{yI(x,y)} M01=xyyI(x,y)
  •        计算搜索窗的质心:
    x c = M 10 M 00 ; x_c=\frac{M_{10}}{M_{00}}; xc=M00M10
    y c = M 01 M 00 ; y_c=\frac{M_{01}}{M_{00}}; yc=M00M01;

💞3)调整搜索窗的大小
宽 度 : s = M 00 256 ; 宽度:s=\sqrt{\frac{M_{00}}{256}}; s=256M00 ;
长 度 : l = 1.2 s ; 长度: l=1.2s; l=1.2s;
💞4)移动搜索窗的中心到质心,如果移动距离大于预设的固定阈值,则重复2)3)4),直到搜索窗的中心与质心间的移动距离小于预设的固定阈值,或者循环运算的次数达到某一最大值,停止计算。关于meanshift的收敛性证明可以google相关文献。

3. camshift

将meanshift算法扩展到连续图像序列,就是camshift算法。它将视频的所有帧做meanshift运算,并将上一帧的结果,即搜索窗的大小和中心,作为下一帧meanshift算法搜索窗的初始值。如此迭代下去,就可以实现对目标的跟踪。

算法过程

(1).初始化搜索窗
(2).计算搜索窗的颜色概率分布(反向投影)
(3).运行meanshift算法,获得搜索窗新的大小和位置。
(4).在下一帧视频图像中用(3)中的值重新初始化搜索窗的大小和位置,再跳转到(2)继续进行。

camshift能有效解决目标变形和遮挡的问题,对系统资源要求不高,时间复杂度低,在简单背景下能够取得良好的跟踪效果。但当背景较为复杂,或者有许多与目标颜色相似像素干扰的情况下,会导致跟踪失败。因为它单纯的考虑颜色直方图,忽略了目标的空间分布特性,所以这种情况下需加入对跟踪目标的预测算法。

4. OpenCV中相关API
1. 直方图

前面已经提过:
OpenCV–017:图像直方图
OpenCV–018:图像直方图均衡化
OpenCV–020:图像直方图反向投影
OpenCV–021:直方图规定化

2. CamShift函数
  • 说明
    查找对象的中心,大小和方向。

  • 声明

    RotatedRect cv::CamShift(
    		InputArray probImage,
    		Rect &window,
    		TermCriteria criteria 
    )		
    Python:
           retval, window=cv.CamShift(	probImage, window, criteria	)
    
  • 参数

    robImage对象直方图的反向投影。
    indow初始搜索窗口。
    riteria底层meanShift的停止条件。

    criterial:
    OpenCV–TermCriteria类
    返回(在旧接口中)CAMSHIFT用于收敛函数的迭代次数,实现CAMSHIFT对象跟踪算法。首先,它使用meanShift找到一个对象中心,然后调整窗口大小,找到最佳旋转。该函数返回旋转后的矩形结构,其中包括对象位置、大小和方向。通过RotatedRect::boundingRect()可以获得搜索窗口的下一个位置。

六、基于颜色特征的目标跟踪

//用HSV中的Hue分量进行跟踪

     Mat image;
	//表示是否要进入反向投影模式,true则表示要进入反向投影模式
	bool backprogectMode = false;

	//表示在选中要跟踪的初始目标,true表示正在用鼠标选择要跟踪的目标
	bool selectObject = false;

	//跟踪目标的个数
	int trackObject = 0;

	//是否显示HUE分量直方图
	bool showHist = true;

	//用于保存鼠标选择第一次单击时点的位置
	Point origin;

	//用于保存鼠标选择的矩形框
	Rect selection;


	int vmin = 10, vmax = 256, smin = 30;

	//鼠标事件,该函数用鼠标进行跟踪目标的选择
	static void onMouse(int event,int x,int y,int ,void*) {
		//鼠标左键按下时,则条件为true,在用鼠标进行目标选择
		//if里面的代码块就是确定所选择的矩形区域selection
		if (selectObject) {
			//确定鼠标点击的矩形左上角顶点的坐标
			selection.x = MIN(x, origin.x);
			cout << "origin: " << origin.x <<",";
			selection.y = MIN(y, origin.y);
			cout << origin.y << endl;

			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 EVENT_LBUTTONDOWN:
			//记录鼠标第一次按下的位置
			origin = Point(x, y);
			//鼠标刚按下去时,初始化了一个矩形区域
			selection = Rect(x, y, 0, 0);
			selectObject = true;
			break;

	    //鼠标松开,完成选择跟踪物体
		case EVENT_LBUTTONUP:
			selectObject = false;
			//如果选择物体有效,则打开跟踪功能
			if (selection.width > 0 && selection.height > 0)
				trackObject = -1;
			break;
		}
	}

	static void help() {
		cout << "\n这是一个基于meanShift算法的demo:\n"
			"   你可以选择一个跟踪的颜色目标,比如你的脸。\n";
		cout << "\n\nHot keys: \n"
			"\tESC - quit the program\n"
			"\tc - stop the tracking\n"
			"\tb - switch to/from backprojection view\n"
			"\th - show/hide object histogram\n"
			"To initialize tracking, select the object with mouse: \n";
	}
	

	static void camshiftTest() {
		help();
		VideoCapture cap;
		Rect trackWindow;

		int hsize = 16;
		float hranges[] = { 0,180 };
		const float* phranges = hranges;
	    
		
		//打开默认的摄像头
		cap.open(0);
		//判断摄像头是否成功打开
		if (!cap.isOpened())
		{
			help();
			cout << "***could not initialize capturing...***\n";
			return ;
		}

		//直方图窗口:用于显示直方图
		namedWindow("Histogram", 0);
		//CamShift :用于显示视频
		namedWindow("CamShift Demo", 0);
		//设置鼠标回调函数
		setMouseCallback("CamShift Demo", onMouse, 0);
		//createTrackbar函数的功能是在对应的CamShift Demo窗口创建滑动条Vmin,Vmax,Smin
		//滑动条的值最大为256,最后一个参数为0代表没有调用滑动拖动的响应函数
		//vmin,vmax,smin初始值分别为10,256,30
		createTrackbar("Vmin", "CamShift Demo", &vmin, 256, 0);
		createTrackbar("Vmax", "CamShift Demo", &vmax, 256, 0);
		createTrackbar("Smin", "CamShift Demo", &smin, 256, 0);

		Mat frame, hsv, hue, mask, hist, histimg = Mat::zeros(200, 320, CV_8UC3), backproj;
		bool paused = false;
		for (;;) {
			if (!paused)
			{
				//从摄像头中读取的一帧存在frame中
				cap >> frame;
				//若读取失败,停止循环
				if (frame.empty())
				{
					break;
				}
			}
			frame.copyTo(image);

			if (!paused)
			{
				//将BGR颜色空间转化成HSV颜色空间
				cvtColor(image, hsv, COLOR_BGR2HSV);

				//判断是否存在需要追踪的目标,非0即有需要跟踪的目标
				if (trackObject)
				{
					//获取进度条中设置的值
					int _vmin = vmin, _vmax = vmax;
					//inRange函数的功能是检查输入数组每个元素大小是否在2个给定数值之间,
					//如果3个通道都在对应的范围内,则mask对应的那个点的值全为1(0xff),否则为0(0x00).

					//可以是多通道的,mask保存0通道的最小值,也就是h分量
				    //这里利用了hsv的3个通道,比较h:0~180,s:smin~256,v:min(vmin,vmax),max(vmin,vmax)。
				    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中,0索引数组
					mixChannels(&hsv, 1, &hue, 1, ch, 1);

					//表示已经选择了有效地待追踪区域
					if (trackObject < 0)
					{
					    //此处的构造函数roi用的是Mat hue的矩阵头,且roi的数据指针指向hue,即共用相同的数据,selection为其感兴趣的区域
						Mat roi(hue, selection);
						//设置掩膜板选择框为ROI
						Mat maskroi(mask, selection);
						//得到选择框内且满足掩膜板内的直方图
						//calcHist()函数:第1个参数:输入矩阵序列
						              //  第2个参数:表示输入的矩阵数目,
								      //  第3个参数:表示将被计算直方图维数通道的列表,
						              //  第4个参数:表示可选的掩码函数
					                  //  第5个参数:表示输出直方图,
						              //  第6个参数: 表示直方图的维数,
						              //  第7个参数: 为每一维直方图数组的大小,
						              //  第8个参数: 为每一维直方图bin的边界
						
						calcHist(&roi, 1, 0, maskroi, hist, 1, &hsize, &phranges);
						
						//将hist矩阵进行归一化,都归一化到0-255
						normalize(hist, hist, 0, 255, NORM_MINMAX);

						trackWindow = selection;
						//置trackObject为1,表明属性提取完成
						//只要鼠标选完区域松开后,且没有按键盘清0键'c',则trackObject一直保持为1,因此该if函数只能执行一次,除非重新选择跟踪区域
						trackObject = 1;

						//与按下‘c’键是一样的,这里的all(0)表示的是标量全部清0
						histimg = Scalar::all(0);

						//histing是一个200*300的矩阵,hsize应该是每一个bin的宽度,也就是histing矩阵能分出几个bin出来
						int binW = histimg.cols / hsize;

						//定义一个缓冲单bin矩阵
						Mat buf(1, hsize, CV_8UC3);
						//saturate_case函数为从一个初始类型准确变换到另一个初始类型
			            for (int i = 0; i < hsize; i++)
						{
							buf.at<Vec3b>(i) = Vec3b(saturate_cast<uchar>(i * 180. / hsize), 255, 255);
						}
						cvtColor(buf, buf, COLOR_HSV2BGR);

						for (int i = 0; i < hsize; i++)
						{
							//at函数为返回一个指定数组元素的参考值														   
						    //画直方图到图像空间,指定左上角和右下角,并定义颜色,大小,线型等
							int val = saturate_cast<int>(hist.at<float>(i) * histimg.rows / 255);
							rectangle(  histimg,
										Point(i * binW, histimg.rows),
										Point((i + 1) * binW, histimg.rows - val),
										Scalar(buf.at<Vec3b>(i)),-1, 8);
						}
					}

					//计算直方图的反向投影
					//计算hue图像0通道直方图hist的反向投影,并让入backproj中
					calcBackProject(&hue, 1, 0, hist, backproj, & phranges);
					backproj &= mask;
					//得到掩膜内的反向投影
				    //trackWindow为鼠标选择的区域,TermCriteria为确定迭代终止的准则
					//使用MeanShift算法对backproj中的内容进行搜索,返回跟踪结果
					RotatedRect trackBox = CamShift(backproj, trackWindow,TermCriteria(1 | 2, 10, 1)); 
					//如果鼠标选择的区域trackWindow小于等于1,重置trackWindow区域
					if (trackWindow.area() <= 1)
					{
						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);
					}
					if (backprogectMode)
						cvtColor(backproj, image, COLOR_GRAY2BGR);

					//画出跟踪结果的位置:以椭圆为代表
					ellipse(image, trackBox, Scalar(0, 0, 255), 3);
				}
			}
			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("CamShift Demo", image);
			imshow("Histogram", histimg);

			char c = (char)waitKey(10);
			if (c == 27)//退出键
				break;
			switch (c)
			{
			case 'b'://反向投影模型交替
				backprogectMode = !backprogectMode;
				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:
				;
			}

		}
	}
int main(){
	//Mat src=imread("D:/test/sh.png");
	//inRangeTest(src);

	camshiftTest();

	waitKey(0);


}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

学习资料

  1. 点滴成海~: 基于颜色特征的目标跟踪

  2. 雷霄骅: Camshift算法原理及其Opencv实现

    ps:虽然不认识,但是每次看到都想哭,一想到他就会想起对我很重要的一个人。愿他们在天上能快快乐乐的。

  3. qq_35971623: opencv–颜色物体识别跟踪

  4. opencv3/C++基于颜色的目标跟踪

  5. opencv库

  6. a &=b这是什么意思啊?

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值