2022 grouping and test mode updates (#381)
Add 2022 images, "2orMore" grouping mode, and 2022 test mode Test mode now preserves old settings
@@ -51,7 +51,7 @@
|
||||
name="Target Grouping"
|
||||
tooltip="Whether or not every two targets are paired with each other (good for e.g. 2019 targets)"
|
||||
:select-cols="largeBox"
|
||||
:list="['Single','Dual']"
|
||||
:list="['Single','Dual','2orMore']"
|
||||
@input="handlePipelineData('contourGroupingMode')"
|
||||
/>
|
||||
<CVselect
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.photonvision.common.util;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import edu.wpi.first.cscore.CameraServerCvJNI;
|
||||
import edu.wpi.first.math.util.Units;
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -97,6 +98,26 @@ public class TestUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public enum WPI2022Image {
|
||||
kTerminal12ft6in(Units.feetToMeters(12.5)),
|
||||
kTerminal22ft6in(Units.feetToMeters(22.5));
|
||||
|
||||
public static double FOV = 68.5;
|
||||
|
||||
public final double distanceMeters;
|
||||
public final Path path;
|
||||
|
||||
Path getPath() {
|
||||
var filename = this.toString().substring(1).replace('_', '-');
|
||||
return Path.of("2022", "WPI", filename + ".png");
|
||||
}
|
||||
|
||||
WPI2022Image(double distanceMeters) {
|
||||
this.distanceMeters = distanceMeters;
|
||||
this.path = getPath();
|
||||
}
|
||||
}
|
||||
|
||||
public enum PolygonTestImages {
|
||||
kPolygons;
|
||||
|
||||
@@ -133,7 +154,8 @@ public class TestUtils {
|
||||
}
|
||||
|
||||
private static Path getResourcesFolderPath(boolean testMode) {
|
||||
return Path.of("../test-resources").toAbsolutePath();
|
||||
System.out.println("CWD: " + Path.of("").toAbsolutePath().toString());
|
||||
return Path.of(testMode ? "" : "../" + "test-resources").toAbsolutePath();
|
||||
}
|
||||
|
||||
public static Path getTestMode2019ImagePath() {
|
||||
@@ -148,6 +170,12 @@ public class TestUtils {
|
||||
.resolve(WPI2020Image.kBlueGoal_108in_Center.path);
|
||||
}
|
||||
|
||||
public static Path getTestMode2022ImagePath() {
|
||||
return getResourcesFolderPath(true)
|
||||
.resolve("testimages")
|
||||
.resolve(WPI2022Image.kTerminal22ft6in.path);
|
||||
}
|
||||
|
||||
public static Path getTestImagesPath(boolean testMode) {
|
||||
return getResourcesFolderPath(testMode).resolve("testimages");
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import org.photonvision.vision.opencv.CVMat;
|
||||
* path}.
|
||||
*/
|
||||
public class FileFrameProvider implements FrameProvider {
|
||||
public static final int MAX_FPS = 120;
|
||||
public static final int MAX_FPS = 10;
|
||||
private static int count = 0;
|
||||
|
||||
private final int thisIndex = count++;
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
*/
|
||||
package org.photonvision.vision.opencv;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.opencv.core.CvType;
|
||||
@@ -192,7 +194,11 @@ public class Contour implements Releasable {
|
||||
|| secondContour.isIntersecting(firstContour, intersectionDirection);
|
||||
}
|
||||
|
||||
private static Contour combineContours(Contour... contours) {
|
||||
public static Contour combineContours(Contour... contours) {
|
||||
return combineContourList(Arrays.asList(contours));
|
||||
}
|
||||
|
||||
public static Contour combineContourList(Collection<Contour> contours) {
|
||||
var points = new MatOfPoint();
|
||||
|
||||
for (var contour : contours) {
|
||||
|
||||
@@ -18,7 +18,8 @@ package org.photonvision.vision.opencv;
|
||||
|
||||
public enum ContourGroupingMode {
|
||||
Single(1),
|
||||
Dual(2);
|
||||
Dual(2),
|
||||
TwoOrMore(2);
|
||||
|
||||
public final int count;
|
||||
|
||||
|
||||
@@ -41,6 +41,16 @@ public class GroupContoursPipe
|
||||
for (var contour : input) {
|
||||
m_targets.add(new PotentialTarget(contour));
|
||||
}
|
||||
}
|
||||
// Check if we have at least 2 targets for 2 or more
|
||||
// This will only ever return 1 contour!
|
||||
else if (params.getGroup() == ContourGroupingMode.TwoOrMore
|
||||
&& input.size() >= ContourGroupingMode.TwoOrMore.count) {
|
||||
// Just blob everything together
|
||||
Contour groupedContour = Contour.combineContourList(input);
|
||||
if (groupedContour != null) {
|
||||
m_targets.add(new PotentialTarget(groupedContour, input));
|
||||
}
|
||||
} else {
|
||||
int groupingCount = params.getGroup().count;
|
||||
|
||||
@@ -52,14 +62,12 @@ public class GroupContoursPipe
|
||||
|
||||
for (int i = 0; i < input.size() - 1; i++) {
|
||||
// make a list of the desired count of contours to group
|
||||
List<Contour> groupingSet;
|
||||
// (Just make sure we don't get an index out of bounds exception
|
||||
if (i < 0 || i + groupingCount > input.size()) continue;
|
||||
|
||||
// If we're in two or more mode, just try to group everything
|
||||
List<Contour> groupingSet = input.subList(i, i + groupingCount);
|
||||
|
||||
// TODO: are these try/catch avoidable?
|
||||
try {
|
||||
groupingSet = input.subList(i, i + groupingCount);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
// FYI: This method only takes 2 contours!
|
||||
Contour groupedContour =
|
||||
|
||||
@@ -21,10 +21,7 @@ import java.util.Objects;
|
||||
import org.photonvision.common.util.numbers.DoubleCouple;
|
||||
import org.photonvision.common.util.numbers.IntegerCouple;
|
||||
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
||||
import org.photonvision.vision.opencv.ContourGroupingMode;
|
||||
import org.photonvision.vision.opencv.ContourIntersectionDirection;
|
||||
import org.photonvision.vision.opencv.ContourShape;
|
||||
import org.photonvision.vision.pipe.impl.CornerDetectionPipe;
|
||||
|
||||
@JsonTypeName("ColoredShapePipelineSettings")
|
||||
public class ColoredShapePipelineSettings extends AdvancedPipelineSettings {
|
||||
@@ -37,23 +34,10 @@ public class ColoredShapePipelineSettings extends AdvancedPipelineSettings {
|
||||
public int minDist = 20;
|
||||
public int maxCannyThresh = 90;
|
||||
public int circleAccuracy = 20;
|
||||
// how many contours to attempt to group (Single, Dual)
|
||||
public ContourGroupingMode contourGroupingMode = ContourGroupingMode.Single;
|
||||
|
||||
// the direction in which contours must intersect to be considered intersecting
|
||||
public ContourIntersectionDirection contourIntersection = ContourIntersectionDirection.Up;
|
||||
|
||||
// 3d settings
|
||||
public CameraCalibrationCoefficients cameraCalibration;
|
||||
|
||||
// Corner detection settings
|
||||
public CornerDetectionPipe.DetectionStrategy cornerDetectionStrategy =
|
||||
CornerDetectionPipe.DetectionStrategy.APPROX_POLY_DP_AND_EXTREME_CORNERS;
|
||||
public boolean cornerDetectionUseConvexHulls = true;
|
||||
public boolean cornerDetectionExactSideCount = false;
|
||||
public int cornerDetectionSideCount = 4;
|
||||
public double cornerDetectionAccuracyPercentage = 10;
|
||||
|
||||
public boolean erode = false;
|
||||
public boolean dilate = false;
|
||||
|
||||
|
||||
@@ -17,26 +17,9 @@
|
||||
package org.photonvision.vision.pipeline;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import org.photonvision.vision.opencv.ContourGroupingMode;
|
||||
import org.photonvision.vision.opencv.ContourIntersectionDirection;
|
||||
import org.photonvision.vision.pipe.impl.CornerDetectionPipe;
|
||||
|
||||
@JsonTypeName("ReflectivePipelineSettings")
|
||||
public class ReflectivePipelineSettings extends AdvancedPipelineSettings {
|
||||
// how many contours to attempt to group (Single, Dual)
|
||||
public ContourGroupingMode contourGroupingMode = ContourGroupingMode.Single;
|
||||
|
||||
// the direction in which contours must intersect to be considered intersecting
|
||||
public ContourIntersectionDirection contourIntersection = ContourIntersectionDirection.Up;
|
||||
|
||||
// Corner detection settings
|
||||
public CornerDetectionPipe.DetectionStrategy cornerDetectionStrategy =
|
||||
CornerDetectionPipe.DetectionStrategy.APPROX_POLY_DP_AND_EXTREME_CORNERS;
|
||||
public boolean cornerDetectionUseConvexHulls = true;
|
||||
public boolean cornerDetectionExactSideCount = false;
|
||||
public int cornerDetectionSideCount = 4;
|
||||
public double cornerDetectionAccuracyPercentage = 10;
|
||||
|
||||
public ReflectivePipelineSettings() {
|
||||
super();
|
||||
pipelineType = PipelineType.Reflective;
|
||||
|
||||
@@ -87,62 +87,100 @@ public class Main {
|
||||
}
|
||||
|
||||
private static void addTestModeSources() {
|
||||
var collectedSources = new ArrayList<VisionSource>();
|
||||
ConfigManager.getInstance().load();
|
||||
|
||||
var camConf2019 =
|
||||
new CameraConfiguration("WPI2019", TestUtils.getTestMode2019ImagePath().toString());
|
||||
camConf2019.FOV = TestUtils.WPI2019Image.FOV;
|
||||
camConf2019.calibrations.add(TestUtils.get2019LifeCamCoeffs(true));
|
||||
ConfigManager.getInstance().getConfig().getCameraConfigurations().get("WPI2019");
|
||||
if (camConf2019 == null) {
|
||||
camConf2019 =
|
||||
new CameraConfiguration("WPI2019", TestUtils.getTestMode2019ImagePath().toString());
|
||||
camConf2019.FOV = TestUtils.WPI2019Image.FOV;
|
||||
camConf2019.calibrations.add(TestUtils.get2019LifeCamCoeffs(true));
|
||||
|
||||
var pipeline2019 = new ReflectivePipelineSettings();
|
||||
pipeline2019.pipelineNickname = "CargoShip";
|
||||
pipeline2019.targetModel = TargetModel.k2019DualTarget;
|
||||
pipeline2019.outputShowMultipleTargets = true;
|
||||
pipeline2019.contourGroupingMode = ContourGroupingMode.Dual;
|
||||
var pipeline2019 = new ReflectivePipelineSettings();
|
||||
pipeline2019.pipelineNickname = "CargoShip";
|
||||
pipeline2019.targetModel = TargetModel.k2019DualTarget;
|
||||
pipeline2019.outputShowMultipleTargets = true;
|
||||
pipeline2019.contourGroupingMode = ContourGroupingMode.Dual;
|
||||
pipeline2019.inputShouldShow = true;
|
||||
|
||||
var psList2019 = new ArrayList<CVPipelineSettings>();
|
||||
psList2019.add(pipeline2019);
|
||||
|
||||
var fvs2019 = new FileVisionSource(camConf2019);
|
||||
var psList2019 = new ArrayList<CVPipelineSettings>();
|
||||
psList2019.add(pipeline2019);
|
||||
camConf2019.pipelineSettings = psList2019;
|
||||
}
|
||||
|
||||
var camConf2020 =
|
||||
new CameraConfiguration("WPI2020", TestUtils.getTestMode2020ImagePath().toString());
|
||||
camConf2020.FOV = TestUtils.WPI2020Image.FOV;
|
||||
camConf2019.calibrations.add(TestUtils.get2019LifeCamCoeffs(true));
|
||||
ConfigManager.getInstance().getConfig().getCameraConfigurations().get("WPI2020");
|
||||
if (camConf2020 == null) {
|
||||
camConf2020 =
|
||||
new CameraConfiguration("WPI2020", TestUtils.getTestMode2020ImagePath().toString());
|
||||
camConf2020.FOV = TestUtils.WPI2020Image.FOV;
|
||||
camConf2020.calibrations.add(TestUtils.get2019LifeCamCoeffs(true));
|
||||
|
||||
var pipeline2020 = new ReflectivePipelineSettings();
|
||||
pipeline2020.pipelineNickname = "OuterPort";
|
||||
pipeline2020.targetModel = TargetModel.k2020HighGoalOuter;
|
||||
camConf2020.calibrations.add(TestUtils.get2020LifeCamCoeffs(true));
|
||||
var pipeline2020 = new ReflectivePipelineSettings();
|
||||
pipeline2020.pipelineNickname = "OuterPort";
|
||||
pipeline2020.targetModel = TargetModel.k2020HighGoalOuter;
|
||||
camConf2020.calibrations.add(TestUtils.get2020LifeCamCoeffs(true));
|
||||
pipeline2020.inputShouldShow = true;
|
||||
|
||||
var psList2020 = new ArrayList<CVPipelineSettings>();
|
||||
psList2020.add(pipeline2020);
|
||||
var psList2020 = new ArrayList<CVPipelineSettings>();
|
||||
psList2020.add(pipeline2020);
|
||||
camConf2020.pipelineSettings = psList2020;
|
||||
}
|
||||
|
||||
var fvs2020 = new FileVisionSource(camConf2020);
|
||||
var camConf2022 =
|
||||
ConfigManager.getInstance().getConfig().getCameraConfigurations().get("WPI2022");
|
||||
if (camConf2022 == null) {
|
||||
camConf2022 =
|
||||
new CameraConfiguration("WPI2022", TestUtils.getTestMode2022ImagePath().toString());
|
||||
camConf2022.FOV = TestUtils.WPI2022Image.FOV;
|
||||
camConf2022.calibrations.add(TestUtils.get2019LifeCamCoeffs(true));
|
||||
|
||||
fvs2019.getCameraConfiguration().pipelineSettings = psList2019;
|
||||
fvs2020.getCameraConfiguration().pipelineSettings = psList2020;
|
||||
collectedSources.add(fvs2019);
|
||||
collectedSources.add(fvs2020);
|
||||
var pipeline2022 = new ReflectivePipelineSettings();
|
||||
pipeline2022.pipelineNickname = "OuterPort";
|
||||
pipeline2022.targetModel = TargetModel.k2020HighGoalOuter;
|
||||
pipeline2022.inputShouldShow = true;
|
||||
// camConf2020.calibrations.add(TestUtils.get2020LifeCamCoeffs(true));
|
||||
|
||||
var psList2022 = new ArrayList<CVPipelineSettings>();
|
||||
psList2022.add(pipeline2022);
|
||||
camConf2022.pipelineSettings = psList2022;
|
||||
}
|
||||
|
||||
// Colored shape testing
|
||||
var camConfShape =
|
||||
new CameraConfiguration(
|
||||
"Shape",
|
||||
TestUtils.getPowercellImagePath(TestUtils.PowercellTestImages.kPowercell_test_1, true)
|
||||
.toString());
|
||||
var settings = new ColoredShapePipelineSettings();
|
||||
settings.hsvHue = new IntegerCouple(0, 35);
|
||||
settings.hsvSaturation = new IntegerCouple(82, 255);
|
||||
settings.hsvValue = new IntegerCouple(62, 255);
|
||||
settings.contourShape = ContourShape.Triangle;
|
||||
settings.outputShowMultipleTargets = true;
|
||||
settings.circleAccuracy = 15;
|
||||
camConfShape.addPipelineSetting(settings);
|
||||
var fvsShape = new FileVisionSource(camConfShape);
|
||||
collectedSources.add(fvsShape);
|
||||
ConfigManager.getInstance().getConfig().getCameraConfigurations().get("Shape");
|
||||
|
||||
// If we haven't saved shape settings, create a new one
|
||||
if (camConfShape == null) {
|
||||
camConfShape =
|
||||
new CameraConfiguration(
|
||||
"Shape",
|
||||
TestUtils.getPowercellImagePath(TestUtils.PowercellTestImages.kPowercell_test_1, true)
|
||||
.toString());
|
||||
var settings = new ColoredShapePipelineSettings();
|
||||
settings.hsvHue = new IntegerCouple(0, 35);
|
||||
settings.hsvSaturation = new IntegerCouple(82, 255);
|
||||
settings.hsvValue = new IntegerCouple(62, 255);
|
||||
settings.contourShape = ContourShape.Triangle;
|
||||
settings.outputShowMultipleTargets = true;
|
||||
settings.circleAccuracy = 15;
|
||||
settings.inputShouldShow = true;
|
||||
camConfShape.addPipelineSetting(settings);
|
||||
}
|
||||
|
||||
var collectedSources = new ArrayList<VisionSource>();
|
||||
|
||||
var fvsShape = new FileVisionSource(camConfShape);
|
||||
var fvs2019 = new FileVisionSource(camConf2019);
|
||||
var fvs2020 = new FileVisionSource(camConf2020);
|
||||
var fvs2022 = new FileVisionSource(camConf2022);
|
||||
|
||||
collectedSources.add(fvs2022);
|
||||
collectedSources.add(fvsShape);
|
||||
collectedSources.add(fvs2020);
|
||||
collectedSources.add(fvs2019);
|
||||
|
||||
// logger.info("Adding " + allSources.size() + " configs to VMM.");
|
||||
VisionModuleManager.getInstance().addSources(collectedSources).forEach(VisionModule::start);
|
||||
ConfigManager.getInstance().addCameraConfigurations(collectedSources);
|
||||
}
|
||||
|
||||
BIN
test-resources/testimages/2022/WPI/Chute8ft6in.png
Normal file
|
After Width: | Height: | Size: 297 KiB |
11
test-resources/testimages/2022/WPI/Details.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
All photos were taken with a Microsoft Lifecam 3000 at 1280x720 with reduced brightness and exposure and fixed white balance.
|
||||
The camera was at a height of approximately 31.25in for all photos.
|
||||
All distances represent an approximate measurement across the ground to the projection of the closest point on the target ring.
|
||||
|
||||
Angle 1 Photos. The following sets were all taken at a single consistent camera angle
|
||||
"Terminal" photos are taken approximately on the line between the center of the terminal and the center of the goal.
|
||||
"Launchpad" photos taken approximately on the line between the center of the each Launchpad and the center of the goal
|
||||
|
||||
Angle 2 Photos. The following sets were taken from a second higher angle.
|
||||
"Tarmac Center" photos are taken approximately on a line perpendicular to the center of a fender
|
||||
"Chute" photo is taken approximately on a line perpendicular to the center of a chute
|
||||
BIN
test-resources/testimages/2022/WPI/FarLaunchpad10ft10in.png
Normal file
|
After Width: | Height: | Size: 447 KiB |
BIN
test-resources/testimages/2022/WPI/FarLaunchpad13ft10in.png
Normal file
|
After Width: | Height: | Size: 392 KiB |
BIN
test-resources/testimages/2022/WPI/FarLaunchpad17ft2in.png
Normal file
|
After Width: | Height: | Size: 465 KiB |
BIN
test-resources/testimages/2022/WPI/FarLaunchpad6ft0in.png
Normal file
|
After Width: | Height: | Size: 400 KiB |
BIN
test-resources/testimages/2022/WPI/FarLaunchpad7ft10in.png
Normal file
|
After Width: | Height: | Size: 417 KiB |
BIN
test-resources/testimages/2022/WPI/NearLaunchpad10ft10in.png
Normal file
|
After Width: | Height: | Size: 478 KiB |
BIN
test-resources/testimages/2022/WPI/NearLaunchpad12ft10in.png
Normal file
|
After Width: | Height: | Size: 464 KiB |
BIN
test-resources/testimages/2022/WPI/NearLaunchpad13ft6in.png
Normal file
|
After Width: | Height: | Size: 502 KiB |
BIN
test-resources/testimages/2022/WPI/NearLaunchpad5ft4in.png
Normal file
|
After Width: | Height: | Size: 559 KiB |
BIN
test-resources/testimages/2022/WPI/NearLaunchpad6ft10in.png
Normal file
|
After Width: | Height: | Size: 524 KiB |
BIN
test-resources/testimages/2022/WPI/NearLaunchpad8ft10in.png
Normal file
|
After Width: | Height: | Size: 490 KiB |
BIN
test-resources/testimages/2022/WPI/TarmacCenter2ft10in.png
Normal file
|
After Width: | Height: | Size: 326 KiB |
BIN
test-resources/testimages/2022/WPI/TarmacCenter3ft10in.png
Normal file
|
After Width: | Height: | Size: 287 KiB |
BIN
test-resources/testimages/2022/WPI/TarmacCenter4ft10in.png
Normal file
|
After Width: | Height: | Size: 318 KiB |
BIN
test-resources/testimages/2022/WPI/TarmacCenter5ft10in.png
Normal file
|
After Width: | Height: | Size: 352 KiB |
BIN
test-resources/testimages/2022/WPI/TarmacCenter6ft10in.png
Normal file
|
After Width: | Height: | Size: 241 KiB |
BIN
test-resources/testimages/2022/WPI/TarmacCenter7ft10in.png
Normal file
|
After Width: | Height: | Size: 222 KiB |
BIN
test-resources/testimages/2022/WPI/Terminal10ft6in.png
Normal file
|
After Width: | Height: | Size: 510 KiB |
BIN
test-resources/testimages/2022/WPI/Terminal12ft6in.png
Normal file
|
After Width: | Height: | Size: 487 KiB |
BIN
test-resources/testimages/2022/WPI/Terminal14ft6in.png
Normal file
|
After Width: | Height: | Size: 458 KiB |
BIN
test-resources/testimages/2022/WPI/Terminal16ft6in.png
Normal file
|
After Width: | Height: | Size: 400 KiB |
BIN
test-resources/testimages/2022/WPI/Terminal18ft6in.png
Normal file
|
After Width: | Height: | Size: 396 KiB |
BIN
test-resources/testimages/2022/WPI/Terminal22ft6in.png
Normal file
|
After Width: | Height: | Size: 366 KiB |
BIN
test-resources/testimages/2022/WPI/Terminal5ft6in.png
Normal file
|
After Width: | Height: | Size: 464 KiB |
BIN
test-resources/testimages/2022/WPI/Terminal8ft6in.png
Normal file
|
After Width: | Height: | Size: 477 KiB |