CMT跟踪算法分析

来源:互联网 发布:h网络快还是e网络快 编辑:程序博客网 时间:2024/06/05 02:23

1 简介

这个算法的全称是Clustering of Static-Adaptive Correspondences for Deformable Object Tracking,文章发表在CVPR2015上,官方的网址为:
http://www.gnebehay.com/cmt/

这个作者就是OpenTLD的C++版本的作者,包括ROS版的OpenTLD也是基于他的代码改的。
对于CMT这个跟踪算法,我在iPhone5s上做了实际测试,跟踪效果超乎想象,在我比较了TLD,CT,Color Tracking,Struck等等顶级算法的效果之后得到了这样的结论。这个算法是可以实用的跟踪算法,虽然它也有缺点,之后我会说。实时性和跟踪效果都一流。

本文主要目的是分析这个跟踪算法。

2 基本原理

对于物体的视觉跟踪,基本的思路就是能够不断监测到物体的特征,从而不断的得到物体的位置,实现跟踪。常见的有三种方法:

第1是基于整体的模型来跟踪,比如说TLD,通过不断的更新模型(也就是学习的过程)来实现对物体特征的良好表示。

第2是基于物体的局部来跟踪,就是将物体分解为多个部分,对每一部分进行单独的跟踪,采用光流等方法

第3是基于物体的特征点来跟踪,就是实时的监测物体的特征点,与一开始的特征点进行匹配的方法来实现物体的跟踪。

从上面的方法看现在的跟踪算法确实不能简单的用跟踪两个字来描述,里面的算法其实使用了物体检测,识别,机器学习等各种各样的方法。只要能框住视频中的一个物体,然后不断跟着,那么这个算法就是跟踪算法,跟踪算法的好坏也完全取决于能不能很好的框住。实际上,现在很多跟踪算法就是检测的算法。

那么CMT算法采用的是第三种思路,就是利用特征点。为什么?第一个理由恐怕是快!如果以神经网络来获取特征,那么那么多的参数,算到什么时候?不知道。而采用特征点的话,我们知道OpenCV中集成了很多检测特征点的算法,比如SIFT,FAST,BRISK等等,有的比如FAST速度很快的。而且这么做连滑动窗口检测都可以省掉啦。

那么问题在于如何判断下一帧的图像中哪些特征点是与当前的框中的特征点相匹配的问题了?只要能够很好地找到下一帧中物体的特征点,跟踪也就完成了。

因此,为了解决这个问题,作者做了一个看起来很简单的创新:就是计算特征点的相对位置,以框的中心来进行计算,对于不形变的物体而言,不管物体怎么移动旋转,其上面的特征点相对中心的距离是在缩放比例下是确定的,因此可以由此来排除不是的特征点。

作者获取下一帧的特征点做了两部分工作:1个是计算前一帧的框中的特征点的光流,从而得到当前帧的特征点位置,另一个方法是直接计算当前帧的特征点,并与上一帧的特征点进行匹配,得到相匹配的特征点,然后把两个得到的特征点都融合在一起。就得到了下一帧的初步的特征点。然后在对特征点进行筛选,采用的就是上一段说的方法。

基本原理就是上面说的,下面我们从代码级别来分析一下这个算法。

3 CMT.cpp代码分析

在使用CMT算法时,我们要先初始化,也就是确定第一帧以及框框,然后进行处理下一帧。

void initialize(const Mat im_gray, const cv::Rect rect); void processFrame(const Mat im_gray);
  • 1
  • 1

先看初始化的代码:

void CMT::initialize(const Mat im_gray, const cv::Rect rect){    //Remember initial size 存储跟踪区域的初始大小    size_initial = rect.size();    //Remember initial image 存储初始灰度图像    im_prev = im_gray;    //Compute center of rect 计算跟踪区域的中心位置    Point2f center = Point2f(rect.x + rect.width/2.0, rect.y + rect.height/2.0);    //Initialize detector and descriptor 初始化检测器FAST和描述器BRISK    detector = FeatureDetector::create(str_detector);    descriptor = DescriptorExtractor::create(str_descriptor);    //Get initial keypoints in whole image and compute their descriptors    vector<KeyPoint> keypoints;    detector->detect(im_gray, keypoints); // 检测初始图像的所有关键点    //Divide keypoints into foreground and background keypoints according to selection 分离出前景和背景的关键点,前景即跟踪框内    vector<KeyPoint> keypoints_fg;    vector<KeyPoint> keypoints_bg;    for (size_t i = 0; i < keypoints.size(); i++)    {        KeyPoint k = keypoints[i];        Point2f pt = k.pt;        if (pt.x > rect.x && pt.y > rect.y && pt.x < rect.br().x && pt.y < rect.br().y)        {            keypoints_fg.push_back(k);        }        else        {            keypoints_bg.push_back(k);        }    }    //Create foreground classes 创建前景索引序号(即每个序号对应一个特征点)    vector<int> classes_fg;    classes_fg.reserve(keypoints_fg.size());    for (size_t i = 0; i < keypoints_fg.size(); i++)    {        classes_fg.push_back(i);    }    //Compute foreground/background features 计算前景和背景的特征描述    Mat descs_fg;    Mat descs_bg;    descriptor->compute(im_gray, keypoints_fg, descs_fg);    descriptor->compute(im_gray, keypoints_bg, descs_bg);    //Only now is the right time to convert keypoints to points, as compute() might remove some keypoints 将关键点转换为点存储    vector<Point2f> points_fg;    vector<Point2f> points_bg;    for (size_t i = 0; i < keypoints_fg.size(); i++)    {        points_fg.push_back(keypoints_fg[i].pt);    }    for (size_t i = 0; i < keypoints_bg.size(); i++)    {        points_bg.push_back(keypoints_bg[i].pt);    }    //Create normalized points 创建正规化的点,即计算前景的关键点到前景框中的相对位置作为正规化的点的坐标    vector<Point2f> points_normalized;    for (size_t i = 0; i < points_fg.size(); i++)    {        points_normalized.push_back(points_fg[i] - center);    }    //Initialize matcher 初始化匹配器    matcher.initialize(points_normalized, descs_fg, classes_fg, descs_bg, center);    //Initialize consensus 初始化一致器    consensus.initialize(points_normalized);    //Create initial set of active keypoints 创建初始的活动点,即前景关键点的坐标    for (size_t i = 0; i < keypoints_fg.size(); i++)    {        points_active.push_back(keypoints_fg[i].pt);        classes_active = classes_fg;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93

再看处理的函数:

void CMT::processFrame(Mat im_gray) {    //Track keypoints    vector<Point2f> points_tracked;    vector<unsigned char> status;    // 利用光流法计算关键点的当前位置。    tracker.track(im_prev, im_gray, points_active, points_tracked, status);    //keep only successful classes 只保留正确的类    vector<int> classes_tracked;    for (size_t i = 0; i < classes_active.size(); i++)    {        if (status[i])        {            classes_tracked.push_back(classes_active[i]);        }    }    //Detect keypoints, compute descriptors 计算当前图像的关键点    vector<KeyPoint> keypoints;    detector->detect(im_gray, keypoints);    // 计算当前图像特征点的描述    Mat descriptors;    descriptor->compute(im_gray, keypoints, descriptors);    //Match keypoints globally 在全局和之前的数据库匹配特征点,计算出匹配的特征点    vector<Point2f> points_matched_global;    vector<int> classes_matched_global;    matcher.matchGlobal(keypoints, descriptors, points_matched_global, classes_matched_global);    //Fuse tracked and globally matched points    //融合跟踪和匹配的点 将两种点都放在一起,并且不重复    vector<Point2f> points_fused;    vector<int> classes_fused;    fusion.preferFirst(points_tracked, classes_tracked, points_matched_global, classes_matched_global,            points_fused, classes_fused);    // 估计旋转和缩放利用最终的融合点    //Estimate scale and rotation from the fused points    float scale;    float rotation;    consensus.estimateScaleRotation(points_fused, classes_fused, scale, rotation);    //Find inliers and the center of their votes    //计算一致性,获取相关的在框内的点inlier和中心    Point2f center;    vector<Point2f> points_inlier;    vector<int> classes_inlier;    consensus.findConsensus(points_fused, classes_fused, scale, rotation,            center, points_inlier, classes_inlier);    //Match keypoints locally 局部匹配    vector<Point2f> points_matched_local;    vector<int> classes_matched_local;    matcher.matchLocal(keypoints, descriptors, center, scale, rotation, points_matched_local, classes_matched_local);    //Clear active points    points_active.clear();    classes_active.clear();    //Fuse locally matched points and inliers    // 融合局部匹配的点和inliers    fusion.preferFirst(points_matched_local, classes_matched_local, points_inlier, classes_inlier, points_active, classes_active);    //TODO: Use theta to suppress result    // 计算出新的跟踪窗口    bb_rot = RotatedRect(center,  size_initial * scale, rotation/CV_PI * 180);    //Remember current image 更新上一帧图像    im_prev = im_gray;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

从上面的代码可以比较清晰的知道整个处理流程,具体的细节比如匹配的方式,检测一致性的算法,得在具体看其他文件的代码,本文暂时不展开。

4 CMT算法评价

缺点:
1、没有更新模型的过程,导致物体角度变化大时找不到特征点
2、特征点太多的时候会导致速度变慢
3、特征点少的时候就跟踪不上
4、移动物体在很多情况下特征点会发生变化,很容易导致跟踪不上。

优点:
1、代码简单,C++和Python都有,使用OpenCV实现
2、速度还是比较快的,至少我iPhone上测试还不错
3、跟踪效果特别静态物体简直近乎完美。够了。

0 0
原创粉丝点击