云计算百科
云计算领域专业知识百科平台

VIO导航--第 03 篇 · 特征点提取与光流跟踪

· 本篇你将学到

  • Shi-Tomasi 角点判据与 Harris 的关系,以及 goodFeaturesToTrack 的作用;

  • KLT 金字塔光流的亮度恒定假设与 Lucas-Kanade 方程;

  • 正反向光流校验(FLOW_BACK)如何剔除外点;

  • Mask 掩膜法如何让特征在图像上均匀分布、且优先保留长寿命点;

  • trackImage 全流程精读,以及 featureFrame 7 维 [x,y,z,u,v,vx,vy] 的真实来源。


  • 一、前端视觉的作用与挑战

    VIO 后端优化的视觉残差,需要的是「同一个物理点在多帧间的观测对应」。前端(feature tracker)的职责就是建立这种对应:输入一串灰度图,输出每帧中若干特征点的归一化坐标与唯一 ID,让后端知道「第 5 帧的 37 号点」和「第 6 帧的 37 号点」是同一个世界点。

    挑战有三:①提取–哪些像素适合长期跟踪(角点优于平坦区域);②跟踪–已知上一帧角点,如何在当前帧找到对应(光流);③管理–特征要分布均匀、丢失要及时补充、外点要剔除。VINS 的前端用 Shi-Tomasi 角点 + KLT 光流 + Mask 均匀化三件套解决。

    架构差异提醒:原版 VINS-Mono 的前端入口叫 readImage(返回 void,featureFrame 在 ROS 层构建);VINS-Fusion 重构为 trackImage(feature_tracker.cpp:94),直接返回 featureFrame。本系列以 VINS-Fusion 代码为准,精读 trackImage。


    二、Shi-Tomasi 角点提取

    2.1 为什么是角点?

    特征跟踪依赖「邻域纹理」。平坦区域(如白墙)各方向梯度都小,无法定位;边缘(如桌沿)只有一个方向有梯度,沿边缘方向存在孔径问题(光流只能求出一个分量);只有角点(如墙角)两个方向都有强梯度,才能被唯一、稳定地跟踪。

    2.2 结构张量与角点响应

    对每个像素取邻域窗口,计算图像梯度 Ix,IyI_x, I_yIx,Iy,构造结构张量(二阶矩矩阵):

    M=∑(x,y)∈W[Ix2IxIyIxIyIy2]
    M = \\sum_{(x,y)\\in W} \\begin{bmatrix} I_x^2 & I_x I_y \\\\ I_x I_y & I_y^2 \\end{bmatrix}
    M=(x,y)W[Ix2IxIyIxIyIy2]

    MMM 的两个特征值 λ1,λ2\\lambda_1, \\lambda_2λ1,λ2 描述了窗口在两个主方向上的灰度变化强度:

    区域λ1,λ2\\lambda_1, \\lambda_2λ1,λ2含义
    平坦 都很小 无纹理,不可跟踪
    边缘 一大一小 单向梯度,孔径问题
    角点 都很大 双向梯度,可稳定跟踪

    Harris 角点响应为 R=λ1λ2−k(λ1+λ2)2R = \\lambda_1\\lambda_2 – k(\\lambda_1+\\lambda_2)^2R=λ1λ2k(λ1+λ2)2,依赖经验参数 kkk。Shi-Tomasi(即 goodFeaturesToTrack 用的 cv::GOODS_FROM_EIGEN)直接取更稳健的判据:

    R=min⁡(λ1,λ2)>τ
    R = \\min(\\lambda_1, \\lambda_2) > \\tau
    R=min(λ1,λ2)>τ

    无需调 kkk,且与「最小特征值越大越稳定」的直觉一致。代码里:

    // feature_tracker.cpp:184

    cv::goodFeaturesToTrack(cur_img, n_pts, MAX_CNT cur_pts.size(), 0.01, MIN_DIST, mask);

    参数依次:图像、输出点、最大数量(MAX_CNT=150 减去已跟踪数)、质量等级 0.01、点间最小距离 MIN_DIST=30、掩膜 mask。掩膜的作用下一节讲。


    三、KLT 稀疏光流跟踪

    3.1 亮度恒定假设

    Lucas-Kanade 光流的核心假设:同一个点在相邻帧间亮度不变,且邻域内所有点运动一致:

    I(x,y,t)=I(x+u,y+v,t+1)
    I(x, y, t) = I(x+u, y+v, t+1)
    I(x,y,t)=I(x+u,y+v,t+1)

    对右边做一阶泰勒展开:

    I(x+u,y+v,t+1)≈I(x,y,t)+Ixu+Iyv+It
    I(x+u,y+v,t+1) \\approx I(x,y,t) + I_x u + I_y v + I_t
    I(x+u,y+v,t+1)I(x,y,t)+Ixu+Iyv+It

    代入恒等式得 Lucas-Kanade 方程:

    [IxIy][uv]=−It
    \\begin{bmatrix} I_x & I_y \\end{bmatrix} \\begin{bmatrix} u \\\\ v \\end{bmatrix} = -I_t
    [IxIy][uv]=It

    邻域内 nnn 个点叠加(每个点一个方程),得到超定方程 Ad=bA\\mathbf{d} = \\mathbf{b}Ad=b,其中 d=[u,v]T\\mathbf{d}=[u,v]^Td=[u,v]TAAA 的每行是 [Ix,Iy][I_x, I_y][Ix,Iy],最小二乘解为:

    d=(ATA)−1ATb,ATA=∑[Ix2IxIyIxIyIy2]=M
    \\mathbf{d} = (A^T A)^{-1} A^T \\mathbf{b}, \\qquad A^T A = \\sum \\begin{bmatrix} I_x^2 & I_x I_y \\\\ I_x I_y & I_y^2 \\end{bmatrix} = M
    d=(ATA)1ATb,ATA=[Ix2IxIyIxIyIy2]=M

    注意 ATAA^TAATA 正是上一节的结构张量 MMM–角点处 MMM 可逆,光流可解;边缘/平坦处 MMM 奇异,光流不可靠。这就是「跟踪要选角点」的数学根因。

    3.2 图像金字塔:解决大位移

    单层光流假设位移小(一阶泰勒有效)。相机快速运动时特征位移几十像素,单层直接失败。金字塔光流(calcOpticalFlowPyrLK)从最粗层开始估计大位移,逐层细化到原图,把「大位移」拆成多次「小位移」。

    代码里第三个金字塔层数参数是关键:

    // feature_tracker.cpp:133 常规跟踪:3 层金字塔
    cv::calcOpticalFlowPyrLK(prev_img, cur_img, prev_pts, cur_pts, status, err, cv::Size(21,21), 3);

    窗口 21×21,金字塔 3 层。

    3.3 正反向校验:剔除外点

    光流可能跟丢或跟错(如运动模糊、遮挡)。VINS 用前向+反向双向光流校验(FLOW_BACK=1,yaml:41):

    // feature_tracker.cpp:135-151
    if(FLOW_BACK) {
    vector<cv::Point2f> reverse_pts = prev_pts;
    cv::calcOpticalFlowPyrLK(cur_img, prev_img, cur_pts, reverse_pts, ...); // 反向
    for(size_t i = 0; i < status.size(); i++)
    if(status[i] && reverse_status[i] && distance(prev_pts[i], reverse_pts[i]) <= 0.5)
    status[i] = 1; // 正向到当前帧,再反向回上一帧,若回到原位则可信
    else
    status[i] = 0; // 否则判为外点剔除
    }

    直觉:从 A 跟到 B,再从 B 反向跟回,若落在 A 附近(阈值 0.5 像素)则该匹配自洽可信。这是一个零成本的外点剔除技巧,比 RANSAC 轻量。

    代码细节:feature_tracker.cpp:169 的 rejectWithF()(基础矩阵 RANSAC)被注释掉了,VINS-Fusion 默认只靠双向光流剔点,不再做 F 矩阵 RANSAC。rejectWithF 函数体(:308)仍保留,需要时可打开。这是 VINS-Fusion 相对原版的简化。


    四、特征均匀化:Mask 掩膜法

    如果不加约束,goodFeaturesToTrack 会在纹理密集处扎堆提取,导致特征分布不均、某些区域无观测。VINS 用掩膜解决,且做得更聪明–优先保留跟踪寿命长的点。

    【函数】feature_tracker.cpp:55 void setMask()

    作用:构建掩膜,让新检测的角点既不与已有点过近,又优先保留长寿命点。

    核心代码:

    // feature_tracker.cpp:57

    mask = cv::Mat(row, col, CV_8UC1, cv::Scalar(255)); // 全 255=允许检测

    // :60-68 按 track_cnt 降序排列 (跟踪次数, (点坐标, id))

    vector<pair<int, pair<cv::Point2f, int>>> cnt_pts_id;

    for (unsigned int i = 0; i < cur_pts.size(); i++)

    cnt_pts_id.push_back(make_pair(track_cnt[i], make_pair(cur_pts[i], ids[i])));

    sort(cnt_pts_id.begin(), cnt_pts_id.end(),

    [](const auto &a, const auto &b){ return a.first > b.first; });

    // :70-83 清空后按寿命从长到短重新放回,每个点周围 MIN_DIST 范围内置 0

    cur_pts.clear(); ids.clear(); track_cnt.clear();

    for (auto &it : cnt_pts_id) {

    if (mask.at<uchar>(it.second.first) == 255) { // 该位置仍允许

    cur_pts.push_back(it.second.first);

    ids.push_back(it.second.second);

    track_cnt.push_back(it.first);

    cv::circle(mask, it.second.first, MIN_DIST, 0, 1); // 周围画黑,禁止再挤入

    }

    }

    逐行注释:

    • 掩膜初始全白(255),表示「全图允许检测新点」;

    • 按 track_cnt 降序排序是关键设计:跟踪次数多 = 寿命长 = 质量高,优先占位。寿命短的点若落在长寿命点的 MIN_DIST 邻域内,会被挤掉;

    • cv::circle(mask, pt, MIN_DIST, 0, -1) 在已保留点周围画半径 MIN_DIST=30 的黑圆(0=禁止),保证后续新点与之至少相距 30 像素,实现空间均匀。

    这样 goodFeaturesToTrack 传入该 mask 后,只会在「未被占用」的区域补新点,且补的点天然远离老点。


    五、代码精读:trackImage 全流程

    trackImage(feature_tracker.cpp:94)是前端唯一对外接口,输入一帧(双目可选第二帧)+ 时间戳,输出 featureFrame。整体流程如下:

    图 1 · trackImage 全流程

    前端唯一对外接口 trackImage 的端到端流程:从读入图像出发,经光流跟踪、反向校验剔外点、掩膜均匀化、补充新点,再到去畸变归一化、计算速度并构建 7 维 featureFrame 输出。

    #mermaid-svg-ATUGTkoJK9CZT3Dk{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ATUGTkoJK9CZT3Dk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ATUGTkoJK9CZT3Dk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ATUGTkoJK9CZT3Dk .error-icon{fill:#552222;}#mermaid-svg-ATUGTkoJK9CZT3Dk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ATUGTkoJK9CZT3Dk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ATUGTkoJK9CZT3Dk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ATUGTkoJK9CZT3Dk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ATUGTkoJK9CZT3Dk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ATUGTkoJK9CZT3Dk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ATUGTkoJK9CZT3Dk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ATUGTkoJK9CZT3Dk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ATUGTkoJK9CZT3Dk .marker.cross{stroke:#333333;}#mermaid-svg-ATUGTkoJK9CZT3Dk svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ATUGTkoJK9CZT3Dk p{margin:0;}#mermaid-svg-ATUGTkoJK9CZT3Dk .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-ATUGTkoJK9CZT3Dk .cluster-label text{fill:#333;}#mermaid-svg-ATUGTkoJK9CZT3Dk .cluster-label span{color:#333;}#mermaid-svg-ATUGTkoJK9CZT3Dk .cluster-label span p{background-color:transparent;}#mermaid-svg-ATUGTkoJK9CZT3Dk .label text,#mermaid-svg-ATUGTkoJK9CZT3Dk span{fill:#333;color:#333;}#mermaid-svg-ATUGTkoJK9CZT3Dk .node rect,#mermaid-svg-ATUGTkoJK9CZT3Dk .node circle,#mermaid-svg-ATUGTkoJK9CZT3Dk .node ellipse,#mermaid-svg-ATUGTkoJK9CZT3Dk .node polygon,#mermaid-svg-ATUGTkoJK9CZT3Dk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ATUGTkoJK9CZT3Dk .rough-node .label text,#mermaid-svg-ATUGTkoJK9CZT3Dk .node .label text,#mermaid-svg-ATUGTkoJK9CZT3Dk .image-shape .label,#mermaid-svg-ATUGTkoJK9CZT3Dk .icon-shape .label{text-anchor:middle;}#mermaid-svg-ATUGTkoJK9CZT3Dk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ATUGTkoJK9CZT3Dk .rough-node .label,#mermaid-svg-ATUGTkoJK9CZT3Dk .node .label,#mermaid-svg-ATUGTkoJK9CZT3Dk .image-shape .label,#mermaid-svg-ATUGTkoJK9CZT3Dk .icon-shape .label{text-align:center;}#mermaid-svg-ATUGTkoJK9CZT3Dk .node.clickable{cursor:pointer;}#mermaid-svg-ATUGTkoJK9CZT3Dk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ATUGTkoJK9CZT3Dk .arrowheadPath{fill:#333333;}#mermaid-svg-ATUGTkoJK9CZT3Dk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ATUGTkoJK9CZT3Dk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ATUGTkoJK9CZT3Dk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ATUGTkoJK9CZT3Dk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ATUGTkoJK9CZT3Dk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ATUGTkoJK9CZT3Dk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ATUGTkoJK9CZT3Dk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ATUGTkoJK9CZT3Dk .cluster text{fill:#333;}#mermaid-svg-ATUGTkoJK9CZT3Dk .cluster span{color:#333;}#mermaid-svg-ATUGTkoJK9CZT3Dk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ATUGTkoJK9CZT3Dk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ATUGTkoJK9CZT3Dk rect.text{fill:none;stroke-width:0;}#mermaid-svg-ATUGTkoJK9CZT3Dk .icon-shape,#mermaid-svg-ATUGTkoJK9CZT3Dk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ATUGTkoJK9CZT3Dk .icon-shape p,#mermaid-svg-ATUGTkoJK9CZT3Dk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ATUGTkoJK9CZT3Dk .icon-shape .label rect,#mermaid-svg-ATUGTkoJK9CZT3Dk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ATUGTkoJK9CZT3Dk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ATUGTkoJK9CZT3Dk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ATUGTkoJK9CZT3Dk :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-ATUGTkoJK9CZT3Dk .input>*{fill:#eef!important;stroke:#88f!important;}#mermaid-svg-ATUGTkoJK9CZT3Dk .input span{fill:#eef!important;stroke:#88f!important;}#mermaid-svg-ATUGTkoJK9CZT3Dk .branch>*{fill:#ffe!important;stroke:#fc6!important;}#mermaid-svg-ATUGTkoJK9CZT3Dk .branch span{fill:#ffe!important;stroke:#fc6!important;}#mermaid-svg-ATUGTkoJK9CZT3Dk .out>*{fill:#efe!important;stroke:#3c3!important;}#mermaid-svg-ATUGTkoJK9CZT3Dk .out span{fill:#efe!important;stroke:#3c3!important;}

    输入图像 + 时间戳

    IMU 预测?hasPrediction

    IMU 辅助预测1 层金字塔初值

    光流跟踪calcOpticalFlowPyrLK 3 层

    FLOW_BACK?

    正反向校验反向光流阈值 0.5

    剔除失败点/外点reduceVector 同步剔

    边界检查剔除越界点

    寿命加一track_cnt++

    掩膜均匀化 setMask按寿命降序 + MIN_DIST

    需补新点?

    补充新点goodFeaturesToTrack 分配 ID

    去畸变归一化undistortedPts / liftProjective

    计算归一化平面速度ptsVelocity

    构建特征帧 featureFrame7 维 x,y,z,u,v,vx,vy

    输出 featureFrame交给后端 processMeasurements

    5.1 跟踪与剔除(:112-162)

    // feature_tracker.cpp:164-165 跟踪成功者寿命 +1

    for (auto &n : track_cnt) n++;

    跟踪失败的点已在前面 reduceVector 中与 prev_pts/cur_pts/ids/track_cnt 同步剔除(:156-159)–四个 vector 用同一个 status,保证下标对齐。这是 VINS 前端管理多组并行数据的核心约定,后续 feature_manager 也沿用。

    5.2 补充新点(:167-197)

    // feature_tracker.cpp:177-195

    int n_max_cnt = MAX_CNT static_cast<int>(cur_pts.size());

    if (n_max_cnt > 0)

    cv::goodFeaturesToTrack(cur_img, n_pts, n_max_cnt, 0.01, MIN_DIST, mask);

    for (auto &p : n_pts) {

    cur_pts.push_back(p);

    ids.push_back(n_id++); // 新点分配全局唯一递增 id

    track_cnt.push_back(1); // 初始寿命=1

    }

    新点用 n_id++ 分配唯一 ID,这是特征在全生命周期的身份标识,后端靠它做数据关联。n_id 是 FeatureTracker 的成员(feature_tracker.h:82),单调递增不回收。

    5.3 IMU 辅助预测(进阶,:117-131)

    若 hasPrediction(后端优化后由 setPrediction 设置),前端用 IMU 预测的位姿把特征点投影到当前帧作为光流初值,仅用 1 层金字塔即可快速收敛;若预测成功数 <10(预测不可靠),降级回 3 层。这是 VIO「视觉-惯性耦合」在前端的体现–IMU 不是只在后端用,也提前帮视觉缩小搜索范围。该机制涉及后端 predictPtsInNextFrame,第 15 篇再展开。


    六、featureFrame 的 7 维含义

    我们说 featureFrame 装着「7 维观测」但没展开,现在补上。构建逻辑在 trackImage 末尾(:260-279):

    // feature_tracker.cpp:263-278

    double x, y, z;

    x = cur_un_pts[i].x; // 去畸变后的归一化坐标

    y = cur_un_pts[i].y;

    z = 1; // 归一化平面 z=1

    double p_u, p_v;

    p_u = cur_pts[i].x; // 原始像素坐标(含畸变)

    p_v = cur_pts[i].y;

    double velocity_x, velocity_y;

    velocity_x = pts_velocity[i].x;

    velocity_y = pts_velocity[i].y;

    Eigen::Matrix<double, 7, 1> xyz_uv_velocity;

    xyz_uv_velocity << x, y, z, p_u, p_v, velocity_x, velocity_y;

    featureFrame[feature_id].emplace_back(camera_id, xyz_uv_velocity);

    逐字段含义

    维度变量含义来源
    0-2 x, y, z 去畸变归一化相机坐标,z=1 undistortedPts
    3-4 p_u, p_v 原始像素坐标(含畸变) cur_pts
    5-6 velocity_x, velocity_y 归一化平面速度 ptsVelocity

    去畸变:undistortedPts(:391-402)

    // feature_tracker.cpp:396-399

    Eigen::Vector2d a(pts[i].x, pts[i].y); // 像素

    Eigen::Vector3d b;

    cam->liftProjective(a, b); // 像素 -> 归一化射线 (去畸变)

    un_pts.push_back(cv::Point2f(b.x() / b.z(), b.y() / b.z())); // 投影到 z=1 平面

    liftProjective 是相机模型(针孔/MEI)的逆投影:把带畸变的像素 (u,v)(u,v)(u,v) 还原成相机坐标系的归一化射线 (X/Z,Y/Z,1)(X/Z, Y/Z, 1)(X/Z,Y/Z,1)。后端视觉残差用的是这组归一化坐标(第 12 篇),因为它与相机内参解耦,几何意义干净。EuRoC 用 MEI 模型(cam0_mei.yaml),liftProjective 会按 MEI 的全向模型去畸变。

    速度:ptsVelocity(:404-442)–一个易误解点

    // feature_tracker.cpp:425-426

    double v_x = (pts[i].x it->second.x) / dt; // pts 是 cur_un_pts(归一化坐标!)

    double v_y = (pts[i].y it->second.y) / dt;

    注意 trackImage:200 传入 ptsVelocity 的是 cur_un_pts(归一化坐标),不是像素坐标。所以第 5、6 维虽常被称作「像素速度」,实际是归一化平面上的速度 Δ(x/z,y/z)/Δt\\Delta(x/z, y/z)/\\Delta tΔ(x/z,y/z)t。它用于 setPrediction/fastPredictIMU 的特征运动预测。后续篇以本篇为准。

    featureFrame 的外层结构是 map<feature_id, vector<pair<camera_id, 7维>>>:key 是特征 ID,value 是它在各相机的观测列表(单目只有 camera_id=0 一项,双目还有 camera_id=1)。这就是 featureBuf 里那串吓人模板的真实语义。


    小结

    本篇拆解了 VINS 前端三件套:

    • Shi-Tomasi 角点:结构张量特征值判据,goodFeaturesToTrack + mask 提取;

    • KLT 金字塔光流:亮度恒定假设下的 Lucas-Kanade 方程,金字塔解大位移,双向校验剔外点;

    • Mask 均匀化:按寿命排序优先保留长寿命点,MIN_DIST 圆保证空间均匀。

    trackImage 最终输出 featureFrame,每条观测是 7 维 [归一化x,y,z=1, 像素u,v, 归一化速度vx,vy],其中归一化坐标供后端视觉残差、像素速度供 IMU 辅助预测。下一帧到来时,这些带 ID 的特征会被送进 FeatureManager 判断是否构成关键帧。


    下一篇预告:第 04 篇《关键帧选择与特征点管理》–视差与时间阈值的关键帧判据、FeatureManager/FeaturePerId/FeaturePerFrame 的生命周期管理、逆深度参数化初探,精读 feature_manager.cpp 的 addFeatureCheckParallax。难度 ★★★☆☆。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » VIO导航--第 03 篇 · 特征点提取与光流跟踪
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!