18.Lucas-Kanade光流及OpenCV中的calcOpticalFlowPyrLK

news2024/11/17 20:46:14

文章目录

    • 光流法介绍
    • OpenCV中`calcOpticalFlowPyrLK`函数
    • 补充
      • reference


欢迎访问个人网络日志🌹🌹知行空间🌹🌹


光流法介绍

光流描述了像素在图像中的运动,就像彗星☄划过天空中流动图像。同一个像素,随着时间的流逝,会在图像中运动,光流法就是追踪它的运动过程。

光流法根据追踪的像素数又可以分成稀疏光流法稠密光流法

  • 稀疏光流法:计算部分像素的运动,稀疏法以Lucas-Kanade 光流为代表,可以用来目标追踪中跟踪特征点的位置。
  • 稠密光流法:计算所有像素的运动,稠密光流法以Horn-Schunck光流为代表。

Lucas-Kanade光流中,将相机的图像看成是随时间变化的,图像 I I I t t t时刻位置为 ( x , y ) (x,y) (x,y)处的像素,它的灰度值可以写成:

I ( x , y , t ) I(x,y,t) I(x,y,t)

通过这种方式将图像看成了关于位置和时间的函数。

考虑固定的空间点,在世界坐标系中的其位置是固定不变的,在 t t t时刻,其在图像中的像素坐标为 ( x , y ) (x,y) (x,y)。由于相机在运动,该空间点在 t + 1 t+1 t+1时刻在图像中的像素坐标将发生变动,如何估计在 t + 1 t+1 t+1时刻同个空间点的像素坐标呢?这正是光流法要解决的问题。

光流法的基本假设:同一个空间点的像素灰度值,在各个图像中的是固定不变的。

上述假设使用公式描述就是说,

t t t时刻在图像中位置 ( x , y ) (x,y) (x,y)处的像素 I ( x , y , t ) I(x,y,t) I(x,y,t)

t + d t t+dt t+dt时刻运动到了图像的 ( x + d x , y + d y ) (x+dx,y+dy) (x+dx,y+dy)处,

基于灰度假设有以下关系成立:

I ( x , y , t ) = I ( x + d x , y + d y , t + d t ) I(x,y,t) = I(x+dx,y+dy,t+dt) I(x,y,t)=I(x+dx,y+dy,t+dt)

灰度假设是一个很强的假设,实际中很可能不成立,由于物体的材质/相机成想的角度/光照条件发生变化的时候,同一个空间点的像素灰度值很有可能发生变化,因此光流法的结果不一定可靠。

在此假设成立的前提下,来看下如何计算像素的运动。

对上式右侧进行泰勒展开:

I ( x + d x , y + d y , t + d t ) ≈ I ( x , y , t ) + ∂ I ∂ x d x + ∂ I ∂ y d y + ∂ I ∂ t d t I(x+dx,y+dy,t+dt)\approx I(x,y,t)+\frac{\partial I}{\partial x}dx+\frac{\partial I}{\partial y}dy+\frac{\partial I}{\partial t}dt I(x+dx,y+dy,t+dt)I(x,y,t)+xIdx+yIdy+tIdt

因为假设了灰度不变,即 I ( x + d x , y + d y , t + d t ) = I ( x , y , t ) I(x+dx,y+dy,t+dt)=I(x,y,t) I(x+dx,y+dy,t+dt)=I(x,y,t),因此:

∂ I ∂ x d x + ∂ I ∂ y d y + ∂ I ∂ t d t = 0 \frac{\partial I}{\partial x}dx+\frac{\partial I}{\partial y}dy+\frac{\partial I}{\partial t}dt=0 xIdx+yIdy+tIdt=0

对上式两边同时除以 d t dt dt得:

∂ I ∂ x d x d t + ∂ I ∂ y d y d t = − ∂ I ∂ t \frac{\partial I}{\partial x}\frac{dx}{dt}+\frac{\partial I}{\partial y}\frac{dy}{dt}=-\frac{\partial I}{\partial t} xIdtdx+yIdtdy=tI

d x d t \frac{dx}{dt} dtdx为像素在x轴上的速度,记为 μ \mu μ

d y d t \frac{dy}{dt} dtdy为像素在y轴上的速度,记为 v v v

∂ I ∂ x \frac{\partial I}{\partial x} xI为图像在该点 x x x方向上的梯度,记为 I x I_x Ix

∂ I ∂ y \frac{\partial I}{\partial y} yI为图像在该点 y y y方向上的梯度,记为 I y I_y Iy

