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

文章目录

    • 光流法介绍
    • 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));
      elsenextPt = 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.mzph.cn/news/871.shtml

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

相关文章

手写对象浅比较(React中pureComponent和Component区别)

PureComponent和Component的区别 PureComponent会给类组件默认加一个shouldComponentUpdate这样的周期函数 //PureComponent类似自动加了这段shouldComponentUpdate(nextProps,nextState){let { props, state } this;// props/state:修改之前的属性状态// nextProps/nextState…

047、TiDB特性_TopSQL

TopSQL 之前 之前没有办法找单个TiKV Server的语句。只能查找整个集群的慢语句。 TopSQL之后 指定TiDB及TiKV实例正在执行的SQL语句CPU开销最多的Top 5 SQL每秒请求数、平均延迟等信息 TopSQL 使用 选择需要观察负载的具体TiDB Server或TiKV实例 观察Top 5 类SQL 查看某…

用IDEA写第一个Spring程序 HelloWorld

用IDEA写第一个Spring程序 HelloWorld 环境 Orcal JDK&#xff1a;1.8.0_341 maven&#xff1a;3.9.3 Spring&#xff1a;5.3.10 IDEA&#xff1a;2023.1.2 1. 安装JDK和IDEA 2. 安装maven并配置环境变量、换源 3. IDEA中maven属性配置&#xff0c;主要是版本和settings文件及…

python+selenium进行cnblog的自动化登录测试

Web登录测试是很常见的测试&#xff0c;手动测试大家再熟悉不过了&#xff0c;那如何进行自动化登录测试呢&#xff01;本文就基于pythonselenium结合unittest单元测试框架来进行一次简单但比较完整的cnblog自动化登录测试&#xff0c;可提供点参考&#xff01;下面就包括测试代…

centos7 docker 安装sql server 2019

contos7安装sql server docker最低1.8或更高 卸载旧的docker sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine 装docker依赖包 #安装所需资源包 sudo yum install -…

uniapp实现微信小程序自带的分享功能

