Skip to content

Commit de1af7c

Browse files
committed
move limelight only conversion into its own io
1 parent f2bdd93 commit de1af7c

File tree

4 files changed

+114
-102
lines changed

4 files changed

+114
-102
lines changed

src/main/java/org/steelhawks/subsystems/vision/VisionConstants.java

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import edu.wpi.first.math.util.Units;
1212
import org.steelhawks.RobotContainer;
1313
import org.steelhawks.subsystems.swerve.Swerve;
14+
import org.steelhawks.subsystems.vision.VisionConstants.Factors.ObjFactors.LimelightFactors;
1415
import org.steelhawks.subsystems.vision.objdetect.ObjectVisionIO;
1516
import org.steelhawks.subsystems.vision.objdetect.ObjectVisionIOLimelight;
1617
import org.steelhawks.subsystems.vision.objdetect.ObjectVisionIOPhoton;
@@ -37,19 +38,46 @@ default Double[] getFactors() {
3738
class ObjFactors implements Factors {
3839
private final Double[] factors;
3940

41+
public enum LimelightFactors {
42+
LIMELIGHT_4(82.0, 56.2),
43+
LIMELIGHT_3(62.5, 48.9),
44+
LIMELIGHT_2(62.5, 48.9)
45+
;
46+
47+
private final double horizontalFov;
48+
private final double verticalFov;
49+
50+
LimelightFactors(double horizontalFov, double verticalFov) {
51+
this.horizontalFov = horizontalFov;
52+
this.verticalFov = verticalFov;
53+
}
54+
}
55+
4056
/**
41-
* Only used for Limelight, to calculate confidence score.
57+
* Only used for Limelight
4258
*
43-
* @param w1 How much the angle matters to confidence
44-
* @param w2 How much the shape matters to confidence
45-
* @param w3 How much the area matters to confidence
59+
* @param horizontalFov The horizontal FOV of the camera.
60+
* @param verticalFov The vertical FOV of the camera.
61+
* @param resolutionWidth The current resolution width selected in the Limelight config page.
62+
* @param resolutionHeight The current resolution height selected in the Limelight config page.
63+
* @param confidence The confidence level set in the Limelight config page.
4664
*/
47-
public ObjFactors(double w1, double w2, double w3) {
48-
factors = new Double[] { w1, w2, w3 };
65+
public ObjFactors(double confidence, double horizontalFov, double verticalFov, double resolutionWidth, double resolutionHeight) {
66+
factors = new Double[] {
67+
confidence,
68+
horizontalFov,
69+
verticalFov,
70+
resolutionWidth,
71+
resolutionHeight
72+
};
73+
}
74+
75+
public ObjFactors(double confidence, LimelightFactors fov, double resolutionWidth, double resolutionHeight) {
76+
this(confidence, fov.horizontalFov, fov.verticalFov, resolutionWidth, resolutionHeight);
4977
}
5078

5179
public ObjFactors() {
52-
this(0.0, 0.0, 0.0);
80+
this(0.0, 0.0, 0.0, 0.0, 0.0);
5381
}
5482

5583
@Override
@@ -181,7 +209,7 @@ public enum CameraType {
181209
Units.degreesToRadians(15.0)
182210
)
183211
),
184-
new Factors.ObjFactors(0.5, 0.3, 0.2),
212+
new Factors.ObjFactors(0.48, LimelightFactors.LIMELIGHT_4, 1280.0, 800.0),
185213
CameraType.LIMELIGHT
186214
)
187215
};

src/main/java/org/steelhawks/subsystems/vision/objdetect/ObjectVision.java

Lines changed: 30 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.steelhawks.subsystems.vision.objdetect;
22

