Skip to content

Implemented UP2p minimal solver#834

Open
namanguptagit wants to merge 7 commits intokornia:mainfrom
namanguptagit:feat/UP2P-minimal-solver
Open

Implemented UP2p minimal solver#834
namanguptagit wants to merge 7 commits intokornia:mainfrom
namanguptagit:feat/UP2P-minimal-solver

Conversation

@namanguptagit
Copy link
Copy Markdown
Contributor

📝 Description

This PR adds the Upright 2-Point Perspective (UP2P) minimal solver to kornia-3d, allowing camera pose estimation from just 2 points when gravity is known from an IMU. It strictly integrates into the existing solve_pnp_ransac pipeline, significantly reducing required RANSAC iterations while proving far more robust to noise than unconstrained solvers like EPnP.

⚠️ Issue Link Required: This PR must be linked to an approved and assigned issue. See Contributing Guide for details.
#826
Fixes/Relates to: # 826

Important:

  • Ensure you are assigned to the linked issue before submitting this PR
  • This PR should strictly implement what the linked issue describes
  • Do not include changes beyond the scope of the linked issue

🛠️ Changes Made

  • Implemented the Upright 2-Point Perspective (UP2P) minimal solver in crates/kornia-3d/src/pnp/up2p.rs based on the mathematical formulation from the PoseLib C++ reference.
  • Integrated UP2P directly into the existing solve_pnp_ransac pipeline in crates/kornia-3d/src/pnp/ransac.rs, including adding a solve_pnp_multi matching function to evaluate multiple polynomial root hypotheses.
  • Implemented dynamic RANSAC sample size adjustment (automatically lowering to 2 for UP2P vs 5 for EPnP) to exponentially reduce required RANSAC iterations.
  • Fixed RANSAC refinement logic to explicitly utilize EPnP as a non-linear least-squares fallback when refining the UP2P robust inlier subset, preventing minimal-solver degradation on all inliers.

🧪 How Was This Tested?

  • Unit Tests: Added extensive deterministic verification tests directly in up2p.rs (test_up2p_zero_yaw, test_up2p_large_yaw, test_up2p_noise, test_solve_quadratic_real) to mirror the PoseLib reference test behavior constraints.
  • Manual Verification: Wrote and executed a local integration script running the full kornia-3d pipeline (ORB -> Two-View -> UP2P RANSAC) on the EuRoC MH_01_easy stereo frames. Verified that UP2P yields highly stable bounding rotations (4.6° error) vs unconstrained EPnP over-fitting (49° error) on noisy visual data.
  • Performance/Edge Cases: Implemented rigorous handling of degenerate mathematical inputs (detecting collinear points or parallel rays) by checking determinant < 1e-8 to prevent divide-by-zero panics or imaginary quadratic roots (d < 0), safely returning empty vectors.
    Used these images :
mh01_frame1 mh01_frame2

🕵️ AI Usage Disclosure

Check one of the following:

  • 🟢 No AI used.
  • 🟡 AI-assisted: I used AI for Writing edge case for test case , for documenting and writing the test script to test the functionality on the real image .Also used it in some places where i was getting error while converting maths from PoseLib C++ reference to rust .But i have verified everything thoghrougly and can explain what code i have written and why and also tested everything on my end as well.
  • 🔴 AI-generated:

🚦 Checklist

  • I am assigned to the linked issue (required before PR submission)
  • The linked issue has been approved by a maintainer
  • This PR strictly implements what the linked issue describes (no scope creep)
  • I have performed a self-review of my code (no "ghost" variables or hallucinations).
  • My code follows the existing style guidelines of this project.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • (Optional) I have attached screenshots/recordings for UI changes.

💭 Additional Context

By using gravity data from a device's IMU, the UP2P solver already knows which way is "down." This means it doesn't have to guess the camera's tilt (roll and pitch).
Because it only has to figure out which direction the camera is facing (yaw) and its location, it only needs 2 points to solve the camera pose instead of the 5 needed by normal solvers like EPnP. This makes it much faster to compute, and much more accurate when dealing with messy, real-world data where normal solvers would just get confused by the noise.

@namanguptagit
Copy link
Copy Markdown
Contributor Author

One Question @cjpurackal should i push the script code too which i wrote to test the functionality on the images .

@namanguptagit namanguptagit mentioned this pull request Mar 19, 2026
14 tasks
@cjpurackal
Copy link
Copy Markdown
Member

