mirror of
https://github.com/PhotonVision/photonvision
synced 2026-07-02 02:51:40 +00:00
Add colored shape to the UI (#258)
* Fixup colored shape backend code * More colored shape stuff * Start adding shape change to drawing * Mostly works! * Add powercell image for shape test mode * Make super-duper-sure to release stuff Fixes colored shape leak * Move approx poly dp into Contour * Adjust epsilon threshold * Add dialog to change pipeline type * Run spotless * Make yes red :> * Move "no" button * Fix duplication/deletion name logic and switching * Fix compilation errors from rebase * Update VisionSourceManager.java * Update type dialog, remove duplicate popup The dropdown still switches even if the user says "no" tho Co-authored-by: Banks Troutman <btrout.dhrs@gmail.com>
This commit is contained in:
@@ -35,13 +35,17 @@ public class FileVisionSource extends VisionSource {
|
||||
|
||||
public FileVisionSource(CameraConfiguration cameraConfiguration) {
|
||||
super(cameraConfiguration);
|
||||
var calibration =
|
||||
cameraConfiguration.calibrations.size() > 0
|
||||
? cameraConfiguration.calibrations.get(0)
|
||||
: null;
|
||||
frameProvider =
|
||||
new FileFrameProvider(
|
||||
Path.of(cameraConfiguration.path),
|
||||
cameraConfiguration.FOV,
|
||||
FileFrameProvider.MAX_FPS,
|
||||
cameraConfiguration.camPitch,
|
||||
cameraConfiguration.calibrations.get(0));
|
||||
calibration);
|
||||
settables =
|
||||
new FileSourceSettables(cameraConfiguration, frameProvider.get().frameStaticProperties);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class CVMat implements Releasable {
|
||||
private StringBuilder getStackTraceBuilder() {
|
||||
var trace = Thread.currentThread().getStackTrace();
|
||||
|
||||
final int STACK_FRAMES_TO_SKIP = 4;
|
||||
final int STACK_FRAMES_TO_SKIP = 3;
|
||||
final var traceStr = new StringBuilder();
|
||||
for (int idx = STACK_FRAMES_TO_SKIP; idx < trace.length; idx++) {
|
||||
traceStr.append("\t\n").append(trace[idx]);
|
||||
|
||||
@@ -17,13 +17,18 @@
|
||||
|
||||
package org.photonvision.vision.opencv;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.opencv.core.MatOfPoint2f;
|
||||
import org.opencv.core.MatOfPoint3f;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.opencv.core.Point;
|
||||
|
||||
public class CVShape {
|
||||
public class CVShape implements Releasable {
|
||||
public final Contour contour;
|
||||
public final ContourShape shape;
|
||||
|
||||
@Nullable public final ContourShape shape;
|
||||
|
||||
public double radius = 0;
|
||||
public Point center = null;
|
||||
|
||||
private MatOfPoint3f customTarget = null;
|
||||
|
||||
@@ -34,6 +39,12 @@ public class CVShape {
|
||||
this.shape = shape;
|
||||
}
|
||||
|
||||
public CVShape(Contour contour, Point center, double radius) {
|
||||
this(contour, ContourShape.Circle);
|
||||
this.radius = radius;
|
||||
this.center = center;
|
||||
}
|
||||
|
||||
public CVShape(Contour contour, MatOfPoint3f targetPoints) {
|
||||
this.contour = contour;
|
||||
this.shape = ContourShape.Custom;
|
||||
@@ -44,36 +55,10 @@ public class CVShape {
|
||||
return contour;
|
||||
}
|
||||
|
||||
public MatOfPoint2f getApproxPolyDp(double epsilon, boolean closed) {
|
||||
@Override
|
||||
public void release() {
|
||||
if (customTarget != null) customTarget.release();
|
||||
approxCurve.release();
|
||||
approxCurve = new MatOfPoint2f();
|
||||
|
||||
Imgproc.approxPolyDP(contour.getMat2f(), approxCurve, epsilon, closed);
|
||||
return approxCurve;
|
||||
}
|
||||
|
||||
public MatOfPoint2f getApproxPolyDpConvex(double epsilon, boolean closed) {
|
||||
approxCurve.release();
|
||||
approxCurve = new MatOfPoint2f();
|
||||
|
||||
Imgproc.approxPolyDP(contour.getConvexHull(), approxCurve, epsilon, closed);
|
||||
return approxCurve;
|
||||
}
|
||||
|
||||
boolean approxPolyMatchesShape() {
|
||||
var pointList = approxCurve.toList();
|
||||
|
||||
// TODO: @Matt
|
||||
switch (shape) {
|
||||
case Custom:
|
||||
break;
|
||||
case Circle:
|
||||
break;
|
||||
case Triangle:
|
||||
break;
|
||||
case Quadrilateral:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
contour.release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.photonvision.vision.opencv;
|
||||
|
||||
import java.util.Comparator;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.MatOfInt;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
@@ -45,6 +46,7 @@ public class Contour implements Releasable {
|
||||
private Moments moments = null;
|
||||
|
||||
private MatOfPoint2f convexHull = null;
|
||||
private MatOfPoint2f approxPolyDp = null;
|
||||
|
||||
public Contour(MatOfPoint mat) {
|
||||
this.mat = mat;
|
||||
@@ -68,6 +70,19 @@ public class Contour implements Releasable {
|
||||
return convexHull;
|
||||
}
|
||||
|
||||
public MatOfPoint2f getApproxPolyDp(double epsilon, boolean closed) {
|
||||
if (this.approxPolyDp == null) {
|
||||
approxPolyDp = new MatOfPoint2f();
|
||||
Imgproc.approxPolyDP(getConvexHull(), approxPolyDp, epsilon, closed);
|
||||
}
|
||||
return approxPolyDp;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MatOfPoint2f getApproxPolyDp() {
|
||||
return this.approxPolyDp;
|
||||
}
|
||||
|
||||
public double getArea() {
|
||||
if (Double.isNaN(area)) {
|
||||
area = Imgproc.contourArea(mat);
|
||||
@@ -197,6 +212,7 @@ public class Contour implements Releasable {
|
||||
if (mat != null) mat.release();
|
||||
if (mat2f != null) mat2f.release();
|
||||
if (convexHull != null) convexHull.release();
|
||||
if (approxPolyDp != null) approxPolyDp.release();
|
||||
}
|
||||
|
||||
public static MatOfPoint2f convertIndexesToPoints(MatOfPoint contour, MatOfInt indexes) {
|
||||
|
||||
@@ -21,8 +21,8 @@ import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
|
||||
public enum ContourShape {
|
||||
Custom(-1),
|
||||
Circle(0),
|
||||
Custom(-1),
|
||||
Triangle(3),
|
||||
Quadrilateral(4);
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ public class Collect2dTargetsPipe
|
||||
params.frameStaticProperties);
|
||||
|
||||
for (PotentialTarget target : in) {
|
||||
targets.add(new TrackedTarget(target, calculationParams));
|
||||
targets.add(new TrackedTarget(target, calculationParams, target.shape));
|
||||
}
|
||||
|
||||
return targets;
|
||||
|
||||
@@ -27,6 +27,8 @@ import org.photonvision.common.logging.LogGroup;
|
||||
import org.photonvision.common.logging.Logger;
|
||||
import org.photonvision.common.util.ColorHelper;
|
||||
import org.photonvision.vision.frame.FrameDivisor;
|
||||
import org.photonvision.vision.opencv.CVShape;
|
||||
import org.photonvision.vision.opencv.ContourShape;
|
||||
import org.photonvision.vision.pipe.MutatingPipe;
|
||||
import org.photonvision.vision.target.TrackedTarget;
|
||||
|
||||
@@ -53,6 +55,7 @@ public class Draw2dTargetsPipe
|
||||
var centroidColour = ColorHelper.colorToScalar(params.centroidColor);
|
||||
var maximumBoxColour = ColorHelper.colorToScalar(params.maximumBoxColor);
|
||||
var rotatedBoxColour = ColorHelper.colorToScalar(params.rotatedBoxColor);
|
||||
var circleColor = ColorHelper.colorToScalar(params.circleColor);
|
||||
var shapeColour = ColorHelper.colorToScalar(params.shapeOutlineColour);
|
||||
|
||||
for (int i = 0; i < (params.showMultipleTargets ? in.getRight().size() : 1); i++) {
|
||||
@@ -72,13 +75,35 @@ public class Draw2dTargetsPipe
|
||||
dividePointArray(vertices);
|
||||
contour.fromArray(vertices);
|
||||
|
||||
if (params.showRotatedBox) {
|
||||
if (params.shouldShowRotatedBox(target.getShape())) {
|
||||
Imgproc.drawContours(
|
||||
in.getLeft(),
|
||||
List.of(contour),
|
||||
0,
|
||||
rotatedBoxColour,
|
||||
(int) Math.ceil(imageSize * params.kPixelsToBoxThickness));
|
||||
} else if (params.shouldShowCircle(target.getShape())) {
|
||||
Imgproc.circle(
|
||||
in.getLeft(),
|
||||
target.getShape().center,
|
||||
(int) target.getShape().radius,
|
||||
circleColor,
|
||||
(int) Math.ceil(imageSize * params.kPixelsToBoxThickness));
|
||||
} else {
|
||||
// draw approximate polygon
|
||||
var poly = target.getApproximateBoundingPolygon();
|
||||
|
||||
// fall back on the shape's approx poly dp
|
||||
if (poly == null && target.getShape() != null)
|
||||
poly = target.getShape().getContour().getApproxPolyDp();
|
||||
if (poly != null) {
|
||||
// divideMat2f(poly, pointMat);
|
||||
var mat = new MatOfPoint();
|
||||
mat.fromArray(poly.toArray());
|
||||
Imgproc.drawContours(
|
||||
in.getLeft(), List.of(mat), -1, ColorHelper.colorToScalar(Color.blue), 2);
|
||||
mat.release();
|
||||
}
|
||||
}
|
||||
|
||||
if (params.showMaximumBox) {
|
||||
@@ -193,12 +218,21 @@ public class Draw2dTargetsPipe
|
||||
public Color maximumBoxColor = Color.RED;
|
||||
public Color shapeOutlineColour = Color.MAGENTA;
|
||||
public Color textColor = Color.GREEN;
|
||||
public Color circleColor = Color.RED;
|
||||
|
||||
public final boolean showMultipleTargets;
|
||||
public final boolean shouldDraw;
|
||||
|
||||
public final FrameDivisor divisor;
|
||||
|
||||
public boolean shouldShowRotatedBox(CVShape shape) {
|
||||
return showRotatedBox && (shape == null || shape.shape.equals(ContourShape.Quadrilateral));
|
||||
}
|
||||
|
||||
public boolean shouldShowCircle(CVShape shape) {
|
||||
return shape != null && shape.shape.equals(ContourShape.Circle);
|
||||
}
|
||||
|
||||
public Draw2dTargetsParams(
|
||||
boolean shouldDraw, boolean showMultipleTargets, FrameDivisor divisor) {
|
||||
this.shouldDraw = shouldDraw;
|
||||
|
||||
@@ -17,13 +17,18 @@
|
||||
|
||||
package org.photonvision.vision.pipe.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.photonvision.vision.frame.FrameStaticProperties;
|
||||
import org.photonvision.vision.opencv.CVShape;
|
||||
import org.photonvision.vision.opencv.ContourShape;
|
||||
import org.photonvision.vision.pipe.CVPipe;
|
||||
|
||||
public class FilterShapesPipe
|
||||
extends CVPipe<List<CVShape>, List<CVShape>, FilterShapesPipe.FilterShapesPipeParams> {
|
||||
|
||||
List<CVShape> outputList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Runs the process for the pipe.
|
||||
*
|
||||
@@ -32,30 +37,50 @@ public class FilterShapesPipe
|
||||
*/
|
||||
@Override
|
||||
protected List<CVShape> process(List<CVShape> in) {
|
||||
in.removeIf(
|
||||
shape ->
|
||||
shape.shape != params.desiredShape
|
||||
|| shape.contour.getArea() > params.maxArea
|
||||
|| shape.contour.getArea() < params.minArea
|
||||
|| shape.contour.getPerimeter() > params.maxPeri
|
||||
|| shape.contour.getPerimeter() < params.minPeri);
|
||||
return in;
|
||||
outputList.forEach(CVShape::release);
|
||||
outputList.clear();
|
||||
outputList = new ArrayList<>();
|
||||
|
||||
for (var shape : in) {
|
||||
if (!shouldRemove(shape)) outputList.add(shape);
|
||||
}
|
||||
|
||||
return outputList;
|
||||
}
|
||||
|
||||
private boolean shouldRemove(CVShape shape) {
|
||||
return shape.shape != params.desiredShape
|
||||
|| shape.contour.getArea() / params.getFrameStaticProperties().imageArea > params.maxArea
|
||||
|| shape.contour.getArea() / params.getFrameStaticProperties().imageArea < params.minArea
|
||||
|| shape.contour.getPerimeter() > params.maxPeri
|
||||
|| shape.contour.getPerimeter() < params.minPeri;
|
||||
}
|
||||
|
||||
public static class FilterShapesPipeParams {
|
||||
private final ContourShape desiredShape;
|
||||
private final FrameStaticProperties frameStaticProperties;
|
||||
private final double minArea;
|
||||
private final double maxArea;
|
||||
private final double minPeri;
|
||||
private final double maxPeri;
|
||||
|
||||
public FilterShapesPipeParams(
|
||||
ContourShape desiredShape, double minArea, double maxArea, double minPeri, double maxPeri) {
|
||||
ContourShape desiredShape,
|
||||
double minArea,
|
||||
double maxArea,
|
||||
double minPeri,
|
||||
double maxPeri,
|
||||
FrameStaticProperties frameStaticProperties) {
|
||||
this.desiredShape = desiredShape;
|
||||
this.minArea = minArea;
|
||||
this.maxArea = maxArea;
|
||||
this.minPeri = minPeri;
|
||||
this.maxPeri = maxPeri;
|
||||
this.frameStaticProperties = frameStaticProperties;
|
||||
}
|
||||
|
||||
public FrameStaticProperties getFrameStaticProperties() {
|
||||
return frameStaticProperties;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.opencv.imgproc.Moments;
|
||||
import org.photonvision.vision.opencv.CVShape;
|
||||
import org.photonvision.vision.opencv.Contour;
|
||||
import org.photonvision.vision.opencv.ContourShape;
|
||||
import org.photonvision.vision.pipe.CVPipe;
|
||||
|
||||
public class FindCirclesPipe
|
||||
@@ -49,6 +49,10 @@ public class FindCirclesPipe
|
||||
circles.release();
|
||||
List<CVShape> output = new ArrayList<>();
|
||||
|
||||
var diag = params.diagonalLengthPx;
|
||||
var minRadius = (int) (params.minRadius * diag / 100.0);
|
||||
var maxRadius = (int) (params.maxRadius * diag / 100.0);
|
||||
|
||||
Imgproc.HoughCircles(
|
||||
in.getLeft(),
|
||||
circles,
|
||||
@@ -64,10 +68,15 @@ public class FindCirclesPipe
|
||||
params.minDist,
|
||||
params.maxCannyThresh,
|
||||
params.accuracy,
|
||||
params.minRadius,
|
||||
params.maxRadius);
|
||||
minRadius,
|
||||
maxRadius);
|
||||
// Great, we now found the center point of the circle and it's radius, but we have no idea what
|
||||
// contour it corresponds to
|
||||
// Each contour can only match to one circle, so we keep a list of unmatched contours around and
|
||||
// only match against them
|
||||
// This does mean that contours closer than allowableThreshold aren't matched to anything if
|
||||
// there's a 'better' option
|
||||
var unmatchedContours = in.getRight();
|
||||
for (int x = 0; x < circles.cols(); x++) {
|
||||
// Grab the current circle we are looking at
|
||||
double[] c = circles.get(0, x);
|
||||
@@ -75,17 +84,26 @@ public class FindCirclesPipe
|
||||
double x_center = c[0];
|
||||
double y_center = c[1];
|
||||
|
||||
for (Contour contour : in.getRight()) {
|
||||
for (Contour contour : unmatchedContours) {
|
||||
// Grab the moments of the current contour
|
||||
Moments mu = contour.getMoments();
|
||||
// Determine if the contour is within the threshold of the detected circle
|
||||
// NOTE: This means that the centroid of the contour must be within the "allowable
|
||||
// threshold"
|
||||
// of the center of the circle
|
||||
if (Math.abs(x_center - (mu.m10 / mu.m00)) <= params.allowableThreshold
|
||||
&& Math.abs(y_center - (mu.m01 / mu.m00)) <= params.allowableThreshold) {
|
||||
// If it is, then add it to the output array
|
||||
output.add(new CVShape(contour, ContourShape.Circle));
|
||||
output.add(new CVShape(contour, new Point(c[0], c[1]), c[2]));
|
||||
unmatchedContours.remove(contour);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Release everything we don't use
|
||||
for (var c : unmatchedContours) c.release();
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -96,6 +114,7 @@ public class FindCirclesPipe
|
||||
private final int minDist;
|
||||
private final int maxCannyThresh;
|
||||
private final int accuracy;
|
||||
private final double diagonalLengthPx;
|
||||
|
||||
/*
|
||||
* @params minDist - Minimum distance between the centers of the detected circles.
|
||||
@@ -114,13 +133,15 @@ public class FindCirclesPipe
|
||||
int minDist,
|
||||
int maxRadius,
|
||||
int maxCannyThresh,
|
||||
int accuracy) {
|
||||
int accuracy,
|
||||
double diagonalLengthPx) {
|
||||
this.allowableThreshold = allowableThreshold;
|
||||
this.minRadius = minRadius;
|
||||
this.maxRadius = maxRadius;
|
||||
this.minDist = minDist;
|
||||
this.maxCannyThresh = maxCannyThresh;
|
||||
this.accuracy = accuracy;
|
||||
this.diagonalLengthPx = diagonalLengthPx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.photonvision.vision.pipe.CVPipe;
|
||||
|
||||
public class FindPolygonPipe
|
||||
extends CVPipe<List<Contour>, List<CVShape>, FindPolygonPipe.FindPolygonPipeParams> {
|
||||
private final MatOfPoint2f approx = new MatOfPoint2f();
|
||||
List<CVShape> shapeList = new ArrayList<>();
|
||||
|
||||
/*
|
||||
* Runs the process for the pipe.
|
||||
@@ -38,18 +38,20 @@ public class FindPolygonPipe
|
||||
*/
|
||||
@Override
|
||||
protected List<CVShape> process(List<Contour> in) {
|
||||
// List containing all the output shapes
|
||||
List<CVShape> output = new ArrayList<>();
|
||||
shapeList.forEach(CVShape::release);
|
||||
shapeList.clear();
|
||||
shapeList = new ArrayList<>();
|
||||
|
||||
for (Contour contour : in) output.add(getShape(contour));
|
||||
for (Contour contour : in) {
|
||||
shapeList.add(getShape(contour));
|
||||
}
|
||||
|
||||
return output;
|
||||
return shapeList;
|
||||
}
|
||||
|
||||
private CVShape getShape(Contour in) {
|
||||
|
||||
int corners = getCorners(in);
|
||||
corners = getCorners(in);
|
||||
|
||||
/*The contourShape enum has predefined shapes for Circles, Triangles, and Quads
|
||||
meaning any shape not fitting in those predefined shapes must be a custom shape.
|
||||
@@ -70,14 +72,11 @@ public class FindPolygonPipe
|
||||
}
|
||||
|
||||
private int getCorners(Contour contour) {
|
||||
// Release previous approx
|
||||
approx.release();
|
||||
Imgproc.approxPolyDP(
|
||||
contour.getMat2f(),
|
||||
approx,
|
||||
// Converts an accuracy percentage between 1-100 to an epsilon
|
||||
params.accuracyPercentage / 600.0 * Imgproc.arcLength(contour.getMat2f(), true),
|
||||
true);
|
||||
var approx =
|
||||
contour.getApproxPolyDp(
|
||||
(100 - params.accuracyPercentage) / 100.0 * Imgproc.arcLength(contour.getMat2f(), true),
|
||||
true);
|
||||
|
||||
// The height of the resultant approximation is the number of vertices
|
||||
return (int) approx.size().height;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ public class SpeckleRejectPipe
|
||||
for (Contour c : in) {
|
||||
if (c.getArea() >= minAllowedArea) {
|
||||
m_despeckledContours.add(c);
|
||||
} else {
|
||||
c.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Point;
|
||||
import org.photonvision.common.util.math.MathUtils;
|
||||
import org.photonvision.raspi.PicamJNI;
|
||||
import org.photonvision.vision.frame.Frame;
|
||||
import org.photonvision.vision.opencv.*;
|
||||
import org.photonvision.vision.pipe.CVPipe.CVPipeResult;
|
||||
@@ -38,13 +40,11 @@ public class ColoredShapePipeline
|
||||
private final RotateImagePipe rotateImagePipe = new RotateImagePipe();
|
||||
private final ErodeDilatePipe erodeDilatePipe = new ErodeDilatePipe();
|
||||
private final HSVPipe hsvPipe = new HSVPipe();
|
||||
private final OutputMatPipe outputMatPipe = new OutputMatPipe();
|
||||
private final SpeckleRejectPipe speckleRejectPipe = new SpeckleRejectPipe();
|
||||
private final FindContoursPipe findContoursPipe = new FindContoursPipe();
|
||||
private final FindPolygonPipe findPolygonPipe = new FindPolygonPipe();
|
||||
private final FindCirclesPipe findCirclesPipe = new FindCirclesPipe();
|
||||
private final FilterShapesPipe filterShapesPipe = new FilterShapesPipe();
|
||||
private final GroupContoursPipe groupContoursPipe = new GroupContoursPipe();
|
||||
private final SortContoursPipe sortContoursPipe = new SortContoursPipe();
|
||||
private final Collect2dTargetsPipe collect2dTargetsPipe = new Collect2dTargetsPipe();
|
||||
private final CornerDetectionPipe cornerDetectionPipe = new CornerDetectionPipe();
|
||||
@@ -54,7 +54,6 @@ public class ColoredShapePipeline
|
||||
private final Draw3dTargetsPipe draw3dTargetsPipe = new Draw3dTargetsPipe();
|
||||
private final CalculateFPSPipe calculateFPSPipe = new CalculateFPSPipe();
|
||||
|
||||
private final Mat rawInputMat = new Mat();
|
||||
private final Point[] rectPoints = new Point[4];
|
||||
|
||||
public ColoredShapePipeline() {
|
||||
@@ -88,9 +87,6 @@ public class ColoredShapePipeline
|
||||
new HSVPipe.HSVParams(settings.hsvHue, settings.hsvSaturation, settings.hsvValue);
|
||||
hsvPipe.setParams(hsvParams);
|
||||
|
||||
OutputMatPipe.OutputMatParams outputMatParams = new OutputMatPipe.OutputMatParams();
|
||||
outputMatPipe.setParams(outputMatParams);
|
||||
|
||||
SpeckleRejectPipe.SpeckleRejectParams speckleRejectParams =
|
||||
new SpeckleRejectPipe.SpeckleRejectParams(settings.contourSpecklePercentage);
|
||||
speckleRejectPipe.setParams(speckleRejectParams);
|
||||
@@ -105,28 +101,25 @@ public class ColoredShapePipeline
|
||||
|
||||
FindCirclesPipe.FindCirclePipeParams findCirclePipeParams =
|
||||
new FindCirclesPipe.FindCirclePipeParams(
|
||||
settings.allowableThreshold,
|
||||
settings.minRadius,
|
||||
settings.circleDetectThreshold,
|
||||
settings.contourRadius.getFirst(),
|
||||
settings.minDist,
|
||||
settings.maxRadius,
|
||||
settings.contourRadius.getSecond(),
|
||||
settings.maxCannyThresh,
|
||||
settings.accuracy);
|
||||
settings.circleAccuracy,
|
||||
Math.hypot(frameStaticProperties.imageWidth, frameStaticProperties.imageHeight));
|
||||
findCirclesPipe.setParams(findCirclePipeParams);
|
||||
|
||||
FilterShapesPipe.FilterShapesPipeParams filterShapesPipeParams =
|
||||
new FilterShapesPipe.FilterShapesPipeParams(
|
||||
settings.desiredShape,
|
||||
settings.minArea,
|
||||
settings.maxArea,
|
||||
settings.minPeri,
|
||||
settings.maxPeri);
|
||||
settings.contourShape,
|
||||
settings.contourArea.getFirst(),
|
||||
settings.contourArea.getSecond(),
|
||||
settings.contourPerimeter.getFirst(),
|
||||
settings.contourPerimeter.getSecond(),
|
||||
frameStaticProperties);
|
||||
filterShapesPipe.setParams(filterShapesPipeParams);
|
||||
|
||||
GroupContoursPipe.GroupContoursParams groupContoursParams =
|
||||
new GroupContoursPipe.GroupContoursParams(
|
||||
settings.contourGroupingMode, settings.contourIntersection);
|
||||
groupContoursPipe.setParams(groupContoursParams);
|
||||
|
||||
SortContoursPipe.SortContoursParams sortContoursParams =
|
||||
new SortContoursPipe.SortContoursParams(
|
||||
settings.contourSortMode,
|
||||
@@ -193,17 +186,41 @@ public class ColoredShapePipeline
|
||||
protected CVPipelineResult process(Frame frame, ColoredShapePipelineSettings settings) {
|
||||
long sumPipeNanosElapsed = 0L;
|
||||
|
||||
var rotateImageResult = rotateImagePipe.run(frame.image.getMat());
|
||||
sumPipeNanosElapsed += rotateImageResult.nanosElapsed;
|
||||
CVPipeResult<Mat> hsvPipeResult;
|
||||
Mat rawInputMat;
|
||||
if (frame.image.getMat().channels() != 1) {
|
||||
var rotateImageResult = rotateImagePipe.run(frame.image.getMat());
|
||||
sumPipeNanosElapsed = rotateImageResult.nanosElapsed;
|
||||
|
||||
rawInputMat.release();
|
||||
frame.image.getMat().copyTo(rawInputMat);
|
||||
rawInputMat = frame.image.getMat();
|
||||
|
||||
var erodeDilateResult = erodeDilatePipe.run(rawInputMat);
|
||||
sumPipeNanosElapsed += erodeDilateResult.nanosElapsed;
|
||||
hsvPipeResult = hsvPipe.run(rawInputMat);
|
||||
sumPipeNanosElapsed += hsvPipeResult.nanosElapsed;
|
||||
} else {
|
||||
// Try to copy the color frame.
|
||||
long inputMatPtr = PicamJNI.grabFrame(true);
|
||||
if (inputMatPtr != 0) {
|
||||
// If we grabbed it (in color copy mode), make a new Mat of it
|
||||
rawInputMat = new Mat(inputMatPtr);
|
||||
} else {
|
||||
// Otherwise, the input mat is frame we got from the camera
|
||||
rawInputMat = frame.image.getMat();
|
||||
}
|
||||
|
||||
CVPipeResult<Mat> hsvPipeResult = hsvPipe.run(rawInputMat);
|
||||
sumPipeNanosElapsed += hsvPipeResult.nanosElapsed;
|
||||
// We can skip a few steps if the image is single channel because we've already done them on
|
||||
// the GPU
|
||||
hsvPipeResult = new CVPipeResult<>();
|
||||
hsvPipeResult.output = frame.image.getMat();
|
||||
hsvPipeResult.nanosElapsed = MathUtils.wpiNanoTime() - frame.timestampNanos;
|
||||
|
||||
sumPipeNanosElapsed += hsvPipeResult.nanosElapsed;
|
||||
}
|
||||
|
||||
// var erodeDilateResult = erodeDilatePipe.run(rawInputMat);
|
||||
// sumPipeNanosElapsed += erodeDilateResult.nanosElapsed;
|
||||
//
|
||||
// CVPipeResult<Mat> hsvPipeResult = hsvPipe.run(rawInputMat);
|
||||
// sumPipeNanosElapsed += hsvPipeResult.nanosElapsed;
|
||||
|
||||
CVPipeResult<List<Contour>> findContoursResult = findContoursPipe.run(hsvPipeResult.output);
|
||||
sumPipeNanosElapsed += findContoursResult.nanosElapsed;
|
||||
@@ -212,8 +229,8 @@ public class ColoredShapePipeline
|
||||
speckleRejectPipe.run(findContoursResult.output);
|
||||
sumPipeNanosElapsed += speckleRejectResult.nanosElapsed;
|
||||
|
||||
List<CVShape> shapes;
|
||||
if (settings.desiredShape == ContourShape.Circle) {
|
||||
List<CVShape> shapes = null;
|
||||
if (settings.contourShape == ContourShape.Circle) {
|
||||
CVPipeResult<List<CVShape>> findCirclesResult =
|
||||
findCirclesPipe.run(Pair.of(hsvPipeResult.output, speckleRejectResult.output));
|
||||
sumPipeNanosElapsed += findCirclesResult.nanosElapsed;
|
||||
@@ -228,15 +245,11 @@ public class ColoredShapePipeline
|
||||
CVPipeResult<List<CVShape>> filterShapeResult = filterShapesPipe.run(shapes);
|
||||
sumPipeNanosElapsed += filterShapeResult.nanosElapsed;
|
||||
|
||||
CVPipeResult<List<PotentialTarget>> groupContoursResult =
|
||||
groupContoursPipe.run(
|
||||
filterShapeResult.output.stream()
|
||||
.map(CVShape::getContour)
|
||||
.collect(Collectors.toList()));
|
||||
sumPipeNanosElapsed += groupContoursResult.nanosElapsed;
|
||||
|
||||
CVPipeResult<List<PotentialTarget>> sortContoursResult =
|
||||
sortContoursPipe.run(groupContoursResult.output);
|
||||
sortContoursPipe.run(
|
||||
filterShapeResult.output.stream()
|
||||
.map(shape -> new PotentialTarget(shape.getContour(), shape))
|
||||
.collect(Collectors.toList()));
|
||||
sumPipeNanosElapsed += sortContoursResult.nanosElapsed;
|
||||
|
||||
CVPipeResult<List<TrackedTarget>> collect2dTargetsResult =
|
||||
@@ -245,7 +258,7 @@ public class ColoredShapePipeline
|
||||
|
||||
List<TrackedTarget> targetList;
|
||||
|
||||
if (settings.solvePNPEnabled && settings.desiredShape == ContourShape.Circle) {
|
||||
if (settings.solvePNPEnabled && settings.contourShape == ContourShape.Circle) {
|
||||
var cornerDetectionResult = cornerDetectionPipe.run(collect2dTargetsResult.output);
|
||||
collect2dTargetsResult.output.forEach(
|
||||
shape -> {
|
||||
@@ -262,37 +275,6 @@ public class ColoredShapePipeline
|
||||
targetList = collect2dTargetsResult.output;
|
||||
}
|
||||
|
||||
// Draw 2D Crosshair on input and output
|
||||
var draw2dCrosshairResultOnInput = draw2dCrosshairPipe.run(Pair.of(rawInputMat, targetList));
|
||||
sumPipeNanosElapsed += draw2dCrosshairResultOnInput.nanosElapsed;
|
||||
|
||||
var draw2dCrosshairResultOnOutput =
|
||||
draw2dCrosshairPipe.run(Pair.of(hsvPipeResult.output, targetList));
|
||||
sumPipeNanosElapsed += draw2dCrosshairResultOnOutput.nanosElapsed;
|
||||
|
||||
// Draw 2D contours on input and output
|
||||
var draw2dContoursResultOnInput =
|
||||
draw2DTargetsPipe.run(Pair.of(rawInputMat, collect2dTargetsResult.output));
|
||||
sumPipeNanosElapsed += draw2dContoursResultOnInput.nanosElapsed;
|
||||
|
||||
var draw2dContoursResultOnOutput =
|
||||
draw2DTargetsPipe.run(Pair.of(hsvPipeResult.output, collect2dTargetsResult.output));
|
||||
sumPipeNanosElapsed += draw2dContoursResultOnOutput.nanosElapsed;
|
||||
|
||||
if (settings.solvePNPEnabled && settings.desiredShape == ContourShape.Circle) {
|
||||
var drawOnInputResult =
|
||||
draw3dTargetsPipe.run(Pair.of(rawInputMat, collect2dTargetsResult.output));
|
||||
sumPipeNanosElapsed += drawOnInputResult.nanosElapsed;
|
||||
|
||||
var drawOnOutputResult =
|
||||
draw3dTargetsPipe.run(Pair.of(hsvPipeResult.output, collect2dTargetsResult.output));
|
||||
sumPipeNanosElapsed += drawOnOutputResult.nanosElapsed;
|
||||
}
|
||||
|
||||
// Convert single-channel HSV output mat to 3-channel BGR in preparation for streaming
|
||||
var outputMatPipeResult = outputMatPipe.run(hsvPipeResult.output);
|
||||
sumPipeNanosElapsed += outputMatPipeResult.nanosElapsed;
|
||||
|
||||
var fpsResult = calculateFPSPipe.run(null);
|
||||
var fps = fpsResult.output;
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ package org.photonvision.vision.pipeline;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
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;
|
||||
@@ -27,19 +29,15 @@ import org.photonvision.vision.pipe.impl.CornerDetectionPipe;
|
||||
|
||||
@JsonTypeName("ColoredShapePipelineSettings")
|
||||
public class ColoredShapePipelineSettings extends AdvancedPipelineSettings {
|
||||
public ContourShape desiredShape = ContourShape.Triangle;
|
||||
public double minArea = Integer.MIN_VALUE;
|
||||
public double maxArea = Integer.MAX_VALUE;
|
||||
public double minPeri = Integer.MIN_VALUE;
|
||||
public double maxPeri = Integer.MAX_VALUE;
|
||||
public ContourShape contourShape = ContourShape.Triangle;
|
||||
public DoubleCouple contourPerimeter = new DoubleCouple(0, Double.MAX_VALUE);
|
||||
public double accuracyPercentage = 10.0;
|
||||
// Circle detection
|
||||
public int allowableThreshold = 5;
|
||||
public int minRadius = 0;
|
||||
public int maxRadius = 0;
|
||||
public int minDist = 10;
|
||||
public int circleDetectThreshold = 5;
|
||||
public IntegerCouple contourRadius = new IntegerCouple(0, 100);
|
||||
public int minDist = 20;
|
||||
public int maxCannyThresh = 90;
|
||||
public int accuracy = 20;
|
||||
public int circleAccuracy = 20;
|
||||
// how many contours to attempt to group (Single, Dual)
|
||||
public ContourGroupingMode contourGroupingMode = ContourGroupingMode.Single;
|
||||
|
||||
@@ -71,61 +69,50 @@ public class ColoredShapePipelineSettings extends AdvancedPipelineSettings {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
ColoredShapePipelineSettings that = (ColoredShapePipelineSettings) o;
|
||||
return Double.compare(that.minArea, minArea) == 0
|
||||
&& Double.compare(that.maxArea, maxArea) == 0
|
||||
&& Double.compare(that.minPeri, minPeri) == 0
|
||||
&& Double.compare(that.maxPeri, maxPeri) == 0
|
||||
&& Double.compare(that.accuracyPercentage, accuracyPercentage) == 0
|
||||
&& allowableThreshold == that.allowableThreshold
|
||||
&& minRadius == that.minRadius
|
||||
&& maxRadius == that.maxRadius
|
||||
return Double.compare(that.accuracyPercentage, accuracyPercentage) == 0
|
||||
&& circleDetectThreshold == that.circleDetectThreshold
|
||||
&& minDist == that.minDist
|
||||
&& maxCannyThresh == that.maxCannyThresh
|
||||
&& accuracy == that.accuracy
|
||||
&& solvePNPEnabled == that.solvePNPEnabled
|
||||
&& circleAccuracy == that.circleAccuracy
|
||||
&& cornerDetectionUseConvexHulls == that.cornerDetectionUseConvexHulls
|
||||
&& cornerDetectionExactSideCount == that.cornerDetectionExactSideCount
|
||||
&& cornerDetectionSideCount == that.cornerDetectionSideCount
|
||||
&& Double.compare(that.cornerDetectionAccuracyPercentage, cornerDetectionAccuracyPercentage)
|
||||
== 0
|
||||
&& desiredShape == that.desiredShape
|
||||
&& erode == that.erode
|
||||
&& dilate == that.dilate
|
||||
&& contourShape == that.contourShape
|
||||
&& Objects.equals(contourArea, that.contourArea)
|
||||
&& Objects.equals(contourPerimeter, that.contourPerimeter)
|
||||
&& Objects.equals(contourRadius, that.contourRadius)
|
||||
&& contourGroupingMode == that.contourGroupingMode
|
||||
&& contourIntersection == that.contourIntersection
|
||||
&& Objects.equals(cameraCalibration, that.cameraCalibration)
|
||||
&& Objects.equals(targetModel, that.targetModel)
|
||||
&& cornerDetectionStrategy == that.cornerDetectionStrategy
|
||||
&& erode == that.erode
|
||||
&& dilate == that.dilate;
|
||||
&& cornerDetectionStrategy == that.cornerDetectionStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
super.hashCode(),
|
||||
desiredShape,
|
||||
minArea,
|
||||
maxArea,
|
||||
minPeri,
|
||||
maxPeri,
|
||||
contourShape,
|
||||
contourArea,
|
||||
contourPerimeter,
|
||||
accuracyPercentage,
|
||||
allowableThreshold,
|
||||
minRadius,
|
||||
maxRadius,
|
||||
circleDetectThreshold,
|
||||
contourRadius,
|
||||
minDist,
|
||||
maxCannyThresh,
|
||||
accuracy,
|
||||
circleAccuracy,
|
||||
contourGroupingMode,
|
||||
contourIntersection,
|
||||
solvePNPEnabled,
|
||||
cameraCalibration,
|
||||
targetModel,
|
||||
cornerDetectionStrategy,
|
||||
cornerDetectionUseConvexHulls,
|
||||
cornerDetectionExactSideCount,
|
||||
cornerDetectionSideCount,
|
||||
cornerDetectionAccuracyPercentage,
|
||||
erode,
|
||||
dilate,
|
||||
accuracy);
|
||||
dilate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,9 @@ package org.photonvision.vision.pipeline;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import org.photonvision.common.util.numbers.DoubleCouple;
|
||||
import org.photonvision.vision.processes.PipelineManager;
|
||||
import org.photonvision.vision.target.RobotOffsetPointMode;
|
||||
|
||||
@JsonTypeName("DriverModePipelineSettings")
|
||||
public class DriverModePipelineSettings extends CVPipelineSettings {
|
||||
public RobotOffsetPointMode offsetPointMode = RobotOffsetPointMode.None;
|
||||
public DoubleCouple offsetPoint = new DoubleCouple();
|
||||
|
||||
public DriverModePipelineSettings() {
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.photonvision.vision.frame.Frame;
|
||||
import org.photonvision.vision.frame.FrameStaticProperties;
|
||||
import org.photonvision.vision.opencv.CVMat;
|
||||
import org.photonvision.vision.opencv.ContourShape;
|
||||
import org.photonvision.vision.opencv.DualOffsetValues;
|
||||
import org.photonvision.vision.pipe.impl.*;
|
||||
import org.photonvision.vision.pipeline.result.CVPipelineResult;
|
||||
@@ -108,7 +109,10 @@ public class OutputStreamPipeline {
|
||||
sumPipeNanosElapsed += pipeProfileNanos[4] = draw2dCrosshairResultOnOutput.nanosElapsed;
|
||||
|
||||
// Draw 3D Targets on input and output if necessary
|
||||
if (settings.solvePNPEnabled) {
|
||||
if (settings.solvePNPEnabled
|
||||
|| (settings.solvePNPEnabled
|
||||
&& settings instanceof ColoredShapePipelineSettings
|
||||
&& ((ColoredShapePipelineSettings) settings).contourShape == ContourShape.Circle)) {
|
||||
var drawOnInputResult = draw3dTargetsPipe.run(Pair.of(inMat, targetsToDraw));
|
||||
sumPipeNanosElapsed += pipeProfileNanos[7] = drawOnInputResult.nanosElapsed;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ public enum PipelineType {
|
||||
Calib3d(-2, Calibrate3dPipeline.class),
|
||||
DriverMode(-1, DriverModePipeline.class),
|
||||
Reflective(0, ReflectivePipeline.class),
|
||||
ColoredShape(0, ColoredShapePipeline.class);
|
||||
ColoredShape(1, ColoredShapePipeline.class);
|
||||
|
||||
public final int baseIndex;
|
||||
public final Class clazz;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.photonvision.vision.processes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import org.photonvision.common.configuration.CameraConfiguration;
|
||||
@@ -243,23 +244,37 @@ public class PipelineManager {
|
||||
}
|
||||
|
||||
public CVPipelineSettings addPipeline(PipelineType type, String nickname) {
|
||||
var added = createSettingsForType(type, nickname);
|
||||
if (added == null) {
|
||||
logger.error("Cannot add null pipeline!");
|
||||
return null;
|
||||
}
|
||||
addPipelineInternal(added);
|
||||
reassignIndexes();
|
||||
return added;
|
||||
}
|
||||
|
||||
private CVPipelineSettings createSettingsForType(PipelineType type, String nickname) {
|
||||
CVPipelineSettings newSettings;
|
||||
switch (type) {
|
||||
case Reflective:
|
||||
{
|
||||
var added = new ReflectivePipelineSettings();
|
||||
added.pipelineNickname = nickname;
|
||||
addPipelineInternal(added);
|
||||
return added;
|
||||
}
|
||||
case ColoredShape:
|
||||
{
|
||||
var added = new ColoredShapePipelineSettings();
|
||||
addPipelineInternal(added);
|
||||
added.pipelineNickname = nickname;
|
||||
return added;
|
||||
}
|
||||
default:
|
||||
{
|
||||
logger.error("Got invalid pipeline type: " + type.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
reassignIndexes();
|
||||
return null;
|
||||
}
|
||||
|
||||
private void addPipelineInternal(CVPipelineSettings settings) {
|
||||
@@ -268,30 +283,41 @@ public class PipelineManager {
|
||||
reassignIndexes();
|
||||
}
|
||||
|
||||
private void removePipelineInternal(int index) {
|
||||
/**
|
||||
* Remove a pipeline settings at the given index and return the new current index
|
||||
*
|
||||
* @param index The idx to remove
|
||||
*/
|
||||
private int removePipelineInternal(int index) {
|
||||
userPipelineSettings.remove(index);
|
||||
currentPipelineIndex = Math.min(index, userPipelineSettings.size() - 1);
|
||||
reassignIndexes();
|
||||
return currentPipelineIndex;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
this.setPipelineInternal(index);
|
||||
}
|
||||
|
||||
public void removePipeline(int index) {
|
||||
public int removePipeline(int index) {
|
||||
if (index < 0) {
|
||||
return;
|
||||
return currentPipelineIndex;
|
||||
}
|
||||
// TODO should we block/lock on a mutex?
|
||||
removePipelineInternal(index);
|
||||
setIndex(currentPipelineIndex);
|
||||
return removePipelineInternal(index);
|
||||
}
|
||||
|
||||
public void renameCurrentPipeline(String newName) {
|
||||
getCurrentPipelineSettings().pipelineNickname = newName;
|
||||
}
|
||||
|
||||
public void duplicatePipeline(int index) {
|
||||
/**
|
||||
* Duplicate a pipeline at a given index
|
||||
*
|
||||
* @param index the index of the target pipeline
|
||||
* @return The new index
|
||||
*/
|
||||
public int duplicatePipeline(int index) {
|
||||
var settings = userPipelineSettings.get(index);
|
||||
var newSettings = settings.clone();
|
||||
newSettings.pipelineNickname =
|
||||
@@ -300,24 +326,81 @@ public class PipelineManager {
|
||||
logger.debug("Duplicating pipe " + index + " to " + newSettings.pipelineNickname);
|
||||
userPipelineSettings.add(newSettings);
|
||||
reassignIndexes();
|
||||
|
||||
// Now we look for the index of the new pipeline and return it
|
||||
return userPipelineSettings.indexOf(newSettings);
|
||||
}
|
||||
|
||||
private static String createUniqueName(
|
||||
String nickname, List<CVPipelineSettings> existingSettings) {
|
||||
int index = 0;
|
||||
String uniqueName = nickname;
|
||||
while (true) {
|
||||
String finalUniqueName = uniqueName;
|
||||
String finalUniqueName = uniqueName; // To get around lambda capture
|
||||
var conflictingName =
|
||||
existingSettings.stream().anyMatch(it -> it.pipelineNickname.equals(finalUniqueName));
|
||||
if (!conflictingName) return uniqueName;
|
||||
index++;
|
||||
uniqueName = nickname + " (" + index + ")";
|
||||
|
||||
if (index == 6
|
||||
&& existingSettings.stream()
|
||||
.noneMatch(it -> it.pipelineNickname.equals(nickname + "( dQw4w9WgXcQ )")))
|
||||
return nickname + "( dQw4w9WgXcQ )";
|
||||
if (!conflictingName) {
|
||||
// If no conflict, we're done
|
||||
return uniqueName;
|
||||
} else {
|
||||
// Otherwise, we need to add a suffix to the name
|
||||
// If the string doesn't already end in "([0-9]*)", we'll add it
|
||||
// If it does, we'll increment the number in the suffix
|
||||
|
||||
if (uniqueName.matches(".*\\([0-9]*\\)")) {
|
||||
// Because java strings are immutable, we have to do this curstedness
|
||||
// This is like doing "New pipeline (" + 2 + ")"
|
||||
|
||||
var parenStart = uniqueName.lastIndexOf('(');
|
||||
var parenEnd = uniqueName.length() - 1;
|
||||
var number = Integer.parseInt(uniqueName.substring(parenStart + 1, parenEnd)) + 1;
|
||||
|
||||
uniqueName = uniqueName.substring(0, parenStart + 1) + number + ")";
|
||||
} else {
|
||||
uniqueName += " (1)";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void changePipelineType(int newType) {
|
||||
// Find the PipelineType proposed
|
||||
// To do this we look at all the PipelineType entries and look for one with matching
|
||||
// base indexes
|
||||
PipelineType type =
|
||||
Arrays.stream(PipelineType.values())
|
||||
.filter(it -> it.baseIndex == newType)
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
if (type == null) {
|
||||
logger.error("Could not match type " + newType + " to a PipelineType!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (type.baseIndex == getCurrentPipelineSettings().pipelineType.baseIndex) {
|
||||
logger.debug(
|
||||
"Not changing settings as "
|
||||
+ type
|
||||
+ " and "
|
||||
+ getCurrentPipelineSettings().pipelineType
|
||||
+ " are identical!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Our new settings will be totally nuked, but that's ok
|
||||
// We *could* set things in common between the two, if we want
|
||||
// But they're different enough it shouldn't be an issue
|
||||
var name = getCurrentPipelineSettings().pipelineNickname;
|
||||
var newSettings = createSettingsForType(type, name);
|
||||
|
||||
var idx = currentPipelineIndex;
|
||||
if (idx < 0) {
|
||||
logger.error("Cannot replace non-user pipeline!");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Adding new pipe of type " + type.toString() + " at idx " + idx);
|
||||
userPipelineSettings.set(idx, newSettings);
|
||||
setPipelineInternal(idx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,11 +257,15 @@ public class VisionModule {
|
||||
try {
|
||||
var osr = outputStreamPipeline.process(inputFrame, outputFrame, settings, targets);
|
||||
consumeFpsLimitedResult(osr);
|
||||
} catch (Exception e) {
|
||||
// Never die
|
||||
logger.error("Exception while running stream runnable!", e);
|
||||
}
|
||||
try {
|
||||
inputFrame.release();
|
||||
outputFrame.release();
|
||||
} catch (Exception e) {
|
||||
// Never die
|
||||
logger.error("Exception in stream runnable!", e);
|
||||
logger.error("Exception freeing frames", e);
|
||||
}
|
||||
} else {
|
||||
// busy wait! hurray!
|
||||
|
||||
@@ -89,7 +89,8 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
|
||||
case "deleteCurrPipeline":
|
||||
var indexToDelete = parentModule.pipelineManager.getCurrentPipelineIndex();
|
||||
logger.info("Deleting current pipe at index " + indexToDelete);
|
||||
parentModule.pipelineManager.removePipeline(indexToDelete);
|
||||
int newIndex = parentModule.pipelineManager.removePipeline(indexToDelete);
|
||||
parentModule.setPipeline(newIndex);
|
||||
parentModule.saveAndBroadcastAll();
|
||||
return;
|
||||
case "changePipeline": // change active pipeline
|
||||
@@ -110,7 +111,8 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
|
||||
parentModule.takeCalibrationSnapshot();
|
||||
return;
|
||||
case "duplicatePipeline":
|
||||
parentModule.pipelineManager.duplicatePipeline((Integer) newPropValue);
|
||||
int idx = parentModule.pipelineManager.duplicatePipeline((Integer) newPropValue);
|
||||
parentModule.setPipeline(idx);
|
||||
parentModule.saveAndBroadcastAll();
|
||||
return;
|
||||
case "robotOffsetPoint":
|
||||
@@ -154,6 +156,10 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
|
||||
}
|
||||
}
|
||||
return;
|
||||
case "changePipelineType":
|
||||
parentModule.pipelineManager.changePipelineType((Integer) newPropValue);
|
||||
parentModule.saveAndBroadcastAll();
|
||||
return;
|
||||
}
|
||||
|
||||
// special case for camera settables
|
||||
@@ -183,8 +189,8 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber {
|
||||
var actual = new DoubleCouple(orig.get(0), orig.get(1));
|
||||
propField.set(currentSettings, actual);
|
||||
} else if (propType.isAssignableFrom(IntegerCouple.class)) {
|
||||
var orig = (ArrayList<Integer>) newPropValue;
|
||||
var actual = new IntegerCouple(orig.get(0), orig.get(1));
|
||||
var orig = (ArrayList<Number>) newPropValue;
|
||||
var actual = new IntegerCouple(orig.get(0).intValue(), orig.get(1).intValue());
|
||||
propField.set(currentSettings, actual);
|
||||
} else if (propType.equals(Double.TYPE)) {
|
||||
propField.setDouble(currentSettings, ((Number) newPropValue).doubleValue());
|
||||
|
||||
@@ -22,6 +22,7 @@ import edu.wpi.cscore.UsbCameraInfo;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import org.photonvision.common.configuration.CameraConfiguration;
|
||||
import org.photonvision.common.configuration.ConfigManager;
|
||||
import org.photonvision.common.dataflow.DataChangeService;
|
||||
@@ -144,6 +145,11 @@ public class VisionSourceManager {
|
||||
"After matching, "
|
||||
+ unmatchedLoadedConfigs.size()
|
||||
+ " configs remained unmatched. Is your camera disconnected?");
|
||||
logger.warn(
|
||||
"Unloaded configs: "
|
||||
+ unmatchedLoadedConfigs.stream()
|
||||
.map(it -> it.nickname)
|
||||
.collect(Collectors.joining()));
|
||||
hasWarned = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.photonvision.vision.target;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.opencv.core.RotatedRect;
|
||||
import org.photonvision.vision.opencv.CVShape;
|
||||
import org.photonvision.vision.opencv.Contour;
|
||||
import org.photonvision.vision.opencv.Releasable;
|
||||
|
||||
@@ -27,15 +28,24 @@ public class PotentialTarget implements Releasable {
|
||||
|
||||
public final Contour m_mainContour;
|
||||
public final List<Contour> m_subContours;
|
||||
public final CVShape shape;
|
||||
|
||||
public PotentialTarget(Contour inputContour) {
|
||||
m_mainContour = inputContour;
|
||||
m_subContours = new ArrayList<>(); // empty
|
||||
this(inputContour, List.of());
|
||||
}
|
||||
|
||||
public PotentialTarget(Contour inputContour, List<Contour> subContours) {
|
||||
this(inputContour, subContours, null);
|
||||
}
|
||||
|
||||
public PotentialTarget(Contour inputContour, List<Contour> subContours, CVShape shape) {
|
||||
m_mainContour = inputContour;
|
||||
m_subContours = new ArrayList<>(subContours);
|
||||
this.shape = shape;
|
||||
}
|
||||
|
||||
public PotentialTarget(Contour inputContour, CVShape shape) {
|
||||
this(inputContour, List.of(), shape);
|
||||
}
|
||||
|
||||
public RotatedRect getMinAreaRect() {
|
||||
@@ -53,5 +63,6 @@ public class PotentialTarget implements Releasable {
|
||||
sc.release();
|
||||
}
|
||||
m_subContours.clear();
|
||||
if (shape != null) shape.release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,7 @@ import org.opencv.core.MatOfPoint2f;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.RotatedRect;
|
||||
import org.photonvision.vision.frame.FrameStaticProperties;
|
||||
import org.photonvision.vision.opencv.Contour;
|
||||
import org.photonvision.vision.opencv.DualOffsetValues;
|
||||
import org.photonvision.vision.opencv.Releasable;
|
||||
import org.photonvision.vision.opencv.*;
|
||||
|
||||
public class TrackedTarget implements Releasable {
|
||||
public final Contour m_mainContour;
|
||||
@@ -47,11 +45,15 @@ public class TrackedTarget implements Releasable {
|
||||
|
||||
private Transform2d m_cameraToTarget = new Transform2d();
|
||||
|
||||
private CVShape m_shape;
|
||||
|
||||
private Mat m_cameraRelativeTvec, m_cameraRelativeRvec;
|
||||
|
||||
public TrackedTarget(PotentialTarget origTarget, TargetCalculationParameters params) {
|
||||
public TrackedTarget(
|
||||
PotentialTarget origTarget, TargetCalculationParameters params, CVShape shape) {
|
||||
this.m_mainContour = origTarget.m_mainContour;
|
||||
this.m_subContours = origTarget.m_subContours;
|
||||
this.m_shape = shape;
|
||||
calculateValues(params);
|
||||
}
|
||||
|
||||
@@ -170,13 +172,15 @@ public class TrackedTarget implements Releasable {
|
||||
cameraRelativeRvec.copyTo(this.m_cameraRelativeRvec);
|
||||
}
|
||||
|
||||
public CVShape getShape() {
|
||||
return m_shape;
|
||||
}
|
||||
|
||||
public void setShape(CVShape shape) {
|
||||
this.m_shape = shape;
|
||||
}
|
||||
|
||||
public HashMap<String, Object> toHashMap() {
|
||||
// pitch: 0,
|
||||
// yaw: 0,
|
||||
// skew: 0,
|
||||
// area: 0,
|
||||
// // 3D only
|
||||
// pose: {x: 0, y: 0, rot: 0},
|
||||
var ret = new HashMap<String, Object>();
|
||||
ret.put("pitch", getPitch());
|
||||
ret.put("yaw", getYaw());
|
||||
|
||||
@@ -68,8 +68,8 @@ public class ShapeBenchmarkTest {
|
||||
pipeline.getSettings().outputShowMultipleTargets = true;
|
||||
pipeline.getSettings().contourGroupingMode = ContourGroupingMode.Single;
|
||||
pipeline.getSettings().contourIntersection = ContourIntersectionDirection.Up;
|
||||
pipeline.getSettings().desiredShape = ContourShape.Custom;
|
||||
pipeline.getSettings().allowableThreshold = 10;
|
||||
pipeline.getSettings().contourShape = ContourShape.Custom;
|
||||
pipeline.getSettings().circleDetectThreshold = 10;
|
||||
pipeline.getSettings().accuracyPercentage = 30.0;
|
||||
var frameProvider =
|
||||
new FileFrameProvider(
|
||||
@@ -89,8 +89,8 @@ public class ShapeBenchmarkTest {
|
||||
pipeline.getSettings().outputShowMultipleTargets = true;
|
||||
pipeline.getSettings().contourGroupingMode = ContourGroupingMode.Single;
|
||||
pipeline.getSettings().contourIntersection = ContourIntersectionDirection.Up;
|
||||
pipeline.getSettings().desiredShape = ContourShape.Custom;
|
||||
pipeline.getSettings().allowableThreshold = 10;
|
||||
pipeline.getSettings().contourShape = ContourShape.Custom;
|
||||
pipeline.getSettings().circleDetectThreshold = 10;
|
||||
pipeline.getSettings().accuracyPercentage = 30.0;
|
||||
|
||||
var frameProvider =
|
||||
@@ -111,8 +111,8 @@ public class ShapeBenchmarkTest {
|
||||
pipeline.getSettings().outputShowMultipleTargets = true;
|
||||
pipeline.getSettings().contourGroupingMode = ContourGroupingMode.Single;
|
||||
pipeline.getSettings().contourIntersection = ContourIntersectionDirection.Up;
|
||||
pipeline.getSettings().desiredShape = ContourShape.Custom;
|
||||
pipeline.getSettings().allowableThreshold = 10;
|
||||
pipeline.getSettings().contourShape = ContourShape.Custom;
|
||||
pipeline.getSettings().circleDetectThreshold = 10;
|
||||
pipeline.getSettings().accuracyPercentage = 30.0;
|
||||
|
||||
var frameProvider =
|
||||
@@ -133,8 +133,8 @@ public class ShapeBenchmarkTest {
|
||||
pipeline.getSettings().outputShowMultipleTargets = true;
|
||||
pipeline.getSettings().contourGroupingMode = ContourGroupingMode.Single;
|
||||
pipeline.getSettings().contourIntersection = ContourIntersectionDirection.Up;
|
||||
pipeline.getSettings().desiredShape = ContourShape.Custom;
|
||||
pipeline.getSettings().allowableThreshold = 10;
|
||||
pipeline.getSettings().contourShape = ContourShape.Custom;
|
||||
pipeline.getSettings().circleDetectThreshold = 10;
|
||||
pipeline.getSettings().accuracyPercentage = 30.0;
|
||||
|
||||
var frameProvider =
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.photonvision.vision.pipeline;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import edu.wpi.first.wpilibj.geometry.Rotation2d;
|
||||
import java.util.stream.Collectors;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -30,7 +31,9 @@ import org.photonvision.vision.frame.provider.FileFrameProvider;
|
||||
import org.photonvision.vision.opencv.CVMat;
|
||||
import org.photonvision.vision.opencv.ContourGroupingMode;
|
||||
import org.photonvision.vision.opencv.ContourIntersectionDirection;
|
||||
import org.photonvision.vision.opencv.ContourShape;
|
||||
import org.photonvision.vision.pipeline.result.CVPipelineResult;
|
||||
import org.photonvision.vision.target.TargetModel;
|
||||
import org.photonvision.vision.target.TrackedTarget;
|
||||
|
||||
public class CirclePNPTest {
|
||||
@@ -80,46 +83,43 @@ public class CirclePNPTest {
|
||||
assertEquals(5, cameraCalibration.getCameraExtrinsicsMat().cols());
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void testCircle() {
|
||||
// var pipeline = new ColoredShapePipeline();
|
||||
//
|
||||
// pipeline.getSettings().hsvHue.set(0, 100);
|
||||
// pipeline.getSettings().hsvSaturation.set(100, 255);
|
||||
// pipeline.getSettings().hsvValue.set(100, 255);
|
||||
// pipeline.getSettings().outputShouldDraw = true;
|
||||
// pipeline.getSettings().maxCannyThresh = 50;
|
||||
// pipeline.getSettings().accuracy = 15;
|
||||
// pipeline.getSettings().allowableThreshold = 5;
|
||||
// pipeline.getSettings().solvePNPEnabled = true;
|
||||
// pipeline.getSettings().cornerDetectionAccuracyPercentage = 4;
|
||||
// pipeline.getSettings().cornerDetectionUseConvexHulls = true;
|
||||
// pipeline.getSettings().cameraCalibration = getCoeffs(LIFECAM_480P_CAL_FILE);
|
||||
// pipeline.getSettings().targetModel = TargetModel.kCircularPowerCell7in;
|
||||
// pipeline.getSettings().outputShouldDraw = true;
|
||||
// pipeline.getSettings().outputShowMultipleTargets = false;
|
||||
// pipeline.getSettings().contourGroupingMode = ContourGroupingMode.Single;
|
||||
// pipeline.getSettings().contourIntersection = ContourIntersectionDirection.Up;
|
||||
// pipeline.getSettings().desiredShape = ContourShape.Circle;
|
||||
// pipeline.getSettings().allowableThreshold = 10;
|
||||
// pipeline.getSettings().minRadius = 30;
|
||||
// pipeline.getSettings().accuracyPercentage = 30.0;
|
||||
//
|
||||
// var frameProvider =
|
||||
// new FileFrameProvider(
|
||||
//
|
||||
// TestUtils.getPowercellImagePath(TestUtils.PowercellTestImages.kPowercell_test_6, false),
|
||||
// TestUtils.WPI2020Image.FOV,
|
||||
// new Rotation2d(),
|
||||
// TestUtils.get2020LifeCamCoeffs(false));
|
||||
//
|
||||
// CVPipelineResult pipelineResult = pipeline.run(frameProvider.get(),
|
||||
// QuirkyCamera.DefaultCamera);
|
||||
// printTestResults(pipelineResult);
|
||||
//
|
||||
// TestUtils.showImage(pipelineResult.outputFrame.image.getMat(), "Pipeline output",
|
||||
// 999999);
|
||||
// }
|
||||
@Test
|
||||
public void testCircle() {
|
||||
var pipeline = new ColoredShapePipeline();
|
||||
|
||||
pipeline.getSettings().hsvHue.set(0, 100);
|
||||
pipeline.getSettings().hsvSaturation.set(100, 255);
|
||||
pipeline.getSettings().hsvValue.set(100, 255);
|
||||
pipeline.getSettings().outputShouldDraw = true;
|
||||
pipeline.getSettings().maxCannyThresh = 50;
|
||||
pipeline.getSettings().circleAccuracy = 15;
|
||||
pipeline.getSettings().circleDetectThreshold = 5;
|
||||
pipeline.getSettings().solvePNPEnabled = true;
|
||||
pipeline.getSettings().cornerDetectionAccuracyPercentage = 4;
|
||||
pipeline.getSettings().cornerDetectionUseConvexHulls = true;
|
||||
pipeline.getSettings().cameraCalibration = getCoeffs(LIFECAM_480P_CAL_FILE);
|
||||
pipeline.getSettings().targetModel = TargetModel.kCircularPowerCell7in;
|
||||
pipeline.getSettings().outputShouldDraw = true;
|
||||
pipeline.getSettings().outputShowMultipleTargets = false;
|
||||
pipeline.getSettings().contourGroupingMode = ContourGroupingMode.Single;
|
||||
pipeline.getSettings().contourIntersection = ContourIntersectionDirection.Up;
|
||||
pipeline.getSettings().contourShape = ContourShape.Circle;
|
||||
pipeline.getSettings().circleDetectThreshold = 10;
|
||||
pipeline.getSettings().contourRadius.setFirst(30);
|
||||
pipeline.getSettings().accuracyPercentage = 30.0;
|
||||
|
||||
var frameProvider =
|
||||
new FileFrameProvider(
|
||||
TestUtils.getPowercellImagePath(TestUtils.PowercellTestImages.kPowercell_test_6, false),
|
||||
TestUtils.WPI2020Image.FOV,
|
||||
new Rotation2d(),
|
||||
TestUtils.get2020LifeCamCoeffs(true));
|
||||
|
||||
CVPipelineResult pipelineResult = pipeline.run(frameProvider.get(), QuirkyCamera.DefaultCamera);
|
||||
printTestResults(pipelineResult);
|
||||
|
||||
TestUtils.showImage(pipelineResult.outputFrame.image.getMat(), "Pipeline output", 999999);
|
||||
}
|
||||
|
||||
private static void continuouslyRunPipeline(Frame frame, ReflectivePipelineSettings settings) {
|
||||
var pipeline = new ReflectivePipeline();
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.photonvision.vision.pipeline;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.photonvision.common.util.TestUtils;
|
||||
import org.photonvision.vision.camera.QuirkyCamera;
|
||||
import org.photonvision.vision.frame.Frame;
|
||||
@@ -39,7 +40,7 @@ public class ColoredShapePipelineTest {
|
||||
|
||||
public static void testQuadrilateralDetection(
|
||||
ColoredShapePipeline pipeline, ColoredShapePipelineSettings settings, Frame frame) {
|
||||
settings.desiredShape = ContourShape.Quadrilateral;
|
||||
settings.contourShape = ContourShape.Quadrilateral;
|
||||
pipeline.settings = settings;
|
||||
CVPipelineResult colouredShapePipelineResult = pipeline.run(frame, QuirkyCamera.DefaultCamera);
|
||||
TestUtils.showImage(
|
||||
@@ -49,7 +50,7 @@ public class ColoredShapePipelineTest {
|
||||
|
||||
public static void testCustomShapeDetection(
|
||||
ColoredShapePipeline pipeline, ColoredShapePipelineSettings settings, Frame frame) {
|
||||
settings.desiredShape = ContourShape.Custom;
|
||||
settings.contourShape = ContourShape.Custom;
|
||||
pipeline.settings = settings;
|
||||
CVPipelineResult colouredShapePipelineResult = pipeline.run(frame, QuirkyCamera.DefaultCamera);
|
||||
TestUtils.showImage(
|
||||
@@ -57,36 +58,33 @@ public class ColoredShapePipelineTest {
|
||||
printTestResults(colouredShapePipelineResult);
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public static void testCircleShapeDetection(
|
||||
// ColoredShapePipeline pipeline, ColoredShapePipelineSettings settings, Frame frame) {
|
||||
// settings.desiredShape = ContourShape.Circle;
|
||||
// pipeline.settings = settings;
|
||||
// CVPipelineResult colouredShapePipelineResult = pipeline.run(frame,
|
||||
// QuirkyCamera.DefaultCamera);
|
||||
// TestUtils.showImage(
|
||||
// colouredShapePipelineResult.outputFrame.image.getMat(), "Pipeline output:
|
||||
// Circle.");
|
||||
// printTestResults(colouredShapePipelineResult);
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public static void testPowercellDetection(
|
||||
// ColoredShapePipelineSettings settings, ColoredShapePipeline pipeline) {
|
||||
//
|
||||
// settings.hsvHue.set(10, 40);
|
||||
// settings.hsvSaturation.set(100, 255);
|
||||
// settings.hsvValue.set(100, 255);
|
||||
// settings.maxCannyThresh = 50;
|
||||
// settings.accuracy = 15;
|
||||
// settings.allowableThreshold = 5;
|
||||
// var frameProvider =
|
||||
// new FileFrameProvider(
|
||||
//
|
||||
// TestUtils.getPowercellImagePath(TestUtils.PowercellTestImages.kPowercell_test_6, false),
|
||||
// TestUtils.WPI2019Image.FOV);
|
||||
// testCircleShapeDetection(pipeline, settings, frameProvider.get());
|
||||
// }
|
||||
@Test
|
||||
public static void testCircleShapeDetection(
|
||||
ColoredShapePipeline pipeline, ColoredShapePipelineSettings settings, Frame frame) {
|
||||
settings.contourShape = ContourShape.Circle;
|
||||
pipeline.settings = settings;
|
||||
CVPipelineResult colouredShapePipelineResult = pipeline.run(frame, QuirkyCamera.DefaultCamera);
|
||||
TestUtils.showImage(
|
||||
colouredShapePipelineResult.outputFrame.image.getMat(), "Pipeline output: Circle.");
|
||||
printTestResults(colouredShapePipelineResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public static void testPowercellDetection(
|
||||
ColoredShapePipelineSettings settings, ColoredShapePipeline pipeline) {
|
||||
|
||||
settings.hsvHue.set(10, 40);
|
||||
settings.hsvSaturation.set(100, 255);
|
||||
settings.hsvValue.set(100, 255);
|
||||
settings.maxCannyThresh = 50;
|
||||
settings.circleAccuracy = 15;
|
||||
settings.circleDetectThreshold = 5;
|
||||
var frameProvider =
|
||||
new FileFrameProvider(
|
||||
TestUtils.getPowercellImagePath(TestUtils.PowercellTestImages.kPowercell_test_6, false),
|
||||
TestUtils.WPI2019Image.FOV);
|
||||
testCircleShapeDetection(pipeline, settings, frameProvider.get());
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
TestUtils.loadLibraries();
|
||||
@@ -104,8 +102,8 @@ public class ColoredShapePipelineTest {
|
||||
settings.outputShowMultipleTargets = true;
|
||||
settings.contourGroupingMode = ContourGroupingMode.Single;
|
||||
settings.contourIntersection = ContourIntersectionDirection.Up;
|
||||
settings.desiredShape = ContourShape.Triangle;
|
||||
settings.allowableThreshold = 10;
|
||||
settings.contourShape = ContourShape.Triangle;
|
||||
settings.circleDetectThreshold = 10;
|
||||
settings.accuracyPercentage = 30.0;
|
||||
|
||||
ColoredShapePipeline pipeline = new ColoredShapePipeline();
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.vision.processes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.photonvision.common.util.TestUtils;
|
||||
import org.photonvision.vision.pipeline.DriverModePipelineSettings;
|
||||
import org.photonvision.vision.pipeline.PipelineType;
|
||||
|
||||
public class PipelineManagerTest {
|
||||
|
||||
@Test
|
||||
public void testUniqueName() {
|
||||
TestUtils.loadLibraries();
|
||||
PipelineManager manager = new PipelineManager(new DriverModePipelineSettings(), List.of());
|
||||
manager.addPipeline(PipelineType.Reflective, "Another");
|
||||
|
||||
// We now have ["New Pipeline", "Another"]
|
||||
// After we duplicate 0 and 1, we expect ["New Pipeline", "Another", "New Pipeline (1)",
|
||||
// "Another (1)"]
|
||||
manager.duplicatePipeline(0);
|
||||
manager.duplicatePipeline(1);
|
||||
|
||||
// Should add "Another (2)"
|
||||
manager.duplicatePipeline(3);
|
||||
// Should add "Another (3)
|
||||
manager.duplicatePipeline(3);
|
||||
// Should add "Another (4)
|
||||
manager.duplicatePipeline(1);
|
||||
|
||||
// Should add "Another (5)" through "Another (15)"
|
||||
for (int i = 5; i < 15; i++) {
|
||||
manager.duplicatePipeline(1);
|
||||
}
|
||||
|
||||
var nicks = manager.getPipelineNicknames();
|
||||
var expected =
|
||||
new ArrayList<>(List.of("New Pipeline", "Another", "New Pipeline (1)", "Another (1)"));
|
||||
for (int i = 2; i < 15; i++) {
|
||||
expected.add("Another (" + i + ")");
|
||||
}
|
||||
Assertions.assertEquals(expected, nicks);
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ public class TrackedTargetTest {
|
||||
new Point(426.22, 302),
|
||||
new Point(400, 302))); // gives contour with center of 426, 300
|
||||
Contour contour = new Contour(mat);
|
||||
|
||||
var pTarget = new PotentialTarget(contour);
|
||||
|
||||
var imageSize = new Size(800, 600);
|
||||
@@ -61,7 +62,7 @@ public class TrackedTargetTest {
|
||||
34.3,
|
||||
imageSize.area());
|
||||
|
||||
var trackedTarget = new TrackedTarget(pTarget, setting);
|
||||
var trackedTarget = new TrackedTarget(pTarget, setting, null);
|
||||
// TODO change these hardcoded values
|
||||
assertEquals(12.0, trackedTarget.getYaw(), 0.05, "Yaw was incorrect");
|
||||
assertEquals(0, trackedTarget.getPitch(), 0.05, "Pitch was incorrect");
|
||||
|
||||
Reference in New Issue
Block a user