OpenCV实战(28)——光流估计

news2024/9/22 11:41:07

OpenCV实战(28)——光流估计

    • 0. 前言
    • 1. 光流估计原理
    • 2. 光流算法实现
    • 3. 完整代码
    • 小结
    • 系列链接

0. 前言

当相机进行拍摄时,拍摄到的亮度图案会投射到图像传感器上,从而形成图像。在视频序列中,我们通常需要捕捉运动模式,即不同场景元素的 3D 运动在图像平面上的投影,这种投影 3D 运动矢量的图像称为运动场 (motion field)。但是,我们无法从相机传感器直接测量场景点的 3D 运动,我们所观察到的只是一种逐帧运动的亮度模式,亮度图案的这种运动称为光流 (optical flow)。运动场并不完全等同于光流,一个简单的例子是拍摄无明显变化的物体,例如,如果摄像机在白墙前移动,则不会产生光流;另一个经典的例子是旋转杆产生的运动错觉:

转灯

在这种情况下,当垂直圆柱体围绕其主轴旋转时,运动场为水平方向的运动矢量。然而,在视觉上这种运动看起来是红色和蓝色条带向上移动,这也是光流所展现的内容。尽管存在这些差异,但光流通常可以认为是运动场的有效近似。本节将学习如何估计图像序列的光流。

1. 光流估计原理

光流估计意味着量化图像序列中亮度模式的运动。因此,考虑在给定时刻的一帧视频,如果查看当前帧上 ( x , y ) (x, y) (x,y) 的一个特定像素,我们想知道该点在后续帧中移动到哪里。该点的坐标随时间移动可以表示为 ( x ( t ) , y ( t ) ) (x(t), y(t)) (x(t),y(t)),我们的目标是估计该点的速度 ( d x d t , d y d t ) (\frac {dx}{dt}, \frac {dy}{dt}) (dtdx,dtdy)。可以通过查看序列的相应帧,即 I ( x ( t ) , y ( t ) , t ) I(x(t), y(t), t) I(x(t),y(t),t) 来获得该特定点在给定时间点 t t t 的亮度。根据图像亮度恒定假设,我们可以假设该点的亮度不随时间变化:
d I ( x ( t ) , y ( t ) , t ) d t = 0 \frac {dI(x(t), y(t), t)} {dt}=0 dtdI(x(t),y(t),t)=0
根据链式法则,可以得到以下等式:
d I d x d x d t + d I d y d y d t + d I d t = 0 \frac {dI} {dx}\frac {dx} {dt}+\frac {dI} {dy}\frac {dy} {dt}+\frac {dI} {dt}=0 dxdIdtdx+dydIdtdy+dtdI=0
该方程称为亮度恒定方程,它将光流分量(即 x x x y y y 相对于时间的导数)与图像导数相关联。这与我们在特征点追踪一节中推导出的方程完全相同,仅仅使用不同的形式来表示。
然而,这个方程(由两个未知数组成)不足以计算像素位置的光流。因此,我们需要添加一个额外的约束。一个常见的做法是假设光流是平滑的,这意味着相邻的光流向量应该相似,此约束基于光流的拉普拉斯算子:
∂ 2 ∂ x 2 d x d t + ∂ 2 ∂ y 2 d y d t \frac {\partial^2}{\partial x^2}\frac {dx} {dt}+\frac {\partial^2}{\partial y^2}\frac {dy} {dt} x22dtdx+y22dtdy
因此,目标是找到最小化与亮度恒定方程和流向量拉普拉斯算子的偏差的光流场。

2. 光流算法实现

我们可以使用 cv::DualTVL1OpticalFlow 类解决密集光流估计问题,该类构建为通用 cv::Algorithm 基类的子类。

(1) 首先创建 cv::DualTVL1OpticalFlow 类的实例并获取它的指针:

    // 创建光流算法
    cv::Ptr<cv::optflow::DualTVL1OpticalFlow> tvl1 = cv::optflow::createOptFlow_DualTVL1();

