论文名称:VINet: Visual-Inertial Odometry as a Sequence-to-Sequence Learning Problem
开源项目地址:github.com/HTLife/VINe…
期刊:AAAI
时间:2017
提出了一种端到端可训练的深度学习框架,用于解决单目视觉-惯性里程计(VIO)问题
即将视觉(RGB 图像) 和惯性测量单元(IMU)数据融合,估计相机/机器人/无人机的运动轨迹
VINet 是第一个端到端可训练的视觉-惯性里程计系统,使用CNN+LSTM结构在EuRoC和KITTI数据集上达到了与传统优化方法相当甚至更好的精度,同时具备对标定误差和时间同步误差的鲁棒性
所用数据集
EuRoC MAV Dataset数据集,室内无人机类型,传感器为 IMU + 单目相机,高精度 Vicon 地面真值
projects.asl.ethz.ch/datasets/do…
KITTI Odometry Dataset数据集,户外驾驶类型,传感器为 IMU + 单目相机,用于自动驾驶场景评估
数据均非自己采集
主要工作
任务为视觉-惯性里程计(VIO)
输入为单目RGB图像+IMU数据(角速度、加速度),输出为相机/设备的6自由度位姿(3D平移+四元数旋转)
方法:使用端到端的序列到序列学习模型(CNN + LSTM) ,称为 VINet
创新点:
- 首次提出端到端可训练的VIO系统
- 引入SE(3) 流形上的可微分层处理旋转和平移
- 支持多频率输入(IMU 100Hz,图像 10~20Hz)
- 无需手动标定相机与IMU之间的外参和时间同步
- 可通过训练增强对传感器标定误差的鲁棒性
大方向为视觉-惯性融合导航(VIO),预测类型为长期轨迹预测(整个序列的位姿)
模型类型为序列到序列回归模型(CNN+LSTM),支持端到端,支持实时(10~20Hz图像频率,100Hz IMU)
深度学习模型结构
原始传感器输入(网络入口)
单目传感器RGB,频率为10-20Hz,单帧原始维度3×H×W,EuRoC:3×480×752;KITTI:3×370×1226
IMU频率100-200Hz,单帧原始维度6×1,3-axis acc + 3-axis gyro,已归一化
训练时采用「滑动窗口 + 状态 carry」方式,窗口长度 T ≈ 8-16 帧图像(对应 80-160 个 IMU 采样)。
Visual-CNN 模块
帧间运动特征提取,目的是把「两张连续 RGB」映射成一条低维「视觉运动向量」
Visual−CNN 并不是 VINet 自己重新设计的“新架构”,而是直接裁剪自 FlowNet-S(Dosovitskiy 等人 2015 年提出的光流网络)。作者保留它“ encoder 部分”,也就是能把两张连续 RGB 映射成“运动敏感张量” 的所有卷积层,砍掉后面专门上采样回稠密光流的后半段
补充说明
kernel 中文里叫卷积核/滤波器,其本质为一个小的权重矩阵
-
比如 conv1 的参数 kernel-size =7×7,每个通道对应 7×7 个可学习权重
此时输入有 6 个通道,则一个完整的 kernel 形状是 (64, 6, 7, 7)
- 64 表示“要输出 64 张特征图”,也称 输出通道数
C_out - 6 表示“与输入 6 通道逐一做内积”,也称 输入通道数
C_in - 7×7 就是空间上的“小窗口”
- 64 表示“要输出 64 张特征图”,也称 输出通道数
stride 为步长,表示 kernel 窗口在输入平面上“滑动的步长”
-
conv1 中 stide=2,表示横向、纵向都隔 2 像素取样一次
- 就会造成 480 → 240,752 → 376(正好减半)
H_out = (H_in + 2×pad − kernel_size) // stride + 1
pad 即为 padding 填充
控制输出空间尺寸,同时保证边界像素也被充分利用
- conv1:pad = 3 ⇒ 上下左右各补 3 圈 0
代入上式:(480 + 2×3 − 7)/2 + 1 = 240
如果不 pad,输出会是 237,尺寸“不整齐”且边界信息丢失多
一个卷积层权重矩阵形状 (C_out, C_in, kH, kW) ,另外每个输出通道还要1个bias。
Params = (C_in × kH × kW + 1) × C_out
- conv1 代入得到 = (6 × 7 × 7 + 1) × 64 = (294 + 1) × 64 = 18 880
每个输出通道共享同一组 (C_in×kH×kW) 个权重——所谓“权值共享”
所以不管输入图像有多大,参数量只取决于 (kernel 面积 × 输入通道 + 1) × 输出通道,与空间尺寸无关
输入
输入为一对时间相邻的RGB帧,I_t, I_{t+1} ∈ ℝ^{(3×H×W)},先沿 channel 维拼接,得到 6×H×W
所有卷积后面都接ReLU,所有 stride=2 的 conv 同时完成“降采样 + 通道升维”
省略 BatchNorm 细节(原 FlowNet 没有 BN,VINet 也未加)
conv1
(首层降采样+大感受野)
- 输入为 6×480×752,kernel 为 7×7, stride=2, pad=3,输出通道 64,输出尺寸为 64×240×376
- 参数量为 (7×7×6)×64+64=18,880
把“双图拼接”后的 6 通道图像一次性压到 64 维,同时降采样 1/2,减轻后续计算量;7×7 大 kernel 直接给出较大运动范围的感受野
感受野表示特征图上的一个点在原始输入图像上能“看到”的区域大小
- 第一层就用 7×7,意味着 conv1 输出中的一个像素已经融合了输入图 7×7 邻域的信息
- 后续再经过 stride =2 的层层降采样,到 conv6 时一个特征点对应原图 大于 200×200 的像素
- 对于光流/ego-motion 任务,大位移运动需要大感受野才能被“一次性”捕获,而小 3×3 要靠很多层才能攒出同样视野(7×7 是“一步到位”)
conv2
- 输入为 640×240×376,kernel 为 5×5, stride=2, pad=2,输出通道 128,输出尺寸 128×120×188
- 参数量为 (5×5×64)×128 + 128 = 204,928
进一步缩小空间,提高语义级别;5×5 kernel 保持适中运动范围。
conv3 → conv3_1
(两个连续 3×3,通道翻倍)
- conv3 参数 128×120×188 → 256×60×94 (stride=2),参数量 3×3×128×256 + 256 = 295,168
conv3_1参数 256×60×94 → same (stride=1),参数量 3×3×256×256 + 256 = 590,080
stride=2 负责再降 1/2;conv3_1 是“same-resolution” refinement,增强非线性表达能力
conv4 → conv4_1
(重复套路,512 维)
- conv4 参数 256×60×94 → 512×30×47 (stride=2),参数量 3×3×256×512 + 512 = 1,180,160
conv4_1参数 512×30×47 → same (stride=1),参数量 3×3×512×512 + 512 = 2,359,808
空间降到 1/8 原尺寸,通道升至 512,语义 / 运动抽象级别更高。
conv5 → conv5_1
(进入 1024 维高语义)
- conv5 参数 512×30×47 → 512×15×24 (stride=2),参数量 3×3×512×512 + 512 = 2,359,808
conv5_1参数 512×15×24 → same,参数量 同上
最后一次 2 倍降采样;15×24 已接近“特征图”级别,可粗略认为每个像素对应原图 32×32 区域。
conv6
(得到 1024-d “运动向量”)
- 输入参数 512×15×24,kernel 为 3×3, stride=2, pad=1,输出通道 1024,输出尺寸 1024×8×12,
- 参数量 3×3×512×1024 + 1024 = 4,719,616
空间压到 8×12 ≈ 100 个 cell,每个 cell 感受野 ≈ 1/8 图像
通道升至 1024,形成高维、大感受野的“帧间运动描述子”
压平 → 视觉特征向量
AdaptiveAvgPool2d(1) 或直接 .view(1024,-1).mean(-1)
conv6 输出1024×8×12,要把它变成固定长度1024维向量,才能和IMU向量拼在一起
有两种等价的方法:
-
AdaptiveAvgPool2d(1)
官方Pytorch写法,不管输入空间是8×12还是5×9,强制做全局平均池化,输出尺寸 = (1024, 1, 1)
再 .squeeze() → 1024
-
手工一行代码
x = x.view(1024, -1) # reshape 成 [1024, 96]
x = x.mean(dim=-1) # 对最后一维求平均 → [1024]
效果与 AdaptiveAvgPool 完全一致,但更直观:把每个通道的所有空间位置取平均,得到通道级全局特征。
- 消除空间尺寸不确定性,输出维度永远 = C_out
- 运算无参数、可微分,可端到端训练
- 对微小平移不敏感,起到平移鲁棒的作用
参数 1024×8×12 → 1024×1
再经 fc(1024→1024)(FlowNet 原实现没有,VINet 加了一层线性,参数 1024×1024+1024≈1M)
最终输出 v_{vis} ∈ ℝ^{(1024×1)} —— 这就是送入 Core-LSTM 的“视觉运动线索”
推理阶段只跑前向,且只需到 conv6;原 FlowNet 后面还有 4 级 upsample/refine 层(总计 38 M)被全部砍掉,因此 VINet 实际视觉前端比完整 FlowNet 轻量约 40%
各步骤在 VINet 中的功能角色
- 逐级降采样:把像素级光流任务转成“高层运动语义”任务,降低计算。
- 通道扩增:补偿空间信息损失,获得足够判别维度。
- 最后 1024-d 向量:与 IMU-LSTM 的 1000-d 向量长度接近,便于 Core-LSTM 拼接融合。
- 端到端可微:所有 conv/fc 均参与 BPTT,视觉权重会根据 pose-loss 自动调整——网络自己学会“提取对 ego-motion 最有用的那部分光流 / 结构 / 边缘特征”
IMU LSTM模块
IMU-LSTM 模块负责把高频惯性数据(通常 100–200 Hz)压缩成与图像帧同步的单一特征向量,再交给 Core-LSTM 做跨模态融合
- 以 EuRoC 数据集为例(IMU 200 Hz,图像 20 Hz)逐步拆解
原始输入数据使用IMU传感器,200Hz频率,单帧维度维6×1,表示3轴加速度+3轴角速度
一个图像周期(0.05s)内共有K=10个IMU采样,把这段10步序列记作 U=[u₁, u₂, …, u₁₀]∈ℝ^{(6×10)}
数据缓冲与对齐子模块(无参数)
输入为实时IMU缓存队列,输出为滑动窗口 U ∈ ℝ^{(6×10)}(每次移1帧图像)
- 保证图像帧 t 到来时,网路拿到的是 (t-0.05 s → t] 的惯性数据;
- 后续所有计算都以固定长度10步为输入,便于批训练
线性嵌入层
(Embedding FC)
- 输入为单步 u_{k} ∈ ℝ^6,输出 x_k ∈ ℝ^{32}(论文隐藏维=32,官方代码也可配置为 64/128)
运算为 x_k = W_{emb} · u_k + b_{emb},W ∈ ℝ^{(32×6)}
把 6 维物理量映射到更高维隐空间,让网络自由学习“惯性特征” 而非受限于原始加速度/角速度刻度
参数量 32×6+32 = 224
2 层 LSTM 编码器
(核心可训练部分)
层数为 2,隐藏节点 =1000(与 Visual-CNN 输出维度 1024 接近,便于拼接)
方向仅正向(只沿时间正序,足够捕捉短期动力学)
- t=1,输入 x_{1}, (h_{0}, c_{0}),维度 x_{1}∈ℝ^{32}, h_{0}=c_{0}=zeros(1000),数据来自于嵌入层 / 零初始化
- t=2…10,输入 x_k, (h_{k-1}, c_{k-1}),维度同上,数据来自于上一步隐状态
可训练参数(单层):4 个门,每门 (W_x, W_h, b) → (32×1000 + 1000×1000 + 1000) × 4 ≈ 4.13 M
双层 ×2 → 8.26 M(IMU 分支的主要参数量集中在这里)
状态抽取子模块(无参数)
输入第 10 步隐状态 h_{10} ∈ ℝ^{1000},输出 v_{imu} = h_{10}(直接复用,不再额外线性变换)
原因:
- LSTM 最后隐状态已把整个 10 步动力学信息压缩;
- 维度 1000 与 Visual-CNN 的 1024 几乎对齐,后续 Core-LSTM 可直接拼接
Core-LSTM 模块
Core-LSTM 把视觉CNN给出的 1024-D 帧间运动线索、IMU-LSTM 给出的 1000-D 高频惯性线索、上一时刻自己的位姿估计(7-D 四元数+平移)拼成一个超大输入向量,然后用两层隐藏单元 1000 的 LSTM 完成:
- 跨模态融合(视觉 + 惯性 + 位姿先验)
- 时序动力学建模(加速度、角速度积分误差补偿)
- 输出 SE(3) 位姿更新(帧间速度 ξ ∈ ℝ^6)
把视觉、惯性、先验位姿拉到同一向量,让网络自己决定权重
输入张量准备(无参数)
- Visual-CNN 来的张量 v_{vis},维度为 1024×1,已全局平均池化
- IMU-LSTM 来的张量 v_{imu},维度为 1000×1,取最后隐状态 h_{10}
- 上一时刻位姿 T_{t-1},维度 7×1,其为 四元数q(4)+平移 r(3)
拼接方式 x_t = [v_{vis}; v_{imu}; T_{t-1}] ∈ ℝ^{(1024+1000+7)} = 2031×1
位姿先验被重复利用,形成“输出-反馈”闭环,使网络能累积修正漂移。
线性降维嵌入
(Optional FC,官方代码可旁路)
- 输入 x_{t} ∈ ℝ^{2031},输出 \hat x_{t} ∈ ℝ^{512}(或 1024,可配置)
- 运算 \hat x_{t} = W_{reduce} · x_{t} + b_{reduce}
把 2031 维超大向量压到 512/1024,减少 LSTM 参数量;让不同模型数值范围先做一次自适应缩放
参数量 512×2031+ 512 ≈ 1.04 M
若关闭该选项,则直接把 2031 喂 LSTM,下文按 dim = D = 2031 描述
2031 维太大,先乘个矩阵压到 512 维 → 减少参数量,顺便把不同模态数值尺度拉到同一量级
2 层 LSTM 融合编码器
(核心可训练部分)
层数 =2 (双层),隐藏节点 = 1000(与 IMU 分支一致,便于拼接),方向为单向(只沿时间正序)
-
t 时刻输入x_t, (h_{t-1}^{(1,2)}, c_{t-1}^{(1,2)}),维度x_t∈ℝ^D, 隐状态∈ℝ^{1000},数据来源 上游拼接 / 上一时步
i_t = σ(W_{xi}x_t + W_{hi}h_{t-1} + b_i) f_t = σ(W_{xf}x_t + W_{hf}h_{t-1} + b_f) o_t = σ(W_{xo}x_t + W_{ho}h_{t-1} + b_o) g_t = tanh(W_{xc}x_t + W_{hc}h_{t-1} + b_c) c_t = f_t ⊙ c_{t-1} + i_t ⊙ g_t h_t = o_t ⊙ tanh(c_t)
参数量(单层):4 × (D×1000 + 1000×1000 + 1000) = 4 × (2 031 000 + 1 000 000 + 1 000) ≈ 12.13 M
双层 ×2 → 24.26 M(占 VINet 总参数量 55 M 的近一半)
建模长期动力学,补偿积分漂移,融合多模态
双层 1000 节点 LSTM 对“压缩后的多模态序列”逐时刻更新隐状态 → 既融合视觉+惯性,又记住过去 N 帧的动力学,用来预测下一步该怎么动
帧间速度回归头
(se(3) 代数向量)
- 输入为第二层隐状态 h_t^{(2)} ∈ ℝ^{1000},输出 ξ_t ∈ ℝ^6(3-D 线速度 v + 3-D 角速度 ω)
运算为 ξ_t = W_{vel}·h_t^{(2)} + b_{vel},W ∈ ℝ^{(6×1000)}
- 把融合后的高维状态映射到李代数 se(3) ,无正交约束,可直接回归;
- 后续通过指数映射得到 SE(3) 位姿增量
参数量:6×1000 + 6 = 6006
无约束输出帧间速度,免掉 SO(3) 正交投影麻烦
把 1000 维隐状态再乘个矩阵 → 直接输出 6 个数(3 轴线速度 + 3 轴角速度),这叫 se(3) 代数,不用管旋转矩阵正交不正交
SE(3) 可微分层
(无参数,仅矩阵运算)
- 输入:ξ_t ∈ ℝ^6,输出:ΔT_t ∈ SE(3)(4×4 刚体矩阵)
两步完成:
-
∧ 算子:ξ_t → 4×4 矩阵
[ξ]^∧ = [ [ω]_× v 0 0 ] ∈ se(3)6 维向量 → 4×4 矩阵:前三列放“角速度反对称矩阵”,最后一列放“线速度”,得到李代数元素
-
指数映射:ΔT_t = exp([ξ]^∧) ∈ SE(3)
对 4×4 矩阵做矩阵指数 → 得到 4×4 刚体变换矩阵 ΔT,满足旋转部分自动是 SO(3),平移部分也自动正确
反向传播 exp(·) 与矩阵乘法均为解析可微,Theano/TensorFlow 自定义 op 已实现梯度
位姿累积与反馈(无参数)
- 输入:ΔT_t(4×4),上一时刻全局位姿 T_{t-1}(4×4),输出 当前全局位姿 T_t = T_{t-1} · ΔT_t
- 再分解:把 T_t 转成 7-D 向量 [q; r] → 下一时刻重新喂回 Core-LSTM 输入,形成循环闭环
上一帧的全局位姿 T_{t-1} 左乘 ΔT → 得到当前全局位姿 T_t,完成“积分”
损失函数入口(训练阶段)
帧间 loss:L_{se}(3) = ∥v−v̂∥ + β∥ω−ω̂∥(用 ξ_t 与真值 ξ̂_t)
- 帧间 6 维速度对比真值 → 保证每一步别跑飞
全局 loss:L_{SE}(3) = ∥q−q̂∥ + β∥r−r̂∥(用 T_t 与真值 T̂_t)
- 全局 7 维位姿对比真值 → 保证整条轨迹不漂移
联合训练,先帧间后全局,防止局部极小;两个 loss 轮流加权,端到端反向传播,所有权重一起更新
SE(3) Concatenation Layer
SE(3) Concatenation Layer,自定义可微分层,将帧间位姿累积为全局轨迹,保持SE(3)结构约束
这一部分和上述 Core-LSTM 模块的 SE(3)可微分层、位姿累积与反馈 部分功能完全重叠
-
从 ξ_t → 全局位姿 T_t 的整条链路只有一个,官方论文把它叫做 SE(3) Concatenation Layer,也常被口头说成“可微分 exp 层”或“几何累积层”
内部顺序:
ξ_t ──∧──► ξ^∧ ──exp──► ΔT_t ──左乘──► T_t ──分解──► 7-D pose_t
训练方式与损失
训练方式使用Backpropagation Through Time (BPTT),联合优化两个损失:
帧间位姿误差(se(3))、全局轨迹误差(SE(3))
VINet 的训练方式可以概括为 “滑动窗口 BPTT + 双损失课程调度” ,它与普通 CNN 或单损失 LSTM 训练有 3 个显著不同:
- 长序列不一次展开到底,而是用窗口滑动 + 隐状态 carry 近似完整 BPTT;
- 同时优化 帧间速度 loss 和 全局位姿 loss,且权重按课程表切换;
- 训练集里主动注入标定误差(时间偏移、外参旋转),让网络自己学会鲁棒。
滑动窗口
问题是 EuRoC 一条轨迹 40 s,图像 20 Hz → 800 帧;每帧 480×752,显存无法一次展开 800 步
VINet 做法:
- 设定窗口长度 T = 8~16 帧图像(对应 80~160 个 IMU 步);
- 每跑完一个窗口,只反向传播这 8~16 步的梯度,然后把两个 LSTM 的隐状态 (h, c) 直接 carry 到下一个窗口,作为初始状态继续训练;
- 整个序列依次滑过去,近似得到“全序列 BPTT”的效果,但显存占用恒定在 T 步。
模型性能对比
| 对比方法 | 类型 | 简介 | 效果对比 |
|---|---|---|---|
| OK-VIS | 传统优化型VIO | 基于非线性优化的关键帧VIO(Leutenegger et al.) | VINet在标定误差下更鲁棒,精度相当 |
| MSCKF | 滤波型VIO | 多状态约束卡尔曼滤波 | VINet在精度上略优,鲁棒性更强 |
| LIBVISO2 + EKF | 视觉+IMU融合 | 传统视觉里程计 + EKF融合IMU | VINet在KITTI上翻译误差更低,旋转略差 |