How did you test/verify this?

@namanguptagit
Copy link
Copy Markdown
Contributor Author

I wrote a script to run test it on the image i provided in the description i can push that code if you allow .
@cjpurackal

@cjpurackal
Copy link
Copy Markdown
Member

what's you understanding of this solver and how does it translate to the test you wrote?

@namanguptagit
Copy link
Copy Markdown
Contributor Author

namanguptagit commented Mar 19, 2026

@cjpurackal
I understand that we implemented this UP2P solver to reduce the number of ransac iteration when gravity vector is known .It only requires 2 points unlike EPnP which required at least 4 points to solve it to find $X, Y, Z$ and $Yaw$ values that perfectly explain how those 3D world points appeared on the 2D camera image constraint.
To prove this works outside of synthetic data, I wrote an integration test using the EuRoC MH_01_easy dataset (a real-world stereo dataset that is gravity-aligned).

  1. The test extracts and matches ORB features between two real frames, then uses two-view geometry to triangulate noisy 3D points.
  2. Using these 3D - 2D correspondences, I run both the EPnP solver and the UP2P solver within a RANSAC pipeline.
  3. The results show that, by simply providing the known gravity vector (Vec3AF32::new(0.0, -1.0, 0.0)), UP2P can reliably recover the camera’s pose (translation and rotation) with strong inlier counts on real data. It also demonstrates that the RANSAC sample size drops from 5 points to just 2, highlighting the efficiency gained from the gravity constraint.

@cjpurackal
Copy link
Copy Markdown
Member

@cjpurackal I understand that we implemented this UP2P solver to reduce the number of ransac iteration when gravity vector is known .It only requires 2 points unlike EPnP which required at least 4 points to solve it to find X , Y , Z and Y a w values that perfectly explain how those 3D world points appeared on the 2D camera image constraint. To prove this works outside of synthetic data, I wrote an integration test using the EuRoC MH_01_easy dataset (a real-world stereo dataset that is gravity-aligned).

1. The test extracts and matches ORB features between two real frames, then uses two-view geometry to triangulate noisy 3D points.

2. Using these 3D - 2D correspondences, I run both the  EPnP solver and the UP2P solver within a RANSAC pipeline.

3. The results show that, by simply providing the known gravity vector (Vec3AF32::new(0.0, -1.0, 0.0)), UP2P can reliably recover the camera’s pose (translation and rotation) with strong inlier counts on real data. It also demonstrates that the RANSAC sample size drops from 5 points to just 2, highlighting the efficiency gained from the gravity constraint.

okay, now can you give me some plots comparing epnp and up2p for this dataset confirming the claim?

@namanguptagit
Copy link
Copy Markdown
Contributor Author

@cjpurackal I understand that we implemented this UP2P solver to reduce the number of ransac iteration when gravity vector is known .It only requires 2 points unlike EPnP which required at least 4 points to solve it to find X , Y , Z and Y a w values that perfectly explain how those 3D world points appeared on the 2D camera image constraint. To prove this works outside of synthetic data, I wrote an integration test using the EuRoC MH_01_easy dataset (a real-world stereo dataset that is gravity-aligned).

1. The test extracts and matches ORB features between two real frames, then uses two-view geometry to triangulate noisy 3D points.

2. Using these 3D - 2D correspondences, I run both the  EPnP solver and the UP2P solver within a RANSAC pipeline.

3. The results show that, by simply providing the known gravity vector (Vec3AF32::new(0.0, -1.0, 0.0)), UP2P can reliably recover the camera’s pose (translation and rotation) with strong inlier counts on real data. It also demonstrates that the RANSAC sample size drops from 5 points to just 2, highlighting the efficiency gained from the gravity constraint.

okay, now can you give me some plots comparing epnp and up2p for this dataset confirming the claim?

The tests I wrote did not generate plots ,they output a comparison table showing inlier counts, reprojection RMSE, translation, and rotation angle for both EPnP and UP2P on the same EuRoC MH_01_easy dataset.

I will write a plotting script to visualize the results and will share those plots here shortly.
@cjpurackal

This is the table :

EPnP vs UP2P — Real Image Comparison (EuRoC MH_01_easy)

[✓] Loaded EuRoC MH_01_easy frames
Frame 1: 752x480
Frame 2: 752x480
[✓] ORB detection: 496 features in frame1, 504 in frame2
[✓] ORB matching: 49 matches found
[✓] Two-view estimation: recovered 25 3D points
[✓] Built 25 PnP correspondences (3D<->2D)

