在上次的笔记《OpenCV4学习笔记(36)——基于均值迁移(MeanShift)算法和直方图反向投影的目标移动跟踪》中,整理记录了一种针对目标的移动跟踪算法,主要是基于均值迁移和直方图反向投影来实现的。而今天要记录的笔记内容,依然是一种针对目标的移动跟踪算法——CAMShift目标移动跟踪算法,这种算法是基于MeanShift目标移动跟踪算法的改进。
CAMShift算法其实就是连续自适应的MeanShift算法,是对MeanShift 算法的改进,它能够自动调节搜索窗口大小来适应目标的大小,可以跟踪视频中尺寸变化的目标。
CAMShift算法相比起MeanShift算法主要具有两个改进方向:
一是CAMShift算法会根据跟踪对象的大小变化来自动调整搜索窗口大小;
二是返回的检测目标信息更加完整,不仅包含位置信息,同时还包含了角度信息,也即返回的是目标所在的最小外接旋转矩形。
在OpenCV中提供了CamShift()
这个API来实现CAMShift算法,它的返回值为一个包含位置和角度的旋转矩形,其参数列表如下:
第一个参数probImage:输入的概率分布图像,也就是反向投影图像;
第二个参数window:目标位置的初始窗口;
第三个参数criteria:迁移终止条件,TermCriteria::EPS为迁移距离小于阈值时停止迁移,TermCriteria::COUNT为迁移迭代次数达到设定的最大值时停止迁移,可以两者同时使用。
下面看一下具体的代码演示:
VideoCapture capture;
capture.open("D:\\opencv_c++\\opencv_tutorial\\data\\images\\balltest.mp4");
//capture.open(0);
if (!capture.isOpened())
{
return 0;
}
//提取第一帧图像中ROI区域并将其转化为HSV图像
Mat first_image, first_hsv, roi_image, roi_hsv;
capture.read(first_image);
Rect roi_rect = selectROI("first_image", first_image, false, false);
roi_image = first_image(roi_rect).clone();
cvtColor(roi_image, roi_hsv, COLOR_BGR2HSV);
//计算ROI图像的直方图
Mat roi_hist;
int dims = 2;
int channels[2] = { 0,1 };
int histSize[2] = { 180,255 };
float ranges_h[2] = { 0,180 };
float ranges_s[2] = { 0,255 };
const float* histRanges[2] = { ranges_h, ranges_s };
calcHist(&roi_hsv, 1, channels, Mat(), roi_hist, dims, histSize, histRanges);
normalize(roi_hist, roi_hist, 0, 255, NORM_MINMAX);
Mat frame, frame_hsv;
vector<Point2f> centers;
while (capture.read(frame))
{
flip(frame, frame, 1);
cvtColor(frame, frame_hsv, COLOR_BGR2HSV);
//对每一帧图像进行反向投影
Mat backProject;
calcBackProject(&frame_hsv, 1, channels, roi_hist, backProject, histRanges);
//进行CamShift算法,不断更新目标窗口位置;返回值为一个包含位置和角度的旋转矩形
RotatedRect new_rect = CamShift(backProject, roi_rect, TermCriteria(TermCriteria::COUNT | TermCriteria::EPS, 10, 1));
// //参数probImage:输入的概率分布图像,也就是反向投影图像;
// //参数window:目标位置的初始窗口;
// //参数criteria:迁移终止条件;TermCriteria::EPS为迁移距离小于阈值时停止迁移,TermCriteria::COUNT为迁移迭代次数达到设定的最大值时停止迁移,可以两者同时使用;
ellipse(frame, new_rect, Scalar(0, 255, 0), 1, 8); //绘制目标区域位置
//获取当前目标中心坐标,并存入点集中;不绘制完整目标移动轨迹,如果点集中的点数量达到规定个数,就清空点集;也就是每次只绘制最近的移动轨迹
Point2f center = new_rect.center;
if (centers.size() % 20 == 0)
{
centers.clear();
}
centers.push_back(center);
if (centers.size() > 2) //只有当目标出现移动时,也就是目标中心坐标至少有两个点时才进行绘制
{
for (size_t k = 1; k < centers.size(); k++)
{
float flag = float(k) / float(centers.size()); //标记当前点位于当前绘制的轨迹中的位置
//通过标记位置的不同,来绘制不同粗细的曲线,从而使绘制出的轨迹曲线有逐渐消失的效果,类似于彗星尾巴
if (flag < 0.2)
{
line(frame, centers[k - 1], centers[k], Scalar(0, 255, 0), 1, LINE_AA, 0);
}
else if(flag >= 0.2 && flag <= 0.4)
{
line(frame, centers[k - 1], centers[k], Scalar(0, 255, 0), 2, LINE_AA, 0);
}
else if (flag >= 0.4 && flag <= 0.6)
{
line(frame, centers[k - 1], centers[k], Scalar(0, 255, 0), 3, LINE_AA, 0);
}
else if(flag >= 0.6 && flag <= 0.8)
{
line(frame, centers[k - 1], centers[k], Scalar(0, 255, 0), 4, LINE_AA, 0);
}
else if (flag >= 0.8 && flag <= 1)
{
line(frame, centers[k - 1], centers[k], Scalar(0, 255, 0), 5, LINE_AA, 0);
}
}
}
imshow("frame", frame);
char ch = waitKey(20);
if (ch == 27)
{
break;
}
}
capture.release();
在上述代码中,首先读取视频流,然后提取第一帧图像中我们需要的ROI区域图像并将其转化到HSV色彩空间,并且计算该ROI图像的直方图。
接着读取视频流的每一帧图像,对每一帧图像都转化到HSV色彩空间进行反向投影,得到反向投影图。并对获取到的反向投影图进行CAMShift算法计算,得到在每帧图像中检测到的目标位置,以一个旋转矩形来表示。
在进行跟踪显示的时候,我们做一点视觉上的优化处理。我们不绘制移动目标的完整移动轨迹,也就是每次只绘制最近若干帧图像中的移动轨迹,并且对于轨迹的颜色是从深到浅变化,粗细程度是从粗到细变化、直至消失。这样,就把移动轨迹变成了类似于彗星拖尾的感觉。
下面看一下演示效果截图:
这是通过鼠标来进行ROI区域的提取。
上面4张图像是视频播放过程的截图。可以看到,随着画面中球体目标的不断运动,用于标记目标位置的椭圆也不断随之运动,而且尺寸、形状也是不断变化,并且后续的运动轨迹也是符合我们的视觉要求,形成一条类似的”彗尾”。
这样我们就实现了对视频流中目标物体的移动跟踪,并进行视觉优化显示。
好了,今天的笔记内容就整理到此,谢谢阅读~
PS:本人的注释比较杂,既有自己的心得体会也有网上查阅资料时摘抄下的知识内容,所以如有雷同,纯属我向前辈学习的致敬,如果有前辈觉得我的笔记内容侵犯了您的知识产权,请和我联系,我会将涉及到的博文内容删除,谢谢!