CAMshift是一种基于对视频序列采用MeanShift算法进行运算,并将上一帧的结果(即搜索窗口的中心位置和窗口大小)作为下一帧MeanShift算法的搜索窗口的初始值的算法。如此迭代下去,我们便可以对视频中移动的物体进行追踪。
MeanShift算法:
首先,我们假设平面空间有这样随机分布的点,如下: 我们随机以某点为圆心,合适的半径r作圆:
然后落在圆中的所有点与圆心连接形成向量:
这样我们不难看出,点分布较密集的区域形成的向量数量较多,于是我们取点最密集的方向作为圆心偏移的方向,所有向量模的平均值为圆心偏移的位移
这样逐渐进行下去,最终我们的圆将会停留在空间中点的分布最为密集的区域。
这其中的数学公式我们不作详细推导。
而将此算法应用在图像处理中,一般一个图像就是个矩阵,像素点均匀的分布在图像上,就没有点的稠密性。 如果我们就算点x的概率密度,采用的方法如下:以x为圆心,以r为半径。落在球内的点位xi 定义二个模式规则。
(1)x像素点的颜色与xi像素点颜色越相近,我们定义概率密度越高。 (2)离x的位置越近的像素点xi,定义概率密度越高。
所以定义总的概率密度,是二个规则概率密度乘积的结果可以表示为:
其中:右式第二个因式代表空间位置的信息,离远点越近,其值就越大,第三个表示颜色信息,颜色越相似,其值越大。如图左上角图片,按照(4)计算的概率密度如图右上
因此我们通过计算图像的直方图反向投影,就可以根据上述的算法实现目标追踪。
相关API:
1、calcHist()直方图计算函数
void calcHist(&roi,1,0,maskroi,hist,1,&hist,&phranges)
&roi:输入图像
1:第一个参数中存放了多少张图像
0:需要统计的通道索引
maskroi:输入图像的mask,可选的操作掩码,这里的非零掩码元素用于标记出统计直方图的数组元素数据
hist:,输出的目标直方图
1:需要计算的直方图的维度
&hist:存放每个维度的直方图尺寸的数组
&phranges:每一维数取值范围。
2、calcBackProject()直方图反向投影
void calcBackProject(&hue,1,0,hist,backProj,&phranges)
Hue:输入图像
1:输入图像的个数
0:需要统计的通道索引
Hist:输入的直方图
backProj:目标反向投影阵列,必须为单通道
Phranges:每一个维度数组的边界
3、CamShift()实现目标追踪
void CamShift(backProj,trackWindow,TerCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER,10,1))
backProj:输入图像直方图的反向投影图
trackWindow:跟踪目标的初始位置矩形框
TerCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER,10,1):算法结束条件
操作步骤:
1、获取跟踪目标的hsv图像,并且分离出其中h部分;
2、根据阈值用inRange函数构建掩膜;
3、计算直方图,并且归一化;
4、直方图反向投影calcBackProject();
5、CamShift算法跟踪,获得跟踪目标的旋转外接矩形RotatedRect;
6、在原视频中绘制矩形锁定跟踪目标。
代码演示:
#include <opencv2/opencv.hpp>#include <iostream>#include <math.h>using namespace cv;using namespace std;int smin = 15;int vmin = 40;int vmax = 256;int bins = 16;int main(int argc, char** argv) { VideoCapture capture(0); if (!capture.isOpened()) { printf("could not find video data file...\n"); 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::zeros(300, 300, CV_8UC3);while (capture.read(frame)) { if (firstRead) { Rect2d first = selectROI("CAMShift Tracking", frame); selection.x = first.x; selection.y = first.y; selection.width = first.width; selection.height = first.height; printf("ROI.x= %d, ROI.y= %d, width = %d, height= %d", selection.x, selection.y, selection.width, selection.height); }cvtColor(frame, hsv, COLOR_BGR2HSV); inRange(hsv, Scalar(0, smin, vmin), Scalar(180, vmax, vmax), mask); hue = Mat(hsv.size(), hsv.depth()); int channels[] = { 0, 0 }; mixChannels(&hsv, 1, &hue, 1, channels, 1);if (firstRead) { // ROI 直方图计算 Mat roi(hue, selection); Mat maskroi(mask, selection); calcHist(&roi, 1, 0, maskroi, hist, 1, &bins, &hranges); normalize(hist, hist, 0, 255, NORM_MINMAX);int binw = drawImg.cols / bins; 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++) { int val = saturate_cast<int>(hist.at<float>(i)*drawImg.rows / 255); rectangle(drawImg, Point(i*binw, drawImg.rows), Point((i + 1)*binw, drawImg.rows - val), Scalar(colorIndex.at<Vec3b>(0, i)), -1, 8, 0); } }// back projection calcBackProject(&hue, 1, 0, hist, backprojection, &hranges); // CAMShift tracking backprojection &= mask; RotatedRect trackBox = CamShift(backprojection, selection, TermCriteria((TermCriteria::COUNT | TermCriteria::EPS), 10, 1)); Point2f vertices[4]; trackBox.points(vertices); for (size_t i = 0; i < 4; i++) line(frame, vertices[i], vertices[(i + 1) % 4], Scalar(0, 0, 255), 3, 8);capture.release(); waitKey(0); return 0;}
效果展示:
首先我们选定要识别跟踪的区域:
按下空格键,开始识别跟踪。
原文:https://juejin.cn/post/7095936555520360484