Skip to content

Commit 43bdd67

Browse files
committed
Refactor object detection to use confidence calculation
Introduces a new confidence calculation for object detections using area, shape, and angle metrics, and filters observations based on a tunable confidence threshold. Updates the ObjectVisionIO interfaces and implementations to support new detection info structures and camera indices, and modifies constructors and usages accordingly.
1 parent cdbfd87 commit 43bdd67

File tree

5 files changed

+109
-36
lines changed

5 files changed

+109
-36
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,8 @@ public static ObjectVisionIO[] getObjIO() {
192192
for (int i = 0; i < config.length; i++) {
193193
if (RobotBase.isReal()) {
194194
switch (config[i].cameraType) {
195-
case LIMELIGHT -> io[i] = new ObjectVisionIOLimelight(config[i].name);
196-
case PHOTON -> io[i] = new ObjectVisionIOPhoton(config[i].name, config[i].robotToCamera);
195+
case LIMELIGHT -> io[i] = new ObjectVisionIOLimelight(config[i].name, i);
196+
case PHOTON -> io[i] = new ObjectVisionIOPhoton(config[i].name, i, config[i].robotToCamera);
197197
}
198198
} else if (Constants.getRobot() == Constants.RobotType.SIMBOT && !RobotBase.isReal()) {
199199
io[i] = new ObjectVisionSim(

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

Lines changed: 69 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package org.steelhawks.subsystems.vision.objdetect;
22

3+
import edu.wpi.first.math.MathUtil;
34
import edu.wpi.first.math.geometry.*;
45
import edu.wpi.first.wpilibj.Timer;
56
import edu.wpi.first.wpilibj.smartdashboard.FieldObject2d;
67
import edu.wpi.first.wpilibj2.command.SubsystemBase;
78
import org.littletonrobotics.junction.Logger;
9+
import org.steelhawks.Constants;
810
import org.steelhawks.FieldConstants;
911
import org.steelhawks.RobotContainer;
1012
import org.steelhawks.subsystems.vision.VisionConstants;
13+
import org.steelhawks.util.LoggedTunableNumber;
1114

1215
import java.util.*;
1316
import java.util.stream.Collectors;
@@ -17,6 +20,11 @@ public class ObjectVision extends SubsystemBase {
1720
private static final double coralOverlap = 0.5; // meters
1821
private static final double coralMaxAge = 10.0; // seconds
1922

23+
private static final LoggedTunableNumber maxArea =
24+
new LoggedTunableNumber("ObjectVision/MaxArea", 20.0);
25+
private static final LoggedTunableNumber confidenceThreshold =
26+
new LoggedTunableNumber("ObjectVision/ConfidenceThreshold", 0.3);
27+
2028
private final ObjectVisionIO[] io;
2129
private final ObjectVisionIOInputsAutoLogged[] inputs;
2230

@@ -26,7 +34,7 @@ record CoralPose(
2634

2735
private final FieldObject2d coralObjects = FieldConstants.FIELD_2D.getObject("Corals");
2836
private final ArrayList<ObjectVisionIO.ObjectObservation> allObservations = new ArrayList<>();
29-
private Set<CoralPose> coralPoses = new HashSet<>();
37+
private final Set<CoralPose> coralPoses = new HashSet<>();
3038

3139
public ObjectVision() {
3240
this.io = VisionConstants.getObjIO();
@@ -36,24 +44,39 @@ public ObjectVision() {
3644
}
3745
}
3846

39-
@Override
40-
public void periodic() {
41-
for (int i = 0; i < inputs.length; i++) {
42-
io[i].updateInputs(inputs[i]);
43-
Logger.processInputs("ObjectVision/" + io[i].getName(), inputs[i]);
44-
allObservations.addAll(Arrays.asList(inputs[i].observations));
45-
}
46-
allObservations.stream()
47-
.sorted(Comparator.comparingDouble(ObjectVisionIO.ObjectObservation::timestamp))
48-
.forEach(this::addCoralObservationToPose);
49-
50-
coralObjects.setPoses(
51-
coralPoses.stream()
52-
.map(coral -> new Pose2d(coral.translation, new Rotation2d())) // zero rotation
53-
.collect(Collectors.toList()));
54-
allObservations.clear();
47+
/*
48+
* NEED TO TUNE MAX AREA
49+
* NEED TO TUNE W1, W2, W3
50+
* Log values to tune properly
51+
*/
52+
public static double calcConfidence(ObjectVisionIO.DetectionInfo d, int cameraIndex) {
53+
String logName = "ObjectVision/" +
54+
Objects.requireNonNull(VisionConstants.getObjDetectConfig())[cameraIndex].name() + "/";
55+
56+
// areaScore: normalized 0–1
57+
double areaScore = Math.min(d.area() / maxArea.get(), 1.0);
58+
59+
// shapeScore: 0 = skewed, 1 = rectangle
60+
double width = Math.abs(d.tx()[0] - d.tx()[1]);
61+
double height = Math.abs(d.ty()[0] - d.ty()[2]);
62+
double diag1 = Math.hypot(d.tx()[0] - d.tx()[2], d.ty()[0] - d.ty()[2]);
63+
double diag2 = Math.hypot(d.tx()[1] - d.tx()[3], d.ty()[1] - d.ty()[3]);
64+
double ratio = Math.min(diag1, diag2) / Math.max(diag1, diag2); // 0–1, shapeScore
65+
66+
// angleScore: penalize extreme horizontal angles
67+
double txAvg = (d.tx()[0] + d.tx()[1] + d.tx()[2] + d.tx()[3]) / 4.0;
68+
double angleScore = Math.cos(txAvg); // roughly favors smaller offsets
69+
70+
double w1 = 0.5; // how much the area matters to confidence
71+
double w2 = 0.3; // how much the shape matters to confidence
72+
double w3 = 0.2; // how much the angle matters to confidence
73+
74+
return MathUtil.clamp(Constants.loggedValue(logName + "w1", w1 * areaScore) +
75+
Constants.loggedValue(logName + "w2", w2 * ratio) +
76+
Constants.loggedValue(logName + "w3", w3 * angleScore), 0.0, 1.0);
5577
}
5678

79+
5780
private void addCoralObservationToPose(ObjectVisionIO.ObjectObservation observation) {
5881
double now = Timer.getFPGATimestamp();
5982
Optional<Pose2d> oldWheelOdomPose = RobotContainer.s_Swerve.getPoseAtTime(observation.timestamp());
@@ -67,7 +90,8 @@ private void addCoralObservationToPose(ObjectVisionIO.ObjectObservation observat
6790
// robot actually moved according to the wheel odometry since the observation
6891
Pose2d fieldToRobot =
6992
estimatedPose.transformBy(new Transform2d(wheelOdometryPose, oldWheelOdomPose.get()));
70-
Pose3d robotToCamera = new Pose3d(0.0, 0.0, 0.0, new Rotation3d());
93+
Transform3d robotToCamera =
94+
Objects.requireNonNull(VisionConstants.getObjDetectConfig())[observation.camIndex()].robotToCamera();
7195

7296
// find object midpoint
7397
double tx = (observation.tx()[2] + observation.tx()[3]) / 2.0;
@@ -86,23 +110,43 @@ private void addCoralObservationToPose(ObjectVisionIO.ObjectObservation observat
86110
double lateralCorrection = 1.0 / Math.cos(-tx); // correction for horizontal angle to get lateral distance
87111
double cameraToObjectNorm = forwardDistance * lateralCorrection;
88112

89-
Pose2d fieldToCamera = fieldToRobot.transformBy(new Transform2d(robotToCamera.toPose2d().toMatrix()));
113+
Transform2d robotToCamera2d =
114+
new Transform2d(
115+
robotToCamera.getTranslation().toTranslation2d(),
116+
robotToCamera.getRotation().toRotation2d());
117+
Pose2d fieldToCamera = fieldToRobot.transformBy(robotToCamera2d);
90118
Pose2d fieldToCoral =
91119
fieldToCamera
92120
.transformBy(new Transform2d(Translation2d.kZero, new Rotation2d(-tx)))
93121
.transformBy(
94122
new Transform2d(new Translation2d(cameraToObjectNorm, 0), Rotation2d.kZero));
95123
CoralPose coralPose = new CoralPose(fieldToCoral.getTranslation(), observation.timestamp());
96124
// delete coral once close enough to robot and deletes coral that has existed longer than 10 seconds
97-
coralPoses =
98-
coralPoses.stream()
99-
.filter(
100-
(c) -> c.translation.getDistance(fieldToCoral.getTranslation()) > coralOverlap)
101-
.filter((c) -> now - c.timestamp <= coralMaxAge)
102-
.collect(Collectors.toSet());
125+
coralPoses.removeIf(
126+
c -> c.translation.getDistance(fieldToCoral.getTranslation()) <= coralOverlap
127+
|| now - c.timestamp > coralMaxAge);
103128
coralPoses.add(coralPose);
104129
}
105130

131+
@Override
132+
public void periodic() {
133+
for (int i = 0; i < inputs.length; i++) {
134+
io[i].updateInputs(inputs[i]);
135+
Logger.processInputs("ObjectVision/" + io[i].getName(), inputs[i]);
136+
allObservations.addAll(Arrays.asList(inputs[i].observations));
137+
}
138+
allObservations.stream()
139+
.filter(o -> calcConfidence(o.info(), o.camIndex()) >= confidenceThreshold.get())
140+
.sorted(Comparator.comparingDouble(ObjectVisionIO.ObjectObservation::timestamp))
141+
.forEach(this::addCoralObservationToPose);
142+
143+
coralObjects.setPoses(
144+
coralPoses.stream()
145+
.map(coral -> new Pose2d(coral.translation, new Rotation2d())) // zero rotation
146+
.collect(Collectors.toList()));
147+
allObservations.clear();
148+
}
149+
106150
public void reset() {
107151
coralPoses.clear();
108152
}

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

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,43 @@
11
package org.steelhawks.subsystems.vision.objdetect;
22

33
import org.littletonrobotics.junction.AutoLog;
4+
import org.steelhawks.util.LimelightHelpers;
45

56
public interface ObjectVisionIO {
67

78
record ObjectObservation(
8-
String label,
9-
double confidence,
9+
int camIndex,
10+
int classId,
11+
DetectionInfo info,
12+
double confidence, // TODO if Photon doesnt give you a confidence val just remove this and calc on your own
1013
double[] tx,
1114
double[] ty,
1215
double area,
1316
double timestamp
1417
) {}
1518

19+
record DetectionInfo(
20+
int classId, // Class of the object
21+
double area, // Target area (e.g., ta from Limelight)
22+
double[] tx, // Corner X positions
23+
double[] ty // Corner Y positions
24+
) {
25+
public DetectionInfo(LimelightHelpers.RawDetection raw) {
26+
this(
27+
raw.classId,
28+
raw.ta,
29+
new double[] {raw.corner0_X, raw.corner1_X, raw.corner2_X, raw.corner3_X},
30+
new double[] {raw.corner0_Y, raw.corner1_Y, raw.corner2_Y, raw.corner3_Y}
31+
);
32+
}
33+
}
34+
35+
1636
@AutoLog
1737
class ObjectVisionIOInputs {
1838
public boolean connected = false;
1939
public ObjectObservation latestTargetObservation =
20-
new ObjectObservation("", 0.0, new double[0], new double[0], 0.0, 0.0);
40+
new ObjectObservation(0, 0, new DetectionInfo(0, 0.0, new double[0], new double[0]), 0.0, new double[0], new double[0], 0.0, 0.0);
2141
public ObjectObservation[] observations = new ObjectObservation[0];
2242
}
2343

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@
1212
public class ObjectVisionIOLimelight implements ObjectVisionIO {
1313

1414
private final String name;
15+
private final int camIndex;
1516
private final DoubleSubscriber latencySubscriber;
1617
private final DoubleSubscriber txSubscriber;
1718
private final DoubleSubscriber tySubscriber;
1819
private final DoubleSubscriber taSubscriber; // target area
1920
private final IntegerSubscriber targetValidSubscriber;
2021

21-
public ObjectVisionIOLimelight(String name) {
22+
public ObjectVisionIOLimelight(String name, int camIndex) {
2223
this.name = name;
24+
this.camIndex = camIndex;
2325
var table = NetworkTableInstance.getDefault().getTable(name);
2426
latencySubscriber = table.getDoubleTopic("tl").subscribe(0.0);
2527
txSubscriber = table.getDoubleTopic("tx").subscribe(0.0);
@@ -38,8 +40,6 @@ public void updateInputs(ObjectVisionIOInputs inputs) {
3840
LimelightHelpers.getRawDetections(name);
3941

4042
for (LimelightHelpers.RawDetection detection : detections) {
41-
String label =
42-
detection.classId == 0 ? "coral" : "other";
4343
// pack corners into tx ty arrays
4444
double[] tx = {
4545
detection.corner0_X,
@@ -53,12 +53,14 @@ public void updateInputs(ObjectVisionIOInputs inputs) {
5353
detection.corner2_Y,
5454
detection.corner3_Y
5555
};
56-
double confidence = detection.ta; // make an algo later
5756
double limelightLatencySec = latencySubscriber.get() / 1000.0;
5857
double timestamp = RobotController.getFPGATime() / 1e6 - limelightLatencySec;
5958
observations.add(
6059
new ObjectObservation(
61-
label, confidence, tx, ty,
60+
camIndex, detection.classId,
61+
new DetectionInfo(detection),
62+
0.0, // TODO 0.0 for limelight, photon vision might just give you a confidence value in code but im not sure confirm and fix
63+
tx, ty,
6264
detection.ta,
6365
timestamp // timestamp in seconds
6466
)
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
package org.steelhawks.subsystems.vision.objdetect;
22

33
import edu.wpi.first.math.geometry.Transform3d;
4+
import org.photonvision.PhotonCamera;
45

56
public class ObjectVisionIOPhoton implements ObjectVisionIO {
67

78
private final String name;
9+
private final int camIndex;
810
private final Transform3d robotToCamera;
911

10-
public ObjectVisionIOPhoton(String name, Transform3d robotToCamera) {
12+
private final PhotonCamera camera;
13+
14+
public ObjectVisionIOPhoton(String name, int camIndex, Transform3d robotToCamera) {
1115
this.name = name;
16+
this.camIndex = camIndex;
1217
this.robotToCamera = robotToCamera;
18+
19+
camera = new PhotonCamera(name);
1320
}
1421
}

0 commit comments

Comments
 (0)