OpenCV视频篇——颜色跟踪
@[TOC]
在这里插入图片描述
一、黑白图片
来自:轻舞飞扬
在了解色彩空间前,先了解一下黑白图片的形成。
用光线对着传感器,从传感器的立场上来看的话,一部分接收到了光线,那么这一部分就是明亮的,一部分没有接收到光线,那么这一部分将会是黑暗的,我们可以视为传感器存在两个通道。
如果传感器存在多个通道的话,意思就是说我们将光线进行分类,高能量的,中等能量的,低能量的等等多种分类,传感器对这多种能力进行区分显示出不同的颜色。
不同的颜色有不同的能量分布,一般来说红色(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沿立方体对角线的白色顶点向黑色顶点观察,就可以看到立方体的六边形外形。六边形边界表示色彩,水平轴表示纯度,明度沿垂直轴测量。<center>
在这里插入图片描述
在这里插入图片描述
</center>
在这里插入图片描述
三、OpenCV中的HSV
HSV颜色空间与人眼较为接近,一般以HSV为颜色检测和识别。
opencv H范围(0-180) S(0-255) V(0-255)。
1. HSV二值化处理的函数:
-
说明
检查数组元素是否位于其他两个数组的元素之间。
该功能检查范围如下:🎈 对于单通道输入数组的每个元素:
🎈 对于两通道阵列:
也就是说,如果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个分量的取值范围分别为,,按照的形式排列成一个矢量,则其范围为:设颜色i的像素点个数为,图像的像素点的总数为:<center>
在这里插入图片描述 </center>则颜色i的出现概率,即被定义为颜色直方图,即<center> 在这里插入图片描述
</center>
2.基于颜色直方图的目标跟踪
因为颜色直方图是矢量,以此作为特征进行目标跟踪时,即基于颜色直方图的目标跟踪时,可采用Bhattacharyya距离作为两直方图相似度的度量。计算公式为:<center> 在这里插入图片描述 在这里插入图片描述</center>
其中:为两直方图的Bhattacharyya系数;p为候选目标直方图分布;为模板直方图分布;为两直方图的Bhattacharyya距离,其值越小,表明两直方图的相似度越高;反之,两直方图相似度越低。
五、camshift算法原理
camshift就是利用目标的颜色直方图模型将图像转换为颜色概率分布图,初始化一个搜索窗的大小和位置,并根据上一帧得到的结果自适应调整搜索窗口的位置和大小,从而定位出当前图像中目标的中心位置。
分为三个部分:
1. 色彩投影图(反向投影):
(1) RGB颜色空间对光照亮度变化较为敏感,为了减少此变化对跟踪效果的影响,==首先将图像从RGB空间转换到HSV空间==。
(2) 然后==对其中的H分量作直方图,在直方图中代表了不同H分量值出现的概率或者像素个数==,就是说可以查找出H分量大小为h的概率或者像素个数,即得到了颜色概率查找表。
(3) ==将图像中每个像素的值用其颜色出现的概率对替换,就得到了颜色概率分布图==。这个过程就叫反向投影,颜色概率分布图是一个灰度图像。
2. meanshift
meanshift算法是一种密度函数梯度估计的非参数方法,通过迭代寻优找到概率分布的极值来定位目标。
算法过程:
💞1). 在颜色概率分布图中选取搜索窗W
💞2). 计算阶距:
- 计算零阶矩
- 计算一阶矩:
- 计算搜索窗的质心:
💞3)调整搜索窗的大小
💞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 )
-
参数
probImage 对象直方图的反向投影。 window 初始搜索窗口。 criteria 底层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);
}
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
学习资料
-
ps:虽然不认识,但是每次看到都想哭,一想到他就会想起对我很重要的一个人。愿他们在天上能快快乐乐的。