4 · 光流与直接法:LK光流跟踪参数调优、直接法光度误差模型、半直接法(SVO)的精度权衡
说到光流和直接法,很多刚入行的朋友容易把它们当成“黑盒子”。其实说白了,它们都是利用图像亮度来估计运动的手段,但脾气秉性完全不同。我最早接触LK光流是在一个无人机视觉里程计项目里,当时觉得“不就是像素追踪嘛”,结果参数没调好,飞了不到十米就漂得一塌糊涂……后来才明白,调参这件事,光靠默认值真的不行。
这一章咱们就掰开揉碎聊聊:LK光流怎么调参才能又稳又准?直接法的光度误差模型到底在优化什么?还有半直接法SVO,它凭什么能在精度和速度之间找到平衡?我会结合自己踩过的坑,给你一些实在的建议。
4.1 LK光流跟踪参数调优 —— 别让默认值坑了你
LK光流(Lucas-Kanade)假设像素邻域内运动一致,通过最小化灰度误差来求偏移。听起来简单,但实际调参时你会发现,窗口大小、金字塔层数、迭代终止条件……每一个都影响巨大。
- 窗口大小(winSize):我个人的习惯是,纹理丰富的场景用 21×21 或 31×31,弱纹理区域可以放大到 41×41。但窗口太大容易模糊运动边界,太小又容易跟丢。你想想看,如果窗口只有 5×5,稍微有点噪声就崩了。
- 金字塔层数(maxLevel):一般设 3~4 层。我曾经在快速运动场景只用了2层,结果光流直接发散。金字塔的本质是“由粗到精”,层数越多,对大位移的容忍度越高,但计算量也上去了。
- 迭代终止条件(criteria):我通常设最大迭代 30 次,精度 0.01。别小看这个,迭代太少收敛不稳,太多浪费时间。
💡 我的小技巧: 如果你用OpenCV的calcOpticalFlowPyrLK,记得把flags设成OPTFLOW_USE_INITIAL_FLOW,尤其是用预测值初始化的时候,收敛快很多。
// C++ 示例:LK光流参数配置(我常用的模板)
cv::TermCriteria criteria(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 30, 0.01);
cv::calcOpticalFlowPyrLK(prevImg, nextImg, prevPts, nextPts, status, err,
cv::Size(31, 31), 4, criteria,
cv::OPTFLOW_USE_INITIAL_FLOW);
4.2 直接法光度误差模型 —— 优化的是“像素差异”
直接法不提取特征,而是直接比较像素亮度。光度误差模型就是让两帧之间对应点的亮度差最小。公式很简单:e = I1(p) - I2(π(T·π⁻¹(p, d)))。但实际用起来,坑不少。
- 光度一致性假设:这玩意儿在光照变化时特别脆弱。我做过一个室内数据集,灯一关,误差直接爆炸。所以后来我加了曝光补偿参数(affine brightness),效果好了很多。
- 鲁棒核函数:Huber核是标配。我习惯把阈值设成 0.1~0.3(归一化后),太大就退化成最小二乘,太小又容易丢弃有效信息。
- 图像金字塔:直接法对初值极其敏感。没有金字塔,基本就是原地踏步。我一般用 4~5 层,从最顶层开始优化。
⚠️ 注意: 直接法对相机内参和畸变非常敏感。我曾经因为标定板没贴平,导致光度误差一直降不下去,折腾了两天才发现是标定问题。所以,先确保标定准确,再谈调参。
// 光度误差计算片段 (简化)
float computePhotometricError(const cv::Mat& I1, const cv::Mat& I2,
const Eigen::Vector2f& p1, const Eigen::Vector2f& p2) {
float diff = I1.at<uint8_t>(p1.y(), p1.x()) - I2.at<uint8_t>(p2.y(), p2.x());
return diff; // 实际会加鲁棒核
}
4.3 半直接法(SVO)的精度权衡
SVO(Semi-direct Visual Odometry)是个很有意思的方案。它结合了特征法和直接法的优点:用直接法跟踪特征点(稀疏图像对齐),再用特征法做位姿优化。说白了,就是“用直接法的速度,拿特征法的精度”。
- 稀疏图像对齐:SVO只对关键点周围的4×4 patch做直接法对齐,速度极快。我试过在嵌入式平台跑,能到 30fps 以上。
- 特征对齐 refinement:对齐之后,再用特征匹配(比如重投影误差)做一次优化,把漂移拉回来。这一步很关键,不然纯直接法容易飘。
- 精度权衡:SVO的精度介于纯直接法和特征法之间。如果你追求极致速度,可以降低refinement的频率;如果更看重精度,就多优化几次。我个人建议在快速运动场景下,refinement每帧都做,不然容易丢。
🚨 避坑指南: 我曾经在低纹理走廊里用SVO,结果稀疏图像对齐频繁失败。后来我增加了关键点数量(从100提到200),并且用了更小的patch(2×2),才勉强稳住。记住,半直接法对纹理分布很敏感,别盲目相信默认参数。
| 方法 | 精度 | 速度 | 鲁棒性 | 适用场景 |
|---|---|---|---|---|
| LK光流 (稀疏) | 中等 | 快 | 中等 | 纹理丰富、运动平滑 |
| 直接法 (稠密/半稠密) | 高 (光照稳定) | 中 | 低 (光照变化) | 光照恒定、计算资源足 |
| SVO (半直接) | 较高 | 很快 | 中等 | 嵌入式、实时场景 |
你看,没有银弹。每个方法都有自己的脾气。我个人的经验是:如果你做无人机或AR,SVO是很好的起点;如果做高精度建图,直接法+光度标定更靠谱;如果只是做视觉里程计,LK光流调好参数也能打。
📌 总结一下: LK光流调参记住“窗口、金字塔、迭代”三要素;直接法注意光度一致性和鲁棒核;SVO的权衡在于稀疏对齐和特征refinement的比例。多试试,找到适合你数据的组合。