OpenCV4学习笔记(36)——基于均值迁移(MeanShift)算法和直方图反向投影的目标移动跟踪

在前几篇博文中,整理记录了一些对于视频流进行运动物体检测的方法,例如稀疏光流跟踪、稠密光流跟踪、帧差法等等,但是这些方法都是面向视频中所有物体而言的。而有时候,我们需要的是对某一个特定目标进行跟踪分析,所以这就需要针对目标的移动跟踪算法来实现这种需求。
这就是今天要整理的笔记内容:基于均值迁移(MeanShift)算法的目标移动跟踪。
在之前的博文《OpenCV4学习笔记(15)——图像边缘保留滤波》中,也曾经使用过均值迁移(MeanShift)算法,在图像处理模块中使用均值迁移(MeanShift)算法可以实现去噪、边缘保留滤波等操作,达到中和图像中色彩分布相近的颜色,平滑色彩细节,侵蚀掉面积较小的颜色区域等效果,同时具有边缘保留效果。
而在视频分析模块中使用均值迁移算法结合直方图反向投影算法能够实现对视频流中特定目标的移动跟踪分析,是一种比较稳定的视频移动对象跟踪算法。其核心的思想是对视频流的每一帧图像进行目标直方图的反向投影,并对反向投影之后的图像做均值迁移(meanshift),从而发现密度最高的区域,也是对象分布概率最大的区域。
对于反向投影图进行均值迁移的大致步骤如下:首先规定一个窗口,对窗口区域内的所有元素计算均值,然后计算窗口内每个元素相对于均值的变化量delta,并将窗口向着变化量delta大的方向移动。
一直重复上述的计算均值、窗口移动操作,直至窗口的中心位置即为delta最大位置(窗口偏移量达到最小),或者移动次数达到最大值时,停止迁移。对于反向投影图而言,此时窗口位置就是概率密度最大的位置,该位置就是目标最可能存在的位置。
对视频流中每帧图像都进行上述步骤操作,获取每一帧图像中目标最可能存在的位置并标注出来,这就实现了对特定目标的运动跟踪
而为了增强对于目标物体的针对性,需要对目标物体所在区域转化到HSV空间进行直方图的计算,基本思想是以视频图像中运动目标的颜色信息作为特征,对输入图像的每一帧分别作MeanShift运算,并将上一帧的目标中心和搜索窗口大小作为下一帧MeanShift算法的中心和搜索窗口大小的初始值,如此迭代下去,实现对特定目标的跟踪。
【OpenCV4学习笔记(36)——基于均值迁移(MeanShift)算法和直方图反向投影的目标移动跟踪】在具体实现中,可以分为以下具体步骤:
(1)需要读取视频的首帧图像并选取目标区域;
(2)将选定的目标区域作为ROI图像提取出来,再转化到HSV色彩空间;
(3)获取ROI图像中HSV通道的高低值,再通过inRange()提取出ROI区域的内容作为掩膜mask;
(4)通过掩膜mask计算ROI区域的直方图;
(5)在视频流中,对每帧图像转化到HSV色彩空间,并利用ROI图像直方图进行反向投影;
(6)将获得的反向投影图进行均值迁移,找到其中密度最大的区域,并将窗口更新到该位置;
(7)在每帧图像的最大密度区域进行标记,例如绘制矩形框。
在OpenCV中提供了一个实现均值迁移算法的 APImeanShift(probImage, window, TermCriteria(TermCriteria::EPS | TermCriteria::COUNT, 10, 1)),其参数列表如下:
第一个参数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; } //读取第一帧图像 Mat first_frame; capture.read(first_frame); //选择目标区域,并将目标区域图像转为HSV色彩空间 Rect roi_rect; roi_rect = selectROI("first_frame", first_frame, false, false); Mat roi_image = first_frame(roi_rect).clone(); Mat roi_hsv; cvtColor(roi_image, roi_hsv, COLOR_BGR2HSV); //分离H、S、V三通道图像,分别获取H、S、V通道图像中的低值和高值 vectormodel_hsv; split(roi_hsv, model_hsv); double minVal_h, maxVal_h, minVal_s, maxVal_s, minVal_v, maxVal_v; minMaxLoc(model_hsv[0], &minVal_h, &maxVal_h); minMaxLoc(model_hsv[1], &minVal_s, &maxVal_s); minMaxLoc(model_hsv[2], &minVal_v, &maxVal_v); Mat mask; inRange(roi_hsv, Scalar(minVal_h, minVal_s, minVal_v), Scalar(maxVal_h, maxVal_s, maxVal_v), mask); //计算目标模板区域的直方图 //定义计算直方图参数 Mat roi_hist; int channels[2] = { 0, 1 }; int histSize[2] = { 180, 255 }; float range_h[2] = { 0, 180 }; float range_s[2] = { 0, 255 }; const float* HistRanges[2] = { range_h, range_s }; calcHist(&roi_hsv, 1, channels, mask, roi_hist, 2, histSize, HistRanges, true, false); normalize(roi_hist, roi_hist, 0, 255, NORM_MINMAX); Mat frame, frame_hsv; while (capture.read(frame)) { //将当前帧图像转为HSV图像,并计算模板图像直方图在当前帧HSV图像中的反向投影 cvtColor(frame, frame_hsv, COLOR_BGR2HSV); Mat backProject; calcBackProject(&frame_hsv, 1, channels, roi_hist, backProject, HistRanges); //利用当前帧的反向投影图进行均值迁移,不断更新目标区域的位置并绘制出来 meanShift(backProject, roi_rect, TermCriteria(TermCriteria::EPS | TermCriteria::COUNT, 10, 1)); rectangle(frame, roi_rect, Scalar(0, 255, 0), 1, 8, 0); imshow("frame", frame); char ch = cv::waitKey(20); if (ch == 27) { break; } } capture.release();

首先读取第一帧图像:
OpenCV4学习笔记(36)——基于均值迁移(MeanShift)算法和直方图反向投影的目标移动跟踪
文章图片

然后选取ROI区域:
OpenCV4学习笔记(36)——基于均值迁移(MeanShift)算法和直方图反向投影的目标移动跟踪
文章图片

再进行特定目标跟踪:
OpenCV4学习笔记(36)——基于均值迁移(MeanShift)算法和直方图反向投影的目标移动跟踪
文章图片

OpenCV4学习笔记(36)——基于均值迁移(MeanShift)算法和直方图反向投影的目标移动跟踪
文章图片

基于均值迁移(MeanShift)算法和直方图反向投影实现的目标移动跟踪,在光照比较均衡稳定的情况下所实现的跟踪效果是比较好的,而且运行速度比较快,在视频流播放过程中几乎没有看到明显的卡顿现象,即使是在调用摄像头的实时目标跟踪过程中也没有出现明显的卡顿现象。
但是这种方法也有缺点,就是对于目标的跟踪是在反向投影图中寻找密度最大的位置,一旦目标离开了视频画面,那么反向投影图中依然存在密度最大位置,但却不是目标的所在位置。所以一旦目标离开了视频画面,那么就会出现错误跟踪的情况,所以这种方法仍然有很大的改进空间。
好的,今天的笔记整理到此结束,谢谢阅读~
PS:本人的注释比较杂,既有自己的心得体会也有网上查阅资料时摘抄下的知识内容,所以如有雷同,纯属我向前辈学习的致敬,如果有前辈觉得我的笔记内容侵犯了您的知识产权,请和我联系,我会将涉及到的博文内容删除,谢谢!

    推荐阅读