
前言
CamShift算法,全称是 Continuously AdaptiveMeanShift,顾名思义,它是对Mean Shift 算法的改进,能够自动调节搜索窗口大小来适应目标的大小,可以跟踪视频中尺寸变化的目标。它也是一种半自动跟踪算法,需要手动标定跟踪目标。
CamShift基本思想是以视频图像中运动物体的颜色信息作为特征,对输入图像的每一帧分别作 Mean-Shift 运算,并将上一帧的目标中心和搜索窗口大小(核函数带宽)作为下一帧 Mean shift 算法的中心和搜索窗口大小的初始值,如此迭代下去,就可以实现对目标的跟踪。
因为在每次搜索前将搜索窗口的位置和大小设置为运动目标当前中心的位置和大小,而运动目标通常在这区域附近,缩短了搜索时间;另外,在目标运动过程中,颜色变化不大,故该算法具有良好的鲁棒性。已被广泛应用到运动人体跟踪,人脸跟踪等领域。
视频效果
相关API
RotatedRect CamShift(InputArray probImage, Rect& window, TermCriteria criteria)
参数说明:
实现步骤
第一步:选中物体,记录你输入的方框和物体。
第二步:求出视频中有关物体的反向投影图。
第三步:根据反向投影图和输入的方框进行meanshift迭代,由于它是向重心移动,即向反向投影图中概率大的地方移动,所以始终会移动到目标上。
第四步:然后下一帧图像时用上一帧输出的方框来迭代即可。
代码演示
我们再新建一个项目名为opencv--videocamshift,按照配置属性(VS2017配置OpenCV通用属性),然后在源文件写入#include和main方法

因为用到了鼠标选中跟踪目标,所以我们也定义了鼠标相关的方法及参数

鼠标事件



CamShift计算时的核心代码

完整代码如下:
#include <opencv2/opencv.hpp>
#include <iostream>
cv::Mat frame, gray; //源图像和源灰度图像
cv::Mat framecopy; //用于拷贝出的源图像
cv::Mat framerect, framerecthsv; //矩形选取后的图像
cv::Rect rect; //鼠标选取的矩形
//直方图
int histSize = 200;
float histR[] = { 0,255 };
const float *histRange = histR;
int channels[] = { 0,1 };
cv::Mat dstHist;
//保存目标轨迹
std::vector<cv::Point> pt;
//鼠标控制
bool firstleftbutton = false;
bool leftButtonDownFlag = false; //左键单击后视频暂停播放的标志位
cv::Point rectstartPoint; //矩形框起点
cv::Point rectstopPoint; //矩形框终点
void onMouse(int event, int x, int y, int flags, void* ustc); //鼠标回调函数
int main(int argc, char** argv)
{
cv::VideoCapture video;
video.open("E:/KK_Movies/test5.mp4");
if (!video.isOpened())
{
printf("could not open video....\r\n");
getchar();
return -1;
}
double fps = video.get(CV_CAP_PROP_FPS); //获取视频帧率
double pauseTime = 1000 / fps; //两幅画面中间间隔
cv::namedWindow("srcvideo", CV_WINDOW_NORMAL);
//设置图像中鼠标事件
cv::setMouseCallback("srcvideo", onMouse, 0);
bool first = false;
while (true)
{
//当鼠标左键没有按下时
if (!leftButtonDownFlag)
{
video >> frame;
try
{
cv::resize(frame, frame, cv::Size(300, 600));
}
catch (const std::exception& exception)
{
printf(exception.what());
break;
}
}
//图像为空或Esc键按下退出播放
if (!frame.data || cv::waitKey(pauseTime) == 27)
{
break;
}
//如果已经截取了图像进行处理
if (rectstartPoint != rectstopPoint && !leftButtonDownFlag)
{
cv::Mat imageHSV;
cv::Mat calcBackImage;
cvtColor(frame, imageHSV, cv::COLOR_BGR2HSV);
//反向投影
cv::calcBackProject(&imageHSV, 2, channels,
dstHist, calcBackImage, &histRange);
cv::TermCriteria criteria(cv::TermCriteria::MAX_ITER +
cv::TermCriteria::EPS, 10, 1);
cv::CamShift(calcBackImage, rect, criteria);
//更新模板
cv::Mat imageROI = imageHSV(rect);
framerecthsv = imageHSV(rect);
calcHist(&imageROI, 2, channels, cv::Mat(),
dstHist, 1, &histSize, &histRange);
normalize(dstHist, dstHist, 0.0, 1.0, cv::NORM_MINMAX); //归一化
rectangle(frame, rect, cv::Scalar(255, 0, 0), 3); //目标绘制
pt.push_back(cv::Point(rect.x + rect.width / 2,
rect.y + rect.height / 2));
for (int i = 0; i < pt.size() - 1; i++)
{
cv::line(frame, pt[i], pt[i + 1], cv::Scalar(0, 255, 0), 2.5);
}
}
cv::imshow("srcvideo", frame);
cv::waitKey(50);
}
video.release();
cv::waitKey(0);
return 0;
}
//鼠标回调函数
void onMouse(int event, int x, int y, int flags, void * ustc)
{
//鼠标左键按下
if (event == CV_EVENT_LBUTTONDOWN)
{
leftButtonDownFlag = true; //更新按下标志位
rectstartPoint = cv::Point(x, y); //设置矩形的开始点
rectstopPoint = rectstartPoint; //刚按下时结束点和开始点一样
}
//当鼠标按下并且开始移动时
else if (event == CV_EVENT_MOUSEMOVE && leftButtonDownFlag)
{
framecopy = frame.clone(); //复制源图像
rectstopPoint = cv::Point(x, y); //设置矩形的结束点
if (rectstartPoint != rectstopPoint)
{
//当矩形的开始点和结束点不同后在复制的图像上绘制矩形
cv::rectangle(framecopy, rectstartPoint, rectstopPoint,
cv::Scalar(255, 255, 255));
}
cv::imshow("srcvideo", framecopy);
}
//当鼠标抬起时
else if (event == CV_EVENT_LBUTTONUP)
{
leftButtonDownFlag = false;//按下鼠标标志位复位
rect = cv::Rect(rectstartPoint, rectstopPoint);//设置选中后的矩形
framerect = frame(rect); //通过矩形获取到选取后的图像
cv::imshow("selectimg", framerect);//显示出来选择后的图像
cv::cvtColor(framerect, framerecthsv, cv::COLOR_BGR2HSV);
//直方图计算
calcHist(&framerecthsv, 2, channels, cv::Mat(), dstHist, 1, &histSize, &histRange, true, false);
//归一化显示
normalize(dstHist, dstHist, 0, 255, CV_MINMAX);
}
}
下面一张是视频中的截图效果

-END-