一、原理介绍
CAMShift是基于MeanShift算法的基础上而来的,所以CAMShift又叫连续的自适应的MeanShift算法。
MeanShift 算法
如下图所示:
- 首先在图中选取一个点作为中心点,然后得到一个窗口。
- 然后计算窗口内所有点的均值,计算完得到一个点(图中蓝色的),然后将这个蓝色的点作为中心点又得到一个窗口。
- 迭代若干次数后,中心的距离之差为0,就说明了找打了密度最大的点。
CAMShift 算法
如下图所示:
- 选择特征模型;
- 用MeanShift算出密度空间的特征,选择转换为特征空间;
- 然后在密度窗口内,算出直方图,也就是特征空间PDF模型数据。
- 有了模板特征直方图数据,然后在图中搜索有类似的特征直方图,说明就是那个选择对象,即就跟踪到了。注意直方图的 bin 要相等。
二、代码示例
#include <opencv2/opencv.hpp>
#include <opencv2/tracking.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
using namespace std;
const char* CAMShift_WIN = "CAMShift Tracking";
int smin = 40;
int vmin = 40;
int vmax = 256;
int bins = 16;
int main()
{
VideoCapture capture;
capture.open("D:/source/images/duan3.mp4");
if (!capture.isOpened())
{
puts("dont open video.");
system("pause");
return -1;
}
bool firstRead = true;
Rect selection;
Mat frame, hsv, hue, mask, hist, backProject;
float hrange[] = {0, 180};
const float* hranges = hrange;
namedWindow(CAMShift_WIN, WINDOW_AUTOSIZE);
Mat drawImg = Mat::zeros(300, 300, CV_8UC3);
while (capture.read(frame))
{
if (firstRead)
{
Rect2d first = selectROI(CAMShift_WIN, frame);
// 需要int类型的所以用中间变量转了一下
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\n", selection.x, selection.y, \
selection.width, selection.height);
}
// convert to HSV
cvtColor(frame, hsv, COLOR_BGR2HSV);
// 通过目标对象的颜色给定范围确定mask,如果不加这个效果没那么好
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)
{
// calculate histogram
Mat roi(hue, selection); // 提取色调ROI
Mat maskRoi(mask, selection);
calcHist(&roi, 1, 0, maskRoi, hist, 1, &bins, &hranges);
normalize(hist, hist, 0, 255, NORM_MINMAX);
// show histogram
int binw = drawImg.cols / bins;
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++)
{
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, backProject, &hranges);
// CAMShift tracking
backProject &= mask;
RotatedRect trackBox = CamShift(backProject, selection, \
TermCriteria((TermCriteria::COUNT | TermCriteria::EPS), 10, 1));
// draw location on frame
ellipse(frame, trackBox, Scalar(0, 0, 255), 3, 8);
if (firstRead)
firstRead = false;
imshow("draw hisgram", drawImg);
imshow(CAMShift_WIN, frame);
if (27 == waitKey(50)) break;
}
capture.release();
waitKey(0);
return 0;
}