RAFT光流的理解与解读
一、RAFT 算法整体流程详解
🧱 一、模型结构组成
RAFT 网络由三个主要部分组成:
1. 特征提取模块(Feature Encoder)
- 输入:图像对
image1,image2 - 网络:
BasicEncoderorSmallEncoder - 输出:高维特征图
fmap1,fmap2(大小为原图的1/8)
2. 上下文编码模块(Context Encoder)
-
仅对
image1进行编码 -
输出:
net: 初始 GRU hidden state(通过 tanh 激活)inp: 上下文特征输入(通过 ReLU 激活)
-
尺寸同样为原图的 1/8
3. 相关性模块(Correlation Volume)
-
全对相关性计算(所有像素对)
-
得到一个 6D 的相似度张量:
1
corr[b, h1, w1, 1, h2, w2]
-
构建金字塔(多尺度):avg pooling
4. 迭代更新模块(Update Block, GRU)
-
基于:当前 flow、corr、context 特征
-
输出:
delta_flow:光流增量up_mask:上采样时使用的权重
-
用于更新光流和输出最终的 flow map
🔁 二、整体前向推理流程(Forward Pass)
1 | def forward(image1, image2): |
Step 1: 预处理
- 像素归一化到 [-1, 1]
- 保证图像 shape 一致
Step 2: 特征提取
1 | fmap1, fmap2 = self.fnet([image1, image2]) |
- 提取低分辨率特征,用于匹配
Step 3: 上下文编码(只对 image1)
1 | cnet = self.cnet(image1) |
Step 4: 构建初始 flow 网格
1 | coords0, coords1 = self.initialize_flow(image1) |
- 两个坐标网格,表示初始光流为零
Step 5: 构建 CorrBlock(匹配模块)
1 | corr_fn = CorrBlock(fmap1, fmap2, radius, levels) |
- 提前构建金字塔,用于后续多尺度匹配
Step 6: 多次迭代更新光流
1 | for itr in range(iters): |
- 每次迭代做一次 flow refine
- 输出可以是多个 flow,用于多监督或测试时输出最后一次
🎯 三、训练目标(Loss Function)
RAFT 采用 多尺度 supervision,每一轮的 flow_pred 都参与损失计算:
1 | loss = sum(γ^i * L1(flow_pred[i], ground_truth)) # i 从 coarse 到 fine |
- γ 通常是 0.8,强调晚期的预测;
- L1 或 robust loss 可用;
- 遮挡区域可能被 mask 掉不参与训练。
🧠 四、关键技术要点
| 技术点 | 解释 |
|---|---|
| 全对相关性 | 比 PWCNet 更全面,精度高,代价是内存大 |
| 递归更新 | GRU 融合匹配信息 + 上下文特征,支持错误修复 |
| 上采样模块 | 利用可学习的 mask 做 convex combination,恢复高分辨率 flow |
| 参数共享 | update block 共享参数,每轮复用 |
🚀 五、RAFT 的优势
- 准确度高:在 Sintel、KITTI 等数据集上 SOTA(state-of-the-art)
- 端到端训练,结构清晰,收敛好;
- 鲁棒性强:遮挡/边界处仍有较好表现;
- 不依赖 coarse-to-fine 多分辨率输入(但内部有 multi-scale 匹配)。
📌 六、流程图(简略版)
1 | image1, image2 ─────────┐ |
✅ 总结一句话:
RAFT 把光流估计看作一个 learnable 优化问题,通过构建 dense correlation volume 和递归 GRU 网络,在固定参考帧基础上多次 refinement,精度高、鲁棒性强、结构简洁。
二、Context 特征的含义与作用
在 RAFT 中,context 特征(来自 image1) 的作用是:
🎯 目的:为光流更新提供像素级上下文信息,引导迭代过程。
✅ 1. 提供空间上下文(local context)
- context 特征是通过对 image1 进行 CNN 编码获得的;
- 它携带了每个像素周围区域的纹理、边缘、物体结构等信息;
- 对于纹理弱、遮挡、模糊等区域,corr 特征往往不足;
- context 特征弥补了这些信息盲区。
✅ 2. 作为 GRU 的外部输入 inp
-
在 RAFT 中,更新光流的
update_block是一个小型的 ConvGRU; -
它的输入包括:
- GRU 隐状态
net(从 context 初始化) inp:context 特征(从 image1 提取)- 当前 flow
- corr(当前 flow 对应的匹配信息)
- GRU 隐状态
-
其中
inp是 GRU 在每次迭代中接收的唯一不变输入,相当于一种长期语义记忆。
✅ 3. 稳定全流程,帮助光流估计聚焦
- 相关性(corr)是动态的,随 flow 变化;
- context 是静态的,从 image1 提取,一直保持不变;
- 它为每个像素提供“我要往哪看”的 语义和几何先验;
- 在早期 flow 粗糙、corr 不稳定时,context 提供稳固的支撑。
🧠 类比
把 context 特征看作是“这个像素大概是车轮/边缘/角落”等语义提示,
把 corr 特征看作是“你在图2可能对应这里/那里”等匹配信息,
光流迭代器(GRU)根据两者决定要不要、往哪儿动。
📊 视觉例子
如果你可视化:
- context 特征是图像中每个像素的一种高维语义描述;
- corr 特征是图1的每个像素和图2中可能匹配位置的相似度;
- GRU 会结合这两个信息,递归修正光流估计。
❌ 如果没有 context 特征会怎样?
- GRU 更新失去了一个稳固的语义支撑;
- 模型在复杂区域(遮挡、模糊)会失效;
- 实际实验中表现显著下降(RAFT ablation study 已验证)。
✅ 总结一句话:
Context 特征是对 image1 的高层语义编码,帮助光流估计在每个像素处理解“我是什么”,从而引导匹配与迭代过程。
三、为什么迭代是有效的以及算法是如何迭代的
🌊 RAFT 为什么要“迭代”?
想象你在做一件任务:
“给我两张图,告诉我:每个像素在第一张图的位置,跑到了第二张图的哪儿。”
这听起来像在找“像素对象”的移动轨迹 —— 这就是 光流(optical flow)。
💡 举个例子
假设你正在玩“大家来找茬”👇
🖼️ 图1:一个人站在路上
🖼️ 图2:同一个人向右走了一点点
你的任务是:
👉 对于图1中“这个人”的每个像素,找出他在图2中的位置。
🧠 如果你一次性做这个任务:
你可能凭直觉指一指,“我觉得他应该在这儿。”
✅ 有些地方能对上
❌ 但有些地方可能模糊、被遮住、背景重复、容易搞错。
🔁 RAFT 的做法就像“多轮纠正”
就像这样:
-
第一步:先大概找一下方向
“他大概往右上移动了这么多… 🤔”
-
第二步:仔细看看局部细节
“这个人有蓝衣服,我在右边看到了类似的蓝色区域… 👀”
-
第三步:结合我对图像的理解
“这应该是头,这个形状是肩膀,应该往左一点修正… 🔍”
-
继续迭代几轮:不断对比、调整
“现在越来越准了!👌”
🎨 可视化类比:
🎯 corr 是“你在哪看到过我?”
- 它告诉你:当前这个位置可能匹配图2的哪些区域
- 但它是动态变化的,因为 flow 在更新
🧠 context 是“我是谁,我什么样?”
- 比如“我是一个边缘上的小圆圈”或“我这有个斜线”
- 它来自图1,是静态的,对你自己身份的“描述”
🔄 GRU 是“决策大脑”
- 每次都综合 corr + context + 当前 flow,决定下一步怎么修正
- 你可以把它看作是反复判断“再往哪儿挪一点更合适?”
🔍 例子:如果你找不到小猫的头
- 第一步:你猜它在图2的右边
- corr 匹配了一些“圆形+耳朵”的区域
- context 告诉你:“这个像素的语义是‘毛发+边缘’”
- GRU 根据这些修正方向
- 下一步你看到更像小猫头的地方了,再修正一次
- 越来越准
🧠 总结(像人类怎么做):
| RAFT 模块 | 类比人类思考方式 |
|---|---|
| corr(匹配) | “我在图2哪里见过你?” |
| context(语义) | “你是什么?哦,你是一个耳朵。” |
| GRU 更新器 | “我觉得你应该稍微往右下偏一点…” |
| 迭代(多轮修正) | “一步步靠近目标,不断确认,不怕犯错” |



