一、RGB与HSV颜色系统

数字图像处理中常用的采用模型是RGB(红,绿,蓝)模型和HSV(色调,饱和度,亮度),RGB广泛应用于彩色监视器和彩色视频摄像头,我们平时的图片一般都是RGB模型。而HSV模型更符合人描述和解释颜色的方式,HSV的彩色描述对人来说是自然且非常直观的,CAMshift是基于HSV颜色系统的。
 HSV模型中颜色的参数分别是:色调(H:hue),饱和度(S:saturation),亮度(V:value)。由A. R. Smith在1978年创建的一种颜色空间, 也称六角锥体模型(Hexcone Model)。
OpenCV学习之路(九)——CAMShift视频对象跟踪

  1. 色调(H:hue):用角度度量,取值范围为0°~360°,从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°。它们的补色是:黄色为60°,青色为180°,品红为300°;
  2. 饱和度(S:saturation):取值范围为0.0~1.0,值越大,颜色越饱和。
  3. 亮度(V:value):取值范围为0(黑色)~255(白色)

OpenCV下有个函数可以直接将RGB模型转换为HSV模型,OpenCV中H∈ [0, 180), S ∈ [0, 255], V ∈ [0, 255]。我们知道H分量基本能表示一个物体的颜色,但是S和V的取值也要在一定范围内,因为S代表的是H所表示的那个颜色和白色的混合程度,也就说S越小,颜色越发白,也就是越浅;V代表的是H所表示的那个颜色和黑色的混合程度,也就说V越小,颜色越发黑。
经过实验,一些基本的颜色H的取值可以如下设置:
Green 38-75,Blue 75-130,Red 160-179
 OpenCV 的文档中是这样解释的:原本输出的 HSV 的取值范围分别是 0-360, 0-1, 0-1;但是为了匹配目标数据类型 OpenCV 将每个通道的取值范围都做了修改,于是就变成了 0-180, 0-255, 0-255,并且同时解释道:为了适应 8bit 0-255 的取值范围,将 hue 通道 0-360 的取值范围做了减半处理,这就是为什么 OpenCV 中 H通道的取值范围是0-180。
OpenCV学习之路(九)——CAMShift视频对象跟踪

二、图像的前置操作

  • 读取视频
    //打开视频
	VideoCapture capture;
	capture.open("test.mp4");
	if (!capture.isOpened())
	{
		printf("could not find \n");
	}
	//命名窗口
	namedWindow("Origin", CV_WINDOW_AUTOSIZE);
	namedWindow("ROI", CV_WINDOW_AUTOSIZE);
  • 选中ROI区域
    此处直接调用selectROI,selectROI是跟踪API的一部分,需要引用Opencv_contrib库。
	Rect2d first = selectROI("Origin", frame);
	// int 转换 double
	selection.x = first.x;
	selection.y = first.y;
	selection.width = first.width;
	selection.height = first.height;
	printf("ROLx = %d;ROLy=%d,width =%d;height = %d", selection.x, selection.y, selection.width, selection.height);
  • 将图像从RGB表示转化为HSV表示
    OpenCV中使用cvtColor()函数就可以实现颜色空间的转换。注意opencv中颜色方块的排列顺序是BGR,而不是熟悉的RGB,因此颜色映射码是COLOR_BGR2HSV。
	//转换到 HSV
	cvtColor(frame, hsv, COLOR_BGR2HSV);
  • 提取Hue分量
    获取hsv图像后,需要提取出其中的Hue分量,即颜色分割。
	inRange(hsv, Scalar(0, smin, vmin), Scalar(180, smax, vmax), mask);

inRange()函数的功能是检查输入数组的每个元素是不是在给定范围内。代码中可以看出,检查的是hsv的像素的Hue分量是否在0-180之间,Saturation分量是否在smin-255之间,Value分量是否在vmin和vmax之间,返回验证矩阵mask,如果hsv的像素点满足条件,那么mask矩阵中对应位置的点置255,不满足条件的置0。
HSV范围是opencv中规定的,因此Hue的范围是0-180,Saturation和Value的范围是0-255。如果是Photoshop,那么HSV的范围就是0-360°和0-1了。

  • 提取Hue分量
    通道复制函数mixChannels(),此函数由输入参数复制某通道到输出参数特定的通道中。
void mixChannels(
const Mat* src, //输入的数组,所有的数组必须有相同的尺寸和深度
size_t nsrcs,   //第一个参数src输入的矩阵数
Mat* dst,   //输出的数组,所有矩阵必须被初始化,且大小和深度必须与src[0]相同
size_t ndsts,   //第三个参数dst输入的矩阵数
const int* fromTo,//对指定的通道进行复制的数组索引
size_t npairs)  //第五个参数fromTo的索引数
mixChannels(&hsv, 1, &hue, 1, channels, 1);

输入一个矩阵hsv,输出一个size和depth与hsv完全相同的矩阵hue,复制hsv[0]通道到hue[0]通道,也就是“提取”图像的Hue分量,储存在hue矩阵中。
拿到图像Hue分量的数据后,图像的前置处理完成。

三、ROI区域直方图计算与绘制

图像的前置处理完成并获取hue矩阵后,需要计算ROI区域关于hue的一维颜色直方图。这个直方图除非重新框选,否则在循环中只计算和绘制一次。
calcHist()函数原型如下:

calcHist(
 const Mat* images, //输入的数组
 int nimages,       //输入数组的个数
 const int* channels,   //需要统计的通道索引
 InputArray mask,   //掩膜
 OutputArray hist,  //输出的目标直方图
 int dims,      //需要计算的直方图维度
 const int* histSize,   //在每一维上直方图的个数。如果是一维直方图,就是竖条(bin)的个数。
 const float** ranges,  //每一维数值的取值范围数组
 bool uniform,      //直方图是否均匀的标识符
 bool accumulate    //是否累加。如果为true,在下次计算的时候不会首先清空hist
)
//ROI直方图计算
					Mat roi(hue, selection);
					Mat maskroi(mask, selection);
					//输入的数组&roi只有一个,统计的通道是0通道
					//使用的掩膜是maskroi,输出一维直方图,有16个竖条,取值范围是{0,180}。 
					calcHist(&roi, 1, 0, maskroi, hist, 1, &bins, &Phranges);
					//直方图之后再归一化到0-255
					normalize(hist, hist, 0, 255, NORM_MINMAX);
					//画出直方图
					//每个条的宽度
					int binw = drawing.cols / bins;
					//定义一个缓冲单bin矩阵,1行16列,用于存放颜色数据,用于直方图hsize个bin的“染色”。
					Mat colorIndex = Mat(1, bins, CV_8UC3);
					for (int i = 0;i < bins;i++)
					{
						colorIndex.at<Vec3b>(0, i) = Vec3b(saturate_cast<uchar>(i * 180 / bins), 255, 255);
					}
					cvtColor(colorIndex, colorIndex, COLOR_HSV2BGR);
					for (int i = 0;i < bins; i++)
					{
						//val是直方图hist的相对histimg的高度。hist.at(i)获取了第i个bin直方图数据,除以255后得到百分比
						//再乘以histimg的行数就得到了相对高度,最后进行int的强制类型转换,转换为整数。
						int val = saturate_cast<int>(hist.at<float>(i)*drawing.rows / 255);
						//之后使用rectangle()函数进行16个bin的绘制值得注意的是矩阵的坐标系以左上角为原点,y轴是向下的,而需要展示给人看的直方图图案是左下角为原点,y轴向上的
						//因此rectangle的两个标定点的纵坐标是histimg.rows和(histimg.rows - val)而不是0和val。
						rectangle(drawing, Point(i*binw, drawing.rows), Point((i + 1)*binw, drawing.rows - val), Scalar(colorIndex.at<Vec3b>(0, i)), -1, 8, 0);
					}

四、反向投影

calcBackProject()函数原型如下:

void calcBackProject(
const Mat* images,  //输入的数组
int nimages,        //输入数组的个数
const int* channels,    //需要统计的通道索引
InputArray hist,    //输入的直方图
OutputArray backProject,//目标的反向投影
const float** ranges,   //每一位数值的取值范围
double scale=1,     //输出方向投影的缩放因子
bool uniform=true   //指示直方图是否均匀的标识符
)
calcBackProject(&hue, 1, 0, hist, backprojection, &Phranges);
			backprojection &= mask;

五、CAMshift目标追踪

RotatedRect CamShift( 
    InputArray _probImage, 
    Rect& window,
    TermCriteria criteria
)

CamShift函数接受3个参数:反向投影,矩形搜索框,和迭代中止条件。
TermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 ))
示例意思:精度先达到1或者迭代次数先达到10次时,停止迭代
获取CamShift的返回值,是一个旋转矩形,根据旋转矩形绘制一个椭圆形显示在图像上作为追踪结果。

	//CamShift函数接受3个参数:反向投影,矩形搜索框,和迭代中止条件。
	RotatedRect trackBox = CamShift(backprojection, selection, TermCriteria((TermCriteria::COUNT | TermCriteria::EPS), 10, 1));
	//获取CamShift的返回值,是一个旋转矩形,根据旋转矩形绘制一个椭圆形显示在图像上作为追踪结果。
	ellipse(frame, trackBox, Scalar(0, 0, 255), 3, 8);

流程图

OpenCV学习之路(九)——CAMShift视频对象跟踪

效果图

OpenCV学习之路(九)——CAMShift视频对象跟踪
完整源代码已经上传CSDN下载,如需要可自行下载

相关文章: