Matcher::findEpipolarMatchDirect
函数逻辑与原理分析
核心目标:
在极线上搜索参考帧特征点 ref_ftr
在当前帧 cur_frame
中的最佳匹配点,并通过三角化计算深度。
关键步骤解析:
1. 极线端点计算:
const BearingVector A = T_cur_ref.getRotation().rotate(ref_ftr.f) + T_cur_ref.getPosition() * d_min_inv;
const BearingVector B = T_cur_ref.getRotation().rotate(ref_ftr.f) + T_cur_ref.getPosition() * d_max_inv;
cur_frame.cam()->project3(A, &px_A); // 投影到当前帧像素坐标
cur_frame.cam()->project3(B, &px_B);
epi_image_ = px_A - px_B; // 极线方向向量
- 原理:利用深度倒数范围
[d_min_inv, d_max_inv]
计算参考帧特征点对应的3D点在当前帧中的投影范围(px_A
,px_B
)。 - 作用:确定极线搜索区间。
2. 仿射扭曲矩阵计算:
warp::getWarpMatrixAffine(ref_frame.cam_, cur_frame.cam_, ref_ftr.px, ref_ftr.f,1.0 / d_estimate_inv, T_cur_ref, ref_ftr.level, &A_cur_ref_);
- 原理:根据两帧间位姿变换
T_cur_ref
和深度估计值,计算参考帧到当前帧的仿射变换矩阵A_cur_ref_
。 - 作用:补偿视角变化导致的图像变形。
3. 边缘特征方向过滤:
if (isEdgelet(ref_ftr.type) {const Eigen::Vector2d grad_cur = (A_cur_ref_ * ref_ftr.grad).normalized();const double cosangle = fabs(grad_cur.dot(epi_image_.normalized()));if (cosangle < options_.epi_search_edgelet_max_angle) return MatchResult::kFailAngle; // 边缘方向与极线夹角过大
}
- 原理:边缘特征的梯度方向应与极线方向一致(夹角小)。
- 作用:过滤掉梯度方向与极线方向不一致的边缘特征,提升匹配鲁棒性。
4. 图像金字塔预处理:
search_level_ = warp::getBestSearchLevel(A_cur_ref_, ref_frame.img_pyr_.size() - 1);
epi_length_pyramid_ = epi_image_.norm() / (1 << search_level_);
- 原理:根据仿射矩阵尺度选择最佳金字塔层级
search_level_
,并计算该层级的极线长度。 - 作用:在粗分辨率上快速搜索,减少计算量。
5. 参考图像块扭曲:
warp::warpAffine(A_cur_ref_, ref_frame.img_pyr_[ref_ftr.level], ...);
patch_utils::createPatchFromPatchWithBorder(...); // 提取参考图像块
- 原理:将参考帧中的图像块通过仿射变换
A_cur_ref_
扭曲到当前帧视角。 - 作用:生成用于匹配的模板图像块
patch_
。
6. 极线搜索策略:
- 情况1:极线过短(
epi_length_pyramid_ < 2.0
)px_cur_ = (px_A + px_B) / 2.0; // 取极线中点 findLocalMatch(cur_frame, epi_dir_image, search_level_, px_cur_); // 局部搜索
- 情况2:极线较长
PatchScore patch_score(patch_); // 预计算参考图像块评分 scanEpipolarLine(cur_frame, A, B, C, patch_score, ...); // 沿极线扫描
- 原理:
- 短极线:直接在极线中点附近局部搜索。
- 长极线:在极线上滑动参考图像块,通过 ZMSSD(零均值平方和差) 计算匹配得分,选择得分最高的位置。
7. 匹配验证与优化:
if (zmssd_best < PatchScore::threshold()) {if (options_.subpix_refinement) findLocalMatch(...); // 亚像素优化cur_frame.cam()->backProject3(px_cur_, &f_cur_); // 反投影到3DdepthFromTriangulation(T_cur_ref, ref_ftr.f, f_cur_, &depth); // 三角化深度
}
- 原理:
- 亚像素优化:在粗匹配位置附近进行高斯牛顿迭代,提升精度。
- 三角化:利用两帧间位姿和匹配点光线,求解3D点深度。
失败处理:
失败原因 | 返回值 | 触发条件 |
---|---|---|
边缘特征方向与极线夹角过大 | MatchResult::kFailAngle | 夹角余弦值小于阈值 epi_search_edgelet_max_angle |
图像块扭曲失败 | MatchResult::kFailWarp | warpAffine 返回失败 |
匹配得分不足 | MatchResult::kFailScore | 最佳 ZMSSD 得分超过阈值 |
关键算法与技巧:
- 仿射光流(Affine Warping)
- 补偿视角变化,使图像块在不同帧间保持形状一致。
- 金字塔搜索(Pyramid Search)
- 在低分辨率图像上快速定位,逐步细化到高分辨率。
- 极线约束(Epipolar Constraint)
- 将搜索范围从整幅图像压缩到一条直线,减少计算量。
- ZMSSD 匹配度量
- 对光照变化鲁棒:通过减去图像块均值,消除亮度差异影响。
- 亚像素优化(Subpixel Refinement)
- 通过二次拟合或高斯牛顿法,使匹配精度达到亚像素级别。
代码流程总结:
此函数实现了高效且鲁棒的直接法特征匹配,适用于视觉里程计(VO)和SLAM系统中的特征跟踪与深度估计。
Matcher::MatchResult Matcher::findEpipolarMatchDirect(const Frame& ref_frame,const Frame& cur_frame,const Transformation& T_cur_ref,const FeatureWrapper& ref_ftr,const double d_estimate_inv,const double d_min_inv,const double d_max_inv,double& depth){int zmssd_best = PatchScore::threshold();// Compute start and end of epipolar line in old_kf for match search, on image planeconst BearingVector A = T_cur_ref.getRotation().rotate(ref_ftr.f) + T_cur_ref.getPosition() * d_min_inv;const BearingVector B = T_cur_ref.getRotation().rotate(ref_ftr.f) + T_cur_ref.getPosition() * d_max_inv;Eigen::Vector2d px_A, px_B;cur_frame.cam()->project3(A, &px_A);cur_frame.cam()->project3(B, &px_B);epi_image_ = px_A - px_B;//LOG(INFO) << "A:" << A;//LOG(INFO) << "B:" << B;//LOG(INFO) << "px_A:" << px_A;//LOG(INFO) << "px_B:" << px_B;// Compute affine warp matrixwarp::getWarpMatrixAffine(ref_frame.cam_, cur_frame.cam_, ref_ftr.px, ref_ftr.f,1.0 / std::max(0.000001, d_estimate_inv), T_cur_ref, ref_ftr.level, &A_cur_ref_);// feature pre-selectionreject_ = false;if (isEdgelet(ref_ftr.type) && options_.epi_search_edgelet_filtering){const Eigen::Vector2d grad_cur = (A_cur_ref_ * ref_ftr.grad).normalized();const double cosangle = fabs(grad_cur.dot(epi_image_.normalized()));if (cosangle < options_.epi_search_edgelet_max_angle){reject_ = true;return MatchResult::kFailAngle;}}//LOG(INFO) << "A_cur_ref_: " << A_cur_ref_;// prepare for match// - find best search level// - warp the reference patchsearch_level_ = warp::getBestSearchLevel(A_cur_ref_, ref_frame.img_pyr_.size() - 1);// length and direction on SEARCH LEVELepi_length_pyramid_ = epi_image_.norm() / (1 << search_level_);GradientVector epi_dir_image = epi_image_.normalized();if (!warp::warpAffine(A_cur_ref_, ref_frame.img_pyr_[ref_ftr.level], ref_ftr.px,ref_ftr.level, search_level_, kHalfPatchSize + 1, patch_with_border_))return MatchResult::kFailWarp;patch_utils::createPatchFromPatchWithBorder(patch_with_border_, kPatchSize, patch_);// Case 1: direct search locally if the epipolar line is too shortif (epi_length_pyramid_ < 2.0){px_cur_ = (px_A + px_B) / 2.0;MatchResult res = findLocalMatch(cur_frame, epi_dir_image, search_level_, px_cur_);if (res != MatchResult::kSuccess)return res;cur_frame.cam()->backProject3(px_cur_, &f_cur_);f_cur_.normalize();return matcher_utils::depthFromTriangulation(T_cur_ref, ref_ftr.f, f_cur_, &depth);}// Case 2: search along the epipolar line for the best matchPatchScore patch_score(patch_); // precompute for reference patchBearingVector C = T_cur_ref.getRotation().rotate(ref_ftr.f) + T_cur_ref.getPosition() * d_estimate_inv;//LOG(INFO) << "C: " << C;//LOG(INFO) << "px_cur_: " << std::setprecision(15) << px_cur_.transpose();scanEpipolarLine(cur_frame, A, B, C, patch_score, search_level_, &px_cur_, &zmssd_best);//LOG(INFO) << "zmssd_best: " << zmssd_best;// check if the best match is good enoughif (zmssd_best < PatchScore::threshold()){if (options_.subpix_refinement){MatchResult res = findLocalMatch(cur_frame, epi_dir_image, search_level_, px_cur_);if (res != MatchResult::kSuccess)return res;}//LOG(INFO) << "BACK PROJECT";cur_frame.cam()->backProject3(px_cur_, &f_cur_);f_cur_.normalize();//LOG(INFO) << "f_cur_ NORM: " <<std::setprecision(15)<< f_cur_.x() <<" " << f_cur_.y() << " " << f_cur_.z();//LOG(INFO) << "T_cur_ref: \n" << std::setprecision(15) << T_cur_ref;//LOG(INFO) << "ref_ftr.f: " << std::setprecision(15) << ref_ftr.f.transpose();return matcher_utils::depthFromTriangulation(T_cur_ref, ref_ftr.f, f_cur_, &depth);}elsereturn MatchResult::kFailScore;}