use anyhow::Result;
use nalgebra::DMatrix;
use vision_calibration_core::{FxFyCxCySkew, Mat3, Real};
fn v_ij(hmtx: &Mat3, i: usize, j: usize) -> nalgebra::SVector<Real, 6> {
let hi = hmtx.column(i);
let hj = hmtx.column(j);
nalgebra::SVector::<Real, 6>::from_row_slice(&[
hi[0] * hj[0],
hi[0] * hj[1] + hi[1] * hj[0],
hi[1] * hj[1],
hi[2] * hj[0] + hi[0] * hj[2],
hi[2] * hj[1] + hi[1] * hj[2],
hi[2] * hj[2],
])
}
#[derive(Debug, Clone, Copy)]
pub struct PlanarIntrinsicsLinearInit;
pub fn estimate_intrinsics_from_homographies(hmtxs: &[Mat3]) -> Result<FxFyCxCySkew<Real>> {
PlanarIntrinsicsLinearInit::from_homographies(hmtxs)
}
impl PlanarIntrinsicsLinearInit {
pub fn from_homographies(hmtxs: &[Mat3]) -> Result<FxFyCxCySkew<Real>> {
if hmtxs.len() < 3 {
anyhow::bail!("need at least 3 homographies, got {}", hmtxs.len());
}
let m = hmtxs.len();
let mut vmtx = DMatrix::<Real>::zeros(2 * m, 6);
for (k, hmtx) in hmtxs.iter().enumerate() {
let v11 = v_ij(hmtx, 0, 0);
let v22 = v_ij(hmtx, 1, 1);
let v12 = v_ij(hmtx, 0, 1);
vmtx.row_mut(2 * k).copy_from(&v12.transpose());
vmtx.row_mut(2 * k + 1).copy_from(&(v11 - v22).transpose());
}
let svd = vmtx.svd(true, true);
let v_t = svd
.v_t
.ok_or_else(|| anyhow::anyhow!("svd failed during intrinsics estimation"))?;
let b = v_t.row(v_t.nrows() - 1);
let b11 = b[0];
let b12 = b[1];
let b22 = b[2];
let b13 = b[3];
let b23 = b[4];
let b33 = b[5];
let denom = b11 * b22 - b12 * b12;
let denom_norm = b11 * b11 + b22 * b22;
let denom_rel = if denom_norm > 0.0 {
denom.abs() / denom_norm
} else {
0.0
};
if denom_rel <= 1e-6 || b11.abs() <= 1e-12 {
anyhow::bail!("degenerate configuration for intrinsics estimation");
}
let v0 = (b12 * b13 - b11 * b23) / denom;
let lambda = b33 - (b13 * b13 + v0 * (b12 * b13 - b11 * b23)) / b11;
if lambda.signum() != b11.signum() {
anyhow::bail!("invalid sign for lambda; check homographies");
}
let alpha = (lambda / b11).sqrt();
let beta = (lambda * b11 / denom).sqrt();
let gamma = -b12 * alpha * alpha * beta / lambda;
let u0 = gamma * v0 / beta - b13 * alpha * alpha / lambda;
Ok(FxFyCxCySkew {
fx: alpha,
fy: beta,
cx: u0,
cy: v0,
skew: gamma,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use nalgebra::{Isometry3, Matrix3, Rotation3, Translation3, Vector3};
fn make_kmtx() -> (FxFyCxCySkew<Real>, Mat3) {
let intr = FxFyCxCySkew {
fx: 900.0,
fy: 880.0,
cx: 640.0,
cy: 360.0,
skew: 0.0,
};
let kmtx = Matrix3::new(
intr.fx, intr.skew, intr.cx, 0.0, intr.fy, intr.cy, 0.0, 0.0, 1.0,
);
(intr, kmtx)
}
fn synthetic_homography(kmtx: &Mat3, rot: Rotation3<Real>, t: Vector3<Real>) -> Mat3 {
let iso = Isometry3::from_parts(Translation3::from(t), rot.into());
let binding = iso.rotation.to_rotation_matrix();
let r_mat = binding.matrix();
let r1 = r_mat.column(0);
let r2 = r_mat.column(1);
let mut hmtx = Mat3::zeros();
hmtx.set_column(0, &(kmtx * r1));
hmtx.set_column(1, &(kmtx * r2));
hmtx.set_column(2, &(kmtx * t));
hmtx
}
#[test]
fn intrinsics_from_homographies_recovers_kmtx() {
let (intr_gt, kmtx) = make_kmtx();
let hmts: Vec<Mat3> = vec![
synthetic_homography(
&kmtx,
Rotation3::from_euler_angles(0.1, 0.0, 0.05),
Vector3::new(0.1, -0.05, 1.0),
),
synthetic_homography(
&kmtx,
Rotation3::from_euler_angles(-0.05, 0.15, -0.1),
Vector3::new(-0.05, 0.1, 1.2),
),
synthetic_homography(
&kmtx,
Rotation3::from_euler_angles(0.2, -0.1, 0.0),
Vector3::new(0.0, 0.0, 0.9),
),
];
let intr_est = estimate_intrinsics_from_homographies(&hmts).unwrap();
assert!((intr_est.fx - intr_gt.fx).abs() < 5.0, "fx mismatch");
assert!((intr_est.fy - intr_gt.fy).abs() < 5.0, "fy mismatch");
assert!((intr_est.cx - intr_gt.cx).abs() < 10.0, "cx mismatch");
assert!((intr_est.cy - intr_gt.cy).abs() < 10.0, "cy mismatch");
assert!(intr_est.skew.abs() < 1e-6, "skew not ~0: {}", intr_est.skew);
}
}