77
88import org .opencv .core .Mat ;
99import org .opencv .core .Point ;
10+ import org .opencv .core .Point3 ;
1011import org .opencv .core .Rect ;
1112import org .opencv .core .Scalar ;
1213import org .opencv .imgproc .Imgproc ;
14+ import org .usfirst .frc .team69 .util .pid .DisplacementPIDSource ;
1315import 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}
0 commit comments