图像灰度值对时间的变化量,记为 I t I_t It(?_?不是已经假设了图像灰度值不变的吗?_?)

写成矩阵形式有:

[ I x I y ] [ μ v ] = − I t \begin{bmatrix}I_x &I_y\end{bmatrix}\begin{bmatrix}\mu \\ v \end{bmatrix}=-I_t [IxIy][μv]=It

上式中,

I x / I y I_x/I_y Ix/Iyx/y方向的图像梯度,可以从图像中直接求得。

I t I_t It是像素灰度值的变化量,也可从图像中直接求得。

μ / v \mu /v μ/v是像素在x,y方向的运动速度,有了速度,就可以求距离了,因此 μ / v \mu/v μ/v就是我们期望计算的量。

而上式是二元一次方程,单纯的通过上式还无法求出 μ / v \mu/v μ/v

LK光流中,引入了新的假设作为约束,即:某一个窗口内的像素具有相同的运动

考虑一个大小为 w × w w\times w w×w的窗口,其包含 w 2 w^2 w2个像素。因为假设该窗口内像素具有相同的运动,因此可以得到 w 2 w^2 w2个方程,

[ I x I y ] k [ μ v ] = − I t k , k = 1 , 2 , 3.. w 2 \begin{bmatrix}I_x &I_y\end{bmatrix}_k\begin{bmatrix}\mu \\ v \end{bmatrix}=-I_{tk},k=1,2,3..w^2 [IxIy]k[μv]=Itk,k=1,2,3..w2

记:
A = [ [ I x , I y ] 1 . . . [ I x , I y ] k ] , b = [ I t 1 . . . I t k ] A=\begin{bmatrix}[I_x,I_y]_1 \\... \\ [I_x,I_y]_k\end{bmatrix},b=\begin{bmatrix}I_{t1} \\...\\ I_{tk} \end{bmatrix} A= [Ix,Iy]1...[Ix,Iy]k ,b= It1...Itk

上面的方程就变成了,

A [ μ v ] = b A\begin{bmatrix} \mu \\ v \end{bmatrix}=b A[μv]=b

这个是关于 μ / v \mu/v μ/v的超定线性方程,可以使用最小二乘法求解。

[ μ v ] ∗ = − ( A T A ) − 1 A T b \begin{bmatrix} \mu \\ v \end{bmatrix}^*=-(A^TA)^{-1}A^Tb [μv]=(ATA)1ATb

OpenCV中calcOpticalFlowPyrLK函数

该方法使用迭代Lucas-Kanade算法计算稀疏特征点的光流,用来做特征点跟踪,该方法使用了金字塔,因此具有一定的尺度不变性。

void cv::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:上一帧图像
  • nextImg:下一帧图像
  • prevPts:上一帧图像中关键点
  • nextPts:根据光流计算的上一帧关键点在当前帧中的位置
  • status:关键点的跟踪状态,vector,1表示OK,0表示LOST
  • err:每个特征点的跟踪误差vector<float>len(status)==len(nextPts)==len(prePts)==len(err)
  • winSize:在每层金字塔中,LK算法中用来求解计算像素运动而假设具有相同运动的窗口大小。
  • maxLevel:金字塔的层数,层数多,尺度不变性能更好,运算时间更久
  • criteria:迭代搜索算法的终止条件,默认值表示在指定的最大迭代次数criteria.maxCount(30)之后或当搜索窗口移动小于criteria.epsilon(0.01)时终止迭代
  • flags:设置误差或者初始值参数,可选下面两个值:
    • OPTFLOW_USE_INITIAL_FLOW设置使用nextPts中的值作为迭代的初始值,如果不设置为OPTFLOW_USE_INITIAL_FLOW,初始状态就使用prevPts中的值,直接从prevPts复制到nextPts,OpenCV源码中对OPTFLOW_USE_INITIAL_FLOW的使用方式为:
      if( flags & OPTFLOW_USE_INITIAL_FLOW )
          nextPt = nextPts[ptidx]*(float)(1./(1 << level));
      else
          nextPt = prevPt;
      
      • OPTFLOW_LK_GET_MIN_EIGENVALS,flags设置为这个值时使用光流运动方程2x2的正规矩阵,也即空间梯度矩阵的最小特征值作为误差项。如果不设置成OPTFLOW_LK_GET_MIN_EIGENVALS,将原始点和移动点周围像素的 L 1 L_1 L1距离除以窗口中的像素作为误差项。
  • minEigThreshold:迭代LK算法会计算光流运动方程2x2的正规矩阵,也即空间梯度矩阵的最小特征值,然后再除以运动不变窗口中的像素总数作为一个误差评价标准,当其小于minEigThreshold时,说明这个点已经追踪不到了,会将其从追踪特征点中移除,避免其对应相素运动的计算,可提升性能。