(2) 使用创建的对象调用计算两帧之间光流场的方法:

    cv::Mat oflow;
    tvl1->calc(frame1, frame2, oflow);

计算结果为 2D 向量 (cv::Point) 的图像,表示两帧之间每个像素的位移。为了显示结果,我们必须显示这些向量,因此,我们需要创建一个为光流场生成图像映射的函数。

(3) 为了控制向量的可见性,我们使用两个参数。第一个是定义的步幅值,以便仅显示特定数量像素上的向量,步幅值为向量的显示腾出了空间。第二个参数是扩展矢量长度以使其具有更明显的比例因子。绘制的每个光流向量都是一条简单的线,以圆圈结束表示箭头。因此,映射函数如下:

// 在图像上绘制光流矢量
void drawOpticalFlow(const cv::Mat &oflow,  // 光流
            cv::Mat &flowImage,             // 结果图像
            int stride,                     // 矢量步长
            float scale,                    // 向量的乘数 
            const cv::Scalar &color) {      // 颜色
    // 为图像分配内存
    if (flowImage.size()!=oflow.size()) {
        flowImage.create(oflow.size(), CV_8UC3);
        flowImage = cv::Vec3i(255, 255, 255);
    }
    for (int y=0; y<oflow.rows; y+=stride) {
        for (int x=0; x<oflow.cols; x+=stride) {
            // 获取矢量
            cv::Point2f vector = oflow.at<cv::Point2f>(y, x);
            // 绘制直线
            cv::line(flowImage, cv::Point(x, y), 
                    cv::Point(static_cast<int>(x+scale*vector.x+0.5),
                            static_cast<int>(y+scale*vector.y+0.5)),
                    color);
            cv::circle(flowImage, cv::Point(static_cast<int>(x+scale*vector.x+0.5),
                            static_cast<int>(y+scale*vector.y+0.5)),
                    1, color, -1);
        }
    }
}

(4) 我们使用以下两帧:

示例帧画面
(5) 使用以上帧,通过调用绘图函数来可视化估计的光流场:

    // 绘制光流图像
    cv::Mat flowImage;
    drawOpticalFlow(oflow, flowImage, 8, 2, cv::Scalar(0, 0, 0));

光流场
在本节中,我们解释了可以通过最小化结合亮度恒定性约束和平滑度函数的函数来估计光流场。该方法称为 Dual TV L1 方法。它有两个主要组成;第一个是使用平滑约束,旨在最小化光流梯度的绝对值,这种选择减少了平滑项的影响,特别是在不连续区域,例如,移动物体的光流矢量与背景中的光流矢量完全不同;第二个是使用一阶泰勒近似,即亮度恒定约束,这种线性化有助于光流场的迭代估计,但线性近似仅对小位移有效。
在本节中,我们使用了带有默认参数的 Dual TV L1 方法,使用 settergetter 方法可以修改可能对解决方案的质量和计算速度产生影响的参数。例如,可以修改金字塔估计中使用的尺度数或指定严格停止标准;另一个重要参数是与亮度恒定性约束相对于平滑性约束相关联的权重,例如,如果我们需要将亮度恒定性约束的重要性降低两倍,那么可以获得更平滑的光流场:

    // 获得更平滑的光流
    tvl1->setLambda(0.05);

平滑光流场

3. 完整代码

头文件 (videoprocessor.h) 完整代码参考视频序列处理一节,主函数文件 (flow.cpp) 完整代码如下所示:

#include <string>
#include <iostream>
#include <sstream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/video/tracking.hpp>
#include <opencv2/optflow.hpp>
#include "videoprocessor.h"