3-
import edu.wpi.first.math.MathUtil;
43
import edu.wpi.first.math.geometry.*;
54
import edu.wpi.first.wpilibj.Timer;
65
import edu.wpi.first.wpilibj.smartdashboard.FieldObject2d;
@@ -50,23 +49,21 @@ private void addCoralObservationToPose(ObjectVisionIO.ObjectObservation observat
5049
if (Constants.loggedValue("CoralObservation/WheelOdomEmpty", oldWheelOdomPose.isEmpty())) {
5150
return;
5251
}
53-
54-
// latency compensation via interpolation
5552
var estimatedPose = RobotContainer.s_Swerve.getPose();
5653
var wheelOdometryPose = RobotContainer.s_Swerve.getWheelOdomPose();
5754
Pose2d fieldToRobot =
5855
estimatedPose.transformBy(new Transform2d(wheelOdometryPose, oldWheelOdomPose.get()));
5956
Transform3d robotToCamera =
6057
Objects.requireNonNull(VisionConstants.getObjDetectConfig())[observation.camIndex()].robotToCamera();
6158

62-
// Get bounding box corners in PIXEL coordinates
63-
double[] txCorners = observation.info().tx();
64-
double[] tyCorners = observation.info().ty();
59+
// in angle coordinates
60+
double[] txCorners = observation.info().tx(); // degrees
61+
double[] tyCorners = observation.info().ty(); // degrees
6562

66-
Logger.recordOutput("CoralObservation/TxCorners", txCorners);
67-
Logger.recordOutput("CoralObservation/TyCorners", tyCorners);
63+
Constants.loggedValue("CoralObservation/TxCorners_degrees", txCorners);
64+
Constants.loggedValue("CoralObservation/TyCorners_degrees", tyCorners);
6865

69-
// Find bounding box center in pixels
66+
// bounding box center in deg
7067
double minX = Math.min(Math.min(txCorners[0], txCorners[1]),
7168
Math.min(txCorners[2], txCorners[3]));
7269
double maxX = Math.max(Math.max(txCorners[0], txCorners[1]),
@@ -76,95 +73,47 @@ private void addCoralObservationToPose(ObjectVisionIO.ObjectObservation observat
7673
double maxY = Math.max(Math.max(tyCorners[0], tyCorners[1]),
7774
Math.max(tyCorners[2], tyCorners[3]));
7875

79-
double centerX_pixels = (minX + maxX) / 2.0;
80-
double centerY_pixels = (minY + maxY) / 2.0;
81-
82-
// Limelight resolution: 1280x800
83-
double resolutionWidth = 1280.0;
84-
double resolutionHeight = 800.0;
85-
86-
// Convert pixels to normalized coordinates [-1, 1]
87-
// Center of image is at (width/2, height/2)
88-
double centerX = (centerX_pixels - resolutionWidth / 2.0) / (resolutionWidth / 2.0);
89-
double centerY = -(centerY_pixels - resolutionHeight / 2.0) / (resolutionHeight / 2.0); // negative because Y increases downward in pixels
90-
91-
Logger.recordOutput("CoralObservation/centerX_pixels", centerX_pixels);
92-
Logger.recordOutput("CoralObservation/centerY_pixels", centerY_pixels);
93-
Logger.recordOutput("CoralObservation/centerX_normalized", centerX);
94-
Logger.recordOutput("CoralObservation/centerY_normalized", centerY);
95-
96-
// Convert normalized coordinates to angles
97-
// limelight 4 H:82° V:56.2°
98-
double horizontalFOV = 82; // degrees
99-
double verticalFOV = 56.2; // degrees
76+
// in degrees
77+
double tx = (minX + maxX) / 2.0;
78+
double ty = (minY + maxY) / 2.0;
10079

101-
double tx = centerX * (horizontalFOV / 2.0); // angle in degrees
102-
double ty = centerY * (verticalFOV / 2.0); // angle in degrees
80+
Constants.loggedValue("CoralObservation/tx_degrees", tx);
81+
Constants.loggedValue("CoralObservation/ty_degrees", ty);
10382

83+
// trig
10484
double cameraHeight = robotToCamera.getZ();
105-
double cameraPitch = robotToCamera.getRotation().getY(); // in radians
106-
107-
// Debug logging
108-
Logger.recordOutput("CoralObservation/tx_degrees", tx);
109-
Logger.recordOutput("CoralObservation/ty_degrees", ty);
110-
Logger.recordOutput("CoralObservation/cameraPitch_radians", cameraPitch);
111-
112-
// Convert ty from degrees to radians
85+
double cameraPitch = robotToCamera.getRotation().getY(); // radians
86+
Constants.loggedValue("CoralObservation/cameraPitch_radians", cameraPitch);
11387
double tyRadians = Math.toRadians(ty);
114-
115-
// Calculate vertical angle from horizontal
116-
// cameraPitch is camera tilt (positive = up), ty is offset from camera center
11788
double verticalAngleFromHorizontal = cameraPitch + tyRadians;
118-
119-
Logger.recordOutput("CoralObservation/AngleFromHorizontal_radians", verticalAngleFromHorizontal);
120-
Logger.recordOutput("CoralObservation/AngleFromHorizontal_degrees", Math.toDegrees(verticalAngleFromHorizontal));
121-
122-
// Target must be below horizontal to be valid
89+
Constants.loggedValue("CoralObservation/AngleFromHorizontal_radians", verticalAngleFromHorizontal);
90+
Constants.loggedValue("CoralObservation/AngleFromHorizontal_degrees", Math.toDegrees(verticalAngleFromHorizontal));
12391
if (Constants.loggedValue("CoralObservation/VerticalAngleError", verticalAngleFromHorizontal <= 0)) {
12492
return;
12593
}
126-
127-
double targetHeight = 0.0; // coral is on the ground
94+
double targetHeight = 0.0;
12895
double forwardDistance = (cameraHeight - targetHeight) / Math.tan(verticalAngleFromHorizontal);
129-
130-
// Calculate the translation from camera to coral in CAMERA reference frame
13196
double txRadians = Math.toRadians(tx);
132-
133-
// Create vector from camera to coral in camera's frame
134-
// X-axis is forward, Y-axis is left
135-
Translation2d cameraToCoralInCameraFrame = new Translation2d(
136-
forwardDistance * Math.cos(txRadians), // forward component
137-
forwardDistance * Math.sin(txRadians) // lateral component
138-
);
139-
140-
Logger.recordOutput("CoralObservation/cameraToCoralInCameraFrame",
97+
Translation2d cameraToCoralInCameraFrame =
98+
new Translation2d(
99+
forwardDistance * Math.cos(txRadians),
100+
forwardDistance * Math.sin(txRadians));
101+
Constants.loggedValue("CoralObservation/cameraToCoralInCameraFrame",
141102
new Pose2d(cameraToCoralInCameraFrame, new Rotation2d()));
142103

143-
// Transform camera pose to field coordinates
144104
Transform2d robotToCamera2d = new Transform2d(
145105
robotToCamera.getTranslation().toTranslation2d(),
146106
robotToCamera.getRotation().toRotation2d());
147107
Pose2d fieldToCamera = fieldToRobot.transformBy(robotToCamera2d);
148108

149-
// Debug logging
150-
Logger.recordOutput("CoralObservation/robotToCamera2d", robotToCamera2d);
151-
Logger.recordOutput("CoralObservation/cameraYaw_degrees",
152-
Math.toDegrees(robotToCamera.getRotation().getZ()));
153-
Logger.recordOutput("CoralObservation/fieldToRobot", fieldToRobot);
154-
Logger.recordOutput("CoralObservation/fieldToCamera", fieldToCamera);
155-
Logger.recordOutput("CoralObservation/forwardDistance", forwardDistance);
156-
Logger.recordOutput("CoralObservation/txRadians", txRadians);
157-
158-
// Transform the camera-frame vector to field frame
159-
Pose2d fieldToCoral = fieldToCamera.transformBy(
160-
new Transform2d(cameraToCoralInCameraFrame, new Rotation2d())
161-
);
162-
163-
Logger.recordOutput("CoralObservation/fieldToCoral", fieldToCoral);
109+
Constants.loggedValue("CoralObservation/fieldToRobot", fieldToRobot);
110+
Constants.loggedValue("CoralObservation/fieldToCamera", fieldToCamera);
111+
Constants.loggedValue("CoralObservation/forwardDistance", forwardDistance);
164112

113+
Pose2d fieldToCoral = fieldToCamera
114+
.transformBy(new Transform2d(cameraToCoralInCameraFrame, new Rotation2d()));
115+
Constants.loggedValue("CoralObservation/fieldToCoral", fieldToCoral);
165116
CoralPose coralPose = new CoralPose(fieldToCoral.getTranslation(), observation.timestamp());
166-
167-
// Remove overlapping corals and old detections
168117
coralPoses.removeIf(
169118
c -> c.translation.getDistance(fieldToCoral.getTranslation()) <= coralOverlap
170119
|| now - c.timestamp > coralMaxAge);
@@ -183,8 +132,8 @@ public void periodic() {
183132
.sorted(Comparator.comparingDouble(ObjectVisionIO.ObjectObservation::timestamp))
184133
.forEach(this::addCoralObservationToPose);
185134

186-
coralPoses.stream()
187-
.forEach(o -> Logger.recordOutput("CoralDetections/Detection", new Pose2d(o.translation, new Rotation2d())));
135+
coralPoses.forEach(o ->
136+
Logger.recordOutput("CoralDetections/Detection", new Pose2d(o.translation, new Rotation2d())));
188137
coralObjects.setPoses(
189138
coralPoses.stream()
190139
.map(coral -> new Pose2d(coral.translation, new Rotation2d())) // zero rotation

src/main/java/org/steelhawks/subsystems/vision/objdetect/ObjectVisionIO.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public interface ObjectVisionIO {
99
record ObjectObservation(
1010
int camIndex,
1111
DetectionInfo info,
12-
double confidence, // keeping because photonvision gives us its own confidence, so no need to calculate it
12+
double confidence,
1313
double timestamp
1414
) {}
1515

@@ -19,6 +19,14 @@ record DetectionInfo(
1919
double[] tx, // Corner X positions
2020
double[] ty // Corner Y positions
2121
) {
22+
public DetectionInfo(LimelightHelpers.RawDetection raw, double[] txs, double[] tys) {
23+
this(
24+
raw.classId,
25+
raw.ta,
26+
txs,
27+
tys
28+
);
29+
}
2230
public DetectionInfo(LimelightHelpers.RawDetection raw) {
2331
this(
2432
raw.classId,

src/main/java/org/steelhawks/subsystems/vision/objdetect/ObjectVisionIOLimelight.java

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,57 @@
11
package org.steelhawks.subsystems.vision.objdetect;
22

33
import edu.wpi.first.networktables.DoubleSubscriber;
4-
import edu.wpi.first.networktables.IntegerSubscriber;
54
import edu.wpi.first.networktables.NetworkTableInstance;
65
import edu.wpi.first.wpilibj.RobotController;
7-
import org.littletonrobotics.junction.Logger;
6+
import org.steelhawks.subsystems.vision.VisionConstants;
7+
import org.steelhawks.subsystems.vision.VisionConstants.Factors.ObjFactors;
88
import org.steelhawks.util.LimelightHelpers;
99

1010
import java.util.LinkedList;
1111
import java.util.List;
12+
import java.util.Objects;
1213

1314
public class ObjectVisionIOLimelight implements ObjectVisionIO {
1415

1516
private final String name;
1617
private final int camIndex;
1718
private final DoubleSubscriber latencySubscriber;
18-
private final DoubleSubscriber txSubscriber;
19-
private final DoubleSubscriber tySubscriber;
20-
private final DoubleSubscriber taSubscriber; // target area
21-
private final IntegerSubscriber targetValidSubscriber;
19+
20+
private final ObjFactors factors;
2221

2322
public ObjectVisionIOLimelight(String name, int camIndex) {
2423
this.name = name;
2524
this.camIndex = camIndex;
2625
var table = NetworkTableInstance.getDefault().getTable(name);
2726
latencySubscriber = table.getDoubleTopic("tl").subscribe(0.0);
28-
txSubscriber = table.getDoubleTopic("tx").subscribe(0.0);
29-
tySubscriber = table.getDoubleTopic("ty").subscribe(0.0);
30-
taSubscriber = table.getDoubleTopic("ta").subscribe(0.0);
31-
targetValidSubscriber = table.getIntegerTopic("tv").subscribe(0);
27+
factors = (ObjFactors) Objects.requireNonNull(VisionConstants.getObjDetectConfig())[camIndex].factors();
28+
}
29+
30+
private double[] convertPixelsToAngles(double[] pixelCorners, boolean isY) {
31+
final double resolutionWidth = factors.getFactors()[3];
32+
final double resolutionHeight = factors.getFactors()[4];
33+
final double horizontalFov = factors.getFactors()[1];
34+
final double verticalFov = factors.getFactors()[2];
35+
36+
double resolution = isY ? resolutionHeight : resolutionWidth;
37+
double fov = isY ? verticalFov : horizontalFov;
38+
39+
double[] angles = new double[pixelCorners.length];
40+
for (int i = 0; i < pixelCorners.length; i++) {
41+
// norm to [-1, 1]
42+
double normalized = (pixelCorners[i] - resolution / 2.0) / (resolution / 2.0);
43+
if (isY) normalized = -normalized; // y increases downward
44+
45+
// convert to angle
46+
angles[i] = normalized * (fov / 2.0);
47+
}
48+
return angles;
3249
}
3350

3451
@Override
3552
public void updateInputs(ObjectVisionIOInputs inputs) {
53+
final double confidence = factors.getFactors()[0];
54+
3655
inputs.connected =
3756
((RobotController.getFPGATime() - latencySubscriber.getLastChange()) / 1000) < 250;
3857

@@ -43,11 +62,19 @@ public void updateInputs(ObjectVisionIOInputs inputs) {
4362
for (LimelightHelpers.RawDetection detection : detections) {
4463
double limelightLatencySec = latencySubscriber.get() / 1000.0;
4564
double timestamp = RobotController.getFPGATime() / 1e6 - limelightLatencySec;
65+
double[] convertedCornersX = convertPixelsToAngles(
66+
new double[] {
67+
detection.corner0_X, detection.corner1_X,
68+
detection.corner2_X, detection.corner3_X}, false);
69+
double[] convertedCornersY = convertPixelsToAngles(
70+
new double[] {
71+
detection.corner0_Y, detection.corner1_Y,
72+
detection.corner2_Y, detection.corner3_Y}, true);
4673
observations.add(
4774
new ObjectObservation(
4875
camIndex,
49-
new DetectionInfo(detection),
50-
0.48, // 0.0 because limelight doesn't give you a confidence score, we calculate it ourselves
76+
new DetectionInfo(detection, convertedCornersX, convertedCornersY),
77+
confidence,
5178
timestamp // timestamp in seconds
5279
)
5380
);

0 commit comments

Comments
 (0)