///|
/// Result of RANSAC Homography estimation.
pub(all) struct RansacHomographyResult {
homography : Matrix3x3
inliers : Array[Bool]
inlier_count : Int
reprojection_error : Double
} derive(Debug, Eq)
///|
/// Computes symmetric transfer error for a point correspondence under homography H and H^(-1).
pub fn symmetric_transfer_error(
h : Matrix3x3,
h_inv : Matrix3x3,
src : Point2D,
dst : Point2D,
) -> Double {
let dst_est = h.transform_point2d(src)
let src_est = h_inv.transform_point2d(dst)
let err1 = dst.distance_sq(dst_est)
let err2 = src.distance_sq(src_est)
(err1 + err2) * 0.5
}
///|
/// Robust Homography estimation using RANSAC (Random Sample Consensus) algorithm.
pub fn find_homography_ransac(
src_pts : Array[Point2D],
dst_pts : Array[Point2D],
max_iterations? : Int = 1000,
inlier_threshold? : Double = 3.0,
_confidence? : Double = 0.99,
seed? : Int = 42,
) -> RansacHomographyResult? {
let n = src_pts.length()
if n < 4 || dst_pts.length() != n {
return None
}
let thresh_sq = inlier_threshold * inlier_threshold
let mut rng_state = seed.to_int64()
let next_rand = fn() -> Int {
rng_state = rng_state * 6364136223846793005L + 1442695040888963407L
let val = if rng_state < 0L { -rng_state } else { rng_state }
(val % n.to_int64()).to_int()
}
let mut best_inlier_count = 0
let mut best_inliers = Array::make(n, false)
let mut best_h = Matrix3x3::identity()
let mut best_error = 1.0e30
let mut iters = 0
let max_iters = max_iterations
while iters < max_iters {
iters += 1
// Randomly sample 4 unique indices
let idxs = Array::make(4, 0)
idxs[0] = next_rand()
let mut count = 1
while count < 4 {
let r = next_rand()
let mut duplicate = false
for i in 0..
match h.inverse() {
Some(h_inv) => {
let inliers = Array::make(n, false)
let mut inlier_cnt = 0
let mut total_err = 0.0
for i in 0.. best_inlier_count ||
(inlier_cnt == best_inlier_count && total_err < best_error) {
best_inlier_count = inlier_cnt
best_inliers = inliers
best_h = h
best_error = total_err
}
// Adaptive early termination if sufficiently high inlier ratio
let inlier_ratio = inlier_cnt.to_double() / n.to_double()
if inlier_ratio > 0.85 {
break // Early convergence on high inlier consensus
}
}
None => ()
}
None => ()
}
}
if best_inlier_count >= 4 {
// Re-estimate Homography on all inliers using DLT
let inlier_src : Array[Point2D] = []
let inlier_dst : Array[Point2D] = []
for i in 0.. h
None => best_h
}
let avg_err = if best_inlier_count > 0 {
best_error / best_inlier_count.to_double()
} else {
0.0
}
Some({
homography: final_h,
inliers: best_inliers,
inlier_count: best_inlier_count,
reprojection_error: avg_err,
})
} else {
None
}
}