// 在图像上绘制光流矢量
void drawOpticalFlow(const cv::Mat &oflow,  // 光流
            cv::Mat &flowImage,             // 结果图像
            int stride,                     // 矢量步长
            float scale,                    // 向量的乘数 
            const cv::Scalar &color) {      // 颜色
    // 为图像分配内存
    if (flowImage.size()!=oflow.size()) {
        flowImage.create(oflow.size(), CV_8UC3);
        flowImage = cv::Vec3i(255, 255, 255);
    }
    for (int y=0; y<oflow.rows; y+=stride) {
        for (int x=0; x<oflow.cols; x+=stride) {
            // 获取矢量
            cv::Point2f vector = oflow.at<cv::Point2f>(y, x);
            // 绘制直线
            cv::line(flowImage, cv::Point(x, y), 
                    cv::Point(static_cast<int>(x+scale*vector.x+0.5),
                            static_cast<int>(y+scale*vector.y+0.5)),
                    color);
            cv::circle(flowImage, cv::Point(static_cast<int>(x+scale*vector.x+0.5),
                            static_cast<int>(y+scale*vector.y+0.5)),
                    1, color, -1);
        }
    }
}

int main() {
    cv::Mat frame1 = cv::imread("3.png", 0);
    cv::Mat frame2 = cv::imread("4.png", 0);
    // 组合显示
    cv::Mat combined(frame1.rows, frame1.cols + frame2.cols, CV_8U);
    frame1.copyTo(combined.colRange(0, frame1.cols));
    frame2.copyTo(combined.colRange(frame1.cols, frame1.cols+frame2.cols));
    cv::imshow("Frames", combined);
    // 创建光流算法
    cv::Ptr<cv::optflow::DualTVL1OpticalFlow> tvl1 = cv::optflow::createOptFlow_DualTVL1();
	std::cout << "regularization coeeficient: " << tvl1->getLambda() << std::endl;
	std::cout << "Number of scales: " << tvl1->getScalesNumber() << std::endl;
	std::cout << "Scale step: " << tvl1->getScaleStep() << std::endl;
	std::cout << "Number of warpings: " << tvl1->getWarpingsNumber() << std::endl;
	std::cout << "Stopping criteria: " << tvl1->getEpsilon() << " and " << tvl1->getOuterIterations() << std::endl;
    cv::Mat oflow;
    tvl1->calc(frame1, frame2, oflow);
    // 绘制光流图像
    cv::Mat flowImage;
    drawOpticalFlow(oflow, flowImage, 8, 2, cv::Scalar(0, 0, 0));
    cv::imshow("Optical Flow", flowImage);
    // 获得更平滑的光流
    tvl1->setLambda(0.05);
    tvl1->calc(frame1, frame2, oflow);
    // 绘制光流图像
    cv::Mat flowImage2;
    drawOpticalFlow(oflow, flowImage2, 8, 2, cv::Scalar(0, 0, 0));
    cv::imshow("Smoother Optical Flow", flowImage2);
    cv::waitKey();
}

小结

光流估计 (Optical Flow estimation) 在视频理解、动作识别、目标跟踪、全景拼接等领域具有重要应用,在各类视频分析任务中它反映了视频内部的运动信息,是一种重要视觉线索。本节中,介绍了光流估计的基本原理,并使用 cv::DualTVL1OpticalFlow 类解决密集光流估计问题。

系列链接

OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(2)——OpenCV核心数据结构
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作
OpenCV实战(5)——图像运算详解
OpenCV实战(6)——OpenCV策略设计模式
OpenCV实战(7)——OpenCV色彩空间转换
OpenCV实战(8)——直方图详解
OpenCV实战(9)——基于反向投影直方图检测图像内容
OpenCV实战(10)——积分图像详解
OpenCV实战(11)——形态学变换详解
OpenCV实战(12)——图像滤波详解
OpenCV实战(13)——高通滤波器及其应用
OpenCV实战(14)——图像线条提取
OpenCV实战(15)——轮廓检测详解
OpenCV实战(16)——角点检测详解
OpenCV实战(17)——FAST特征点检测
OpenCV实战(18)——特征匹配
OpenCV实战(19)——特征描述符
OpenCV实战(20)——图像投影关系
OpenCV实战(21)——基于随机样本一致匹配图像
OpenCV实战(22)——单应性及其应用
OpenCV实战(23)——相机标定
OpenCV实战(24)——相机姿态估计
OpenCV实战(25)——3D场景重建
OpenCV实战(26)——视频序列处理
OpenCV实战(27)——追踪视频中的特征点

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

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

