mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-22 01:11:40 +00:00
3d, camera calibration, backend settings hookup (#80)
* Implement new UI backend stuff * Kinda partially add resolution accuracy list * camera calibration go brrrrrrrr * ayyyy calibration works * Maybe fix grouping * Reorganize camera view * Fix settings not getting sent * Make pretty (#4) * Reorganize camera view * Apply some cosmetic layout changes to the cameras page * Fix pipeline rollback bug when starting on non-dashboard pages Co-authored-by: Matt <matthew.morley.ca@gmail.com> * Fix naming mismatch * Mostly make stuff work * rename robot-relative pose to camera-relative pose * SolvePNP memes, fix isFovConfigurable * Change config path to photonvision_config * netmask go poof, fix zip download? * Update index.js * Fix multi cam stuff? * Use LinearFilter instead * Fix multicam * aaa * start adding restart device and restart program, fix square size bug * Add some debug stuff * oop * Start fixing tests * Fix tests * Make target box proportinal * run spotless * Make crosshair h o t p i n k * Address review comments * Address review 2 electric booaloo * Possibly implement vendor FOV? * Make centroid crosshair gren * actually use FOV * Fix tests * actually fix tests Co-authored-by: Declan Freeman-Gleason <declanfreemangleason@gmail.com>
This commit is contained in:
@@ -28,31 +28,31 @@ import org.photonvision.common.logging.LogGroup;
|
||||
import org.photonvision.common.logging.LogLevel;
|
||||
import org.photonvision.common.logging.Logger;
|
||||
import org.photonvision.common.util.TestUtils;
|
||||
import org.photonvision.common.util.file.JacksonUtils;
|
||||
import org.photonvision.vision.pipeline.ColoredShapePipelineSettings;
|
||||
import org.photonvision.vision.pipeline.ReflectivePipelineSettings;
|
||||
import org.photonvision.vision.target.TargetModel;
|
||||
|
||||
public class ConfigTest {
|
||||
|
||||
private static final ConfigManager configMgr;
|
||||
private static final CameraConfiguration cameraConfig =
|
||||
new CameraConfiguration("TestCamera", "/dev/video420");
|
||||
private static final ReflectivePipelineSettings REFLECTIVE_PIPELINE_SETTINGS =
|
||||
new ReflectivePipelineSettings();
|
||||
private static final ColoredShapePipelineSettings COLORED_SHAPE_PIPELINE_SETTINGS =
|
||||
new ColoredShapePipelineSettings();
|
||||
|
||||
static {
|
||||
TestUtils.loadLibraries();
|
||||
configMgr = new ConfigManager(Path.of("testconfigdir"));
|
||||
}
|
||||
|
||||
private static ConfigManager configMgr;
|
||||
private static final CameraConfiguration cameraConfig =
|
||||
new CameraConfiguration("TestCamera", "/dev/video420");
|
||||
private static ReflectivePipelineSettings REFLECTIVE_PIPELINE_SETTINGS;
|
||||
private static ColoredShapePipelineSettings COLORED_SHAPE_PIPELINE_SETTINGS;
|
||||
|
||||
@BeforeAll
|
||||
public static void init() {
|
||||
TestUtils.loadLibraries();
|
||||
configMgr = new ConfigManager(Path.of("testconfigdir"));
|
||||
Logger.setLevel(LogGroup.General, LogLevel.TRACE);
|
||||
|
||||
REFLECTIVE_PIPELINE_SETTINGS = new ReflectivePipelineSettings();
|
||||
COLORED_SHAPE_PIPELINE_SETTINGS = new ColoredShapePipelineSettings();
|
||||
|
||||
REFLECTIVE_PIPELINE_SETTINGS.pipelineNickname = "2019Tape";
|
||||
REFLECTIVE_PIPELINE_SETTINGS.targetModel = TargetModel.get2019Target();
|
||||
|
||||
@@ -67,7 +67,6 @@ public class ConfigTest {
|
||||
@Order(1)
|
||||
public void serializeConfig() throws IOException {
|
||||
TestUtils.loadLibraries();
|
||||
JacksonUtils.serializer(Path.of("settings.json"), REFLECTIVE_PIPELINE_SETTINGS);
|
||||
|
||||
Logger.setLevel(LogGroup.General, LogLevel.TRACE);
|
||||
configMgr.getConfig().addCameraConfig(cameraConfig);
|
||||
@@ -75,16 +74,16 @@ public class ConfigTest {
|
||||
|
||||
var camConfDir =
|
||||
new File(
|
||||
Path.of(configMgr.rootFolder.toString(), "cameras", "TestCamera")
|
||||
Path.of(configMgr.configDirectoryFile.toString(), "cameras", "TestCamera")
|
||||
.toAbsolutePath()
|
||||
.toString());
|
||||
Assertions.assertTrue(camConfDir.exists(), "TestCamera config folder not found!");
|
||||
|
||||
Assertions.assertTrue(
|
||||
Files.exists(Path.of(configMgr.rootFolder.toString(), "hardwareConfig.json")),
|
||||
Files.exists(Path.of(configMgr.configDirectoryFile.toString(), "hardwareConfig.json")),
|
||||
"hardwareConfig.json file not found!");
|
||||
Assertions.assertTrue(
|
||||
Files.exists(Path.of(configMgr.rootFolder.toString(), "networkSettings.json")),
|
||||
Files.exists(Path.of(configMgr.configDirectoryFile.toString(), "networkSettings.json")),
|
||||
"networkSettings.json file not found!");
|
||||
}
|
||||
|
||||
@@ -116,7 +115,7 @@ public class ConfigTest {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
FileUtils.cleanDirectory(configMgr.rootFolder);
|
||||
configMgr.rootFolder.delete();
|
||||
FileUtils.cleanDirectory(configMgr.configDirectoryFile);
|
||||
configMgr.configDirectoryFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.photonvision.common.util.TestUtils;
|
||||
public class HardwareManagerTest {
|
||||
|
||||
@Test
|
||||
public void ManagementTest() throws IOException {
|
||||
public void managementTest() throws IOException {
|
||||
var config =
|
||||
new ObjectMapper().readValue(TestUtils.getHardwareConfigJson(), HardwareConfig.class);
|
||||
|
||||
|
||||
@@ -24,33 +24,33 @@ import org.photonvision.common.hardware.GPIO.CustomGPIO;
|
||||
import org.photonvision.common.hardware.GPIO.GPIOBase;
|
||||
import org.photonvision.common.hardware.GPIO.PiGPIO;
|
||||
import org.photonvision.common.hardware.Platform;
|
||||
import org.photonvision.common.hardware.metrics.CPU;
|
||||
import org.photonvision.common.hardware.metrics.GPU;
|
||||
import org.photonvision.common.hardware.metrics.RAM;
|
||||
import org.photonvision.common.hardware.metrics.CPUMetrics;
|
||||
import org.photonvision.common.hardware.metrics.GPUMetrics;
|
||||
import org.photonvision.common.hardware.metrics.RAMMetrics;
|
||||
|
||||
public class HardwareTest {
|
||||
|
||||
@Test
|
||||
public void testHardware() {
|
||||
CPU cpu = CPU.getInstance();
|
||||
RAM ram = RAM.getInstance();
|
||||
GPU gpu = GPU.getInstance();
|
||||
CPUMetrics cpuMetrics = new CPUMetrics();
|
||||
RAMMetrics ramMetrics = new RAMMetrics();
|
||||
GPUMetrics gpuMetrics = new GPUMetrics();
|
||||
|
||||
if (!Platform.isRaspberryPi()) return;
|
||||
|
||||
System.out.println("Testing on platform: " + Platform.CurrentPlatform);
|
||||
|
||||
System.out.println("Printing CPU Info:");
|
||||
System.out.println("Memory: " + cpu.getMemory() + "MB");
|
||||
System.out.println("Temperature: " + cpu.getTemp() + "C");
|
||||
System.out.println("Utilization: : " + cpu.getUtilization() + "%");
|
||||
System.out.println("Memory: " + cpuMetrics.getMemory() + "MB");
|
||||
System.out.println("Temperature: " + cpuMetrics.getTemp() + "C");
|
||||
System.out.println("Utilization: : " + cpuMetrics.getUtilization() + "%");
|
||||
|
||||
System.out.println("Printing GPU Info:");
|
||||
System.out.println("Memory: " + gpu.getMemory() + "MB");
|
||||
System.out.println("Temperature: " + gpu.getTemp() + "C");
|
||||
System.out.println("Memory: " + gpuMetrics.getMemory() + "MB");
|
||||
System.out.println("Temperature: " + gpuMetrics.getTemp() + "C");
|
||||
|
||||
System.out.println("Printing RAM Info: ");
|
||||
System.out.println("Used RAM: : " + ram.getUsedRam() + "MB");
|
||||
System.out.println("Used RAM: : " + ramMetrics.getUsedRam() + "MB");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -17,12 +17,15 @@
|
||||
|
||||
package org.photonvision.vision.pipeline;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import edu.wpi.first.wpilibj.geometry.Rotation2d;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import org.apache.commons.lang3.tuple.Triple;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.opencv.core.Mat;
|
||||
@@ -53,13 +56,19 @@ public class Calibrate3dPipeTest {
|
||||
|
||||
FindBoardCornersPipe findBoardCornersPipe = new FindBoardCornersPipe();
|
||||
findBoardCornersPipe.setParams(
|
||||
new FindBoardCornersPipe.FindCornersPipeParams(11, 4, false, 15));
|
||||
var findBoardCornersPipeOutput = findBoardCornersPipe.run(frames);
|
||||
new FindBoardCornersPipe.FindCornersPipeParams(
|
||||
11, 4, UICalibrationData.BoardType.DOTBOARD, 15));
|
||||
|
||||
List<Triple<Size, Mat, Mat>> foundCornersList = new ArrayList<>();
|
||||
|
||||
for (var f : frames) {
|
||||
foundCornersList.add(findBoardCornersPipe.run(f).output);
|
||||
}
|
||||
|
||||
Calibrate3dPipe calibrate3dPipe = new Calibrate3dPipe();
|
||||
calibrate3dPipe.setParams(new Calibrate3dPipe.CalibratePipeParams(new Size(640, 480)));
|
||||
|
||||
var calibrate3dPipeOutput = calibrate3dPipe.run(findBoardCornersPipeOutput.output);
|
||||
var calibrate3dPipeOutput = calibrate3dPipe.run(foundCornersList);
|
||||
assertTrue(calibrate3dPipeOutput.output.perViewErrors.length > 0);
|
||||
System.out.println(
|
||||
"Per View Errors: " + Arrays.toString(calibrate3dPipeOutput.output.perViewErrors));
|
||||
@@ -71,10 +80,10 @@ public class Calibrate3dPipeTest {
|
||||
File dir = new File(TestUtils.getDotBoardImagesPath().toAbsolutePath().toString());
|
||||
File[] directoryListing = dir.listFiles();
|
||||
|
||||
Calibration3dPipeline calibration3dPipeline = new Calibration3dPipeline();
|
||||
Calibrate3dPipeline calibration3dPipeline = new Calibrate3dPipeline(20);
|
||||
calibration3dPipeline.getSettings().boardHeight = 11;
|
||||
calibration3dPipeline.getSettings().boardWidth = 4;
|
||||
calibration3dPipeline.getSettings().isUsingChessboard = false;
|
||||
calibration3dPipeline.getSettings().boardType = UICalibrationData.BoardType.DOTBOARD;
|
||||
calibration3dPipeline.getSettings().gridSize = 15;
|
||||
calibration3dPipeline.getSettings().resolution = new Size(640, 480);
|
||||
|
||||
@@ -84,28 +93,35 @@ public class Calibrate3dPipeTest {
|
||||
calibration3dPipeline.run(
|
||||
new Frame(
|
||||
new CVMat(Imgcodecs.imread(file.getAbsolutePath())),
|
||||
new FrameStaticProperties(640, 480, 60)));
|
||||
TestUtils.showImage(output.outputFrame.image.getMat());
|
||||
new FrameStaticProperties(640, 480, 60, new Rotation2d(), null)));
|
||||
// TestUtils.showImage(output.outputFrame.image.getMat());
|
||||
}
|
||||
|
||||
assertTrue(
|
||||
calibration3dPipeline.foundCornersList.stream()
|
||||
.map(Triple::getRight)
|
||||
.allMatch(it -> it.width() > 0 && it.height() > 0));
|
||||
|
||||
calibration3dPipeline.removeSnapshot(0);
|
||||
calibration3dPipeline.startCalibration();
|
||||
calibration3dPipeline.run(
|
||||
new Frame(
|
||||
new CVMat(Imgcodecs.imread(directoryListing[0].getAbsolutePath())),
|
||||
new FrameStaticProperties(640, 480, 60)));
|
||||
new FrameStaticProperties(640, 480, 60, new Rotation2d(), null)));
|
||||
|
||||
assertTrue(
|
||||
calibration3dPipeline.foundCornersList.stream()
|
||||
.map(Triple::getRight)
|
||||
.allMatch(it -> it.width() > 0 && it.height() > 0));
|
||||
|
||||
var cal = calibration3dPipeline.tryCalibration();
|
||||
calibration3dPipeline.finishCalibration();
|
||||
System.out.println(
|
||||
"Per View Errors: " + Arrays.toString(calibration3dPipeline.perViewErrors()));
|
||||
System.out.println(
|
||||
"Camera Intrinsics : "
|
||||
+ calibration3dPipeline.cameraCalibrationCoefficients().cameraIntrinsics.toString());
|
||||
System.out.println(
|
||||
"Camera Extrinsics : "
|
||||
+ calibration3dPipeline.cameraCalibrationCoefficients().cameraExtrinsics.toString());
|
||||
System.out.println(
|
||||
"Standard Deviation: "
|
||||
+ calibration3dPipeline.cameraCalibrationCoefficients().standardDeviation);
|
||||
|
||||
assertNotNull(cal);
|
||||
assertNotNull(cal.perViewErrors);
|
||||
System.out.println("Per View Errors: " + Arrays.toString(cal.perViewErrors));
|
||||
System.out.println("Camera Intrinsics : " + cal.cameraIntrinsics.toString());
|
||||
System.out.println("Camera Extrinsics : " + cal.cameraExtrinsics.toString());
|
||||
System.out.println("Standard Deviation: " + cal.standardDeviation);
|
||||
System.out.println(
|
||||
"Mean: " + Arrays.stream(calibration3dPipeline.perViewErrors()).average().toString());
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ public class CirclePNPTest {
|
||||
}
|
||||
|
||||
private CameraCalibrationCoefficients getCoeffs(String filename) {
|
||||
var cameraCalibration = TestUtils.getCoeffs(filename, false);
|
||||
var cameraCalibration = TestUtils.getCoeffs(filename, true);
|
||||
checkCameraCoefficients(cameraCalibration);
|
||||
return cameraCalibration;
|
||||
}
|
||||
@@ -162,7 +162,7 @@ public class CirclePNPTest {
|
||||
System.out.println(
|
||||
"Found targets at "
|
||||
+ pipelineResult.targets.stream()
|
||||
.map(TrackedTarget::getRobotRelativePose)
|
||||
.map(TrackedTarget::getCameraToTarget)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
import edu.wpi.first.wpilibj.geometry.Rotation2d;
|
||||
import edu.wpi.first.wpilibj.util.Units;
|
||||
import java.util.stream.Collectors;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.photonvision.common.util.TestUtils;
|
||||
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
||||
import org.photonvision.vision.frame.Frame;
|
||||
@@ -58,7 +60,7 @@ public class SolvePNPTest {
|
||||
}
|
||||
|
||||
private CameraCalibrationCoefficients getCoeffs(String filename) {
|
||||
var cameraCalibration = TestUtils.getCoeffs(filename, false);
|
||||
var cameraCalibration = TestUtils.getCoeffs(filename, true);
|
||||
checkCameraCoefficients(cameraCalibration);
|
||||
return cameraCalibration;
|
||||
}
|
||||
@@ -97,12 +99,13 @@ public class SolvePNPTest {
|
||||
pipeline.getSettings().contourIntersection = ContourIntersectionDirection.Up;
|
||||
pipeline.getSettings().cornerDetectionUseConvexHulls = true;
|
||||
pipeline.getSettings().targetModel = TargetModel.get2019Target();
|
||||
pipeline.getSettings().cameraCalibration = getCoeffs(LIFECAM_240P_CAL_FILE);
|
||||
|
||||
var frameProvider =
|
||||
new FileFrameProvider(
|
||||
TestUtils.getWPIImagePath(TestUtils.WPI2019Image.kCargoStraightDark48in, false),
|
||||
TestUtils.WPI2019Image.FOV);
|
||||
TestUtils.WPI2019Image.FOV,
|
||||
new Rotation2d(),
|
||||
TestUtils.get2019LifeCamCoeffs(true));
|
||||
|
||||
CVPipelineResult pipelineResult;
|
||||
|
||||
@@ -110,12 +113,13 @@ public class SolvePNPTest {
|
||||
printTestResults(pipelineResult);
|
||||
|
||||
// these numbers are not *accurate*, but they are known and expected
|
||||
var pose = pipelineResult.targets.get(0).getRobotRelativePose();
|
||||
Assertions.assertEquals(41.96, pose.getTranslation().getX(), 0.05);
|
||||
Assertions.assertEquals(-1.03, pose.getTranslation().getY(), 0.05);
|
||||
Assertions.assertEquals(1.46, pose.getRotation().getDegrees(), 0.05);
|
||||
var pose = pipelineResult.targets.get(0).getCameraToTarget();
|
||||
Assertions.assertEquals(1.1, pose.getTranslation().getX(), 0.05);
|
||||
Assertions.assertEquals(0.0, pose.getTranslation().getY(), 0.05);
|
||||
Assertions.assertEquals(1, pose.getRotation().getDegrees(), 1);
|
||||
|
||||
TestUtils.showImage(pipelineResult.outputFrame.image.getMat(), "Pipeline output", 1000 * 90);
|
||||
Imgcodecs.imwrite("D:\\out.jpg", pipelineResult.outputFrame.image.getMat());
|
||||
TestUtils.showImage(pipelineResult.outputFrame.image.getMat(), "Pipeline output", 999999);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -129,23 +133,23 @@ public class SolvePNPTest {
|
||||
pipeline.getSettings().solvePNPEnabled = true;
|
||||
pipeline.getSettings().cornerDetectionAccuracyPercentage = 4;
|
||||
pipeline.getSettings().cornerDetectionUseConvexHulls = true;
|
||||
pipeline.getSettings().cameraCalibration = getCoeffs(LIFECAM_480P_CAL_FILE);
|
||||
pipeline.getSettings().targetModel = TargetModel.get2020Target(36);
|
||||
pipeline.getSettings().cameraPitch = Rotation2d.fromDegrees(0.0);
|
||||
pipeline.getSettings().targetModel = TargetModel.get2020Target();
|
||||
|
||||
var frameProvider =
|
||||
new FileFrameProvider(
|
||||
TestUtils.getWPIImagePath(TestUtils.WPI2020Image.kBlueGoal_224in_Left, false),
|
||||
TestUtils.WPI2020Image.FOV);
|
||||
TestUtils.WPI2020Image.FOV,
|
||||
new Rotation2d(),
|
||||
TestUtils.get2020LifeCamCoeffs(true));
|
||||
|
||||
CVPipelineResult pipelineResult = pipeline.run(frameProvider.get());
|
||||
printTestResults(pipelineResult);
|
||||
|
||||
// these numbers are not *accurate*, but they are known and expected
|
||||
var pose = pipelineResult.targets.get(0).getRobotRelativePose();
|
||||
Assertions.assertEquals(260.26, pose.getTranslation().getX(), 0.05);
|
||||
Assertions.assertEquals(64.26, pose.getTranslation().getY(), 0.05);
|
||||
Assertions.assertEquals(36.88, pose.getRotation().getDegrees(), 0.05);
|
||||
var pose = pipelineResult.targets.get(0).getCameraToTarget();
|
||||
Assertions.assertEquals(Units.inchesToMeters(240.26), pose.getTranslation().getX(), 0.05);
|
||||
Assertions.assertEquals(Units.inchesToMeters(35), pose.getTranslation().getY(), 0.05);
|
||||
Assertions.assertEquals(42, pose.getRotation().getDegrees(), 1);
|
||||
|
||||
TestUtils.showImage(pipelineResult.outputFrame.image.getMat(), "Pipeline output", 999999);
|
||||
}
|
||||
@@ -193,7 +197,7 @@ public class SolvePNPTest {
|
||||
System.out.println(
|
||||
"Found targets at "
|
||||
+ pipelineResult.targets.stream()
|
||||
.map(TrackedTarget::getRobotRelativePose)
|
||||
.map(TrackedTarget::getCameraToTarget)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.photonvision.vision.processes;
|
||||
|
||||
import edu.wpi.cscore.VideoMode;
|
||||
import edu.wpi.first.wpilibj.geometry.Rotation2d;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.*;
|
||||
@@ -78,8 +79,9 @@ public class VisionModuleManagerTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentVideoMode(VideoMode videoMode) {
|
||||
this.frameStaticProperties = new FrameStaticProperties(getCurrentVideoMode(), getFOV());
|
||||
public void setVideoModeInternal(VideoMode videoMode) {
|
||||
this.frameStaticProperties =
|
||||
new FrameStaticProperties(getCurrentVideoMode(), getFOV(), new Rotation2d(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.photonvision.vision.target;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import edu.wpi.first.wpilibj.geometry.Rotation2d;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -35,7 +36,8 @@ public class TargetCalculationsTest {
|
||||
private static final double diagFOV = Math.toRadians(70.0);
|
||||
|
||||
private static final FrameStaticProperties props =
|
||||
new FrameStaticProperties((int) imageSize.width, (int) imageSize.height, diagFOV);
|
||||
new FrameStaticProperties(
|
||||
(int) imageSize.width, (int) imageSize.height, diagFOV, new Rotation2d(), null);
|
||||
private static final TrackedTarget.TargetCalculationParameters params =
|
||||
new TrackedTarget.TargetCalculationParameters(
|
||||
true,
|
||||
|
||||
Reference in New Issue
Block a user