OpenCV --- CAMShift对象跟踪

一、HSV颜色系统

HSV模型中颜色的参数分别是:色调(H:hue),饱和度(S:saturation),亮度(V:

value)。由A. R. Smith在1978年创建的一种颜色空间, 也称六角锥体模型(Hexcone Model)。

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

OpenCV中的cvtColor函数可以直接将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

 二、CAMshift目标追踪流程

CAMshift是基于HSV颜色系统的。

 三、相关API

1. opencv中自带了选择感兴趣区域的函数:selectROI() 函数 :

selectROI(const String& windowName,  //选择的区域被显示在的窗口的名字
         InputArray img,  //要在什么图片上选择ROI
         bool showCrosshair = true, //是否在矩形框里画十字线,默认是true
         bool fromCenter = false); //是否是从矩形框的中心开始画

2. 通道复制函数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的索引对数

3. 直方图计算函数 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
)

4. 直方图反向投影 - calcBackProject函数

//1.函数原型
void cv::calcBackProject(
	const Mat *        images,
	int                nimages,
	const int *        channels,
	InputArray         hist,
	OutputArray        backProject,
	const float **     ranges,
	double             scale = 1,
	bool               uniform = true
	)
 
//2.参数解释
//const Mat* images:输入图像,图像深度必须位CV_8U, CV_16U或CV_32F中的一种,尺寸相同,每一幅图像都可以有任意的通道数
//int nimages : 输入图像的数量
//const int* channels : 用于计算反向投影的通道列表,通道数必须与直方图维度相匹配,第一个数组的通道是从0到image[0].channels() - 1, 第二个数组通道从图像image[0].channels()到image[0].channels() + image[1].channels() - 1计数
//InputArray hist : 输入的直方图,直方图的bin可以是密集(dense)或稀疏(sparse)
//OutputArray backProject : 目标反向投影输出图像,是一个单通道图像,与原图像有相同的尺寸和深度
//const float ranges** : 直方图中每个维度bin的取值范围
//double scale = 1 : 可选输出反向投影的比例因子
//bool uniform = true : 直方图是否均匀分布(uniform)的标识符,有默认值true
 
//另外两种定义
 
void cv::calcBackProject(
	const Mat *          images,
	int	             nimages,
	const int *	     channels,
	const SparseMat &    hist,
	OutputArray          backProject,
	const float **       ranges,
	double               scale = 1,
	bool                 uniform = true
	)
 
void cv::calcBackProject(
	InputArrayOfArrays             images,
	const std::vector< int > &     channels,
	InputArray                     hist,
	OutputArray                    dst,
	const std::vector< float > &   ranges,
	double                         scale
	)

5. CAMshift目标追踪函数:CamShift函数

RotatedRect CamShift( 
    InputArray _probImage, //反向投影
    Rect& window, //矩形搜索框
    TermCriteria criteria  //迭代中止条件。
)

TermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 ))
示例意思:精度先达到1或者迭代次数先达到10次时,停止迭代
获取CamShift的返回值,是一个旋转矩形,根据旋转矩形绘制一个椭圆形显示在图像上作为追踪结果。

四、代码演示

#include<iostream>
#include<opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int smin = 30;
int vmin = 40;
int vmax = 256;
int bins = 16;

