blob: 2ab4b4f5ea5ca8d9d2fe3896d3528f3f6fda258e [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
9namespace y2019 {
10namespace control_loops {
11
12// Represents a target on the field. Currently just consists of a pose and a
13// indicator for whether it is occluded (occlusion is only used by the simulator
14// for testing).
15// Orientation convention:
16// -The absolute position of the pose is the center of the vision target on the
17// field.
18// -The yaw of the pose shall be such that the positive X-axis in the Target's
19// frame wil be pointed straight through the target--i.e., if you are looking
20// at the target head-on, then you will be facing in the same direction as the
21// positive X-axis.
22// E.g., if the Target has a global position of (1, 1, 0) and yaw of pi / 2,
23// then it is at position (1, 1, 0) on the field and is oriented so that if
24// someone were to stand at (1, 0, 0) and turn themselves to have a yaw of
25// pi / 2, they would see the target 1 meter straight ahead of them.
26//
27// Generally, the position of a target should not change dynamically; if we do
28// start having targets that move, we may want to start optimizing certain
29// things (e.g., caching the position of the Target--currently, if the Pose of a
30// target is in an absolute frame, then calling abs_pos will be inexpensive; if
31// that changes, then we start having to recalculate transformations on every
32// frame).
33template <typename Scalar = double>
34class TypedTarget {
35 public:
36 typedef ::frc971::control_loops::TypedPose<Scalar> Pose;
37 TypedTarget(const Pose &pose) : pose_(pose) {}
James Kuszmaul090563a2019-02-09 14:43:20 -080038 TypedTarget() {}
James Kuszmaul57c7c9b2019-01-27 16:16:01 -080039 Pose pose() const { return pose_; }
40
41 bool occluded() const { return occluded_; }
42 void set_occluded(bool occluded) { occluded_ = occluded; }
43
44 // Get a list of points for plotting. These points should be plotted on
45 // an x/y plane in the global frame with lines connecting the points.
46 // Essentially, this provides a Polygon that is a reasonable representation
47 // of a Target.
48 // This should not be called from real-time code, as it will probably
49 // dynamically allocate memory.
50 ::std::vector<Pose> PlotPoints() const {
51 // For the actual representation, we will use a triangle such that the
52 // base of the triangle corresponds to the surface the target is on.
53 // The third point is shown behind the target, so that the user can
54 // visually identify which side of the target is the front.
55 Pose base1(&pose_, {0, 0.125, 0}, 0);
56 Pose base2(&pose_, {0, -0.125, 0}, 0);
57 Pose behind(&pose_, {0.05, 0, 0}, 0);
58 // Include behind at the start and end to indicate that we want to draw
59 // a closed polygon.
60 return {behind, base1, base2, behind};
61 }
62
63 private:
64 Pose pose_;
65 bool occluded_ = false;
66}; // class TypedTarget
67
68typedef TypedTarget<double> Target;
69
70// Represents a camera that can see targets and provide information about their
71// relative positions.
72// Note on coordinate systems:
73// -The camera's Pose shall be such that the camera frame's positive X-axis is
74// pointed straight out of the lens (as always, positive Z will be up; we
75// assume that all cameras mounted level, or compensated for such that this
76// code won't care).
77// -The origin of the camera shall be "at" the camera. For this code, I don't
78// think we care too much about the details of the camera model, so we can just
79// assume that it is an idealized pinhole camera with the pinhole being the
80// location of the camera.
81//
82// Template parameters:
83// -num_targets: The number of targets on the field, should be the same for
84// all the actual cameras on the robot (although it may change in tests).
85// -Scalar: The floating point type to use (double vs. float).
86// -num_obstacles: The number of obstacles on the field to account for; like
87// the number of targets, it should be consistent across actual cameras,
88// although for simulation we may add extra obstacles for testing.
89template <int num_targets, int num_obstacles, typename Scalar = double>
90class TypedCamera {
91 public:
92 typedef ::frc971::control_loops::TypedPose<Scalar> Pose;
93 typedef ::frc971::control_loops::TypedLineSegment<Scalar> LineSegment;
94
95 // TargetView contains the information associated with a sensor reading
96 // from the camera--the readings themselves and noise values, *from the
97 // Camera's persective* for each reading.
98 // Note that the noise terms are just accounting for the inaccuracy you
99 // expect to get due to visual noise, pixel-level resolution, etc. These
100 // do not account for the fact that, e.g., there is noise in the Pose of the
101 // robot which can translate into noise in the target reading.
102 // The noise terms are standard deviations, and so have units identical
103 // to that of the actual readings.
104 struct TargetView {
105 struct Reading {
106 // The heading as reported from the camera; zero = straight ahead,
107 // positive = target in the left half of the image.
108 Scalar heading; // radians
109 // The distance from the camera to the target.
110 Scalar distance; // meters
James Kuszmaul81df16a2019-03-03 17:17:34 -0800111 // Height of the target from the camera.
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800112 Scalar height; // meters
James Kuszmaul289756f2019-03-05 21:52:10 -0800113 // The angle of the target relative to line between the camera and
114 // the center of the target.
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800115 Scalar skew; // radians
116 };
117 Reading reading;
118 Reading noise;
119
120 // The target that this view corresponds to.
James Kuszmaul09f564a2019-02-18 17:37:09 -0800121 const TypedTarget<Scalar> *target = nullptr;
James Kuszmaul090563a2019-02-09 14:43:20 -0800122 // The Pose the camera was at when viewing the target:
123 Pose camera_pose;
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800124 };
125
126 // Important parameters for dealing with camera noise calculations.
127 // Ultimately, this should end up coming from the constants file.
128 struct NoiseParameters {
129 // The maximum distance from which we can see a target head-on (when the
130 // target is not head-on, we adjust for that).
131 Scalar max_viewable_distance; // meters
132
133 // All noises are standard deviations of the noise, assuming an ~normal
134 // distribution.
135
136 // Noise in the heading measurement, which should be constant regardless of
137 // other factors.
138 Scalar heading_noise; // radians
139 // Noise in the distance measurement when the target is 1m away and head-on
140 // to us. This is adjusted by assuming the noise is proportional to the
141 // apparent width of the target (because the target also has height, this
142 // may not be strictly correct).
143 // TODO(james): Is this a good model? It should be reasonable, but there
144 // may be more complexity somewhere.
145 Scalar nominal_distance_noise; // meters
146 // The noise in the skew measurement when the target is 1m away and head-on
147 // to us. Calculated in the same manner with the same caveats as the
148 // distance noise.
149 Scalar nominal_skew_noise; // radians
150 // Noise in the height measurement, same rules as for skew and distance.
151 // TODO(james): Figure out how much noise we will actually get in the
152 // height, since there will be extremely low resolution on it.
153 Scalar nominal_height_noise; // meters
154 };
155
James Kuszmaul09f564a2019-02-18 17:37:09 -0800156 // Provide a default constructor to make life easier.
157 TypedCamera() {}
158
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800159 // Creates a camera:
160 // pose: The Pose of the camera, relative to the robot at least transitively.
161 // fov: The field-of-view of the camera, in radians. Note that this is the
162 // *total* field-of-view in the horizontal plane (left-right), so the angle
163 // from the left edge of the image to the right edge.
164 // targets: The list of targets on the field that could be seen by the camera.
165 // obstacles: The list of known obstacles on the field.
166 TypedCamera(const Pose &pose, Scalar fov,
167 const NoiseParameters &noise_parameters,
168 const ::std::array<TypedTarget<Scalar>, num_targets> &targets,
169 const ::std::array<LineSegment, num_obstacles> &obstacles)
170 : pose_(pose),
171 fov_(fov),
172 noise_parameters_(noise_parameters),
173 targets_(targets),
174 obstacles_(obstacles) {}
175
176 // Returns a list of TargetViews for all the currently visible targets.
177 // These will contain ground-truth TargetViews, so the readings will be
178 // perfect; a pseudo-random number generator should be used to add noise
179 // separately for simulation.
180 ::aos::SizedArray<TargetView, num_targets> target_views() const {
181 ::aos::SizedArray<TargetView, num_targets> views;
James Kuszmaul090563a2019-02-09 14:43:20 -0800182 Pose camera_abs_pose = pose_.Rebase(nullptr);
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800183 // Because there are num_targets in targets_ and because AddTargetIfVisible
184 // adds at most 1 view to views, we should never exceed the size of
185 // SizedArray.
186 for (const auto &target : targets_) {
James Kuszmaul090563a2019-02-09 14:43:20 -0800187 AddTargetIfVisible(target, camera_abs_pose, &views);
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800188 }
189 return views;
190 }
191
192 // Returns a list of list of points for plotting. Each list of points should
193 // be plotted as a line; currently, each list is just a pair drawing a line
194 // from the camera aperture to the target location.
195 // This should not be called from real-time code, as it will probably
196 // dynamically allocate memory.
197 ::std::vector<::std::vector<Pose>> PlotPoints() const {
198 ::std::vector<::std::vector<Pose>> list_of_lists;
199 for (const auto &view : target_views()) {
James Kuszmaul1057ce82019-02-09 17:58:24 -0800200 list_of_lists.push_back({pose_, view.target->pose().Rebase(&pose_)});
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800201 }
202 return list_of_lists;
203 }
204
James Kuszmaul090563a2019-02-09 14:43:20 -0800205 const Pose &pose() const { return pose_; }
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800206 Scalar fov() const { return fov_; }
207
James Kuszmaul09f564a2019-02-18 17:37:09 -0800208 // Estimates the noise values of a target based on the raw readings.
209 // Also estimates whether we would expect the target to be visible, and
210 // populates is_visible if is_visible is not nullptr.
211 void PopulateNoise(TargetView *view, bool *is_visible = nullptr) const {
212 // Calculate the width of the target as it appears in the image.
213 // This number is unitless and if greater than 1, implies that the target is
214 // visible to the camera and if less than 1 implies it is too small to be
215 // registered on the camera.
216 Scalar apparent_width =
217 ::std::max<Scalar>(0.0, ::std::cos(view->reading.skew) *
218 noise_parameters_.max_viewable_distance /
219 view->reading.distance);
220 // As both a sanity check and for the sake of numerical stability, manually
221 // set apparent_width to something "very small" if the distance is too
222 // close.
223 if (view->reading.distance < 0.01) {
224 apparent_width = 0.01;
225 }
226
227 if (is_visible != nullptr) {
228 *is_visible = apparent_width >= 1.0;
229 }
230
231 view->noise.heading = noise_parameters_.heading_noise;
232
233 const Scalar normalized_width =
234 apparent_width / noise_parameters_.max_viewable_distance;
235 view->noise.distance =
236 noise_parameters_.nominal_distance_noise / normalized_width;
237 view->noise.skew =
238 noise_parameters_.nominal_skew_noise / normalized_width;
239 view->noise.height =
240 noise_parameters_.nominal_height_noise / normalized_width;
241 }
242
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800243 private:
244
245 // If the specified target is visible from the current camera Pose, adds it to
246 // the views array.
247 void AddTargetIfVisible(
James Kuszmaul090563a2019-02-09 14:43:20 -0800248 const TypedTarget<Scalar> &target, const Pose &camera_abs_pose,
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800249 ::aos::SizedArray<TargetView, num_targets> *views) const;
250
251 // The Pose of this camera.
James Kuszmaul09f564a2019-02-18 17:37:09 -0800252 Pose pose_;
James Kuszmaul090563a2019-02-09 14:43:20 -0800253
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800254 // Field of view of the camera, in radians.
James Kuszmaul09f564a2019-02-18 17:37:09 -0800255 Scalar fov_;
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800256
257 // Various constants to calclate sensor noise; see definition of
258 // NoiseParameters for more detail.
James Kuszmaul09f564a2019-02-18 17:37:09 -0800259 NoiseParameters noise_parameters_;
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800260
261 // A list of all the targets on the field.
James Kuszmaul090563a2019-02-09 14:43:20 -0800262 // TODO(james): Is it worth creating some sort of cache for the targets and
263 // obstacles? e.g., passing around pointer to the targets/obstacles.
James Kuszmaul09f564a2019-02-18 17:37:09 -0800264 ::std::array<TypedTarget<Scalar>, num_targets> targets_;
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800265 // Known obstacles on the field which can interfere with our view of the
266 // targets. An "obstacle" is a line segment which we cannot see through, as
267 // such a logical obstacle (e.g., the cargo ship) may consist of many
268 // obstacles in this list to account for all of its sides.
James Kuszmaul09f564a2019-02-18 17:37:09 -0800269 ::std::array<LineSegment, num_obstacles> obstacles_;
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800270}; // class TypedCamera
271
272template <int num_targets, int num_obstacles, typename Scalar>
273void TypedCamera<num_targets, num_obstacles, Scalar>::AddTargetIfVisible(
James Kuszmaul090563a2019-02-09 14:43:20 -0800274 const TypedTarget<Scalar> &target, const Pose &camera_abs_pose,
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800275 ::aos::SizedArray<TargetView, num_targets> *views) const {
276 if (target.occluded()) {
277 return;
278 }
279
280 // Precompute the current absolute pose of the camera, because we will reuse
281 // it a bunch.
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800282 const Pose relative_pose = target.pose().Rebase(&camera_abs_pose);
283 const Scalar heading = relative_pose.heading();
284 const Scalar distance = relative_pose.xy_norm();
James Kuszmaul289756f2019-03-05 21:52:10 -0800285 const Scalar skew =
286 ::aos::math::NormalizeAngle(relative_pose.rel_theta() - heading);
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800287
288 // Check if the camera is in the angular FOV.
289 if (::std::abs(heading) > fov_ / 2.0) {
290 return;
291 }
292
James Kuszmaul09f564a2019-02-18 17:37:09 -0800293 TargetView view;
294 view.reading.heading = heading;
295 view.reading.distance = distance;
296 view.reading.skew = skew;
James Kuszmaul81df16a2019-03-03 17:17:34 -0800297 view.reading.height = relative_pose.rel_pos().z();
James Kuszmaul09f564a2019-02-18 17:37:09 -0800298 view.target = &target;
299 view.camera_pose = camera_abs_pose;
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800300
James Kuszmaul09f564a2019-02-18 17:37:09 -0800301 bool is_visible = false;
302
303 PopulateNoise(&view, &is_visible);
304
305 if (!is_visible) {
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800306 return;
307 }
308
309 // Final visibility check is for whether there are any obstacles blocking or
310 // line of sight.
311 for (const auto &obstacle : obstacles_) {
312 if (obstacle.Intersects({camera_abs_pose, target.pose()})) {
313 return;
314 }
315 }
316
James Kuszmaul09f564a2019-02-18 17:37:09 -0800317 // At this point, we've passed all the checks to ensure that the target is
318 // visible and we can add it to the list of targets.
James Kuszmaul57c7c9b2019-01-27 16:16:01 -0800319 views->push_back(view);
320}
321
322} // namespace control_loops
323} // namespace y2019
324
325#endif // Y2019_CONTROL_LOOPS_DRIVETRAIN_CAMERA_H_