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;}