calcOpticalFlowPyrLK通常和goodFeatureToTrack方法一起使用,先使用GFTTDetector提取特征点的位置,再使用calcOpticalFlowPyrLK追踪其在连续视频流中的位置,避免了特征描述子的计算和特征点的匹配,可以极大的提升追踪的性能。


#include <memory>
#include <vector>
#include <cstdlib>

#include <opencv2/features2d.hpp>
#include <opencv2/opencv.hpp>

class TestOpticalFlowLK {
    public:
        typedef std::shared_ptr<TestOpticalFlowLK> Ptr;
        TestOpticalFlowLK();
        ~TestOpticalFlowLK() = default;

        void track(std::vector<cv::String> &filenames) const;

    private:
        cv::Ptr<cv::GFTTDetector> gftt_ptr_;

};

TestOpticalFlowLK::TestOpticalFlowLK()
{
    gftt_ptr_ = cv::GFTTDetector::create(500, 0.2, 50);
}

void TestOpticalFlowLK::track(std::vector<cv::String> &filenames) const
{
    assert(filenames.size() > 1);
    std::vector<cv::KeyPoint> kps1;
    std::vector<cv::Point2f>pts1, pts2;
    std::vector<cv::Scalar> colors;
    cv::Mat last_img = cv::imread(filenames[0], 0), cur_img;
    cv::Mat mask(last_img.size(), CV_8UC1, 255);
    gftt_ptr_->detect(last_img, kps1, mask);
    for(auto &kp : kps1) {
        int r = (int)(255. * rand() / (RAND_MAX + 1.f));
        int g = (int)(255. * rand() / (RAND_MAX + 1.f));
        int b = (int)(255. * rand() / (RAND_MAX + 1.f));
        std::cout << "r:" << r << "g:" << g << "b:" << b << std::endl;
        colors.emplace_back(r, g, b);
        pts1.push_back(kp.pt);
        pts2.push_back(kp.pt);
    }
    std::vector<uchar> status;
    // cv::Mat err;
    std::vector<float> err;
    cv::cvtColor(mask, mask, cv::COLOR_GRAY2BGR);
    cv::Mat frame;
    for (auto &filename : filenames)
    {
        std::cout << "filename: " << filename << std::endl;
        cur_img = cv::imread(filename, 0);
        cv::calcOpticalFlowPyrLK(last_img,
                                 cur_img,
                                 pts1,
                                 pts2,
                                 status,
                                 err,
                                 cv::Size(13, 13),
                                 3,
                                 cv::TermCriteria(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 30, 0.01),
                                 cv::OPTFLOW_USE_INITIAL_FLOW
                                 );
        cv::cvtColor(cur_img, cur_img, cv::COLOR_GRAY2BGR);
        int cnt = 0;
        for(size_t i = 0; i < status.size(); i++) {
            std::cout << " " << err[i];
            if(!status[i]) continue;

            if(abs((pts1[i].x - pts2[i].x)) > 80 | 
               abs((pts1[i].y - pts2[i].y)) > 80) continue;
            cv::line(mask, pts1[i], pts2[i], colors[i], 2);
            cv::circle(cur_img, pts2[i], 10, colors[i], 1);
            pts1[i].x = pts2[i].x;
            pts1[i].y = pts2[i].y;
            cnt += 1;
        }
        std::cout << std::endl;
        cv::addWeighted(mask, 0.5, cur_img, 0.5, -65, frame);
        cv::imshow("frame", frame);
        cv::waitKey(0);
        cv::cvtColor(cur_img, cur_img, cv::COLOR_BGR2GRAY);
        last_img = cur_img;
        cv::imwrite("frame.png", frame);
    }
}

上述代码的运行效果为:

完整演示代码和数据可以在以下仓库中找到:

https://gitee.com/lx_r/basic_cplusplus_examples

补充

因为假设了像素灰度值不变,还可以将其看成非线性优化问题,求解如下方程:

min ⁡ Δ x , Δ y ∣ ∣ I 1 ( x , y ) − I 2 ( x + Δ x , y + Δ y ) ∣ ∣ 2 2 \begin{equation}\mathop{\min}\limits_{\Delta x,\Delta y}||I_1(x,y)-I_2(x+\Delta x,y+\Delta y)||_2^2\end{equation} Δx,Δymin∣∣I1(x,y)I2(x+Δx,y+Δy)22

