11package org .steelhawks .subsystems .vision .objdetect ;
22
3+ import edu .wpi .first .math .MathUtil ;
34import edu .wpi .first .math .geometry .*;
45import edu .wpi .first .wpilibj .Timer ;
56import edu .wpi .first .wpilibj .smartdashboard .FieldObject2d ;
67import edu .wpi .first .wpilibj2 .command .SubsystemBase ;
78import org .littletonrobotics .junction .Logger ;
9+ import org .steelhawks .Constants ;
810import org .steelhawks .FieldConstants ;
911import org .steelhawks .RobotContainer ;
1012import org .steelhawks .subsystems .vision .VisionConstants ;
13+ import org .steelhawks .util .LoggedTunableNumber ;
1114
1215import java .util .*;
1316import 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 }
0 commit comments