OpenCV使用稀疏光流实现视频对象跟踪的方法详解

 更新时间:2023年02月20日 09:33:40   作者:音视频开发老舅  
这篇文章主要为大家详细介绍了OpenCV如何使用稀疏光流实现视频对象跟踪功能,文中的示例代码讲解详细,具有一定的借鉴价值,需要的可以参考一下

1、概述

案例:使用稀疏光流实现对象跟踪

稀疏光流API介绍:

calcOpticalFlowPyrLK( InputArray prevImg, InputArray nextImg,
                                        InputArray prevPts, InputOutputArray nextPts,
                                        OutputArray status, OutputArray err,
                                        Size winSize = Size(21,21), int maxLevel = 3,
                                        TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01),
                                        int flags = 0, double minEigThreshold = 1e-4 );
  • prevImg:视频前一帧图像/金字塔,单通道CV_8UC1
  • nextImg:视频后一帧图像/金字塔,单通道CV_8UC1
  • preVPts:前一帧图像的特征向量(输入)需要找到流的2D点的矢量(vector of 2D points for which the flow needs to be found;);点坐标必须是单精度浮点数
  • nextPts:后一帧图像的特征向量(输出),输出二维点的矢量(具有单精度浮点坐标),包含第二图像中输入特征的计算新位置;当传递OPTFLOW_USE_INITIAL_FLOW标志时,向量必须与输入中的大小相同。
  • status:输出状态向量(无符号字符);如果找到相应特征的流,则向量的每个元素设置为1,否则设置为0
  • err:输出错误的矢量; 向量的每个元素都设置为相应特征的错误,错误度量的类型可以在flags参数中设置; 如果未找到流,则未定义错误(使用status参数查找此类情况)
  • winSize:每个金字塔等级的搜索窗口的winSize大小
  • maxLevel:基于0的最大金字塔等级数;如果设置为0,则不使用金字塔(单级),如果设置为1,则使用两个级别,依此类推;如果将金字塔传递给输入,那么算法将使用与金字塔一样多的级别,但不超过maxLevel
  • criteria:停止条件,指定迭代搜索算法的终止条件(在指定的最大迭代次数criteria.maxCount之后或当搜索窗口移动小于criteria.epsilon时)。
  • flags:操作标志,OPTFLOW_USE_INITIAL_FLOW使用初始估计,存储在nextPts中;如果未设置标志,则将prevPts复制到nextPts并将其视为初始估计。
  • OPTFLOW_LK_GET_MIN_EIGENVALS使用最小特征值作为误差测量(参见minEigThreshold描述);如果没有设置标志,则将原稿周围的色块和移动点之间的L1距离除以窗口中的像素数,用作误差测量
  • minEigThreshold:算法计算光流方程的2x2正常矩阵的最小特征值,除以窗口中的像素数;如果此值小于minEigThreshold,则过滤掉相应的功能并且不处理其流程,因此它允许删除坏点并获得性能提升

算法实现步骤:

1.实例化VideoCapture

2.循环读取视频数据

3.视频帧灰度转换

4.执行角点检测

5.保存角点检测的特征数据

6.初始化时如果检测到前一帧为空,把当前帧的灰度图像给前一帧

7.执行光流跟踪,并输出跟踪后的特征向量

8.遍历光流跟踪的输出特征向量,并得到距离和状态都符合预期的特征向量。让后将其重新填充到fpts[1]中备用

9.重置集合大小

10.绘制光流线

11.交换特征向量的输入和输出

12.将用于跟踪的角点绘制出来

13.展示最终的跟踪效果

14.循环3~13步骤

15.结束

2、代码示例

