diff --git a/.gitignore b/.gitignore index d3df6a7..7329f24 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # will have compiled files and executables debug/ target/ +venv # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html diff --git a/Cargo.toml b/Cargo.toml index 587e6f7..f5b358a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "camera-intrinsic-calibration" -version = "0.5.6" +version = "0.6.0" edition = "2021" authors = ["Powei Lin "] readme = "README.md" @@ -21,13 +21,13 @@ exclude = [ ] [dependencies] -aprilgrid = "0.5.3" +aprilgrid = "0.6.0" camera-intrinsic-model = "0.3.1" clap = { version = "4.5.31", features = ["derive"] } colorous = "1.0.15" env_logger = "0.11.6" -faer = "0.20.2" -glam = "0.29.2" +faer = "0.21.5" +glam = "0.30.0" glob = "0.3.2" image = "0.25.5" indicatif = { version = "0.17.11", features = ["rayon"] } @@ -39,9 +39,9 @@ rayon = "1.10.0" rerun = "0.17.0" serde = { version = "1.0.218", features = ["derive"] } serde_json = "1.0.139" -sqpnp_simple = "0.1.5" +sqpnp_simple = "0.1.6" time = "0.3.37" -tiny-solver = "0.14.2" +tiny-solver = "0.17.0" [[bin]] name = "ccrs" diff --git a/README.md b/README.md index d0769c8..eee6329 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,16 @@ # camera-intrinsic-calibration [![crate](https://img.shields.io/crates/v/camera-intrinsic-calibration.svg)](https://crates.io/crates/camera-intrinsic-calibration) +[![PyPI - Version](https://img.shields.io/pypi/v/camera-intrinsic-calibration.svg)](https://pypi.org/project/camera-intrinsic-calibration) +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/camera-intrinsic-calibration.svg)](https://pypi.org/project/camera-intrinsic-calibration) A pure rust camera intrinsic calibration library. ## Installation ```sh -# install cli +# cargo install cli cargo install camera-intrinsic-calibration +# pip install cli +pip install camera-intrinsic-calibration ``` Or download from the latest [release](https://github.com/powei-lin/camera-intrinsic-calibration-rs/releases). @@ -26,7 +30,11 @@ ccrs dataset-calib-cam1_1024_16 --model eucm ``` ### Visualize details after calibration ```sh +# use cargo to install rerun cargo install rerun-cli --version 0.17.0 +# or use pip to install rerun +pip install rerun-sdk==0.17.0 +# visualize result rerun results/20YYMMDD_HH_MM_SS/logging.rrd ``` example detection diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ab1d285 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,26 @@ +[build-system] +requires = ["maturin>=1.4,<2.0"] +build-backend = "maturin" + +[project] +name = "camera-intrinsic-calibration" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +authors = [ + { name = "Powei Lin", email = "poweilin1994@gmail.com" }, +] +dynamic = ["version"] + +[tool.maturin] +bindings = "bin" +module-name = "ccrs" +strip = true \ No newline at end of file diff --git a/scripts/build_macos.sh b/scripts/build_macos.sh index 4137bb2..b391a2e 100755 --- a/scripts/build_macos.sh +++ b/scripts/build_macos.sh @@ -2,4 +2,5 @@ cargo build -r cp ./target/release/ccrs ./ccrs tar czvf ccrs-aarch64-apple-darwin.tar.gz ./ccrs shasum -a 256 ccrs-aarch64-apple-darwin.tar.gz > ccrs-aarch64-apple-darwin.tar.gz.sha256 -rm ./ccrs \ No newline at end of file +rm ./ccrs +maturin build -r \ No newline at end of file diff --git a/scripts/cross_build_linux_aarch64.sh b/scripts/cross_build_linux_aarch64.sh index 9f68351..e1b4464 100755 --- a/scripts/cross_build_linux_aarch64.sh +++ b/scripts/cross_build_linux_aarch64.sh @@ -2,4 +2,5 @@ cargo zigbuild --release --target=aarch64-unknown-linux-gnu cp ./target/aarch64-unknown-linux-gnu/release/ccrs ./ccrs tar czvf ccrs-aarch64-unknown-linux-gnu.tar.gz ./ccrs shasum -a 256 ccrs-aarch64-unknown-linux-gnu.tar.gz > ccrs-aarch64-unknown-linux-gnu.tar.gz.sha256 -rm ./ccrs \ No newline at end of file +rm ./ccrs +maturin build -r --target aarch64-unknown-linux-gnu --zig \ No newline at end of file diff --git a/scripts/cross_build_linux_x86_64.sh b/scripts/cross_build_linux_x86_64.sh index e72a2a6..85160fc 100755 --- a/scripts/cross_build_linux_x86_64.sh +++ b/scripts/cross_build_linux_x86_64.sh @@ -2,4 +2,5 @@ cargo zigbuild --release --target=x86_64-unknown-linux-gnu cp ./target/x86_64-unknown-linux-gnu/release/ccrs ./ccrs tar czvf ccrs-x86_64-unknown-linux-gnu.tar.gz ./ccrs shasum -a 256 ccrs-x86_64-unknown-linux-gnu.tar.gz > ccrs-x86_64-unknown-linux-gnu.tar.gz.sha256 -rm ./ccrs \ No newline at end of file +rm ./ccrs +maturin build -r --target x86_64-unknown-linux-gnu --zig \ No newline at end of file diff --git a/scripts/cross_build_windows_x86_64.sh b/scripts/cross_build_windows_x86_64.sh index ca21b22..4161b1e 100755 --- a/scripts/cross_build_windows_x86_64.sh +++ b/scripts/cross_build_windows_x86_64.sh @@ -2,4 +2,5 @@ cargo zigbuild --release --target=x86_64-pc-windows-gnu cp ./target/x86_64-pc-windows-gnu/release/ccrs.exe ./ccrs.exe zip ccrs-x86_64-pc-windows-gnu.zip ./ccrs.exe shasum -a 256 ccrs-x86_64-pc-windows-gnu.zip > ccrs-x86_64-pc-windows-gnu.zip.sha256 -rm ./ccrs.exe \ No newline at end of file +rm ./ccrs.exe +maturin build -r --target x86_64-pc-windows-gnu --zig \ No newline at end of file diff --git a/scripts/setup_cross_compile.sh b/scripts/setup_cross_compile.sh index 3071f65..913d6a0 100755 --- a/scripts/setup_cross_compile.sh +++ b/scripts/setup_cross_compile.sh @@ -1,4 +1,5 @@ rustup target add aarch64-unknown-linux-gnu rustup target add x86_64-unknown-linux-gnu +rustup target add x86_64-pc-windows-gnu cargo install cargo-zigbuild brew install zig \ No newline at end of file diff --git a/src/data_loader.rs b/src/data_loader.rs index c9f2bb7..b17f321 100644 --- a/src/data_loader.rs +++ b/src/data_loader.rs @@ -88,7 +88,7 @@ pub fn load_euroc( let mut sorted_path: Vec = img_paths.into_iter().filter_map(img_filter).collect(); - sorted_path.sort_by(|a, b| a.cmp(b)); + sorted_path.sort(); let new_paths: Vec<_> = sorted_path.iter().skip(start_idx).step_by(step).collect(); let mut time_frame: Vec<_> = new_paths .iter() @@ -137,7 +137,7 @@ pub fn load_others( let mut sorted_path: Vec = img_paths.into_iter().filter_map(img_filter).collect(); - sorted_path.sort_by(|a, b| a.cmp(b)); + sorted_path.sort(); let new_paths: Vec<_> = sorted_path .iter() .skip(start_idx) diff --git a/src/optimization/homography.rs b/src/optimization/homography.rs index 0627a2c..6117d12 100644 --- a/src/optimization/homography.rs +++ b/src/optimization/homography.rs @@ -1,4 +1,4 @@ -use faer::prelude::SpSolverLstsq; +use faer::linalg::solvers::SolveLstsqCore; use log::debug; use nalgebra as na; use rand::seq::SliceRandom; @@ -13,18 +13,18 @@ fn h6_l1l2_solver(six_pt_pairs: &[(glam::Vec2, glam::Vec2)]) -> Option<(f32, na: let x_p = pt1.x; let y_p = pt1.y; unsafe { - m1.write_unchecked(r, 0, -x * y_p); - m1.write_unchecked(r, 1, -y * y_p); - m1.write_unchecked(r, 2, -y_p); - m1.write_unchecked(r, 3, x * x_p); - m1.write_unchecked(r, 4, x_p * y); - m1.write_unchecked(r, 5, x_p); - m1.write_unchecked(r, 6, -x * x * y_p - y * y * y_p); - m1.write_unchecked(r, 7, x * x * x_p + x_p * y * y); + *m1.get_mut_unchecked(r, 0) = -x * y_p; + *m1.get_mut_unchecked(r, 1) = -y * y_p; + *m1.get_mut_unchecked(r, 2) = -y_p; + *m1.get_mut_unchecked(r, 3) = x * x_p; + *m1.get_mut_unchecked(r, 4) = x_p * y; + *m1.get_mut_unchecked(r, 5) = x_p; + *m1.get_mut_unchecked(r, 6) = -x * x * y_p - y * y * y_p; + *m1.get_mut_unchecked(r, 7) = x * x * x_p + x_p * y * y; } } // let q_mat = mm1.transpose().qr().q(); - let q_mat = m1.transpose().qr().compute_q(); + let q_mat = m1.transpose().qr().compute_Q(); let q_mat_t = q_mat.transpose(); let n = q_mat_t.subrows(6, 2); let n02 = *n.get(0, 2); @@ -62,7 +62,7 @@ fn h6_l1l2_solver(six_pt_pairs: &[(glam::Vec2, glam::Vec2)]) -> Option<(f32, na: for which_gamma in 0..2 { let gamma = g_result[which_gamma]; let l = -(gamma * n06 + n16) / (-gamma * n02 - n12); - let v1 = gamma * n.row(0) + n.row(1); + let v1 = faer::Scale(gamma) * n.row(0) + n.row(1); temp_h[which_gamma][(0, 0)] = *v1.get(0); temp_h[which_gamma][(0, 1)] = *v1.get(1); temp_h[which_gamma][(0, 2)] = *v1.get(2); @@ -79,12 +79,10 @@ fn h6_l1l2_solver(six_pt_pairs: &[(glam::Vec2, glam::Vec2)]) -> Option<(f32, na: let x_p = pt1.x; let y_p = pt1.y; unsafe { - eq10a.write_unchecked(row, 0, -x * x_p); - eq10a.write_unchecked(row, 1, -x_p * y); - eq10a.write_unchecked(row, 2, -l * x * x * x_p - l * x_p * y * y - x_p); - eq10a.write_unchecked( - row, - 3, + *eq10a.get_mut_unchecked(row, 0) = -x * x_p; + *eq10a.get_mut_unchecked(row, 1) = -x_p * y; + *eq10a.get_mut_unchecked(row, 2) = -l * x * x * x_p - l * x_p * y * y - x_p; + *eq10a.get_mut_unchecked(row, 3) = l * x * x * x_p * x_p * temp_h[which_gamma][(0, 2)] + l * x * x * y_p * y_p * temp_h[which_gamma][(0, 2)] + l * x_p * x_p * y * y * temp_h[which_gamma][(0, 2)] @@ -94,25 +92,21 @@ fn h6_l1l2_solver(six_pt_pairs: &[(glam::Vec2, glam::Vec2)]) -> Option<(f32, na: + x_p * x_p * y * temp_h[which_gamma][(0, 1)] + x_p * x_p * temp_h[which_gamma][(0, 2)] + y * y_p * y_p * temp_h[which_gamma][(0, 1)] - + y_p * y_p * temp_h[which_gamma][(0, 2)], - ); + + y_p * y_p * temp_h[which_gamma][(0, 2)]; - eq10b.write_unchecked( - row, - 0, - -l * x * x * temp_h[which_gamma][(0, 2)] - - l * y * y * temp_h[which_gamma][(0, 2)] - - x * temp_h[which_gamma][(0, 0)] - - y * temp_h[which_gamma][(0, 1)] - - temp_h[which_gamma][(0, 2)], - ); + *eq10b.get_mut_unchecked(row, 0) = -l * x * x * temp_h[which_gamma][(0, 2)] + - l * y * y * temp_h[which_gamma][(0, 2)] + - x * temp_h[which_gamma][(0, 0)] + - y * temp_h[which_gamma][(0, 1)] + - temp_h[which_gamma][(0, 2)]; } } // std::cout << "svd\n"; - let eq10x = eq10a.qr().solve_lstsq(eq10b); - // Eigen::JacobiSVD> svd( - // eq10A, Eigen::ComputeFullU | Eigen::ComputeFullV); - // Eigen::Matrix eq10x = svd.solve(eq10b); + let mut eq10x = eq10b; + eq10a + .qr() + .solve_lstsq_in_place_with_conj(faer::Conj::No, eq10x.as_mut()); + temp_h[which_gamma][(2, 0)] = *eq10x.get(0, 0); temp_h[which_gamma][(2, 1)] = *eq10x.get(1, 0); temp_h[which_gamma][(2, 2)] = *eq10x.get(2, 0); diff --git a/src/util.rs b/src/util.rs index ecd81e0..64aaa93 100644 --- a/src/util.rs +++ b/src/util.rs @@ -241,7 +241,7 @@ pub fn convert_model( HashMap::>::from([("params".to_string(), target_params_init)]); // initialize optimizer - let optimizer = tiny_solver::LevenbergMarquardtOptimizer::default(); + let optimizer = tiny_solver::GaussNewtonOptimizer::default(); // distortion parameter bound set_problem_parameter_bound("params", &mut problem, target_model, false); @@ -311,7 +311,7 @@ pub fn init_ucm( ]); // initialize optimizer - let optimizer = tiny_solver::LevenbergMarquardtOptimizer::default(); + let optimizer = tiny_solver::GaussNewtonOptimizer::default(); if fixed_focal { init_focal_alpha_problem.fix_variable("params", 0); } @@ -346,7 +346,7 @@ pub fn init_ucm( 0, fixed_focal, ) - .unwrap() + .expect("The initial UCM model fitting failed. Might be wrong board configuration.") .0, ) } else { @@ -406,7 +406,7 @@ pub fn calib_camera( } } - let optimizer = tiny_solver::LevenbergMarquardtOptimizer::default(); + let optimizer = tiny_solver::GaussNewtonOptimizer::default(); // let initial_values = optimizer.optimize(&problem, &initial_values, None); set_problem_parameter_bound("params", &mut problem, generic_camera, xy_same_focal); @@ -512,7 +512,7 @@ pub fn init_camera_extrinsic(cam_rtvecs: &[HashMap]) -> Vec