opencv视频分析与对象追踪之基于光流(稀疏光流、密集光流)

光流跟踪

光流的概念是Gibson在1950年首先提出来的。它是空间运动物体在观察成像平面上的像素运动的瞬时速度,是利用图像序列中像素在时间域上的变化以及相邻帧之间的相关性来找到上一帧跟当前帧之间存在的相应关系。从而计算出相邻帧之间物体的运动信息的一种方法。一般而言,光流是因为场景中前景目标本身的移动、相机的运动,或者两者的共同运动所产生的。
研究光流场的目的就是为了从图片序列中近似得到不能直接得到的运动场。运动场,事实上就是物体在三维真实世界中的运动。光流场,是运动场在二维图像平面上(人的眼睛或者摄像头)的投影。
那通俗的讲就是通过一个图片序列,把每张图像中每一个像素的运动速度和运动方向找出来就是光流场。
那怎么找呢?咱们直观理解肯定是:第t帧的时候A点的位置是(x1, y1),那么我们在第t+1帧的时候再找到A点,假如它的位置是(x2,y2),那么我们就能够确定A点的运动了:(ux, vy) = (x2, y2) - (x1,y1)
那怎么知道第t+1帧的时候A点的位置呢? 这就存在非常多的光流计算方法了。
1981年,Horn和Schunck创造性地将二维速度场与灰度相联系,引入光流约束方程,得到光流计算的基本算法。人们基于不同的理论基础提出各种光流计算方法,算法性能各有不同。Barron等人对多种光流计算技术进行了总结。依照理论基础与数学方法的差别把它们分成四种:基于梯度的方法、基于匹配的方法、基于能量的方法、基于相位的方法。
近年来神经动力学方法也颇受学者重视。


基础原理

稀疏光流-KLT

通过金字塔Lucas-Kanade 光流方法计算某些点集的光流(稀疏光流),满足以下要点

  • 亮度恒定
  • 近距离移动
  • 空间一致性

稠密光流-HF

用Gunnar Farneback 的算法计算稠密光流(即图像上全部像素点的光流都计算出来,大致含义为计算当前帧与上一帧画面像素相对位移


相关api

计算稀疏光流api

void calcOpticallFlowPyrLK (InuputArray prevImg, 
                            InputArray nextImg, 
                            InputArray prevPts, 
                            InputOutputArray nextPts, 
                            OutputArray status, 
                            OutputArray err, 
                            Size winSize = Size(21,21), 
                            int maxLevel = 3, 
                            TermCriteriacriteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01),
                            int flags = 0, 
                            double minEigThreshold = 1e-4);

参数说明

  • prevImg: 深度为8位的前一帧图像或金字塔图像。
  • nextImg:和prevImg有相同的大小和类型,后一帧图像或金字塔。
  • prevPts:计算光流所需要的输入2D点矢量,点坐标必须是单精度浮点数。
  • nextPts:输出2D点矢量(也是单精度浮点数坐标),点矢量中包含的是在后一帧图像上计算得到的输入特征新位置。
  • status:输出状态矢量(元素是无符号char类型,uchar),如果相应特征的流发现则矢量元素置为1,否则,为0。
  • err:输出误差矢量。
  • winSize:每个金字塔层搜索窗大小。
  • maxLevel:金字塔层的最大数目;如果置0,金字塔不使用(单层);如果置1,金字塔2层,等等以此类推。
  • criteria:指定搜索算法收敛迭代的类型
  • minEigTheshold:算法计算的光流等式的2x2常规矩阵的最小特征值。

计算密集光流函数api

void cv::calcOpticalFlowFarneback( InputArray _prev0, 
                                   InputArray _next0,
                                   OutputArray _flow0, 
                                   double pyr_scale, 
                                   int levels, 
                                   int winsize,
                                   int iterations, 
                                   int poly_n, 
                                   double poly_sigma, 
                                   int flags )

参数说明

  • _prev0:输入前一帧图像
  • _next0:输入后一帧图像
  • _flow0:输出的光流
  • pyr_scale:金字塔上下两层之间的尺度关系
  • levels:金字塔层数
  • winsize:均值窗口大小,越大越能denoise并且能够检测快速移动目标,但会引起模糊运动区域
  • iterations:迭代次数
  • poly_n:像素领域大小,一般为5,7等
  • poly_sigma:高斯标注差,一般为1-1.5
  • flags:计算方法。主要包括OPTFLOW_USE_INITIAL_FLOWOPTFLOW_FARNEBACK_GAUSSIAN

稀疏光流检测代码

#include 
#include 
#include 

using namespace std;
using namespace cv;

Mat frame,gray;   //当前帧 当前灰度
Mat prev_frame,prev_gray;   //前一帧数据  前一帧灰度图
vector<Point2f> features;  //当前实时检测出的 shi-tomas角点检测 特征数据
vector<Point2f> iniPoints;  //初始化角点检测
vector<Point2f> fpts[2];  //保持当前帧和前一帧的特征点位置  函数参数
vector<uchar> status;  //特征点跟踪成功标志位 函数参数
vector<float> err;   //跟踪区域误差和     函数参数


//检测角点
void detect_features(Mat &inFrame,Mat &inGray);

//绘制角点
void draw_feature(Mat &inFrame);

void draw_trac_lines(void);

//光流追踪计算
void klt_track_feature(void);
int main(void)
{

    VideoCapture cap;

    namedWindow("inputvideo",0);
    resizeWindow("inputvideo",272,480);

    if(!cap.open("/work/opencv_video/color_check.mp4"))
    {
        cout << "video is err" << endl;
        return -1;
    }
    cout << "video is open" << endl;

    while(true)
    {
        cap>>frame;
        if(frame.empty())
        {
            break;
        }
        //图片转化为灰度
        cvtColor(frame,gray,COLOR_BGR2GRAY);

        //由于视频图像不断播放 会导致画面特征点不断变化、减少,光流检测理想状态多数情况下并不存在
        //当特征点数量过于少的时候 重新检测角点 为下一次光流检测做准备
        if(fpts[0].size()<40)
        {
            detect_features(frame,gray);   //再次检测
            fpts[0].insert(fpts[0].end(),features.begin(),features.end());       //更新数据
            iniPoints.insert(iniPoints.end(),features.begin(),features.end());   //更新数据
        }else
        {
            printf("tttttttt\n");
        }


        detect_features(frame,gray);   //检测角点

        //程序初始化运行的时候  把第一帧图像进行保存 
        if(prev_gray.empty())
        {
            gray.copyTo(prev_gray);
        }
        //计算光流
        klt_track_feature();
        draw_feature(frame);  //绘制

        //更新前一帧的数据
        gray.copyTo(prev_gray);
        frame.copyTo(prev_frame);
        imshow("inputvideo",frame);

        if(waitKey(70)>0)
        {
            cout << "video is close" << endl;
            break;
        }

    }
    cap.release();
    destroyAllWindows();
    return 0;

}

void detect_features(Mat &inFrame,Mat &inGray)
{
    double maxCorners = 5000;
    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 << "features Size : "<< features.size() <<endl;
}

void draw_feature(Mat &inFrame)
{
    for(size_t i=0;i<fpts[0].size();i++)
    {
        circle(inFrame,fpts[0][i],2,Scalar(0,0,255),2,8);

    }

}
void klt_track_feature(void)
{
    calcOpticalFlowPyrLK(prev_gray,gray,fpts[0],fpts[1],status,err);
    int k = 0;
    for(size_t 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])  //位移范围够大 该点被标记过
        {
            iniPoints[k] = iniPoints[i];
            fpts[1][k++] = fpts[1][i];
        }
    }
    draw_trac_lines();
    iniPoints.resize(k);  
    fpts[1].resize(k);
    swap(fpts[1],fpts[0]);
}

void draw_trac_lines(void)
{
    for(size_t i=0;i<fpts[0].size();i++)
    {
        //初始位置 指向 当前位置的直线
        line(frame,iniPoints[i],fpts[1][i],Scalar(255,0,0),2,8,0);
    }
}

稀疏光流检测效果

opencv视频分析与对象追踪之基于光流(稀疏光流、密集光流)_第1张图片


稠密光流检测代码

#include 
#include 
#include 

using namespace std;
using namespace cv;

Mat frame,gray;   //当前帧 当前灰度
Mat prev_gray;   //前一帧数据  前一帧灰度图
Mat flow_result,flowdata;

void draw_optical_flowhf(const Mat &flowdata,Mat &image);
int main(void)
{
    VideoCapture cap;

    namedWindow("inputvideo",WINDOW_AUTOSIZE);
    namedWindow("flowvideo",WINDOW_AUTOSIZE);

    if(!cap.open("/work/opencv_video/vtest.avi"))
    {
        cout << "video is err" << endl;
        return -1;
    }
    cout << "video is open" << endl;
    cap>>frame;
    if(frame.empty())
    {
        return -1;
    }
    //赋值给上一帧画面
    cvtColor(frame,prev_gray,COLOR_BGR2GRAY);
    while(true)
    {
        cap>>frame;
        if(frame.empty())
        {
            break;
        }
        //图片转化为灰度
        cvtColor(frame,gray,COLOR_BGR2GRAY);
        //根据图片相对位移计算位移差
        calcOpticalFlowFarneback(prev_gray,gray,flowdata,0.5,3,15,3,5,1.2,0);

        cvtColor(prev_gray,flow_result,COLOR_GRAY2BGR);
        //绘制光流
        draw_optical_flowhf(flowdata,flow_result);
        imshow("inputvideo",frame);
        imshow("flowvideo",flow_result);


        if(waitKey(10)>0)
        {
            cout << "video is close" << endl;
            break;
        }

    }
    cap.release();
    destroyAllWindows();
    return 0;

}
//绘制光流
void draw_optical_flowhf(const Mat &flowdata,Mat &image)
{
    for(int row =0;row<image.rows;row++)
        for(int col =0;col<image.cols;col++)
        {
            const Point2f fxy = flowdata.at<Point2f>(row,col);  //读取相对偏移
            if(fxy.x>2 || fxy.y>2)   //偏移量需要大于一定范围
            {
                line(image,Point(col,row),Point(cvRound(col+fxy.x),cvRound(row+fxy.y)),Scalar(0,255,0),2,8);
                circle(image,Point(col,row),2,Scalar(0,0,255),2);
            }
        }
}

密集光流效果

你可能感兴趣的:(opencv)