相关文章

HCIP--OSPF实验1

1、合理规划IP地址&#xff0c;启用ospf单区域 2、R1-R2之间启用ppp的单向认证 3、R2-R3之间启用ppp的chap认证 4、R3-R5-F6之间使用MGRE&#xff0c;R3为hub端&#xff0c;R5,R6为spoke端&#xff1b; 要求MGRE接口网络型为BMA&#xff0c;spoke之间通信必须经过hub端 5、全…

Linux--进程

什么叫做进程&#xff1f; 程序加载到内存就叫进程&#xff08;看不懂是吧&#xff0c;看下面更详细一些&#xff09; 进程对应的代码和数据进程对应的PCB结构体

MySQL索引原理和优化

目录 1 什么是索引&#xff1f;1.1 引言1.2 索引原理1.3 索引分类1.3.1 主键索引1.3.2 普通索引&#xff08;单列索引&#xff09;1.3.3 复合索引&#xff08;组合索引&#xff09;1.3.4 唯一索引1.3.5 全文索引1.3.6 索引的查询和删除 1.4 索引的优缺点 2 索引数据结构2.1 Has…

做题遇见的PHP函数汇总

mb_substr函数 mb_substr() 函数返回字符串的一部分&#xff0c;之前学过 substr() 函数&#xff0c;它只针对英文字符&#xff0c;如果要分割的中文文字则需要使用 mb_substr() 语法&#xff1a; mb_substr ( $str ,$start [, $length NULL [, $encoding mb_encoding() ]] …

改进版简化路径。

美图 在原有的基础上增加对 cd - 的处理。 在 Unix 命令中&#xff0c;cd - 表示返回上一次所在的目录。我们可以使用一个变量来记录上一次所在的目录&#xff0c;在遇到 cd - 时将当前目录设置为上一次所在的目录。 以下是增加对 cd - 的处理后的代码&#xff1a; 4 cd /…

016 - STM32学习笔记 - SPI读写FLASH(一)

016 - STM32学习笔记 - SPI访问Flash&#xff08;一&#xff09; 之前csdn的名称是宥小稚&#xff0c;后来改成放学校门口见了&#xff0c;所以前面内容看到图片水印不要在意&#xff0c;都是自己学习过程中整理的&#xff0c;不涉及版权啥的。 1、什么是SPI&#xff1f; SP…

Linux项目自动化构建工具-make/Makefile以及git三板斧

目录 一、关于make/makefile的背景知识二、依赖关系和依赖方法三、make/makefile如何书写&#xff1f;四、文件的三个时间(Access、Modify、Change)五、Linux下倒计时和进度条代码的书写5.1 回车换行5.2 缓冲区5.3 倒计时代码实现5.4 进度条代码实现 六、git三板斧6.1 什么是gi…

10.15资源加载

定义&#xff1a; 1.直接属性引用 生成一个actor和声音&#xff1a; 运行时就会产生一个Myactor中设置的Actor并且播放Myactor中设置的声音。 音频&#xff1a;class USoundCue&#xff1b; 纹理&#xff1a;class UTexture&#xff1b; 材质&#xff1a; class UMaterial 模…

TCP/IP出现的背景及其历史【图解TCP/IP(笔记八)】

文章目录 TCP/IP出现的背景及其历史从军用技术的应用谈起ARPANET的诞生TCP/IP的诞生UNIX系统的普及与互联网的扩张商用互联网服务的启蒙 TCP/IP出现的背景及其历史 从军用技术的应用谈起 20世纪60年代&#xff0c;很多大学和研究机构都开始着力于新的通信技术。其中有一家以美…

jmeter列表数据断言

在jmeter接口请求中&#xff0c;通常需要根据接口data列表有无返回的数据断言是接口请求成功&#xff0c;如图1&#xff0c; 通常有这么几种方法&#xff1a; beanshell断言 json断言 响应断言 图1&#xff1a; 失败请求&#xff1a;{"code":0,"msg"…

