From e44ae11c155bc12d82e4e8b4051bff702d7338b2 Mon Sep 17 00:00:00 2001 From: shimat Date: Thu, 23 Jul 2026 12:20:52 +0900 Subject: [PATCH 1/3] Fix SolvePnPMethod enum values to match native OpenCV5 numbering The geometry module removed the broken DLS/UPnP methods (both just fell back to EPnP) and renumbered the remaining SOLVEPNP_* constants, but the managed SolvePnPMethod enum still carried the old OpenCV4 values. As a result AP3P/IPPE silently ran the wrong native algorithm, and IPPE_SQUARE/SQPNP were rejected outright since they fell outside the native flag range. Also drops the obsolete SolvePnPFlags alias enum, which is no longer needed now that OpenCvSharp5 doesn't guarantee source compatibility with the 4.x API. Fixes #2080 --- .../{SolvePnPFlags.cs => SolvePnPMethod.cs} | 56 ++----------------- test/OpenCvSharp.Tests/calib3d/Calib3dTest.cs | 56 +++++++++++++++++++ 2 files changed, 60 insertions(+), 52 deletions(-) rename src/OpenCvSharp/Modules/geometry/Enum/{SolvePnPFlags.cs => SolvePnPMethod.cs} (56%) diff --git a/src/OpenCvSharp/Modules/geometry/Enum/SolvePnPFlags.cs b/src/OpenCvSharp/Modules/geometry/Enum/SolvePnPMethod.cs similarity index 56% rename from src/OpenCvSharp/Modules/geometry/Enum/SolvePnPFlags.cs rename to src/OpenCvSharp/Modules/geometry/Enum/SolvePnPMethod.cs index 261162399..cabcf8aa3 100644 --- a/src/OpenCvSharp/Modules/geometry/Enum/SolvePnPFlags.cs +++ b/src/OpenCvSharp/Modules/geometry/Enum/SolvePnPMethod.cs @@ -27,32 +27,19 @@ public enum SolvePnPMethod /// P3P = 2, - /// - /// **Broken implementation. Using this flag will fallback to EPnP**. - /// Joel A. Hesch and Stergios I. Roumeliotis. "A Direct Least-Squares (DLS) Method for PnP" - /// - DLS = 3, - - /// - /// **Broken implementation. Using this flag will fallback to EPnP.** - /// Method is based on the paper of A.Penate-Sanchez, J.Andrade-Cetto, F.Moreno-Noguer. - /// "Exhaustive Linearization for Robust Camera Pose and Focal Length Estimation" - /// - UPNP = 4, - /// /// Method is based on the paper of T. Ke, S. Roumeliotis /// "An Efficient Algebraic Solution to the Perspective-Three-Point Problem" /// In this case the function requires exactly four object and image points. /// - AP3P = 5, + AP3P = 3, /// /// Method is based on the paper of T. Collins and A. Bartoli. /// "Infinitesimal Plane-Based Pose Estimation". /// This method requires coplanar object points. /// - IPPE = 6, + IPPE = 4, /// /// Method is based on the paper of Toby Collins and Adrien Bartoli. @@ -63,46 +50,11 @@ public enum SolvePnPMethod /// - point 2: [squareLength / 2, -squareLength / 2, 0] /// - point 3: [-squareLength / 2, -squareLength / 2, 0] /// - IPPE_SQUARE = 7, + IPPE_SQUARE = 5, /// /// Method is based on the paper "A Consistently Fast and Globally Optimal Solution to the /// Perspective-n-Point Problem" by G. Terzakis and M. Lourakis. It requires 3 or more points. /// - SQPNP = 8, -} - -/// -/// Obsolete: Use SolvePnPMethod instead. This enum is kept for backward compatibility. -/// -[Obsolete("Use SolvePnPMethod instead", true)] -public enum SolvePnPFlags -{ - /// - /// Iterative method is based on Levenberg-Marquardt optimization. - /// In this case the function finds such a pose that minimizes reprojection error, - /// that is the sum of squared distances between the observed projections imagePoints and the projected (using projectPoints() ) objectPoints . - /// - Iterative = 0, - - /// - /// Method has been introduced by F.Moreno-Noguer, V.Lepetit and P.Fua in the paper “EPnP: Efficient Perspective-n-Point Camera Pose Estimation”. - /// - EPNP = 1, - - /// - /// Method is based on the paper of X.S. Gao, X.-R. Hou, J. Tang, H.-F. Chang“Complete Solution Classification for - /// the Perspective-Three-Point Problem”. In this case the function requires exactly four object and image points. - /// - P3P = 2, - - /// - /// Joel A. Hesch and Stergios I. Roumeliotis. "A Direct Least-Squares (DLS) Method for PnP" - /// - DLS = 3, - - /// - /// A.Penate-Sanchez, J.Andrade-Cetto, F.Moreno-Noguer. "Exhaustive Linearization for Robust Camera Pose and Focal Length Estimation" - /// - UPNP = 4, + SQPNP = 6, } diff --git a/test/OpenCvSharp.Tests/calib3d/Calib3dTest.cs b/test/OpenCvSharp.Tests/calib3d/Calib3dTest.cs index 68a09d27e..d14990e84 100644 --- a/test/OpenCvSharp.Tests/calib3d/Calib3dTest.cs +++ b/test/OpenCvSharp.Tests/calib3d/Calib3dTest.cs @@ -481,6 +481,62 @@ public void SolvePnPTestByArray(bool useExtrinsicGuess) Cv2.SolvePnP(objPts, imgPts, cameraMatrix, dist, ref rvec, ref tvec, useExtrinsicGuess: useExtrinsicGuess); } + [Theory] + [InlineData(SolvePnPMethod.P3P)] + [InlineData(SolvePnPMethod.AP3P)] + [InlineData(SolvePnPMethod.IPPE)] + [InlineData(SolvePnPMethod.SQPNP)] + public void SolvePnPTestByArrayMethods(SolvePnPMethod method) + { + var rvec = new double[] { 3, 0, 0 }; + var tvec = new double[] { 0, 0, -10 }; + var cameraMatrix = new double[,] + { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 } + }; + var dist = new double[] { 0, 0, 0, 0, 0 }; + + var objPts = new[] + { + new Point3f(0,0,1), + new Point3f(1,0,1), + new Point3f(0,1,1), + new Point3f(1,1,1) + }; + + Cv2.ProjectPoints(objPts, rvec, tvec, cameraMatrix, dist, out var imgPts, out _); + + Cv2.SolvePnP(objPts, imgPts, cameraMatrix, dist, ref rvec, ref tvec, flags: method); + } + + [Fact] + public void SolvePnPTestByArrayIppeSquare() + { + var rvec = new double[] { 3, 0, 0 }; + var tvec = new double[] { 0, 0, -10 }; + var cameraMatrix = new double[,] + { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 } + }; + var dist = new double[] { 0, 0, 0, 0, 0 }; + + var objPts = new[] + { + new Point3f(-0.5f, 0.5f, 0), + new Point3f(0.5f, 0.5f, 0), + new Point3f(0.5f, -0.5f, 0), + new Point3f(-0.5f, -0.5f, 0) + }; + + Cv2.ProjectPoints(objPts, rvec, tvec, cameraMatrix, dist, out var imgPts, out _); + + Cv2.SolvePnP(objPts, imgPts, cameraMatrix, dist, ref rvec, ref tvec, flags: SolvePnPMethod.IPPE_SQUARE); + } + [Fact] public void SolvePnPTestByMat() { From 6945bd013486ca4b2f21db0ac8148e6f0def4fdd Mon Sep 17 00:00:00 2001 From: shimat Date: Thu, 23 Jul 2026 13:12:05 +0900 Subject: [PATCH 2/3] Address CodeRabbit feedback on #2081 - Rename SolvePnPMethod.IPPE_SQUARE to IppeSquare: once an acronym is glued to another word to form a compound member name, this repo's enum-naming convention PascalCases the whole compound rather than leaving part of it in caps (see .github/copilot-instructions.md). - Use a positive-Z tvec in the new synthetic poses so the projected points sit in front of the camera instead of behind it. - Strengthen SolvePnPTestByArrayMethods/IppeSquare to reproject with the recovered pose and assert it matches the input points, instead of only asserting the call doesn't throw. Confirmed this actually matters: with the old AP3P/IPPE values, SolvePnP silently dispatches to a different native solver that still completes without throwing, so a throws-only assertion doesn't catch it - the reprojection check does. --- .../Modules/geometry/Enum/SolvePnPMethod.cs | 2 +- test/OpenCvSharp.Tests/calib3d/Calib3dTest.cs | 23 ++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/OpenCvSharp/Modules/geometry/Enum/SolvePnPMethod.cs b/src/OpenCvSharp/Modules/geometry/Enum/SolvePnPMethod.cs index cabcf8aa3..2b003835b 100644 --- a/src/OpenCvSharp/Modules/geometry/Enum/SolvePnPMethod.cs +++ b/src/OpenCvSharp/Modules/geometry/Enum/SolvePnPMethod.cs @@ -50,7 +50,7 @@ public enum SolvePnPMethod /// - point 2: [squareLength / 2, -squareLength / 2, 0] /// - point 3: [-squareLength / 2, -squareLength / 2, 0] /// - IPPE_SQUARE = 5, + IppeSquare = 5, /// /// Method is based on the paper "A Consistently Fast and Globally Optimal Solution to the diff --git a/test/OpenCvSharp.Tests/calib3d/Calib3dTest.cs b/test/OpenCvSharp.Tests/calib3d/Calib3dTest.cs index d14990e84..2d138f9cc 100644 --- a/test/OpenCvSharp.Tests/calib3d/Calib3dTest.cs +++ b/test/OpenCvSharp.Tests/calib3d/Calib3dTest.cs @@ -489,7 +489,7 @@ public void SolvePnPTestByArray(bool useExtrinsicGuess) public void SolvePnPTestByArrayMethods(SolvePnPMethod method) { var rvec = new double[] { 3, 0, 0 }; - var tvec = new double[] { 0, 0, -10 }; + var tvec = new double[] { 0, 0, 10 }; var cameraMatrix = new double[,] { { 1, 0, 0 }, @@ -509,13 +509,23 @@ public void SolvePnPTestByArrayMethods(SolvePnPMethod method) Cv2.ProjectPoints(objPts, rvec, tvec, cameraMatrix, dist, out var imgPts, out _); Cv2.SolvePnP(objPts, imgPts, cameraMatrix, dist, ref rvec, ref tvec, flags: method); + + // Verify the recovered pose actually reprojects onto imgPts (rather than just "didn't throw"): + // a stale/misdirected enum value can silently dispatch to a different native solver that + // still runs to completion without throwing, but produces a wrong pose. + Cv2.ProjectPoints(objPts, rvec, tvec, cameraMatrix, dist, out var reprojected, out _); + for (var i = 0; i < imgPts.Length; i++) + { + Assert.Equal(imgPts[i].X, reprojected[i].X, 3); + Assert.Equal(imgPts[i].Y, reprojected[i].Y, 3); + } } [Fact] public void SolvePnPTestByArrayIppeSquare() { var rvec = new double[] { 3, 0, 0 }; - var tvec = new double[] { 0, 0, -10 }; + var tvec = new double[] { 0, 0, 10 }; var cameraMatrix = new double[,] { { 1, 0, 0 }, @@ -534,7 +544,14 @@ public void SolvePnPTestByArrayIppeSquare() Cv2.ProjectPoints(objPts, rvec, tvec, cameraMatrix, dist, out var imgPts, out _); - Cv2.SolvePnP(objPts, imgPts, cameraMatrix, dist, ref rvec, ref tvec, flags: SolvePnPMethod.IPPE_SQUARE); + Cv2.SolvePnP(objPts, imgPts, cameraMatrix, dist, ref rvec, ref tvec, flags: SolvePnPMethod.IppeSquare); + + Cv2.ProjectPoints(objPts, rvec, tvec, cameraMatrix, dist, out var reprojected, out _); + for (var i = 0; i < imgPts.Length; i++) + { + Assert.Equal(imgPts[i].X, reprojected[i].X, 3); + Assert.Equal(imgPts[i].Y, reprojected[i].Y, 3); + } } [Fact] From c4a86d2b18bb725b3e606545cdf35b4d63243622 Mon Sep 17 00:00:00 2001 From: shimat Date: Thu, 23 Jul 2026 13:19:04 +0900 Subject: [PATCH 3/3] Rename IppeSquare to IPPESquare to match existing acronym+word precedent CodeRabbit's suggested PascalCase compound (IppeSquare) follows the letter of the naming convention doc, but AKAZEDescriptorType already has an established precedent for this exact shape (acronym + plain word) that keeps the acronym in caps: KAZEUpright/MLDBUpright, not KazeUpright/MldbUpright. Match that existing style instead so IPPE (the bare enum member) and the compound built from it stay visually related. --- src/OpenCvSharp/Modules/geometry/Enum/SolvePnPMethod.cs | 2 +- test/OpenCvSharp.Tests/calib3d/Calib3dTest.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OpenCvSharp/Modules/geometry/Enum/SolvePnPMethod.cs b/src/OpenCvSharp/Modules/geometry/Enum/SolvePnPMethod.cs index 2b003835b..98073c11d 100644 --- a/src/OpenCvSharp/Modules/geometry/Enum/SolvePnPMethod.cs +++ b/src/OpenCvSharp/Modules/geometry/Enum/SolvePnPMethod.cs @@ -50,7 +50,7 @@ public enum SolvePnPMethod /// - point 2: [squareLength / 2, -squareLength / 2, 0] /// - point 3: [-squareLength / 2, -squareLength / 2, 0] /// - IppeSquare = 5, + IPPESquare = 5, /// /// Method is based on the paper "A Consistently Fast and Globally Optimal Solution to the diff --git a/test/OpenCvSharp.Tests/calib3d/Calib3dTest.cs b/test/OpenCvSharp.Tests/calib3d/Calib3dTest.cs index 2d138f9cc..bf779ce2b 100644 --- a/test/OpenCvSharp.Tests/calib3d/Calib3dTest.cs +++ b/test/OpenCvSharp.Tests/calib3d/Calib3dTest.cs @@ -522,7 +522,7 @@ public void SolvePnPTestByArrayMethods(SolvePnPMethod method) } [Fact] - public void SolvePnPTestByArrayIppeSquare() + public void SolvePnPTestByArrayIPPESquare() { var rvec = new double[] { 3, 0, 0 }; var tvec = new double[] { 0, 0, 10 }; @@ -544,7 +544,7 @@ public void SolvePnPTestByArrayIppeSquare() Cv2.ProjectPoints(objPts, rvec, tvec, cameraMatrix, dist, out var imgPts, out _); - Cv2.SolvePnP(objPts, imgPts, cameraMatrix, dist, ref rvec, ref tvec, flags: SolvePnPMethod.IppeSquare); + Cv2.SolvePnP(objPts, imgPts, cameraMatrix, dist, ref rvec, ref tvec, flags: SolvePnPMethod.IPPESquare); Cv2.ProjectPoints(objPts, rvec, tvec, cameraMatrix, dist, out var reprojected, out _); for (var i = 0; i < imgPts.Length; i++)