KLT_Object_Tracking::KLT_Object_Tracking(QWidget *parent)
    : MyGraphicsView{parent}
{
    isShowLine = false;
    this->setWindowTitle("KLT稀疏光流实现对象跟踪");
    QPushButton *btn = new QPushButton(this);
    btn->setText("选择视频");
    connect(btn,&QPushButton::clicked,[=](){
        //选择视频
        path = QFileDialog::getOpenFileName(this,"请选择视频","/Users/yangwei/Downloads/",tr("Image Files(*.mp4 *.avi)"));
        qDebug()<<"视频路径:"<<path;
        startKltTracking(path.toStdString().c_str());
    });
    //
    QButtonGroup * group = new QButtonGroup(this);
    QRadioButton * radioNo = new QRadioButton(this);
    radioNo->setText("否");
    radioNo->setChecked(true);
    QRadioButton *radioYes = new QRadioButton(this);
    radioYes->setText("是");
    group->addButton(radioNo,0);
    group->addButton(radioYes,1);
 
    radioNo->move(0,btn->y()+btn->height()+20);
    radioYes->move(radioNo->x()+radioNo->width()+20,btn->y()+btn->height()+20);
    connect(radioNo,&QRadioButton::clicked,[=](){
        isShowLine = false;//显示光流线
    });
    connect(radioYes,&QRadioButton::clicked,[=](){
        isShowLine = true;//不显示光流线
    });
 
}
 
void KLT_Object_Tracking::startKltTracking(const char* filePath){
    //【1】实例化VideoCapture并打开视频
    VideoCapture capture;//实例化视频捕获器
    capture.open(filePath);//打开视频文件(或摄像头)
    if(!capture.isOpened()){//检测文件是否打开,如果没打开直接退出
        qDebug()<<"无法打开视频";
        return;
    }
 
    Mat frame,gray;
    vector<Point2f> features;//检测出来的角点集合
    vector<Point2f> inPoints;//这个主要是为了画线用的
    vector<Point2f> fpts[2];//[0],存入的是是二维特征向量,[1]输出的二维特征向量
    Mat pre_frame,pre_gray;
    vector<uchar> status;//光流输出状态
    vector<float> err;//光流输出错误
    //【2】循环读取视频
    while(capture.read(frame)){//循环读取视频中每一帧的图像
        //【3】将视频帧图像转为灰度图
        cvtColor(frame,gray,COLOR_BGR2GRAY);//ps:角点检测输入要求单通道
 
        //【4】如果特征向量(角点)小于40个我们就重新执行角点检测
        if(fpts[0].size()<40){//如果小于40个角点就重新开始执行角点检测
            //执行角点检测
            goodFeaturesToTrack(gray,features,5000,0.01,10,Mat(),3,false,0.04);
            //【5】将检测到的角点放入fpts[0]中作为,光流跟踪的输入特征向量
            //将检测到的角点插入vector
            fpts[0].insert(fpts[0].begin(),features.begin(),features.end());
            inPoints.insert(inPoints.end(),features.begin(),features.end());
            qDebug()<<"角点检测执行完成,角点个数为:"<<features.size();
        }else{
            qDebug()<<"正在跟踪...";
        }
        //【6】初始化的时候如果检测到前一帧为空,这个把当前帧的灰度图像给前一帧
        if(pre_gray.empty()){//如果前一帧为空就给前一帧赋值一次
            gray.copyTo(pre_gray);
        }
 
        //执行光流跟踪
        qDebug()<<"开始执行光流跟踪";
        //【7】执行光流跟踪,并将输出的特征向量放入fpts[1]中
        calcOpticalFlowPyrLK(pre_gray,gray,fpts[0],fpts[1],status,err);
        qDebug()<<"光流跟踪执行结束";
        //【8】遍历光流跟踪的输出特征向量,并得到距离和状态都符合预期的特征向量。让后将其重新填充到fpts[1]中备用
        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]){//如果距离大于2,status=true(正常)
                inPoints[k] = inPoints[i];
                fpts[1][k++] = fpts[1][i];
            }
        }
        //【9】重置集合大小(由于有错误/不符合条件的输出特征向量),只拿状态正确的
        //重新设置集合大小
        inPoints.resize(k);
        fpts[1].resize(k);
        //【10】绘制光流线,这一步要不要都行
        //绘制光流线
        if(isShowLine){
            for(size_t i = 0;i<fpts[1].size();i++){
                line(frame,inPoints[i],fpts[1][i],Scalar(0,255,0),1,8,0);
                circle(frame, fpts[1][i], 2, Scalar(0, 0, 255), 2, 8, 0);
            }
        }
 
        qDebug()<<"特征向量的输入输出交换数据";
        //【11】交换特征向量的输入和输出,(循环往复/进入下一个循环),此时特征向量的值会递减
        std::swap(fpts[1],fpts[0]);//交换特征向量的输入和输出,此处焦点的总数量会递减
 
        //【12】将用于跟踪的角点绘制出来
        //将角点绘制出来
        for(size_t i = 0;i<fpts[0].size();i++){
            circle(frame,fpts[0][i],2,Scalar(0,0,255),2,8,0);
        }
 
        //【13】重置前一帧图像(每一个循环都要刷新)
        gray.copyTo(pre_gray);
        frame.copyTo(pre_frame);
        //【14】展示最终的效果
        imshow("frame",frame);
        int keyValue = waitKey(100);
        if(keyValue==27){//如果用户按ese键退出播放
            break;
        }
    }
}