从零开始实现LK光流在视觉SLAM十四讲中高博已经实现过了,更读详细信息可以参考slambook2仓库。

https://github.com/gaoxiang12/slambook2


欢迎访问个人网络日志🌹🌹知行空间🌹🌹


reference

致谢:理论部分来自于《视觉SLAM十四讲(第二版)》P201-210.

  • 1.https://zhuanlan.zhihu.com/p/384651830
  • 2.https://docs.opencv.org/4.6.0/dc/d6b/group__video__track.html#ga473e4b886d0bcc6b65831eb88ed93323
  • 3.Pyramidal implementation of the lucas kanade feature tracker

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/734278.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习与深度学习——利用随机梯度下降算法SGD对波士顿房价数据进行线性回归

机器学习与深度学习——利用随机梯度下降算法SGD对波士顿房价数据进行线性回归 我们这次使用随机梯度下降&#xff08;SGD&#xff09;算法对波士顿房价数据进行线性回归的训练&#xff0c;给出每次迭代的权重、损失和梯度&#xff0c;并且绘制损失loss随着epoch变化的曲线图。…

集群 第四章

目录 1. nginx、lvs、haproxy 的区别 2. 实验 3. ssh 升级 4.总结 1. nginx、lvs、haproxy 的区别 2. 实验 Haproxy 服务器&#xff1a;192.168.83.101 Nginx 服务器1&#xff1a;192.168.83.102 Nginx 服务器2&#xff1a;192.168.83.103 …

Mysql之视图,索引及数据的备份与恢复

目录 一、视图 1.视图是什么 2.视图与数据表的区别 3.视图的优缺点 优点&#xff1a; 缺点&#xff1a; 4.视图的应用场景 5.语法运用 二、索引 1.什么是索引 2.为什么要使用索引 3.使用索引的优缺点 4.何时不使用索引 5.索引何时失效 6.索引分类 三、数据的备份…

头结点到底方便了啥?

头结点到底方便了啥&#xff1f; 链表增加头结点的作用如下&#xff1a; (1)便于首元结点的处理 (2)便于空表和非空表的统一处理 (参考&#xff1a;《数据结构 C语言(第2版)》P31) 其实这两句话很抽象&#xff0c;你说方便就方便&#xff0c;你倒是举个粟子或者画个图什么的啊&…

Linux开发工具之vim工具的使用介绍

目录 前言 1.vim的基本概念 命令模式(Normal mode) 插入模式(Insert mode) 末行模式(last line mode) 2.vim的基本操作 命令模式的命令集 移动光标 ​编辑 删除文字 复制 替换 撤销操作 更改 vim末行模式命令集 简单vim配置 总结 前言 大家好呀&#xff0c;许久…

Java动态规划LeetCode1137. 第 N 个泰波那契数

方法1&#xff1a;通过动态规划解题&#xff0c;这道题也是动态规划的一道很好的入门题&#xff0c;因为比较简单和容易理解。 代码如下&#xff1a; public int tribonacci(int n) {//处理特殊情况if(n0){return 0;}if(n1||n2){return 1;}//定义数组int[]dpnew int[n1];//初…

浏览器通过js打开文件,新建文件,静默实时保存文件

资源&#xff0c;点击下载 在线访问Txt Markdown &#x1f61d;&#x1f61d;&#x1f61d;&#x1f61d;&#x1f61d;&#x1f61d; 新建文件后&#xff0c;可以直接保存文件&#xff0c;不需要再次下载文件&#xff0c;也只有第一次保存时候才会出现确认弹窗 html <!D…

尚硅谷React学习笔记(上)

目录 一、React入门 1.1、React简介 为什么要学&#xff1f; React的特点 1.2、React的基本使用 Hello React案例 创建虚拟DOM的两种方式 虚拟DOM与真实DOM 1.3、React JSX 语法规则 JSX小练习 1.4、模块与组件化的理解 模块 组件 模块化 组件化 二、React面向…

E. Scuza - 二分+前缀和

分析&#xff1a; 暴力会超时&#xff0c;可以用二分&#xff0c;构建两个数组&#xff0c;一个是a[i]&#xff0c;作为前缀和数组&#xff0c;一个是f[i]表示第i个台阶之前的最大高度的台阶&#xff0c;然后每次二分来查找k&#xff0c;因为尽可能地走的多&#xff0c;所以查找…

VTK STL 体积 表面积测量 最短路径 读取中文路径