Metric EPnP UP2P
Min sample size 5 2
Inliers found 25 / 25 5 / 25
Reproj RMSE (px) 222.0046 1.0893
Translation X 6.0828 -1.0111
Translation Y -7.5562 0.2121
Translation Z 3.9406 -0.8065
Rotation angle 49.46° 4.60°

[✓] Both solvers ran successfully on real EuRoC data!
[i] UP2P uses only 2-point samples (vs 5 for EPnP)
=> Far fewer RANSAC iterations needed when gravity is known

@namanguptagit
Copy link
Copy Markdown
Contributor Author

up2p_vs_epnp_benchmark @cjpurackal Here is the plot for the same test.

@namanguptagit
Copy link
Copy Markdown
Contributor Author

@cjpurackal please take a look once you have some time.

Comment thread crates/kornia-3d/src/pnp/ransac.rs Outdated
Copilot AI review requested due to automatic review settings March 25, 2026 00:06
@namanguptagit
Copy link
Copy Markdown
Contributor Author

Updated @cjpurackal

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a gravity-aware Upright 2-Point Perspective (UP2P) minimal PnP solver to kornia-3d and integrates it into the existing solve_pnp_ransac pipeline so RANSAC can use 2-point sampling and evaluate multiple pose hypotheses.

Changes:

  • Added a new UP2P solver implementation (including unit tests) and exposed it via the pnp module.
  • Extended PnPMethod with a UP2P(gravity) variant and introduced a multi-hypothesis dispatch (solve_pnp_multi) for RANSAC.
  • Updated solve_pnp_ransac to (a) use dynamic minimal sample sizes (2 for UP2P, 4/5 for EPnP) and (b) evaluate multiple candidates per iteration.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 10 comments.

File Description
crates/kornia-3d/src/pnp/up2p.rs Implements UP2P minimal solver + conversion glue into PnPResult, with unit tests.
crates/kornia-3d/src/pnp/ransac.rs Updates RANSAC to use solve_pnp_multi, handle UP2P’s 2-point sampling, and refine UP2P via EPnP fallback.
crates/kornia-3d/src/pnp/mod.rs Registers UP2P module, adds PnPMethod::UP2P, and adds multi-hypothesis solver routing.

Comment thread crates/kornia-3d/src/pnp/ransac.rs
Comment thread crates/kornia-3d/src/pnp/ransac.rs
Comment thread crates/kornia-3d/src/pnp/ransac.rs
Comment thread crates/kornia-3d/src/pnp/mod.rs
Comment thread crates/kornia-3d/src/pnp/up2p.rs Outdated
Comment thread crates/kornia-3d/src/pnp/ransac.rs Outdated
Comment thread crates/kornia-3d/src/pnp/up2p.rs Outdated
Comment thread crates/kornia-3d/src/pnp/up2p.rs
Comment thread crates/kornia-3d/src/pnp/up2p.rs Outdated
Comment thread crates/kornia-3d/src/pnp/up2p.rs Outdated
Comment thread crates/kornia-3d/src/pnp/ransac.rs Outdated
Comment on lines +261 to +263
.unwrap_or_else(|_| best_pose.clone())
} else {
best_pose
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

propogate the error better here i think this silently discards some errors

@namanguptagit
Copy link
Copy Markdown
Contributor Author

@sidd-27 Addressed the comment,
This commit adds a log::warn! to appropriately
surface those refinement failures, while maintaining the fallback to the
unrefined pose to preserve the robustness of the overall RANSAC pipeline.

Comment on lines +523 to +525
#[test]
fn test_ransac_up2p() -> Result<(), PnPRansacError> {
// Identity rotation and some translation
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add more comphrensive tests for ransac? this one covers the easiest case

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sidd-27 Added few more complex test case , i have used ai to generate them but verified that they are correct .

@namanguptagit
Copy link
Copy Markdown
Contributor Author

Hi @cjpurackal is there anything else needs to be done for this pr ?

@github-actions
Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs within 7 days. Thank you for your contributions!

@github-actions github-actions Bot added the stale label Apr 21, 2026
@namanguptagit
Copy link
Copy Markdown
Contributor Author

@cjpurackal v=can you take a look

@github-actions github-actions Bot removed the stale label Apr 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants