pub mod calibration;
pub mod ecc;
pub mod feature_based;
pub mod gyro_fusion;
pub mod optical_flow;
pub mod panorama;
pub mod phase_correlation;
pub mod stabilization;
pub mod warp;
pub use gyro_fusion::{CameraIntrinsics, GyroFusionFilter, GyroSample};
use crate::error::{CvError, CvResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RegistrationMethod {
FeatureBased,
PhaseCorrelation,
OpticalFlow,
Ecc,
Hybrid,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransformationType {
Translation,
Euclidean,
Affine,
Homography,
}
#[derive(Debug, Clone)]
pub struct RegistrationQuality {
pub success: bool,
pub rmse: f64,
pub inliers: usize,
pub confidence: f64,
pub iterations: usize,
}
impl RegistrationQuality {
#[must_use]
pub const fn new() -> Self {
Self {
success: false,
rmse: 0.0,
inliers: 0,
confidence: 0.0,
iterations: 0,
}
}
#[must_use]
pub fn is_good(&self) -> bool {
self.success && self.confidence > 0.7 && self.rmse < 1.0
}
}
impl Default for RegistrationQuality {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct TransformMatrix {
pub data: [f64; 9],
pub transform_type: TransformationType,
}
impl TransformMatrix {
#[must_use]
pub const fn identity() -> Self {
Self {
data: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
transform_type: TransformationType::Affine,
}
}
#[must_use]
pub fn translation(tx: f64, ty: f64) -> Self {
Self {
data: [1.0, 0.0, tx, 0.0, 1.0, ty, 0.0, 0.0, 1.0],
transform_type: TransformationType::Translation,
}
}
#[must_use]
pub fn rotation(angle: f64) -> Self {
let cos_a = angle.cos();
let sin_a = angle.sin();
Self {
data: [cos_a, -sin_a, 0.0, sin_a, cos_a, 0.0, 0.0, 0.0, 1.0],
transform_type: TransformationType::Euclidean,
}
}
#[must_use]
pub fn scale(sx: f64, sy: f64) -> Self {
Self {
data: [sx, 0.0, 0.0, 0.0, sy, 0.0, 0.0, 0.0, 1.0],
transform_type: TransformationType::Affine,
}
}
#[must_use]
pub fn transform_point(&self, x: f64, y: f64) -> (f64, f64) {
let w = self.data[6] * x + self.data[7] * y + self.data[8];
if w.abs() < f64::EPSILON {
return (x, y);
}
let x_new = (self.data[0] * x + self.data[1] * y + self.data[2]) / w;
let y_new = (self.data[3] * x + self.data[4] * y + self.data[5]) / w;
(x_new, y_new)
}
#[must_use]
pub fn compose(&self, other: &TransformMatrix) -> Self {
let mut result = [0.0; 9];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
result[i * 3 + j] += self.data[i * 3 + k] * other.data[k * 3 + j];
}
}
}
Self {
data: result,
transform_type: TransformationType::Homography,
}
}
pub fn inverse(&self) -> CvResult<Self> {
let det = self.determinant();
if det.abs() < f64::EPSILON {
return Err(CvError::matrix_error("singular matrix, cannot invert"));
}
let inv_det = 1.0 / det;
let a = self.data[0];
let b = self.data[1];
let c = self.data[2];
let d = self.data[3];
let e = self.data[4];
let f = self.data[5];
let g = self.data[6];
let h = self.data[7];
let i = self.data[8];
let mut inv = [0.0; 9];
inv[0] = (e * i - f * h) * inv_det;
inv[1] = (c * h - b * i) * inv_det;
inv[2] = (b * f - c * e) * inv_det;
inv[3] = (f * g - d * i) * inv_det;
inv[4] = (a * i - c * g) * inv_det;
inv[5] = (c * d - a * f) * inv_det;
inv[6] = (d * h - e * g) * inv_det;
inv[7] = (b * g - a * h) * inv_det;
inv[8] = (a * e - b * d) * inv_det;
Ok(Self {
data: inv,
transform_type: self.transform_type,
})
}
#[must_use]
pub fn determinant(&self) -> f64 {
let a = self.data[0];
let b = self.data[1];
let c = self.data[2];
let d = self.data[3];
let e = self.data[4];
let f = self.data[5];
let g = self.data[6];
let h = self.data[7];
let i = self.data[8];
a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g)
}
#[must_use]
pub fn get_translation(&self) -> (f64, f64) {
(self.data[2], self.data[5])
}
#[must_use]
pub fn get_rotation(&self) -> f64 {
self.data[3].atan2(self.data[0])
}
#[must_use]
pub fn get_scale(&self) -> (f64, f64) {
let sx = (self.data[0] * self.data[0] + self.data[3] * self.data[3]).sqrt();
let sy = (self.data[1] * self.data[1] + self.data[4] * self.data[4]).sqrt();
(sx, sy)
}
}
#[derive(Debug, Clone)]
pub struct VideoRegistration {
method: RegistrationMethod,
max_iterations: usize,
convergence_threshold: f64,
use_pyramid: bool,
pyramid_levels: usize,
}
impl VideoRegistration {
#[must_use]
pub const fn new(method: RegistrationMethod) -> Self {
Self {
method,
max_iterations: 100,
convergence_threshold: 1e-6,
use_pyramid: true,
pyramid_levels: 3,
}
}
#[must_use]
pub const fn with_max_iterations(mut self, iterations: usize) -> Self {
self.max_iterations = iterations;
self
}
#[must_use]
pub const fn with_convergence_threshold(mut self, threshold: f64) -> Self {
self.convergence_threshold = threshold;
self
}
#[must_use]
pub const fn with_pyramid(mut self, enabled: bool, levels: usize) -> Self {
self.use_pyramid = enabled;
self.pyramid_levels = levels;
self
}
#[allow(clippy::too_many_arguments)]
pub fn register(
&self,
reference: &[u8],
target: &[u8],
width: u32,
height: u32,
transform_type: TransformationType,
) -> CvResult<(TransformMatrix, RegistrationQuality)> {
if width == 0 || height == 0 {
return Err(CvError::invalid_dimensions(width, height));
}
let size = width as usize * height as usize;
if reference.len() < size || target.len() < size {
return Err(CvError::insufficient_data(
size,
reference.len().min(target.len()),
));
}
match self.method {
RegistrationMethod::FeatureBased => feature_based::register_feature_based(
reference,
target,
width,
height,
transform_type,
),
RegistrationMethod::PhaseCorrelation => {
phase_correlation::register_phase_correlation(reference, target, width, height)
}
RegistrationMethod::OpticalFlow => optical_flow::register_optical_flow(
reference,
target,
width,
height,
transform_type,
),
RegistrationMethod::Ecc => ecc::register_ecc(
reference,
target,
width,
height,
transform_type,
self.max_iterations,
self.convergence_threshold,
),
RegistrationMethod::Hybrid => {
self.register_hybrid(reference, target, width, height, transform_type)
}
}
}
fn register_hybrid(
&self,
reference: &[u8],
target: &[u8],
width: u32,
height: u32,
transform_type: TransformationType,
) -> CvResult<(TransformMatrix, RegistrationQuality)> {
let (init_transform, phase_quality) =
phase_correlation::register_phase_correlation(reference, target, width, height)?;
if phase_quality.confidence > 0.5 {
let (final_transform, ecc_quality) = ecc::register_ecc(
reference,
target,
width,
height,
transform_type,
self.max_iterations,
self.convergence_threshold,
)?;
let composed = init_transform.compose(&final_transform);
Ok((composed, ecc_quality))
} else {
feature_based::register_feature_based(reference, target, width, height, transform_type)
}
}
}
impl Default for VideoRegistration {
fn default() -> Self {
Self::new(RegistrationMethod::Ecc)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transform_matrix_identity() {
let t = TransformMatrix::identity();
let (x, y) = t.transform_point(10.0, 20.0);
assert!((x - 10.0).abs() < 1e-6);
assert!((y - 20.0).abs() < 1e-6);
}
#[test]
fn test_transform_matrix_translation() {
let t = TransformMatrix::translation(5.0, 10.0);
let (x, y) = t.transform_point(0.0, 0.0);
assert!((x - 5.0).abs() < 1e-6);
assert!((y - 10.0).abs() < 1e-6);
}
#[test]
fn test_transform_matrix_scale() {
let t = TransformMatrix::scale(2.0, 3.0);
let (x, y) = t.transform_point(10.0, 20.0);
assert!((x - 20.0).abs() < 1e-6);
assert!((y - 60.0).abs() < 1e-6);
}
#[test]
fn test_transform_matrix_compose() {
let t1 = TransformMatrix::translation(10.0, 0.0);
let t2 = TransformMatrix::translation(0.0, 20.0);
let composed = t1.compose(&t2);
let (x, y) = composed.transform_point(0.0, 0.0);
assert!((x - 10.0).abs() < 1e-6);
assert!((y - 20.0).abs() < 1e-6);
}
#[test]
fn test_transform_matrix_inverse() {
let t = TransformMatrix::translation(10.0, 20.0);
let inv = t.inverse().expect("inverse should succeed");
let identity = t.compose(&inv);
let (x, y) = identity.transform_point(5.0, 15.0);
assert!((x - 5.0).abs() < 1e-6);
assert!((y - 15.0).abs() < 1e-6);
}
#[test]
fn test_registration_quality() {
let q = RegistrationQuality::new();
assert!(!q.is_good());
assert!(!q.success);
}
#[test]
fn test_video_registration_new() {
let reg = VideoRegistration::new(RegistrationMethod::PhaseCorrelation);
assert_eq!(reg.method, RegistrationMethod::PhaseCorrelation);
assert_eq!(reg.max_iterations, 100);
}
#[test]
fn test_video_registration_with_params() {
let reg = VideoRegistration::new(RegistrationMethod::Ecc)
.with_max_iterations(50)
.with_convergence_threshold(1e-5);
assert_eq!(reg.max_iterations, 50);
assert!((reg.convergence_threshold - 1e-5).abs() < 1e-10);
}
}