目录 开发环境&#xff1a; vtkMassProperties 三、中文路径 数据读取 开发环境&#xff1a; 系统&#xff1a;Win10 VTK&#xff1a;8.2.0 Qt&#xff1a;5.12.4 一、结构化对象 体积 面积 vtkMassProperties VTK 计算体积和面积的主要类 vtkMassProperties vtkSm…

C语言进阶之指针的进阶

指针的进阶 1. 字符指针2. 指针数组3. 数组指针3.1 数组指针的定义3.2 &数组名VS数组名3.3 数组指针的使用 4. 数组参数、指针参数4.1 一维数组传参4.2 二维数组传参4.3 一级指针传参4.4 二级指针传参 5. 函数指针6. 函数指针数组7. 指向函数指针数组的指针8. 回调函数9. 指…

【程序员必须掌握的算法】【Matlab】GRNN神经网络遗传算法(GRNN-GA)函数极值寻优——非线性函数求极值

上一篇博客介绍了BP神经网络遗传算法(BP-GA)函数极值寻优——非线性函数求极值&#xff0c;神经网络用的是BP神经网络&#xff0c;本篇博客将BP神经网络替换成GRNN神经网络&#xff0c;希望能帮助大家快速入门GRNN网络。 1.背景条件 要求&#xff1a;对于未知模型&#xff08;…

使用trtexec工具多batch推理tensorrt模型(trt模型)

文章目录 零、pt转onnx模型一、onnx转trt模型二、推理trt模型 零、pt转onnx模型 参考&#xff1a;https://github.com/ultralytics/yolov5 用根目录下的export.py可以转pt为onnx模型&#xff0c;命令如下可以转换成动态batch的onnx模型 python3 export.py --weights./yolov5s…

一款强大易用的截图控件:跨平台,界面简洁,功能丰富,易于集成

当我们在日常工作中沟通交流&#xff0c;或是在开发过程中跟踪反馈问题时&#xff0c;截图无疑是一种最直观有效的方式。然而&#xff0c;传统的截图工具在功能上的局限性&#xff0c;往往无法满足我们日益增长的需求。这时&#xff0c;一款功能强大&#xff0c;易于集成&#…

垃圾收集算法和CMS详解

一、垃圾收集算法 1、分带收集理论 基于新生代和老年代选择不同垃圾回收算法&#xff0c;比如新生代&#xff0c;都是一些暂存对象&#xff0c;而且内存分区域的&#xff0c;可以采用标记复制算法。而老年代只有一块内存区域&#xff0c;使用复制算法比较占用内存空间&#x…

DEVICENET转ETHERCAT网关连接ethercat通讯协议详细解析

你有没有遇到过生产管理系统中&#xff0c;设备之间的通讯问题&#xff1f;两个不同协议的设备进行通讯&#xff0c;是不是很麻烦&#xff1f;今天&#xff0c;我们为大家介绍一款神奇的产品&#xff0c;能够将不同协议的设备进行连接&#xff0c;让现场的数据交换不再困扰&…

MySQL数据库 - 库的操作

目录​​​​​​​ 一、创建数据库 二、创建数据库案例 三、字符集和校验规则 四、校验规则对数据库的影响 五、操纵数据库 1、查看数据库 2、显示创建语句 3、修改数据库 4、删除数据库 六、数据库的备份与恢复 1、数据库的备份 2、数据库的恢复 3、表的备份 4…

【网络系统集成】Pfsense防火墙实验

1.实验名称 Pfsense防火墙实验 2.实验目的 通过动手实践配置pfsense对加深对防火墙的原理与应用的理解。 3.实验内容 (1)安装并完成pfsense防火墙软件的基本配置(WAN, LAN,局域网

刘积仁:东软不太喜欢风口,更看重长期主义

作为数字和软件服务产业一年一度的行业盛宴&#xff0c;2003年&#xff0c;中国国际软件和信息服务交易会&#xff08;简称“软交会”&#xff09;正式诞生。2019年&#xff0c;大会更名为中国国际数字和软件服务交易会&#xff08;简称“数交会”&#xff09;&#xff0c;至今…

【C++修炼之路】string 概述

&#x1f451;作者主页&#xff1a;安 度 因 &#x1f3e0;学习社区&#xff1a;StackFrame &#x1f4d6;专栏链接&#xff1a;C修炼之路 文章目录 一、string 为何使用模板二、string 类认识1、构造/析构/赋值运算符重载2、容量操作3、增删查改4、遍历5、迭代器6、非成员函数…