use crate::core::{
Camera, CameraPose, Point2, Point3, Vector3, Result, ColmapError
};
use nalgebra::{Matrix4, SVD};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TriangulationMethod {
Linear,
Iterative,
Midpoint,
}
#[derive(Debug, Clone)]
pub struct TriangulationConfig {
pub method: TriangulationMethod,
pub min_angle: f64,
pub max_reprojection_error: f64,
pub max_iterations: usize,
pub convergence_threshold: f64,
}
impl Default for TriangulationConfig {
fn default() -> Self {
Self {
method: TriangulationMethod::Linear,
min_angle: 1.0_f64.to_radians(),
max_reprojection_error: 4.0,
max_iterations: 100,
convergence_threshold: 1e-6,
}
}
}
#[derive(Debug, Clone)]
pub struct TriangulationResult {
pub point: Point3,
pub success: bool,
pub reprojection_error: f64,
pub triangulation_angle: f64,
}
#[derive(Debug)]
pub struct Triangulator {
config: TriangulationConfig,
}
impl Triangulator {
pub fn new(config: TriangulationConfig) -> Self {
Self { config }
}
pub fn default() -> Self {
Self::new(TriangulationConfig::default())
}
pub fn triangulate_two_view(
&self,
camera1: &Camera,
pose1: &CameraPose,
point1: &Point2,
camera2: &Camera,
pose2: &CameraPose,
point2: &Point2,
) -> Result<TriangulationResult> {
let angle = self.compute_triangulation_angle(
camera1, pose1, point1,
camera2, pose2, point2,
)?;
if angle < self.config.min_angle {
return Ok(TriangulationResult {
point: Point3::new(0.0, 0.0, 0.0),
success: false,
reprojection_error: f64::INFINITY,
triangulation_angle: angle,
});
}
let point = match self.config.method {
TriangulationMethod::Linear => {
self.triangulate_linear(camera1, pose1, point1, camera2, pose2, point2)?
},
TriangulationMethod::Iterative => {
self.triangulate_iterative(camera1, pose1, point1, camera2, pose2, point2)?
},
TriangulationMethod::Midpoint => {
self.triangulate_midpoint(camera1, pose1, point1, camera2, pose2, point2)?
},
};
let error1 = self.compute_reprojection_error(camera1, pose1, &point, point1)?;
let error2 = self.compute_reprojection_error(camera2, pose2, &point, point2)?;
let reprojection_error = (error1 + error2) / 2.0;
let success = reprojection_error < self.config.max_reprojection_error;
Ok(TriangulationResult {
point,
success,
reprojection_error,
triangulation_angle: angle,
})
}
pub fn triangulate_multi_view(
&self,
observations: &[(Camera, CameraPose, Point2)],
) -> Result<TriangulationResult> {
if observations.len() < 2 {
return Err(ColmapError::Triangulation(
"至少需要两个观测点进行三角化".to_string()
));
}
let (camera1, pose1, point1) = &observations[0];
let (camera2, pose2, point2) = &observations[1];
let mut result = self.triangulate_two_view(
camera1, pose1, point1,
camera2, pose2, point2,
)?;
if !result.success {
return Ok(result);
}
if observations.len() > 2 {
result.point = self.refine_point_multi_view(&result.point, observations)?;
let mut total_error = 0.0;
for (camera, pose, point) in observations {
let error = self.compute_reprojection_error(camera, pose, &result.point, point)?;
total_error += error;
}
result.reprojection_error = total_error / observations.len() as f64;
result.success = result.reprojection_error < self.config.max_reprojection_error;
}
Ok(result)
}
fn triangulate_linear(
&self,
camera1: &Camera,
pose1: &CameraPose,
point1: &Point2,
camera2: &Camera,
pose2: &CameraPose,
point2: &Point2,
) -> Result<Point3> {
let p1 = self.build_projection_matrix(camera1, pose1)?;
let p2 = self.build_projection_matrix(camera2, pose2)?;
let norm1 = camera1.image_to_normalized(point1)?;
let norm2 = camera2.image_to_normalized(point2)?;
let mut a = Matrix4::zeros();
a.set_row(0, &(norm1.x * p1.row(2) - p1.row(0)));
a.set_row(1, &(norm1.y * p1.row(2) - p1.row(1)));
a.set_row(2, &(norm2.x * p2.row(2) - p2.row(0)));
a.set_row(3, &(norm2.y * p2.row(2) - p2.row(1)));
let svd = SVD::new(a, true, true);
let v = svd.v_t.ok_or_else(|| ColmapError::Triangulation(
"SVD分解失败".to_string()
))?;
let solution = v.row(3);
if solution[3].abs() < 1e-10 {
return Err(ColmapError::Triangulation(
"齐次坐标的w分量接近零".to_string()
));
}
Ok(Point3::new(
solution[0] / solution[3],
solution[1] / solution[3],
solution[2] / solution[3],
))
}
fn triangulate_iterative(
&self,
camera1: &Camera,
pose1: &CameraPose,
point1: &Point2,
camera2: &Camera,
pose2: &CameraPose,
point2: &Point2,
) -> Result<Point3> {
let mut point = self.triangulate_linear(camera1, pose1, point1, camera2, pose2, point2)?;
for _ in 0..self.config.max_iterations {
let old_point = point;
let (jacobian, residual) = self.compute_jacobian_and_residual(
&point,
&[(camera1.clone(), pose1.clone(), *point1),
(camera2.clone(), pose2.clone(), *point2)],
)?;
let jtj = jacobian.transpose() * &jacobian;
let jtr = jacobian.transpose() * residual;
let svd = SVD::new(jtj, true, true);
let delta = svd.solve(&jtr, 1e-10).map_err(|_| ColmapError::Triangulation(
"无法求解线性系统".to_string()
))?;
point.coords += delta;
if (point - old_point).norm() < self.config.convergence_threshold {
break;
}
}
Ok(point)
}
fn triangulate_midpoint(
&self,
camera1: &Camera,
pose1: &CameraPose,
point1: &Point2,
camera2: &Camera,
pose2: &CameraPose,
point2: &Point2,
) -> Result<Point3> {
let center1 = pose1.translation;
let center2 = pose2.translation;
let ray1 = self.compute_ray_direction(camera1, pose1, point1)?;
let ray2 = self.compute_ray_direction(camera2, pose2, point2)?;
let center1_point = Point3::new(center1.x, center1.y, center1.z);
let center2_point = Point3::new(center2.x, center2.y, center2.z);
let (t1, t2) = self.compute_closest_points_on_rays(¢er1_point, &ray1, ¢er2_point, &ray2)?;
let point1_on_ray = center1 + t1 * ray1;
let point2_on_ray = center2 + t2 * ray2;
let midpoint = (point1_on_ray + point2_on_ray) / 2.0;
Ok(Point3::new(midpoint.x, midpoint.y, midpoint.z))
}
fn compute_triangulation_angle(
&self,
camera1: &Camera,
pose1: &CameraPose,
point1: &Point2,
camera2: &Camera,
pose2: &CameraPose,
point2: &Point2,
) -> Result<f64> {
let _center1 = pose1.translation;
let _center2 = pose2.translation;
let ray1 = self.compute_ray_direction(camera1, pose1, point1)?;
let ray2 = self.compute_ray_direction(camera2, pose2, point2)?;
let cos_angle = ray1.dot(&ray2) / (ray1.norm() * ray2.norm());
Ok(cos_angle.acos())
}
fn compute_ray_direction(
&self,
camera: &Camera,
pose: &CameraPose,
point: &Point2,
) -> Result<Vector3> {
let normalized = camera.image_to_normalized(point)?;
let ray_camera = Vector3::new(normalized.x, normalized.y, 1.0).normalize();
Ok(pose.rotation * ray_camera)
}
fn compute_closest_points_on_rays(
&self,
origin1: &Point3,
dir1: &Vector3,
origin2: &Point3,
dir2: &Vector3,
) -> Result<(f64, f64)> {
let w = origin1 - origin2;
let a = dir1.dot(dir1);
let b = dir1.dot(dir2);
let c = dir2.dot(dir2);
let d = dir1.dot(&w);
let e = dir2.dot(&w);
let denom = a * c - b * b;
if denom.abs() < 1e-10 {
return Err(ColmapError::Triangulation(
"射线平行,无法三角化".to_string()
));
}
let t1 = (b * e - c * d) / denom;
let t2 = (a * e - b * d) / denom;
Ok((t1, t2))
}
fn build_projection_matrix(&self, camera: &Camera, pose: &CameraPose) -> Result<nalgebra::Matrix3x4<f64>> {
let k = camera.calibration_matrix();
let rt = pose.to_matrix3x4();
Ok(k * rt)
}
fn compute_reprojection_error(
&self,
camera: &Camera,
pose: &CameraPose,
point3d: &Point3,
observed: &Point2,
) -> Result<f64> {
let mut temp_camera = camera.clone();
temp_camera.set_pose(pose.clone());
let projected = temp_camera.world_to_image(point3d)?;
let dx = projected.x - observed.x;
let dy = projected.y - observed.y;
Ok((dx * dx + dy * dy).sqrt())
}
fn refine_point_multi_view(
&self,
initial_point: &Point3,
observations: &[(Camera, CameraPose, Point2)],
) -> Result<Point3> {
let mut point = *initial_point;
for _ in 0..self.config.max_iterations {
let old_point = point;
let (jacobian, residual) = self.compute_jacobian_and_residual(&point, observations)?;
let jtj = jacobian.transpose() * &jacobian;
let jtr = jacobian.transpose() * residual;
let svd = SVD::new(jtj, true, true);
let delta = svd.solve(&jtr, 1e-10).map_err(|_| ColmapError::Triangulation(
"无法求解线性系统".to_string()
))?;
point.coords += delta;
if (point - old_point).norm() < self.config.convergence_threshold {
break;
}
}
Ok(point)
}
fn compute_jacobian_and_residual(
&self,
point: &Point3,
observations: &[(Camera, CameraPose, Point2)],
) -> Result<(nalgebra::DMatrix<f64>, nalgebra::DVector<f64>)> {
let num_observations = observations.len();
let mut jacobian = nalgebra::DMatrix::zeros(2 * num_observations, 3);
let mut residual = nalgebra::DVector::zeros(2 * num_observations);
for (i, (camera, pose, observed)) in observations.iter().enumerate() {
let mut temp_camera = camera.clone();
temp_camera.set_pose(pose.clone());
let projected = temp_camera.world_to_image(point)?;
residual[2 * i] = projected.x - observed.x;
residual[2 * i + 1] = projected.y - observed.y;
let eps = 1e-6;
for j in 0..3 {
let mut point_plus = *point;
point_plus[j] += eps;
let mut temp_camera = camera.clone();
temp_camera.set_pose(pose.clone());
let projected_plus = temp_camera.world_to_image(&point_plus)?;
jacobian[(2 * i, j)] = (projected_plus.x - projected.x) / eps;
jacobian[(2 * i + 1, j)] = (projected_plus.y - projected.y) / eps;
}
}
Ok((jacobian, residual))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_triangulator_creation() {
let triangulator = Triangulator::default();
assert_eq!(triangulator.config.method, TriangulationMethod::Linear);
}
#[test]
fn test_triangulation_config() {
let config = TriangulationConfig {
method: TriangulationMethod::Iterative,
min_angle: 2.0_f64.to_radians(),
max_reprojection_error: 2.0,
max_iterations: 50,
convergence_threshold: 1e-8,
};
let triangulator = Triangulator::new(config);
assert_eq!(triangulator.config.method, TriangulationMethod::Iterative);
assert_eq!(triangulator.config.max_iterations, 50);
}
}