///|
pub(all) struct CalibrationReport {
observations : Int
cost : Double
rms : Double
max_error : Double
valid : Bool
} derive(Debug, Eq)
///|
pub fn calibration_report(
observations : ArrayView[CalibrationObservation],
intrinsics : CameraIntrinsics,
pose : CameraPose,
distortion? : Distortion = Distortion::none(),
) -> CalibrationReport raise @core.GeometryError {
let errors : Array[Double] = []
for observation in observations {
errors.push(observation.residual(intrinsics, pose, distortion~).norm())
}
let cost = if errors.length() == 0 {
0.0
} else {
let mut sum = 0.0
for e in errors {
sum += e * e
}
sum / Double::from_int(errors.length())
}
let rms = if errors.length() == 0 { 0.0 } else { @core.rms_error(errors) }
let maximum = if errors.length() == 0 { 0.0 } else { @core.max_error(errors) }
{
observations: observations.length(),
cost,
rms,
max_error: maximum,
valid: observations.length() > 0 && rms < 5.0,
}
}
///|
pub fn estimate_focal_from_depth(
points : ArrayView[@core.Point3],
pixels : ArrayView[@core.Point2],
principal : @core.Point2,
) -> CameraIntrinsics raise @core.GeometryError {
if points.length() != pixels.length() || points.length() == 0 {
raise @core.GeometryError::NotEnoughPoints(
"focal estimate needs paired points",
)
}
let mut numerator_x = 0.0
let mut denominator_x = 0.0
let mut numerator_y = 0.0
let mut denominator_y = 0.0
for i in 0.. Bool {
pixel_in_bounds(size, observation.pixel)
}
///|
pub fn valid_observations(
observations : ArrayView[CalibrationObservation],
size : ImageSize,
) -> Array[CalibrationObservation] {
let result : Array[CalibrationObservation] = []
for observation in observations {
if observation_in_bounds(observation, size) && observation.weight > 0.0 {
result.push(observation)
}
}
result
}
///|
pub fn weighted_reprojection_cost(
observations : ArrayView[CalibrationObservation],
intrinsics : CameraIntrinsics,
pose : CameraPose,
) -> Double raise @core.GeometryError {
let mut cost = 0.0
let mut weights = 0.0
for observation in observations {
let r = observation.residual(intrinsics, pose)
cost += observation.weight * r.norm2()
weights += observation.weight
}
if weights <= 0.0 {
raise @core.GeometryError::DegenerateInput("weights must be positive")
}
cost / weights
}
///|
pub fn calibration_is_usable(
report : CalibrationReport,
max_rms? : Double = 2.0,
) -> Bool {
report.valid && report.rms <= max_rms
}