3、图像演示

到此这篇关于OpenCV使用稀疏光流实现视频对象跟踪的方法详解的文章就介绍到这了,更多相关OpenCV视频对象跟踪内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++实战之二进制数据处理与封装

    C++实战之二进制数据处理与封装

    在电脑上一切数据都是通过二进制(0或1)进行存储的,通过多位二进制数据可以进而表示整形、浮点型、字符、字符串等各种基础类型数据或者一些更复杂的数据格式。本文将为大家详细讲讲二进制数据处理与封装,需要的可以参考一下
    2022-08-08
  • C++实现神经网络框架SimpleNN的详细过程

    C++实现神经网络框架SimpleNN的详细过程

    本来自己想到用C++实现神经网络主要是想强化一下编码能力并入门深度学习,对C++实现神经网络框架SimpleNN的详细过程感兴趣的朋友一起看看吧
    2021-08-08
  • 对比C语言中memccpy()函数和memcpy()函数的用法

    对比C语言中memccpy()函数和memcpy()函数的用法

    这篇文章主要介绍了对比C语言中memccpy()函数和memcpy()函数的用法,二者都是用于复制内存内容,注意区别,需要的朋友可以参考下
    2015-08-08
  • 详解C语言学习记录之指针

    详解C语言学习记录之指针

    关于指针,其是C语言的重点,C语言学的好坏,其实就是指针学的好坏。其实指针并不复杂,学习指针,要正确的理解指针,本片文章能给就来学习一下
    2021-11-11
  • C++实现井字棋游戏

    C++实现井字棋游戏

    这篇文章主要为大家详细介绍了C++实现井字棋游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-07-07
  • c++如何控制对象的创建方式(禁止创建栈对象or堆对象)和创建的数量

    c++如何控制对象的创建方式(禁止创建栈对象or堆对象)和创建的数量

    这篇文章主要介绍了c++如何控制对象的创建方式和创建的数量,帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-08-08
  • VC++开发中完美解决头文件相互包含问题的方法解析

    VC++开发中完美解决头文件相互包含问题的方法解析

    本文中,为了叙述方便,把class AClass;语句成为类AClass的声明,把class AClass开始的对AClass的类成员变量、成员函数原型等的说明称为类的定义,而把在CPP中的部分称为类的定义
    2013-09-09
  • Visual C++中Tab View的多种实现方法

    Visual C++中Tab View的多种实现方法

    这篇文章主要介绍了Visual C++中Tab View的多种实现方法,包括了CTabCtrl控件、CSheetCtrl标签选择窗口以及静态分割窗口等实现Tab View的方法,需要的朋友可以参考下
    2014-10-10
  • 基于C语言编写简易的英文统计和加密系统

    基于C语言编写简易的英文统计和加密系统

    这篇文章主要介绍如何基于C语言编写一个简易的英文统计和加密系统,实际上就是对字符数组的基本操作的各种使用,感兴趣的可以了解一下
    2023-05-05
  • C语言中的数据类型详解

    C语言中的数据类型详解

    在C语言中,数据类型指的是用于声明不同类型的变量或函数的一个广泛的系统。变量的类型决定了变量存储占用的空间,以及如何解释存储的位模式,本文将详细给大家介绍一下C语言中的基本数据类型,感兴趣的同学可以参考下
    2023-05-05

最新评论