Use relative error threshold in Rotation Vector CV Crosscheck test

The Rotation Vector CV Crosscheck test uses pixels as a measure of
error for the test's accuracy. As a result, the distance at which the
test is performed from the pattern on the ground directly impacts the
result of the test. The test now calculates a dynamic error threshold
based on the current position of the device.

Test: Executed test on multiple devices and ensured it passed
Test: Distorted test pattern and ensured test failed
Test: Verified test fails when rotation vector is not accurate
Bug: 79942057
Change-Id: Iff0b0c7e71765d9441477463696a81a7f21d7638
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVXCheckAnalyzer.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVXCheckAnalyzer.java
index 260ffbf..45439a7 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVXCheckAnalyzer.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/RVCVXCheckAnalyzer.java
@@ -30,6 +30,7 @@
 import org.opencv.core.MatOfFloat;
 import org.opencv.core.MatOfPoint2f;
 import org.opencv.core.MatOfPoint3f;
+import org.opencv.core.Point;
 import org.opencv.core.Size;
 import org.opencv.imgcodecs.Imgcodecs;
 import org.opencv.imgproc.Imgproc;
@@ -65,7 +66,7 @@
 
     private static final boolean OUTPUT_DEBUG_IMAGE = false;
     private static final double VALID_FRAME_THRESHOLD = 0.8;
-    private static final double REPROJECTION_THREASHOLD_RATIO = 0.008;
+    private static final double REPROJECTION_THRESHOLD_RATIO = 0.03;
     private static final boolean FORCE_CV_ANALYSIS  = false;
     private static final boolean TRACE_VIDEO_ANALYSIS = false;
     private static final double DECIMATION_FPS_TARGET = 15.0;
@@ -811,7 +812,9 @@
             Debug.startMethodTracing("cvprocess");
         }
 
-        Size patternSize = new Size(4,11);
+        final int patternWidth = 4;
+        final int patternHeight = 11;
+        Size patternSize = new Size(patternWidth, patternHeight);
 
         float fc = (float)(meta.frameWidth/2.0/Math.tan(meta.fovWidth/2.0));
         Mat camMat = cameraMatrix(fc, new Size(frameSize.width/2, frameSize.height/2));
@@ -884,9 +887,19 @@
                 Log.v(TAG, "Found attitude, re-projection error = " + error);
             }
 
-            // if error is reasonable, add it into the results. use ratio to frame height to avoid
-            // discriminating higher definition videos
-            if (error < REPROJECTION_THREASHOLD_RATIO * frameSize.height) {
+            // Calculate the average distance between opposite corners of the pattern in pixels
+            Point[] centerPoints = centers.toArray();
+            Point bottomLeftPos = centerPoints[0];
+            Point bottomRightPos = centerPoints[patternWidth - 1];
+            Point topLeftPos = centerPoints[(patternHeight * patternWidth) - patternWidth];
+            Point topRightPos = centerPoints[(patternHeight * patternWidth) - 1];
+            double avgPixelDist = (getDistanceBetweenPoints(bottomLeftPos, topRightPos)
+                    + getDistanceBetweenPoints(bottomRightPos, topLeftPos)) / 2;
+
+            // if error is reasonable, add it into the results. Use a dynamic threshold based on
+            // the pixel distance of opposite corners of the pattern to prevent higher resolution
+            // video or the distance between the camera and the test pattern from impacting the test
+            if (error < REPROJECTION_THRESHOLD_RATIO * avgPixelDist) {
                 double [] rv = new double[3];
                 double timestamp;
 
@@ -1325,6 +1338,10 @@
     private static double [] rodr2rpy( double [] r) {
         return quat2rpy(rodr2quat(r));
     }
+
+    private double getDistanceBetweenPoints(Point a, Point b) {
+        return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
+    }
     //////////////////
 
 }