int main(int argc, char** argv)
{
	VideoCapture capture;
	capture.open("E:/技能学习/Opencv视频分析与对象跟踪/test.mp4");
	if (!capture.isOpened())
	{
		cout << "could not find the video file..." << endl;
		return -1;
	}

	namedWindow("CAMShift Tracking", WINDOW_AUTOSIZE);
	namedWindow("ROI Histogram", WINDOW_AUTOSIZE);

	bool firstRead = true;
	float hrange[] = { 0,180 };
	const float* hranges = hrange;
	Rect selection;
	Mat frame, hsv, hue, mask, hist, backprojection;
	Mat drawImg = Mat(300, 300, CV_8UC3);
	while (capture.read(frame))
	{
		if (firstRead)
		{
			//1. 选择ROI区域
			Rect2d first = selectROI("CAMShift Tracking", frame, false, false);
			selection.x = first.x;
			selection.y = first.y;
			selection.width = first.width;
			selection.height = first.height;

			cout << "ROI.x = " << selection.x << "  ROI.y = " << selection.y
				<< "  ROI.width = " << selection.width << "  ROI.height = " << selection.height;
		}

		//2. 转为HSV
		cvtColor(frame, hsv, COLOR_BGR2HSV);
		
        //根据跟踪对象的不同,inRange函数里面的参数要适当调整
		inRange(hsv, Scalar(0, smin, vmin), Scalar(180, vmax, vmax), mask);

		//3. 提取Hue分量
		hue = Mat(hsv.size(), hsv.depth());
		//mixChannels()函数用于将输入数组的指定通道复制到输出数组的指定通道。
		//输入一个矩阵hsv,输出一个size和depth与hsv完全相同的矩阵hue,复制hsv[0]通道到hue[0]通道,
		//也就是“提取”图像的Hue分量,储存在hue矩阵中。
		int channels[] = { 0,0 };
		mixChannels(&hsv, 1, &hue, 1, channels, 1);

		//获取hue矩阵后,需要计算ROI区域关于hue的一维颜色直方图。这个直方图除非重新框选,否则在循环中只计算和绘制一次。
		if (firstRead)
		{
			//4. ROI区域直方图计算
			Mat roi(hue, selection);
			Mat maskroi(mask, selection);

			输入的数组&roi只有一个,统计的通道是0通道
			//使用的掩膜是maskroi,输出一维直方图,有16个竖条,取值范围是{0,180}。
			calcHist(&roi, 1, 0, maskroi, hist, 1, &bins, &hranges);

			//直方图之后再归一化到0-255
			normalize(hist, hist, 0, 255, NORM_MINMAX);

			//5. 画出直方图
			//每个条的宽度
			int binw = drawImg.cols / bins;
			//定义一个缓冲单bin矩阵,1行16列,用于存放颜色数据,用于直方图hsize个bin的“染色”。
			Mat colorIndex = Mat(1, bins, CV_8UC3);
			for (int i = 0; i < bins; i++)
			{
				// H 映射到BGR的时候最大的是180
				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) * drawImg.rows / 255);

				//之后使用rectangle()函数进行16个bin的绘制,值得注意的是矩阵的坐标系以左上角为原点,y轴是向下的,而需要展示给人看的直方图图案是左下角为原点,y轴向上的
				//因此rectangle的两个标定点的纵坐标是histimg.rows和(histimg.rows - val)而不是0和val。
				rectangle(drawImg, Point(i * binw, drawImg.rows), Point((i + 1) * binw, drawImg.rows - val), Scalar(colorIndex.at<Vec3b>(0, i)), -1, 8, 0);

			}
		}
	
		//6. 反向投影
		calcBackProject(&hue, 1, 0, hist, backprojection, &hranges);
		backprojection &= mask;

		//7. CAMShift Tracking
		//RotatedRect表示平面上的旋转矩形
		RotatedRect trackBox = CamShift(backprojection, selection, TermCriteria((TermCriteria::COUNT | TermCriteria::EPS), 10, 0.));
		
		//8. 绘制追踪目标
		//获取CamShift的返回值,是一个旋转矩形,根据旋转矩形绘制一个椭圆形显示在图像上作为追踪结果。
		ellipse(frame, trackBox, Scalar(0, 0, 255), 3, 8);


		if (firstRead)
		{
			firstRead = false;
		}

		imshow("CAMShift Tracking", frame);
		imshow("ROI Histogram", drawImg);
		
		char c = waitKey(50);
		if ( c == 27)
		{
			break;
		}
	}
	capture.release();
	waitKey(0);
	destroyAllWindows();
	return 0;
}

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值