mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-30 02:31:40 +00:00
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:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -30,6 +30,7 @@ public class Draw3dAprilTagsPipe extends Draw3dTargetsPipe {
|
||||
FrameDivisor divisor) {
|
||||
super(shouldDraw, cameraCalibrationCoefficients, targetModel, divisor);
|
||||
this.shouldDrawHull = false;
|
||||
this.redistortPoints = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
BIN
photon-server/lib/libphotonlibcamera.so
Normal file
BIN
photon-server/lib/libphotonlibcamera.so
Normal file
Binary file not shown.
52
test-resources/calibration/limelight_1280_720.json
Normal file
52
test-resources/calibration/limelight_1280_720.json
Normal 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
|
||||
}
|
||||
BIN
test-resources/testimages/apriltag/tag_corner_1280.jpg
Normal file
BIN
test-resources/testimages/apriltag/tag_corner_1280.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 128 KiB |
Reference in New Issue
Block a user