醋醋百科网

Good Luck To You!

视频分析与对象跟踪-基于光流算法的对象跟踪


1. 基于光流算法的对象跟踪

通俗说,对于一个图片序列,把每张图像每个像素在连续帧之间的运动速度和方向(某像素点在连续两帧上的位移矢量)找出来就是光流场。在视频移动对象跟踪中,稀疏光流跟踪是一种经典的对象跟踪算法,可以绘制运动对象的跟踪轨迹与运行方向,是一种简单、实时高效的跟踪算法。

1. 1 算法介绍

例如在下面相邻的两帧 I 和 J 中,存在像素点的运动,也即是上一帧的像素点在下一帧中,其位置会有轻微的变动,这个变动就是位移向量,也就是像素点的光流,如下图,d 为像素点的光流。



要存在光流,即该光流能被计算出来,也就是KLT算法工作的三个假设前提条件:

  • 相邻帧之间的亮度恒定。
  • 短距离移动。
  • 空间一致性,即同一子图像的像素点具有相同的运动。

为什么要有这几个假设?如果判断一个视频的相邻两帧 I、J 在某局部窗口 w 上是一样的,则在窗口w 内有:I(x, y, t) = J(x', y', t+τ),亮度恒定的假设即为了保证其等号成立不受亮度的影响,假设2是为了保证KLT能够找到点,假设3则为以下原因假设(即对于同一个窗口中,所有点的偏移量都相等):

在窗口 w 上,所有(x, y)都往一个方向移动了(dx, dy),从而得到(x', y'),即 t 时刻的(x, y)点在 t+τ 时刻为(x+dx, y+dy),所以寻求匹配的问题可化为对以下的式子寻求最小值,或叫做最小化以下式子:



找角点的的方法一般用** Harris Shi-Tomasi **角点检测。

1. 2 图像的金字塔表示

特征跟踪器的两个关键的特性是准确性和鲁棒性,准确性是指跟踪结果的局部子像素的精度。为了不丢失图像中的细节,小窗口是更好的选择。尤其当图像中两个封闭区域有不同的运动适量时,这种选择就更有必要了。跟踪器的鲁棒性指跟踪性能对光照变化,运动大小等因素的敏感程度。比如,为了处理大运动的情况下,显然选择大的窗口是更合适的,这就要求在选择窗口大小的时候要权衡考虑局部准确性和鲁棒性,为了解决这个问题,提出了一种基于金字塔光流的改进跟踪算法。下图是金字塔光流法的原理示意图。先在图像金字塔的最顶层计算光流,用上层估计到的运动结果作为下一层金字塔的起始点,重复这样的估计直到金字塔的最底层。这样就将不满足运动假设(运动是小而连贯的)的情况转化为满足运动假设的情况来处理,实现对快而且长的运动进行跟踪。


光流跟踪的过程:(1)对一个连续的视频图像帧序列进行处理。(2)针对每一帧图像检测前景目标。(3)如果某一帧出现了前景目标,找到其具有代表性的特征点(一般利用角点来做特征点)。(4)对于之后的两个相邻视频帧,寻找上一帧中出现的特征点在当前帧中的最佳位置,从而得到前景目标在当前帧中的位置信息。(5)如此迭代进行,便可实现目标的跟踪。

例子代码:

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;

Mat frame, grayImg;  // 当前帧
Mat prev_frame,prev_grayImg;  //前一帧
vector<Point2f>features;  //Shi-Tomasi 焦点检测-特征数据
vector<Point2f>fpts[2];  //保证当前帧和前一帧的特征点位置
vector<Point2f>iniPoints;  //初始化特征数据
vector<uchar>status;  //特征点跟踪成功标志位
vector<float>errors;  //跟踪时候区域误差和

//Shi-Tomasi 焦点检测
void detectFeatures(Mat &inFrame, Mat &inGray){
    double maxCorners = 500;
    double qualitylevel = 0.01;
    double minDistance = 10;
    double blockSize = 3;
    double k = 0.04;

    // 算法很快,满足实时性要求
    goodFeaturesToTrack(inGray, features, maxCorners,
        qualitylevel, minDistance, Mat(), blockSize, false, k);
    cout << "detect features:" << features.size() << endl;
}

//绘制特征点
void drawFeature(Mat &inFrame){
    for (int i = 0; i < fpts[0].size(); i++)
    {
        circle(inFrame, fpts[0][i], 2, Scalar(0, 0, 255), 2);
    }
}
//在跟踪到的且移动了的特征点(光流)的开始跟踪的位置到当前跟踪到的位置之间绘制线段
void drawTrackLines(){
    for (int i = 0; i < fpts[1].size(); i++)
    {
        line(frame, iniPoints[i], fpts[1][i], Scalar(0, 255, 0), 2, 8, 0);  //绘制线段
        circle(frame, fpts[1][i], 2, Scalar(0, 0, 255), 2, 8, 0);
    }
}

// 稀疏光流法跟踪 KTL
void KLTrackFeature(){
    calcOpticalFlowPyrLK(prev_grayImg, grayImg, fpts[0], fpts[1], status, errors);
    int k = 0;  //保证跟踪到的特征点数,最后将特征点的尺寸重新设置为 k
    for (int i = 0; i < fpts[1].size(); i++)
    {
        double dist = abs(fpts[0][i].x - fpts[1][i].x) + abs(fpts[0][i].y - fpts[1][i].y);
        if (dist > 2 && status[i])  //跟踪到的特征点,且距离移动了 2 以上的
        {
            //将跟踪到的移动了的特征点在 vector 中连续起来,剔除掉损失的和静止不动的特征点(这些跟踪点在前面帧中)
            iniPoints[k] = iniPoints[i];
            fpts[1][k++] = fpts[1][i];  //同上,只是这些跟踪点在当前帧中
        }
    }
    //保存特征点并绘制跟踪轨迹
    iniPoints.resize(k);
    fpts[1].resize(k);
    drawTrackLines();

    swap(fpts[1], fpts[0]);  //交换,将此帧跟踪到特征点作为下一帧的待跟踪点
}
void test(){

    VideoCapture capture("Video.wmv");
    //VideoCapture capture(0);  //打开摄像头
    if (!capture.isOpened())
    {
        cout << "could not load video file...\n" << endl;
    }

    namedWindow("Result", CV_WINDOW_AUTOSIZE);

    while (capture.read(frame))
    {
        cvtColor(frame, grayImg, COLOR_BGR2GRAY);

        //跟踪40个特征点,如果跟踪的时候损失了一些特征点,重新检测,追加
        if (fpts[0].size() < 40)
        {
            detectFeatures(frame, grayImg);
            fpts[0].insert(fpts[0].end(), features.begin(), features.end());  //追加带跟踪的特征点
            iniPoints.insert(iniPoints.end(), features.begin(), features.end());
        }
        else
        {
            cout << "tracjing...\n" << endl;
        }

        if (prev_grayImg.empty())
        {
            grayImg.copyTo(prev_grayImg);
        }

        KLTrackFeature();  //稀疏光流跟踪 KLT
        drawFeature(frame);  //绘制特征点
        //更新前一帧的数据
        grayImg.copyTo(prev_grayImg);
        frame.copyTo(prev_frame);
        imshow("Result", frame);

        char c = waitKey(50);
        if (c == 27)
        {
            break;
        }
    }
    capture.release();
}
int main(){
    test();
    waitKey(0);
    return 0;
}

效果:


控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言