一、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;
}
|