小甲鱼- python -洗牌算法 —— Fisher-Yates

练习1 自己的原始代码 &#xff08;比较复杂&#xff09; 1.没有把字符串转为列表&#xff0c;所以不能利用pop # 打乱的次数 for i in range(1,4):s "ABCDEF"n 4list []l len(s)while l > 0:k random.randint(1, l)list.append(s[k-1])s s.replac…

电子设备电池容量与充电器功率的关系

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 目录 抛出问题 手机的工作电压 手机的工作电流 手机的电池容量 电能转换公式 充电器功率 充电时间计算 总结 抛出问题 你是否也想过&#xff0c;你的手机电池容量是5000mAh&#xff0c;手机充电器是120W快…

基于低代码平台的项目设计的一般流程及低代码平台(基于iVX)与MVC的关系

基于低代码平台的项目设计的一般流程及低代码平台&#xff08;基于iVX&#xff09;与MVC的关系 1.基于低代码平台的项目设计的一般流程a.流程图b.MVC架构应用于iVX项目的各分层排序&#xff1a;&#xff08;1&#xff09;第一步&#xff1a;写M&#xff08;2&#xff09;第二步…

LeetCode[912]排序数组

难度&#xff1a;Medium 题目&#xff1a; 给你一个整数数组 nums&#xff0c;请你将该数组升序排列。 示例 1&#xff1a; 输入&#xff1a;nums [5,2,3,1] 输出&#xff1a;[1,2,3,5]示例 2&#xff1a; 输入&#xff1a;nums [5,1,1,2,0,0] 输出&#xff1a;[0,0,1,1,2,…

Openlayers实战:显示海量数据

Openlayers地图中通常的加载方式是canvas,另外还一种加载方式是webGL,在绘制海量数据时,使用GPU进行绘制可有效减少CPU的负载,提升绘制时的速度在浏览器中,可以使用WebGL的方式与GPU交互。 在本实战中,使用WebGLPoints显示海量数据。 效果图 源代码 /* * @Author: 大剑…

表单标签from

七、表单标签 form text name属性必须添加&#xff0c;否则后端不知道这个值是什么意思。且name不能重复 添加label主要是方便程序员&#xff0c;一看到用户名称这个label就是为username的&#xff0c;添加或者不添加页面效果一样 2、possword 用户密码显式出来&#xff0c;所…

与一款医疗仪器的往事

这是一款比较冷门的医疗仪器&#xff0c;SOD型多普勒脐动脉血流检测仪。 大约是20年前&#xff0c;有个朋友找到了俺&#xff0c;让俺给他写一个医疗仪器的软件。当时这个仪器是这样子的。这是唯一还能找到的当时的图片。朋友要求用C写。俺就选择了C Builder&#xff0c;比用VC…

vue源码阅读之什么是虚拟dom

前面简单说过数据响应式原理&#xff0c;大体是个怎么流程&#xff0c;数据发生变化&#xff0c;我们界面如何更新。 依赖收集收集的是watcher&#xff0c;然后当数据发生变化的时候dep通知watcher&#xff0c;然后watcher负责updateComponent。 那么更新组件过程中&#xff…

PostgreSQL查询引擎——上拉子链接SubLink

子查询是查询语句中经常出现的一种类型&#xff0c;是比较耗时的操作。优化子查询对查询效率的提升有直接的影响。从子查询出现在SQL语句的位置看&#xff0c;它可以出现在目标列、FROM子句、WHERE子句、JOIN/ON子句、GROUPBY子句、HAVING子句、ORDERBY子句等位置。子查询出现在…

c语言指针进阶(一)

大家好&#xff0c;我是c语言boom成家宝。今天为大家分享的是c语言中很重要的一个知识点------指针的深入讲解。 目录 指针 指针数组 数组指针 函数指针 什么是指针&#xff1f; 首先&#xff0c;指针的本质是一个地址&#xff0c;指针在32位机器上的大小是4个字节&a…