定义 share.js 文件 export default {data() {return {// 默认的全局分享内容share: {title: 标题,path: /pages/index/index, // 全局分享的路径imageUrl: , // 全局分享的图片(可本地可网络)}}},// 定义全局分享// 1.发送给朋友onShareAppMessage(res) {return {title: this…

数据结构与算法——希尔排序(引例、希尔增量序列、原始希尔排序、代码、时间复杂度、Hibbard增量序列、Sedgewick增量序列)

目录 引例 希尔增量序列 原始希尔排序 代码&#xff08;C语言&#xff09; 时间复杂度 更多增量序列 Hibbard增量序列 Sedgewick增量序列 希尔排序&#xff08;by Donald Shell&#xff09; 引例 给以下序列进行排序&#xff1a; 先以5的间隔进行插入排序&#xff1a…

设计模式之桥接模式

写在前面 本文看下桥接设计模式。 1&#xff1a;介绍 1.1&#xff1a;什么时候桥接设计模式 当一个业务场景由多个变化维度组成&#xff0c;并且这多个变化的维度到底有多少种情况是不确定&#xff0c;比如现在我们要为瑞幸咖啡写一个系统&#xff0c;很自然的&#xff0c;…

2023.7.16 第五十九次周报

目录 前言 文献阅读:跨多个时空尺度进行预测的时空 LSTM 模型 背景 本文思路 本文解决的问题 方法论 SPATIAL 自动机器学习模型 数据处理 模型性能 代码 用Python编写的LSTM多变量预测模型 总结 前言 This week, I studied an article that uses LSTM to solve p…

Element分页组件自定义样式

样式效果 页面代码 <el-paginationsize-change"handleSizeChange"current-change"handleCurrentChange":current-page"page.page":page-sizes"[10, 20, 30, 40]":page-size"page.size"layout"total, sizes, prev, …

如何用https协议支持小程序

步骤一&#xff1a;下载SSL证书 登录数字证书管理服务控制台。在左侧导航栏&#xff0c;单击SSL 证书。在SSL证书页面&#xff0c;定位到目标证书&#xff0c;在操作列&#xff0c;单击下载。 在服务器类型为Nginx的操作列&#xff0c;单击下载。 解压缩已下载的SSL证书压缩…

使用 jmeter 进行审批类接口并发测试

目录 前言&#xff1a; 背景&#xff1a; 难点&#xff1a; 场景 a&#xff1a; 场景 b&#xff1a; 前言&#xff1a; 使用JMeter进行审批类接口的并发测试是一种有效的方法&#xff0c;可以模拟多个用户同时对接口进行审批操作&#xff0c;以评估系统在高负载情况下的性…

Java+Vue+Uniapp全端WMS仓库管理系统

详情图片为运行截图,功能列表: 1、数据管理:物料数据管理、物料Bom管理、物料组管理、物料分类管理、供应商管理、仓库管理、货位管理、车间管理 2、采购管理:物料标签管理、入库单管理、入库退货管理 3、质检管理:质检单管理(包括单据号、单据类型、创建时间、检验状态…

4. CSS用户界面样式

4.1什么是界面样式 所谓的界面样式,就是更改一些用户操作样式,以便提高更好的用户体验。 ●更改用户的鼠标样式 ●表单轮廓 ●防止表单域拖拽 4.2鼠标样式cursor li {cursor: pointer; }设置或检索在对象上移动的鼠标指针采用何种系统预定义的光标形状。 4.3轮廓线outline…

排序算法第三辑——交换排序

目录 ​编辑 一&#xff0c;交换排序算法的简介 二&#xff0c;冒泡排序 冒泡排序代码&#xff1a;排升序 三&#xff0c;快速排序 1.霍尔大佬写的快速排序 2.挖坑法 3.前后指针法 四&#xff0c;以上代码的缺陷与改正方法 三数取中 三路划分&#xff1a; 五&#…

【电子学会】2023年05月图形化四级 -- 计算圆的面积和周长

计算圆的面积和周长 编写程序计算圆的面积和周长。输入圆的半径&#xff0c;程序计算出圆的面积和周长&#xff0c;圆的面积等于3.14*半径*半径&#xff1b;圆的周长等于2*3.14*半径。 1. 准备工作 &#xff08;1&#xff09;保留舞台中的小猫角色和白色背景&#xff1b; 2…

Python 列表 sort()函数使用详解

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;小白零基础《Python入门到精通》 sort函数使用详解 1、升序降序2、sort()和sorted()的区别3、切片排序4、指定排序…

TypeScript笔记

文章目录 什么是TS前期准备安装TSTS配置文件 基础类型原始类型 object类型 数组类型 元组类型 枚举 函数类型可选参数和默认参数剩余参数 any任意类型 高级类型交叉类型联合类型 接口类泛型类型别名参考 什么是TS 官网介绍&#xff1a;TypeScript是JavaScript类型的超集&#…

飞书ChatGPT机器人 – 打造智能问答助手实现无障碍交流

文章目录 前言环境列表1.飞书设置2.克隆feishu-chatgpt项目3.配置config.yaml文件4.运行feishu-chatgpt项目5.安装cpolar内网穿透6.固定公网地址7.机器人权限配置8.创建版本9.创建测试企业10. 机器人测试 前言 在飞书中创建chatGPT机器人并且对话&#xff0c;在下面操作步骤中…

Flask结合gunicorn和nginx反向代理的生产环境部署及踩坑记录

个人博客&#xff1a;https://xzajyjs.cn 前言 之前自己写的flask使用gunicorn上线生产环境没有什么问题&#xff0c;但是最近搭建了一个现成的flask项目&#xff0c;当使用python直接运行时不会有问题&#xff0c;而使用gunicorn时则会出现一些问题。 部署过程 运行测试 这…