L-K光流推导及OpenCV代码实现
光流简单的来说就是通过摄像头的移动,在移动过程中,每一帧的图像特征点会发生移动,这个移动的过程中(x1,y1,z1)在我们下一帧的动作中,去找到原来的所有特征点的新坐标,而这个移动路径,就是所谓的,光流。
推导式
在移动的过程中,图像的像素坐标系的局部矢量局部图像流(速度)矢量
必须满足满足下面的条件 第一帧
其中q1,q2,q3,q4是窗口内的像素
是图像在当前时间位置相对于评估点的 x,y,z和时间t的偏导数
以此类推到达第n帧的时候
现在就不妨可以构造一个矩阵进行计算
系统中的数据未知量过大,因此呢,需要一种方法去解决这样的未知数数据。 L-K(Lucas-Kanade)方法通过最小二乘法得到期望的解值 方法如下
求解表达式
将矩阵代入这个式子中得出求解的表达式如下
其中矩阵
通常称为点p处图像的结构张量 这个矩阵表达式很长,下面附上Latex的代码
begin{bmatrix}
V_x\
V_y \
V_z
end{bmatrix}=begin{bmatrix}
sum _i I_x(q_i)^{2} &sum _i I_x(q_i) I_y(q_i) &sum _i I_x(q_i) I_z(q_i) \
sum _i I_x(q_i) I_y(q_i) &sum _i I_y(q_i)^{2} &sum _i I_y(q_i) I_z(q_i) \
sum _i I_x(q_i) I_z(q_i)& sum _i I_y(q_i) I_z(q_i) &sum _i I_z(q_i)^{2}
end{bmatrix}begin{bmatrix}
-sum_i I_xI_t(q_i)\
-sum_i I_yI_t(q_i)\
-sum_i I_zI_t(q_i)
end{bmatrix}
但是上面的算法中,会出现一个问题,就是图像中的所有数据都具有相同的权重,这样的话就会去计算图像边界上的东西,在实际的运行过程当中,要考虑的问题,是图像中间位置的像素点。
加权改进
下面用加权窗口方法对图像中央的一些点进行加权计算(最小二乘法的加权版本)
其中W是包含权重的n × n 对角矩阵 Wii=wi 被分配给像素方程qi 也就是说,它计算 代入矩阵得
权重wi 通常为qi和p的高斯分布的距离
高斯分布公式
f(x|mu ,sigma^{2} )=frac{1}{sqrt{2pi sigma^{2}}}e^{-frac{(x-mu^{2} )}{2sigma^{2}}}
μ 是分布的平均值或期望值 (以及其中位数和模式 )。
是标准偏差
是方差
正态分布的最简单的情况称为标准正态分布 。 这是特殊情况 μ = 0
= 1 其余正太分布方法我会在后面的博客当中给出,暂时按照标准正态分布进行加权
最小二乘法隐式假定图像数据中的误差具有零均值的高斯分布。 如果有人希望窗口包含一定百分比的“ 异常值 ”(严重错误的数据值,不遵循“普通”高斯误差分布),可以使用统计分析来检测它们,并相应地减小它们的权重。 Lucas-Kanade方法本身只能用于图像流向量 V X , V ÿ 两个帧之间的距离足够小以保持光流的微分方程,通常小于像素间隔。 当流向量可能超过该限制时,例如在立体匹配或扭曲图像中,L-K方法仍然可以通过其他方式细化获得的粗略估计; 例如,通过推算为先前帧计算的流向量,或者通过在图像的缩小版本上运行Lucas-Kanade算法。其中最为常用的是特征匹配算法。
下面给出OpenCV的L-K光流计算法: LK.h
void cvCalcOpticalFlowPyrLK(
const CvArr* imgA,//初始图像
const CvArr* imgB,//最终图像
CvArr* pyrA,
CvArr* pyrB,//pyrA,B申请存放两幅图像金字塔的缓存
CvPoint2D32f* featuresA,//存放的是用于寻找运动的点
CvPoint2D32f* featuresB,//存放featuresA中点的新位置
int count,//featuresA中点的数目
CvSize winsize,//定义了计算局部连续运动的窗口尺寸
int level,//用于设置构建的图像金字塔的栈的层数,若设置为0,则不使用金字塔
char* status,//函数调用结束时,status中的每个元素被置1(对应点在第二幅图中发现)或者0(未发现)
float* track_error,//为可选参数,表示被跟踪点的原始图像小区域与此点在第二幅图像的小区域间的差的数组
CvTermCriteria criteria,//迭代终止条件
int flags//标志位
);
LK.cpp
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
const int MAX_CORNERS = 500;
int main()
{
IplImage *imgA = cvLoadImage("0.jpg", CV_LOAD_IMAGE_GRAYSCALE);
IplImage *imgB = cvLoadImage("1.jpg", CV_LOAD_IMAGE_GRAYSCALE);
CvSize img_sz = cvGetSize(imgA);
int win_size = 10;
IplImage *imgC = cvLoadImage("1.jpg", CV_LOAD_IMAGE_UNCHANGED);
IplImage *eig_image = cvCreateImage(img_sz, IPL_DEPTH_32F,1);
IplImage *tmp_image = cvCreateImage(img_sz, IPL_DEPTH_32F, 1);
int corner_count = MAX_CORNERS;
CvPoint2D32f *cornersA = new CvPoint2D32f[MAX_CORNERS];
cvGoodFeaturesToTrack(
imgA,
eig_image,
tmp_image,
cornersA,
&corner_count,
0.01,
5.0,
0,
3,
0,
0.04
);
cvFindCornerSubPix(
imgA,
cornersA,
corner_count,
cvSize(win_size, win_size),
cvSize(-1, -1),
cvTermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, 0.03)
);
char features_found[MAX_CORNERS];
float features_errors[MAX_CORNERS];
CvSize pyr_sz = cvSize(imgA->width + 8, imgB->height / 3);
IplImage *pyrA = cvCreateImage(pyr_sz, IPL_DEPTH_32F, 1);
IplImage *pyrB = cvCreateImage(pyr_sz, IPL_DEPTH_32F, 1);
CvPoint2D32f *cornersB = new CvPoint2D32f[MAX_CORNERS];
cvCalcOpticalFlowPyrLK(
imgA,
imgB,
pyrA,
pyrB,
cornersA,
cornersB,
corner_count,
cvSize(win_size, win_size),
5,
features_found,
features_errors,
cvTermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, 0.3),
0
);
for (int i = 0; i < corner_count; ++i)
{
if (features_found[i] == 0 || features_errors[i] > 550)
{
cout << "Error is " << features_errors[i];
continue;
}
CvPoint p0 = cvPoint(cvRound(cornersA[i].x), cvRound(cornersA[i].y));
CvPoint p1 = cvPoint(cvRound(cornersB[i].x), cvRound(cornersB[i].y));
cvLine(imgC, p0, p1, CV_RGB(255, 0, 0),2);
}
cvNamedWindow("ImageA", 0);
cvNamedWindow("ImageB", 0);
cvNamedWindow("LKpyr_OpticalFlow", 0);
cvShowImage("ImageA", imgA);
cvShowImage("ImageB", imgB);
cvShowImage("LKpyr_OpticalFlow", imgC);
cvWaitKey(0);
return 0;
}
文尾
在大距离移动的时候需要采用图像金字塔的方法,对图像进行计算需要在多层图像缩放金字塔上求解,每一层的求解结果乘以2后加到下一层。
- Javascript对象的方法赋值
- CentOS挂载硬盘
- 使用jQuery UI的draggable和droppable完成拖拽功能--介绍
- Angular企业级开发(6)-使用Gulp构建和打包前端项目
- CSS魔法堂:重拾Border之——解构Border
- Angular企业级开发(7)-MVC之控制器
- Angular企业级开发(8)-控制器的作用域
- 使用jQuery Draggable和Droppable实现拖拽功能
- CSS魔法堂:重拾Border之——图片作边框
- Mobile Web中URL设计问题
- 使用root用户连接Ubuntu16.04时,提示SSH连接被拒绝
- CSS魔法堂:Box-Shadow没那么简单啦:)
- java操作redis: 将string、list、map、自定义的对象保存到redis中
- 运行第一个Docker容器-Docker for Web Developers(1)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 微内核进行开发工作究竟是怎样的感受?
- Taro框架使用第三方icon
- Istio的流量管理(实操三)
- istio的安全(概念)
- 面试题系列第5篇:JDK的运行时常量池、字符串常量池、静态常量池,还傻傻分不清?
- Go by Example 中文版: 互斥锁
- Idea初始化配置大全,以后重装再也不用各种百度了
- 使用这种技巧,可以大大地提高前端布局效率
- Element-UI表格组件实现行拖拽排序
- Vue自定义指令实现拖拽功能
- 小程序 Canvas 层级问题
- JDK 8 新特性之函数式编程 → Stream API
- golang 单元测试框架实践
- 想要成为前端Star 吗?一首歌时间将React/Vue 应用Docker 化
- 60亿次for循环,原来这么多东西