blob: 6e87357854deb013a634b93f9eb8d0353edc222a [file] [log] [blame]
James Kuszmaul57c7c9b2019-01-27 16:16:01 -08001#ifndef Y2019_CONTROL_LOOPS_DRIVETRAIN_CAMERA_H_
2#define Y2019_CONTROL_LOOPS_DRIVETRAIN_CAMERA_H_
3
4#include <vector>
5
6#include "aos/containers/sized_array.h"
7#include "frc971/control_loops/pose.h"
8
Stephan Pleinesd99b1ee2024-02-02 20:56:44 -08009namespace frc971::control_loops {
James Kuszmaul57c7c9b2019-01-27 16:16:01 -080010
11// Represents a target on the field. Currently just consists of a pose and a
12// indicator for whether it is occluded (occlusion is only used by the simulator
13// for testing).
14// Orientation convention:
15// -The absolute position of the pose is the center of the vision target on the
16// field.
17// -The yaw of the pose shall be such that the positive X-axis in the Target's
18// frame wil be pointed straight through the target--i.e., if you are looking
19// at the target head-on, then you will be facing in the same direction as the
20// positive X-axis.
21// E.g., if the Target has a global position of (1, 1, 0) and yaw of pi / 2,
22// then it is at position (1, 1, 0) on the field and is oriented so that if
23// someone were to stand at (1, 0, 0) and turn themselves to have a yaw of
24// pi / 2, they would see the target 1 meter straight ahead of them.
25//
26// Generally, the position of a target should not change dynamically; if we do
27// start having targets that move, we may want to start optimizing certain
28// things (e.g., caching the position of the Target--currently, if the Pose of a
29// target is in an absolute frame, then calling abs_pos will be inexpensive; if
30// that changes, then we start having to recalculate transformations on every
31// frame).
32template <typename Scalar = double>
33class TypedTarget {
34 public:
35 typedef ::frc971::control_loops::TypedPose<Scalar> Pose;
James Kuszmaule093f512019-03-20 06:14:05 -070036 // The nature of the target as a goal--to mark what modes it is a valid
37 // potential goal pose and to mark targets on the opposite side of the field
38 // as not being viable targets.
39 enum class GoalType {
40 // None marks targets that are on the opposite side of the field and not
41 // viable goal poses.
42 kNone,
43 // Spots where we can touch hatch panels.
44 kHatches,
45 // Spots where we can mess with balls.
46 kBalls,
47 // Spots for both (cargo ship, human loading).
48 kBoth,
49 };
James Kuszmaul7d1ef442019-03-23 20:20:50 -070050 // Which target this is within a given field quadrant:
51 enum class TargetType {
52 kHPSlot,
53 kFaceCargoBay,
54 kNearSideCargoBay,
55 kMidSideCargoBay,
56 kFarSideCargoBay,
57 kNearRocket,
58 kFarRocket,
59 kRocketPortal,
60 };
James Kuszmaule093f512019-03-20 06:14:05 -070061 TypedTarget(const Pose &pose, double radius = 0,
James Kuszmaul7d1ef442019-03-23 20:20:50 -070062 TargetType target_type = TargetType::kHPSlot,
James Kuszmaule093f512019-03-20 06:14:05 -070063 GoalType goal_type = GoalType::kBoth)
James Kuszmaul7d1ef442019-03-23 20:20:50 -070064 : pose_(pose),
65 radius_(radius),
66 target_type_(target_type),
67 goal_type_(goal_type) {}
James Kuszmaul090563a2019-02-09 14:43:20 -080068 TypedTarget() {}
James Kuszmaul57c7c9b2019-01-27 16:16:01 -080069 Pose pose() const { return pose_; }
James Kuszmaule093f512019-03-20 06:14:05 -070070 Pose *mutable_pose() { return &pose_; }
James Kuszmaul57c7c9b2019-01-27 16:16:01 -080071
72 bool occluded() const { return occluded_; }
73 void set_occluded(bool occluded) { occluded_ = occluded; }
James Kuszmaule093f512019-03-20 06:14:05 -070074 double radius() const { return radius_; }
75 GoalType goal_type() const { return goal_type_; }
76 void set_goal_type(GoalType goal_type) { goal_type_ = goal_type; }
James Kuszmaul7d1ef442019-03-23 20:20:50 -070077 TargetType target_type() const { return target_type_; }
78 void set_target_type(TargetType target_type) { target_type_ = target_type; }
James Kuszmaul57c7c9b2019-01-27 16:16:01 -080079
80 // Get a list of points for plotting. These points should be plotted on
81 // an x/y plane in the global frame with lines connecting the points.
82 // Essentially, this provides a Polygon that is a reasonable representation
83 // of a Target.
84 // This should not be called from real-time code, as it will probably
85 // dynamically allocate memory.
86 ::std::vector<Pose> PlotPoints() const {
87 // For the actual representation, we will use a triangle such that the
88 // base of the triangle corresponds to the surface the target is on.
89 // The third point is shown behind the target, so that the user can
90 // visually identify which side of the target is the front.
91 Pose base1(&pose_, {0, 0.125, 0}, 0);
92 Pose base2(&pose_, {0, -0.125, 0}, 0);
93 Pose behind(&pose_, {0.05, 0, 0}, 0);
94 // Include behind at the start and end to indicate that we want to draw
95 // a closed polygon.
96 return {behind, base1, base2, behind};
97 }
98
99 private:
100 Pose pose_;
101 bool occluded_ = false;
James Kuszmaule093f512019-03-20 06:14:05 -0700102 // The effective radius of the target--for placing discs, this should be the
103 // radius of the disc; for fetching discs and working with balls this should
104 // be near zero.
105 // TODO(james): We may actually want a non-zero (possibly negative?) number
106 // here for balls.
107 double radius_ = 0.0;
James Kuszmaul7d1ef442019-03-23 20:20:50 -0700108 TargetType target_type_ = TargetType::kHPSlot;
James Kuszmaule093f512019-03-20 06:14:05 -0700109 GoalType goal_type_ = GoalType::kBoth;
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800110}; // class TypedTarget
111
112typedef TypedTarget<double> Target;
113
114// Represents a camera that can see targets and provide information about their
115// relative positions.
116// Note on coordinate systems:
117// -The camera's Pose shall be such that the camera frame's positive X-axis is
118// pointed straight out of the lens (as always, positive Z will be up; we
119// assume that all cameras mounted level, or compensated for such that this
120// code won't care).
121// -The origin of the camera shall be "at" the camera. For this code, I don't
122// think we care too much about the details of the camera model, so we can just
123// assume that it is an idealized pinhole camera with the pinhole being the
124// location of the camera.
125//
126// Template parameters:
127// -num_targets: The number of targets on the field, should be the same for
128// all the actual cameras on the robot (although it may change in tests).
129// -Scalar: The floating point type to use (double vs. float).
130// -num_obstacles: The number of obstacles on the field to account for; like
131// the number of targets, it should be consistent across actual cameras,
132// although for simulation we may add extra obstacles for testing.
133template <int num_targets, int num_obstacles, typename Scalar = double>
134class TypedCamera {
135 public:
136 typedef ::frc971::control_loops::TypedPose<Scalar> Pose;
137 typedef ::frc971::control_loops::TypedLineSegment<Scalar> LineSegment;
138
139 // TargetView contains the information associated with a sensor reading
140 // from the camera--the readings themselves and noise values, *from the
141 // Camera's persective* for each reading.
142 // Note that the noise terms are just accounting for the inaccuracy you
143 // expect to get due to visual noise, pixel-level resolution, etc. These
144 // do not account for the fact that, e.g., there is noise in the Pose of the
145 // robot which can translate into noise in the target reading.
146 // The noise terms are standard deviations, and so have units identical
147 // to that of the actual readings.
148 struct TargetView {
149 struct Reading {
150 // The heading as reported from the camera; zero = straight ahead,
151 // positive = target in the left half of the image.
Austin Schuhd749d932020-12-30 21:38:40 -0800152 Scalar heading; // radians
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800153 // The distance from the camera to the target.
154 Scalar distance; // meters
James Kuszmaul81df16a2019-03-03 17:17:34 -0800155 // Height of the target from the camera.
Austin Schuhd749d932020-12-30 21:38:40 -0800156 Scalar height; // meters
James Kuszmaul289756f2019-03-05 21:52:10 -0800157 // The angle of the target relative to line between the camera and
158 // the center of the target.
Austin Schuhd749d932020-12-30 21:38:40 -0800159 Scalar skew; // radians
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800160 };
161 Reading reading;
162 Reading noise;
163
164 // The target that this view corresponds to.
James Kuszmaul09f564a2019-02-18 17:37:09 -0800165 const TypedTarget<Scalar> *target = nullptr;
James Kuszmaul090563a2019-02-09 14:43:20 -0800166 // The Pose the camera was at when viewing the target:
167 Pose camera_pose;
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800168 };
169
170 // Important parameters for dealing with camera noise calculations.
171 // Ultimately, this should end up coming from the constants file.
172 struct NoiseParameters {
173 // The maximum distance from which we can see a target head-on (when the
174 // target is not head-on, we adjust for that).
175 Scalar max_viewable_distance; // meters
176
177 // All noises are standard deviations of the noise, assuming an ~normal
178 // distribution.
179
180 // Noise in the heading measurement, which should be constant regardless of
181 // other factors.
182 Scalar heading_noise; // radians
183 // Noise in the distance measurement when the target is 1m away and head-on
184 // to us. This is adjusted by assuming the noise is proportional to the
185 // apparent width of the target (because the target also has height, this
186 // may not be strictly correct).
187 // TODO(james): Is this a good model? It should be reasonable, but there
188 // may be more complexity somewhere.
189 Scalar nominal_distance_noise; // meters
190 // The noise in the skew measurement when the target is 1m away and head-on
191 // to us. Calculated in the same manner with the same caveats as the
192 // distance noise.
193 Scalar nominal_skew_noise; // radians
194 // Noise in the height measurement, same rules as for skew and distance.
195 // TODO(james): Figure out how much noise we will actually get in the
196 // height, since there will be extremely low resolution on it.
197 Scalar nominal_height_noise; // meters
198 };
199
James Kuszmaul09f564a2019-02-18 17:37:09 -0800200 // Provide a default constructor to make life easier.
201 TypedCamera() {}
202
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800203 // Creates a camera:
204 // pose: The Pose of the camera, relative to the robot at least transitively.
205 // fov: The field-of-view of the camera, in radians. Note that this is the
206 // *total* field-of-view in the horizontal plane (left-right), so the angle
207 // from the left edge of the image to the right edge.
208 // targets: The list of targets on the field that could be seen by the camera.
209 // obstacles: The list of known obstacles on the field.
210 TypedCamera(const Pose &pose, Scalar fov,
211 const NoiseParameters &noise_parameters,
212 const ::std::array<TypedTarget<Scalar>, num_targets> &targets,
213 const ::std::array<LineSegment, num_obstacles> &obstacles)
214 : pose_(pose),
215 fov_(fov),
216 noise_parameters_(noise_parameters),
217 targets_(targets),
218 obstacles_(obstacles) {}
219
220 // Returns a list of TargetViews for all the currently visible targets.
221 // These will contain ground-truth TargetViews, so the readings will be
222 // perfect; a pseudo-random number generator should be used to add noise
223 // separately for simulation.
224 ::aos::SizedArray<TargetView, num_targets> target_views() const {
225 ::aos::SizedArray<TargetView, num_targets> views;
James Kuszmaul090563a2019-02-09 14:43:20 -0800226 Pose camera_abs_pose = pose_.Rebase(nullptr);
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800227 // Because there are num_targets in targets_ and because AddTargetIfVisible
228 // adds at most 1 view to views, we should never exceed the size of
229 // SizedArray.
230 for (const auto &target : targets_) {
James Kuszmaul090563a2019-02-09 14:43:20 -0800231 AddTargetIfVisible(target, camera_abs_pose, &views);
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800232 }
233 return views;
234 }
235
236 // Returns a list of list of points for plotting. Each list of points should
237 // be plotted as a line; currently, each list is just a pair drawing a line
238 // from the camera aperture to the target location.
239 // This should not be called from real-time code, as it will probably
240 // dynamically allocate memory.
241 ::std::vector<::std::vector<Pose>> PlotPoints() const {
242 ::std::vector<::std::vector<Pose>> list_of_lists;
243 for (const auto &view : target_views()) {
James Kuszmaul1057ce82019-02-09 17:58:24 -0800244 list_of_lists.push_back({pose_, view.target->pose().Rebase(&pose_)});
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800245 }
246 return list_of_lists;
247 }
248
James Kuszmaul090563a2019-02-09 14:43:20 -0800249 const Pose &pose() const { return pose_; }
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800250 Scalar fov() const { return fov_; }
251
James Kuszmaul09f564a2019-02-18 17:37:09 -0800252 // Estimates the noise values of a target based on the raw readings.
253 // Also estimates whether we would expect the target to be visible, and
254 // populates is_visible if is_visible is not nullptr.
255 void PopulateNoise(TargetView *view, bool *is_visible = nullptr) const {
256 // Calculate the width of the target as it appears in the image.
257 // This number is unitless and if greater than 1, implies that the target is
258 // visible to the camera and if less than 1 implies it is too small to be
259 // registered on the camera.
James Kuszmaul6f941b72019-03-08 18:12:25 -0800260 const Scalar cosskew = ::std::cos(view->reading.skew);
261 Scalar apparent_width = ::std::max<Scalar>(
262 0.0, cosskew * noise_parameters_.max_viewable_distance /
263 view->reading.distance);
264 // If we got wildly invalid distance or skew measurements, then set a very
265 // small apparent width.
266 if (view->reading.distance < 0 || cosskew < 0) {
267 apparent_width = 0.01;
268 }
James Kuszmaul09f564a2019-02-18 17:37:09 -0800269 // As both a sanity check and for the sake of numerical stability, manually
James Kuszmaul6f941b72019-03-08 18:12:25 -0800270 // set apparent_width to something "very small" if it is near zero.
271 if (apparent_width < 0.01) {
James Kuszmaul09f564a2019-02-18 17:37:09 -0800272 apparent_width = 0.01;
273 }
274
275 if (is_visible != nullptr) {
276 *is_visible = apparent_width >= 1.0;
277 }
278
279 view->noise.heading = noise_parameters_.heading_noise;
280
281 const Scalar normalized_width =
282 apparent_width / noise_parameters_.max_viewable_distance;
283 view->noise.distance =
284 noise_parameters_.nominal_distance_noise / normalized_width;
Austin Schuhd749d932020-12-30 21:38:40 -0800285 view->noise.skew = noise_parameters_.nominal_skew_noise / normalized_width;
James Kuszmaul09f564a2019-02-18 17:37:09 -0800286 view->noise.height =
287 noise_parameters_.nominal_height_noise / normalized_width;
288 }
289
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800290 private:
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800291 // If the specified target is visible from the current camera Pose, adds it to
292 // the views array.
293 void AddTargetIfVisible(
James Kuszmaul090563a2019-02-09 14:43:20 -0800294 const TypedTarget<Scalar> &target, const Pose &camera_abs_pose,
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800295 ::aos::SizedArray<TargetView, num_targets> *views) const;
296
297 // The Pose of this camera.
James Kuszmaul09f564a2019-02-18 17:37:09 -0800298 Pose pose_;
James Kuszmaul090563a2019-02-09 14:43:20 -0800299
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800300 // Field of view of the camera, in radians.
James Kuszmaul09f564a2019-02-18 17:37:09 -0800301 Scalar fov_;
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800302
303 // Various constants to calclate sensor noise; see definition of
304 // NoiseParameters for more detail.
James Kuszmaul09f564a2019-02-18 17:37:09 -0800305 NoiseParameters noise_parameters_;
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800306
307 // A list of all the targets on the field.
James Kuszmaul090563a2019-02-09 14:43:20 -0800308 // TODO(james): Is it worth creating some sort of cache for the targets and
309 // obstacles? e.g., passing around pointer to the targets/obstacles.
James Kuszmaul09f564a2019-02-18 17:37:09 -0800310 ::std::array<TypedTarget<Scalar>, num_targets> targets_;
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800311 // Known obstacles on the field which can interfere with our view of the
312 // targets. An "obstacle" is a line segment which we cannot see through, as
313 // such a logical obstacle (e.g., the cargo ship) may consist of many
314 // obstacles in this list to account for all of its sides.
James Kuszmaul09f564a2019-02-18 17:37:09 -0800315 ::std::array<LineSegment, num_obstacles> obstacles_;
Austin Schuhd749d932020-12-30 21:38:40 -0800316}; // class TypedCamera
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800317
318template <int num_targets, int num_obstacles, typename Scalar>
319void TypedCamera<num_targets, num_obstacles, Scalar>::AddTargetIfVisible(
James Kuszmaul090563a2019-02-09 14:43:20 -0800320 const TypedTarget<Scalar> &target, const Pose &camera_abs_pose,
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800321 ::aos::SizedArray<TargetView, num_targets> *views) const {
322 if (target.occluded()) {
323 return;
324 }
325
326 // Precompute the current absolute pose of the camera, because we will reuse
327 // it a bunch.
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800328 const Pose relative_pose = target.pose().Rebase(&camera_abs_pose);
329 const Scalar heading = relative_pose.heading();
330 const Scalar distance = relative_pose.xy_norm();
James Kuszmaul289756f2019-03-05 21:52:10 -0800331 const Scalar skew =
332 ::aos::math::NormalizeAngle(relative_pose.rel_theta() - heading);
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800333
334 // Check if the camera is in the angular FOV.
335 if (::std::abs(heading) > fov_ / 2.0) {
336 return;
337 }
338
James Kuszmaul09f564a2019-02-18 17:37:09 -0800339 TargetView view;
340 view.reading.heading = heading;
341 view.reading.distance = distance;
342 view.reading.skew = skew;
James Kuszmaul81df16a2019-03-03 17:17:34 -0800343 view.reading.height = relative_pose.rel_pos().z();
James Kuszmaul09f564a2019-02-18 17:37:09 -0800344 view.target = &target;
345 view.camera_pose = camera_abs_pose;
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800346
James Kuszmaul09f564a2019-02-18 17:37:09 -0800347 bool is_visible = false;
348
349 PopulateNoise(&view, &is_visible);
350
351 if (!is_visible) {
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800352 return;
353 }
354
355 // Final visibility check is for whether there are any obstacles blocking or
356 // line of sight.
357 for (const auto &obstacle : obstacles_) {
358 if (obstacle.Intersects({camera_abs_pose, target.pose()})) {
359 return;
360 }
361 }
362
James Kuszmaul09f564a2019-02-18 17:37:09 -0800363 // At this point, we've passed all the checks to ensure that the target is
364 // visible and we can add it to the list of targets.
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800365 views->push_back(view);
366}
367
Stephan Pleinesd99b1ee2024-02-02 20:56:44 -0800368} // namespace frc971::control_loops
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800369
370#endif // Y2019_CONTROL_LOOPS_DRIVETRAIN_CAMERA_H_