Skip to content

Commit 846a8e3

Browse files
author
James Hagborg
committed
Track height of targets
1 parent 79d5b9c commit 846a8e3

File tree

6 files changed

+223
-65
lines changed

6 files changed

+223
-65
lines changed

src/main/java/org/usfirst/frc/team69/util/vision/ClosestPairTargetProcessor.java

Lines changed: 68 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,43 @@
77

88
import org.opencv.core.Mat;
99
import org.opencv.core.Point;
10+
import org.opencv.core.Point3;
1011
import org.opencv.core.Rect;
1112
import org.opencv.core.Scalar;
1213
import org.opencv.imgproc.Imgproc;
14+
import org.usfirst.frc.team69.util.pid.DisplacementPIDSource;
1315
import org.usfirst.frc.team69.util.pref.IntPreference;
1416

17+
import edu.wpi.first.wpilibj.PIDSource;
18+
1519
/**
1620
* Target processor which finds the closest two targets to the crosshairs, and
1721
* averages their position. This is the algorithm that was used to track the
1822
* boiler in the 2017 steamworks game. It can more generally be applied to
1923
* anything that's two pieces of tape.
2024
*
25+
* This processor uses the height of the targets to measure distance. However,
26+
* it computes x, y, and height all as simple averages, rather than weighting by
27+
* depth. This is a good approximation as long as one target is not
28+
* significantly closer than the other, meaning the camera is not viewing the
29+
* target from a steep angle.
30+
*
2131
* @author James Hagborg
2232
*
2333
*/
24-
public class ClosestPairTargetProcessor extends AbstractTargetProcessor<VisionResult> {
34+
public class ClosestPairTargetProcessor
35+
extends AbstractTargetProcessor<TargetWithHeightResult> {
2536

2637
private final IntSupplier m_xCrosshairs, m_yCrosshairs;
27-
28-
/*
29-
* The most recent point where a target was found. This is stored in
30-
* absolute pixels, rather than relative to the crosshairs, like the result
31-
* type stores. This is only used for drawing indicators.
32-
*/
33-
private Point m_lastPoint;
3438

3539
/**
3640
* Construct a new target processor with the given fixed crosshairs
3741
* position.
3842
*
3943
* @param xCrosshairs
40-
* X coordinate for the crosshairs
44+
* X coordinate for the crosshairs
4145
* @param yCrosshairs
42-
* Y coordinate for the crosshairs
46+
* Y coordinate for the crosshairs
4347
*/
4448
public ClosestPairTargetProcessor(int xCrosshairs, int yCrosshairs) {
4549
this(() -> xCrosshairs, () -> yCrosshairs);
@@ -54,11 +58,12 @@ public ClosestPairTargetProcessor(int xCrosshairs, int yCrosshairs) {
5458
* from any thread.
5559
*
5660
* @param xCrosshairs
57-
* X coordinate for the crosshairs
61+
* X coordinate for the crosshairs
5862
* @param yCrosshairs
59-
* Y coordinate for the crosshairs
63+
* Y coordinate for the crosshairs
6064
*/
61-
public ClosestPairTargetProcessor(IntSupplier xCrosshairs, IntSupplier yCrosshairs) {
65+
public ClosestPairTargetProcessor(IntSupplier xCrosshairs,
66+
IntSupplier yCrosshairs) {
6267
m_xCrosshairs = Objects.requireNonNull(xCrosshairs);
6368
m_yCrosshairs = Objects.requireNonNull(yCrosshairs);
6469
}
@@ -71,7 +76,7 @@ public ClosestPairTargetProcessor(IntSupplier xCrosshairs, IntSupplier yCrosshai
7176
* The target to check
7277
* @return The distance from the crosshairs
7378
*/
74-
private double targetDistance(Point result) {
79+
private double targetDistance(Point3 result) {
7580
double xError = result.x - m_xCrosshairs.getAsInt();
7681
double yError = result.y - m_yCrosshairs.getAsInt();
7782
return xError * xError + yError * yError;
@@ -84,76 +89,98 @@ private double targetDistance(Point result) {
8489
* The target rectangle.
8590
* @return The point at the center of the rectangle.
8691
*/
87-
private Point targetToPoint(Rect rect) {
92+
private Point3 targetToPoint(Rect rect) {
8893
int xCenter = rect.x + rect.width / 2;
8994
int yCenter = rect.y + rect.height / 2;
90-
return new Point(xCenter, yCenter);
95+
int height = rect.height;
96+
return new Point3(xCenter, yCenter, height);
9197
}
9298

9399
/**
94100
* Convert a point to a result, by taking the position to be relative to the
95101
* crosshairs. Since this method is only called when the point given is
96-
* actually the chosen target, this also saves the point for drawing a marker
97-
* later.
102+
* actually the chosen target, this also saves the point for drawing a
103+
* marker later.
98104
*
99-
* @param point The point at the center of the target, in absolute pixels.
105+
* @param point
106+
* The point at the center of the target, in absolute pixels.
100107
* @return The new VisionResult
101108
*/
102-
private VisionResult pointToResult(Point point) {
103-
m_lastPoint = point;
104-
return new VisionResult(
105-
(int) point.x - m_xCrosshairs.getAsInt(),
106-
(int) point.y - m_yCrosshairs.getAsInt(), true);
109+
private TargetWithHeightResult pointToResult(Point3 point) {
110+
return new TargetWithHeightResult(
111+
point.x - m_xCrosshairs.getAsInt(),
112+
point.y - m_yCrosshairs.getAsInt(),
113+
point.x, point.y, point.z, true);
107114
}
108-
115+
109116
/**
110117
* Given two Points, find the average of their positions.
111118
*
112119
* @param a
113120
* @param b
114121
* @return
115122
*/
116-
private Point averagePoints(Point a, Point b) {
117-
return new Point((a.x + b.x) / 2, (a.y + b.y) / 2);
123+
private Point3 averagePoints(Point3 a, Point3 b) {
124+
return new Point3((a.x + b.x) / 2, (a.y + b.y) / 2, (a.z + b.z) / 2);
118125
}
119-
126+
120127
/**
121128
* {@inheritDoc}
122129
*/
123130
@Override
124-
public VisionResult computeResult(List<Rect> targets) {
125-
Point[] result = targets.stream()
126-
.map(this::targetToPoint)
131+
public TargetWithHeightResult computeResult(List<Rect> targets) {
132+
Point3[] result = targets.stream().map(this::targetToPoint)
127133
.sorted(Comparator.comparingDouble(this::targetDistance))
128-
.limit(2)
129-
.toArray(Point[]::new);
130-
if (result.length == 0) {
131-
return getDefaultValue();
132-
} else if (result.length == 1) {
134+
.limit(2).toArray(Point3[]::new);
135+
if (result.length < 2) {
133136
return getDefaultValue();
134137
} else {
135138
return pointToResult(averagePoints(result[0], result[1]));
136139
}
137140
}
141+
142+
/**
143+
* Get a PID source that returns the height of the target.
144+
*
145+
* @return A PID source returning the height of the target.
146+
*/
147+
public PIDSource heightPID() {
148+
return new DisplacementPIDSource() {
149+
@Override
150+
public double pidGet() {
151+
return getLastResult().height();
152+
}
153+
};
154+
}
138155

139156
private static final Scalar MARKER_COLOR = new Scalar(0, 0, 255);
140-
157+
private static final double MARKER_WIDTH = 6;
158+
141159
/**
142160
* {@inheritDoc}
143161
*/
144162
@Override
145163
public void writeOutput(Mat mat) {
146-
if (m_lastPoint != null && getLastResult().foundTarget()) {
147-
Imgproc.drawMarker(mat, m_lastPoint, MARKER_COLOR);
164+
TargetWithHeightResult result = getLastResult();
165+
if (result.foundTarget()) {
166+
Point center = new Point(result.xAbsolute(), result.yAbsolute());
167+
double height = result.height();
168+
Point tl = new Point(center.x - MARKER_WIDTH / 2, center.y - height / 2);
169+
Point tr = new Point(center.x + MARKER_WIDTH / 2, center.y - height / 2);
170+
Point bl = new Point(center.x - MARKER_WIDTH / 2, center.y + height / 2);
171+
Point br = new Point(center.x + MARKER_WIDTH / 2, center.y + height / 2);
172+
Imgproc.drawMarker(mat, center, MARKER_COLOR);
173+
Imgproc.line(mat, tl, tr, MARKER_COLOR);
174+
Imgproc.line(mat, bl, br, MARKER_COLOR);
148175
}
149176
}
150177

151178
/**
152179
* {@inheritDoc}
153180
*/
154181
@Override
155-
public VisionResult getDefaultValue() {
156-
return new VisionResult(0, 0, false);
182+
public TargetWithHeightResult getDefaultValue() {
183+
return new TargetWithHeightResult(0, 0, 0, 0, 0, false);
157184
}
158185

159186
}

src/main/java/org/usfirst/frc/team69/util/vision/ClosestTargetProcessor.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,9 @@ private Point targetToPoint(Rect rect) {
9999
private VisionResult pointToResult(Point point) {
100100
m_lastPoint = point;
101101
return new VisionResult(
102-
(int) point.x - m_xCrosshairs.getAsInt(),
103-
(int) point.y - m_yCrosshairs.getAsInt(), true);
102+
point.x - m_xCrosshairs.getAsInt(),
103+
point.y - m_yCrosshairs.getAsInt(),
104+
point.x, point.y, true);
104105
}
105106

106107
/**
@@ -132,7 +133,7 @@ public void writeOutput(Mat mat) {
132133
*/
133134
@Override
134135
public VisionResult getDefaultValue() {
135-
return new VisionResult(0, 0, false);
136+
return new VisionResult(0, 0, 0, 0, false);
136137
}
137138

138139
}

src/main/java/org/usfirst/frc/team69/util/vision/CrosshairsPipeline.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,17 @@ public class CrosshairsPipeline implements VisionGUIPipeline {
2727
*
2828
* @param x
2929
* Supplier for x coordinate.
30-
* @param y Supplier for y coordinate.
31-
* @param b Blue component of color.
32-
* @param g Green component of color.
33-
* @param r Red component of color.
30+
* @param y
31+
* Supplier for y coordinate.
32+
* @param b
33+
* Blue component of color.
34+
* @param g
35+
* Green component of color.
36+
* @param r
37+
* Red component of color.
3438
*/
35-
public CrosshairsPipeline(IntSupplier x, IntSupplier y, int b, int g, int r) {
39+
public CrosshairsPipeline(IntSupplier x, IntSupplier y, int b, int g,
40+
int r) {
3641
m_color = new Scalar(b, g, r);
3742
m_x = Objects.requireNonNull(x);
3843
m_y = Objects.requireNonNull(y);
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package org.usfirst.frc.team69.util.vision;
2+
3+
import java.util.Objects;
4+
import java.util.function.IntSupplier;
5+
import java.util.function.Supplier;
6+
7+
import org.opencv.core.Mat;
8+
import org.opencv.core.Point;
9+
import org.opencv.core.Scalar;
10+
import org.opencv.imgproc.Imgproc;
11+
12+
/**
13+
* Pipeline that draws an indicator around a target, showing what the goal
14+
* height should be. This is currently done by drawing two bars above and below
15+
* the center of the target.
16+
*
17+
* @author James Hagborg
18+
*
19+
*/
20+
public class HeightIndicatorPipeline implements VisionGUIPipeline {
21+
22+
private final Supplier<? extends VisionResult> m_targetLocation;
23+
private final IntSupplier m_height;
24+
private final Scalar m_color;
25+
26+
/**
27+
* Construct a new pipeline with the specified parameters.
28+
*
29+
* @param targetLocation
30+
* Supplier of the target location, so this pipeline knows where
31+
* to draw the indicator. Usually this is the getLastResult
32+
* method of some pipeline which runs before this one.
33+
* @param height
34+
* The goal height of the target. Usually this is a constant or
35+
* the get method of a preference.
36+
* @param b Blue component of color
37+
* @param g Green component of color
38+
* @param r Red component of color
39+
*/
40+
public HeightIndicatorPipeline(
41+
Supplier<? extends VisionResult> targetLocation, IntSupplier height,
42+
int b, int g, int r) {
43+
m_targetLocation = Objects.requireNonNull(targetLocation);
44+
m_height = Objects.requireNonNull(height);
45+
m_color = new Scalar(b, g, r);
46+
}
47+
48+
/**
49+
* {@inheritDoc}
50+
*/
51+
@Override
52+
public void process(Mat mat) {
53+
// do nothing
54+
}
55+
56+
private static final double MARKER_WIDTH = 4;
57+
58+
/**
59+
* {@inheritDoc}
60+
*/
61+
@Override
62+
public void writeOutput(Mat mat) {
63+
VisionResult res = m_targetLocation.get();
64+
65+
if (res.foundTarget()) {
66+
int height = m_height.getAsInt();
67+
double x = res.xAbsolute();
68+
double y = res.yAbsolute();
69+
70+
Point tl = new Point(x - MARKER_WIDTH / 2.0, y - height / 2.0);
71+
Point tr = new Point(x + MARKER_WIDTH / 2.0, y - height / 2.0);
72+
Point bl = new Point(x - MARKER_WIDTH / 2.0, y + height / 2.0);
73+
Point br = new Point(x + MARKER_WIDTH / 2.0, y + height / 2.0);
74+
Imgproc.line(mat, tl, tr, m_color);
75+
Imgproc.line(mat, bl, br, m_color);
76+
}
77+
}
78+
79+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.usfirst.frc.team69.util.vision;
2+
3+
/**
4+
* Vision result which stores the height of the target found.
5+
*
6+
* @author James Hagoborg
7+
*
8+
*/
9+
public class TargetWithHeightResult extends VisionResult {
10+
11+
private final double m_height;
12+
13+
/**
14+
* Construct a result with the given parameters
15+
*
16+
* @param xError
17+
* error on x-coordinate
18+
* @param yError
19+
* error on y-coordinate
20+
* @param xAbs
21+
* absolute x-coordinate
22+
* @param yAbs
23+
* absolute y-coordinate
24+
* @param height
25+
* height of the target
26+
* @param foundTarget
27+
* whether a target was actually found
28+
*/
29+
public TargetWithHeightResult(double xError, double yError, double xAbs,
30+
double yAbs, double height, boolean foundTarget) {
31+
super(xError, yError, xAbs, yAbs, foundTarget);
32+
m_height = height;
33+
}
34+
35+
public double height() { return m_height; }
36+
37+
}

0 commit comments

Comments
 (0)