Undistort corners in umich pose estimation (#699)

* Undistort corners in umich pose estimation

Add tag corner unit test

Delete hellooo.jpg

Update Draw3dTargetsPipe.java

Update FileFrameProvider.java

* Update AprilTagTest.java
This commit is contained in:
Matt
2023-01-05 10:08:25 -08:00
committed by GitHub
parent e68e6f3181
commit b263fe19cc
9 changed files with 178 additions and 12 deletions

View File

@@ -196,7 +196,8 @@ public class TestUtils {
public enum ApriltagTestImages {
kRobots,
kTag1_640_480;
kTag1_640_480,
kTag_corner_1280;
public final Path path;
@@ -302,6 +303,7 @@ public class TestUtils {
private static final String LIFECAM_240P_CAL_FILE = "lifecam240p.json";
private static final String LIFECAM_480P_CAL_FILE = "lifecam480p.json";
public static final String LIMELIGHT_480P_CAL_FILE = "limelight_1280_720.json";
public static CameraCalibrationCoefficients getCoeffs(String filename, boolean testMode) {
try {

View File

@@ -21,6 +21,11 @@ import edu.wpi.first.apriltag.AprilTagDetection;
import edu.wpi.first.apriltag.AprilTagPoseEstimate;
import edu.wpi.first.apriltag.AprilTagPoseEstimator;
import edu.wpi.first.apriltag.AprilTagPoseEstimator.Config;
import java.util.Arrays;
import org.opencv.calib3d.Calib3d;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
import org.photonvision.vision.pipe.CVPipe;
public class AprilTagPoseEstimatorPipe
@@ -37,8 +42,55 @@ public class AprilTagPoseEstimatorPipe
super();
}
MatOfPoint2f temp = new MatOfPoint2f();
@Override
protected AprilTagPoseEstimate process(AprilTagDetection in) {
// Save the corner points of our detection to an array
Point corners[] = new Point[4];
for (int i = 0; i < 4; i++) {
corners[i] = new Point(in.getCornerX(i), in.getCornerY(i));
}
System.out.println("Before: " + Arrays.toString(corners));
// And shove into our matofpoints
temp.fromArray(corners);
System.out.println("Size " + temp.size().toString());
// Probably overwrites what was in temp before. I hope
Calib3d.undistortPoints(
temp,
temp,
params.calibration.getCameraIntrinsicsMat(),
params.calibration.getDistCoeffsMat());
// Save out undistorted corners
corners = temp.toArray();
// Apriltagdetection expects an array in form [x1 y1 x2 y2 ...]
var fixedCorners = new double[8];
for (int i = 0; i < 4; i++) {
// https://stackoverflow.com/questions/8499984/how-to-undistort-points-in-camera-shot-coordinates-and-obtain-corresponding-undi
// perform transformation.
// In fact this is equivalent to multiplication to camera matrix
fixedCorners[i * 2] = corners[i].x * params.config.fx + params.config.cx;
fixedCorners[i * 2 + 1] = corners[i].y * params.config.fy + params.config.cy;
}
System.out.println("After: " + Arrays.toString(fixedCorners));
// Create a new Detection with the fixed corners
var corrected =
new AprilTagDetection(
in.getFamily(),
in.getId(),
in.getHamming(),
in.getDecisionMargin(),
in.getHomography(),
in.getCenterX(),
in.getCenterY(),
fixedCorners);
return m_poseEstimator.estimateOrthogonalIteration(in, params.nIters);
}
@@ -57,11 +109,14 @@ public class AprilTagPoseEstimatorPipe
public static class AprilTagPoseEstimatorPipeParams {
final AprilTagPoseEstimator.Config config;
final CameraCalibrationCoefficients calibration;
final int nIters;
public AprilTagPoseEstimatorPipeParams(Config config, int nIters) {
public AprilTagPoseEstimatorPipeParams(
Config config, CameraCalibrationCoefficients cal, int nIters) {
this.config = config;
this.nIters = nIters;
this.calibration = cal;
}
@Override

View File

@@ -30,6 +30,7 @@ public class Draw3dAprilTagsPipe extends Draw3dTargetsPipe {
FrameDivisor divisor) {
super(shouldDraw, cameraCalibrationCoefficients, targetModel, divisor);
this.shouldDrawHull = false;
this.redistortPoints = true;
}
}
}

View File

@@ -93,7 +93,12 @@ public class Draw3dTargetsPipe
params.cameraCalibrationCoefficients.getDistCoeffsMat(),
tempMat,
jac);
// Distort the points so they match the image they're being overlaid on
if (params.redistortPoints) {
// Distort the points so they match the image they're being overlaid on
distortPoints(tempMat, tempMat);
}
var bottomPoints = tempMat.toList();
Calib3d.projectPoints(
@@ -104,6 +109,12 @@ public class Draw3dTargetsPipe
params.cameraCalibrationCoefficients.getDistCoeffsMat(),
tempMat,
jac);
if (params.redistortPoints) {
// Distort the points so they match the image they're being overlaid on
distortPoints(tempMat, tempMat);
}
var topPoints = tempMat.toList();
dividePointList(bottomPoints);
@@ -290,6 +301,8 @@ public class Draw3dTargetsPipe
public final CameraCalibrationCoefficients cameraCalibrationCoefficients;
public final FrameDivisor divisor;
public boolean redistortPoints = false;
public Draw3dContoursParams(
boolean shouldDraw,
CameraCalibrationCoefficients cameraCalibrationCoefficients,

View File

@@ -97,7 +97,9 @@ public class AprilTagPipeline extends CVPipeline<CVPipelineResult, AprilTagPipel
poseEstimatorPipe.setParams(
new AprilTagPoseEstimatorPipeParams(
new Config(tagWidth, fx, fy, cx, cy), settings.numIterations));
new Config(tagWidth, fx, fy, cx, cy),
frameStaticProperties.cameraCalibration,
settings.numIterations));
}
}
}

View File

@@ -18,7 +18,6 @@
package org.photonvision.vision.pipeline;
import edu.wpi.first.math.geometry.Translation3d;
import java.io.IOException;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
@@ -33,7 +32,7 @@ import org.photonvision.vision.target.TrackedTarget;
public class AprilTagTest {
@BeforeEach
public void Init() throws IOException {
public void Init() {
TestUtils.loadLibraries();
}
@@ -67,22 +66,21 @@ public class AprilTagTest {
// Draw on input
var outputPipe = new OutputStreamPipeline();
outputPipe.process(
pipelineResult.inputAndOutputFrame, pipeline.getSettings(), pipelineResult.targets);
var ret =
outputPipe.process(
pipelineResult.inputAndOutputFrame, pipeline.getSettings(), pipelineResult.targets);
TestUtils.showImage(
pipelineResult.inputAndOutputFrame.colorImage.getMat(), "Pipeline output", 999999);
TestUtils.showImage(ret.inputAndOutputFrame.processedImage.getMat(), "Pipeline output", 999999);
// these numbers are not *accurate*, but they are known and expected
var pose = pipelineResult.targets.get(0).getBestCameraToTarget3d();
Assertions.assertEquals(2, pose.getTranslation().getX(), 0.2);
Assertions.assertEquals(0.0, pose.getTranslation().getY(), 0.2);
Assertions.assertEquals(0.0, pose.getTranslation().getY(), 0.2);
Assertions.assertEquals(0.0, pose.getTranslation().getZ(), 0.2);
var objX = new Translation3d(1, 0, 0).rotateBy(pose.getRotation()).getY();
var objY = new Translation3d(0, 1, 0).rotateBy(pose.getRotation()).getZ();
var objZ = new Translation3d(0, 0, 1).rotateBy(pose.getRotation()).getX();
System.out.printf("Object x %.2f y %.2f z %.2f\n", objX, objY, objZ);
// We expect the object X to be forward, or -X in world space
Assertions.assertEquals(
@@ -94,6 +92,49 @@ public class AprilTagTest {
Assertions.assertEquals(1, new Translation3d(0, 0, 1).rotateBy(pose.getRotation()).getZ(), 0.1);
}
@Test
public void testApriltagDistorted() {
var pipeline = new AprilTagPipeline();
pipeline.getSettings().inputShouldShow = true;
pipeline.getSettings().outputShouldDraw = true;
pipeline.getSettings().solvePNPEnabled = true;
pipeline.getSettings().cornerDetectionAccuracyPercentage = 4;
pipeline.getSettings().cornerDetectionUseConvexHulls = true;
pipeline.getSettings().targetModel = TargetModel.k200mmAprilTag;
pipeline.getSettings().tagFamily = AprilTagFamily.kTag16h5;
var frameProvider =
new FileFrameProvider(
TestUtils.getApriltagImagePath(TestUtils.ApriltagTestImages.kTag_corner_1280, false),
TestUtils.WPI2020Image.FOV,
TestUtils.getCoeffs(TestUtils.LIMELIGHT_480P_CAL_FILE, false));
frameProvider.requestFrameThresholdType(pipeline.getThresholdType());
CVPipelineResult pipelineResult;
try {
pipelineResult = pipeline.run(frameProvider.get(), QuirkyCamera.DefaultCamera);
printTestResults(pipelineResult);
} catch (RuntimeException e) {
// For now, will throw coz rotation3d ctor
return;
}
// Draw on input
var outputPipe = new OutputStreamPipeline();
var ret =
outputPipe.process(
pipelineResult.inputAndOutputFrame, pipeline.getSettings(), pipelineResult.targets);
TestUtils.showImage(ret.inputAndOutputFrame.processedImage.getMat(), "Pipeline output", 999999);
// these numbers are not *accurate*, but they are known and expected
var pose = pipelineResult.targets.get(0).getBestCameraToTarget3d();
Assertions.assertEquals(4, pose.getTranslation().getX(), 0.2);
Assertions.assertEquals(2, pose.getTranslation().getY(), 0.2);
Assertions.assertEquals(0.0, pose.getTranslation().getZ(), 0.2);
}
private static void printTestResults(CVPipelineResult pipelineResult) {
double fps = 1000 / pipelineResult.getLatencyMillis();
System.out.println(

Binary file not shown.

View File

@@ -0,0 +1,52 @@
{
"resolution": {
"width": 1280.0,
"height": 720.0
},
"cameraIntrinsics": {
"rows": 3,
"cols": 3,
"type": 6,
"data": [
1011.3749416937393,
0.0,
645.4955139388737,
0.0,
1008.5391755084075,
508.32877656020196,
0.0,
0.0,
1.0
]
},
"cameraExtrinsics": {
"rows": 1,
"cols": 5,
"type": 6,
"data": [
0.13730101577061535,
-0.2904345656989261,
8.32475714507539E-4,
-3.694397782014239E-4,
0.09487962227027584
]
},
"perViewErrors": [
0.4683044439074296,
0.3000488602347135,
0.3618225279245435,
0.3697739589395535,
0.3774014417410483,
0.3421990728973305,
0.26226949827209556,
0.2781325613823759,
0.35303624904310615,
0.4002431454419046,
0.364279228602911,
0.3616749616010646,
0.4458968983384897,
0.47429903475059804,
0.45590617711230197
],
"standardDeviation": 0.06352731093235896
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB