blob: ba399b738014112209594af8fa17cd778ce6af4e [file] [log] [blame]
milind-u92195982022-01-22 20:29:31 -08001#include "y2022/vision/target_estimator.h"
2
Milind Upadhyayf61e1482022-02-11 20:42:55 -08003#include "absl/strings/str_format.h"
4#include "aos/time/time.h"
5#include "ceres/ceres.h"
6#include "frc971/control_loops/quaternion_utils.h"
Milind Upadhyay8f38ad82022-03-03 10:06:18 -08007#include "geometry.h"
Milind Upadhyayf61e1482022-02-11 20:42:55 -08008#include "opencv2/core/core.hpp"
9#include "opencv2/core/eigen.hpp"
10#include "opencv2/features2d.hpp"
11#include "opencv2/highgui/highgui.hpp"
12#include "opencv2/imgproc.hpp"
milind-ucafdd5d2022-03-01 19:58:57 -080013#include "y2022/constants.h"
Milind Upadhyayf61e1482022-02-11 20:42:55 -080014
Milind Upadhyay8f38ad82022-03-03 10:06:18 -080015DEFINE_bool(freeze_roll, false, "If true, don't solve for roll");
Milind Upadhyayf61e1482022-02-11 20:42:55 -080016DEFINE_bool(freeze_pitch, false, "If true, don't solve for pitch");
17DEFINE_bool(freeze_yaw, false, "If true, don't solve for yaw");
18DEFINE_bool(freeze_camera_height, true,
19 "If true, don't solve for camera height");
20DEFINE_bool(freeze_angle_to_camera, true,
21 "If true, don't solve for polar angle to camera");
22
23DEFINE_uint64(max_num_iterations, 200,
24 "Maximum number of iterations for the ceres solver");
25DEFINE_bool(solver_output, false,
26 "If true, log the solver progress and results");
27
milind-u92195982022-01-22 20:29:31 -080028namespace y2022::vision {
29
Milind Upadhyay8f38ad82022-03-03 10:06:18 -080030namespace {
31
32constexpr size_t kNumPiecesOfTape = 16;
33// Width and height of a piece of reflective tape
34constexpr double kTapePieceWidth = 0.13;
35constexpr double kTapePieceHeight = 0.05;
36// Height of the center of the tape (m)
37constexpr double kTapeCenterHeight = 2.58 + (kTapePieceHeight / 2);
38// Horizontal distance from tape to center of hub (m)
39constexpr double kUpperHubRadius = 1.22 / 2;
40
41std::vector<cv::Point3d> ComputeTapePoints() {
Milind Upadhyayf61e1482022-02-11 20:42:55 -080042 std::vector<cv::Point3d> tape_points;
milind-u92195982022-01-22 20:29:31 -080043
Milind Upadhyayf61e1482022-02-11 20:42:55 -080044 constexpr size_t kNumVisiblePiecesOfTape = 5;
45 for (size_t i = 0; i < kNumVisiblePiecesOfTape; i++) {
46 // The center piece of tape is at 0 rad, so the angle indices are offset
47 // by the number of pieces of tape on each side of it
48 const double theta_index =
49 static_cast<double>(i) - ((kNumVisiblePiecesOfTape - 1) / 2);
50 // The polar angle is a multiple of the angle between tape centers
51 double theta = theta_index * ((2.0 * M_PI) / kNumPiecesOfTape);
52 tape_points.emplace_back(kUpperHubRadius * std::cos(theta),
Milind Upadhyay8f38ad82022-03-03 10:06:18 -080053 kUpperHubRadius * std::sin(theta),
54 kTapeCenterHeight);
Milind Upadhyayf61e1482022-02-11 20:42:55 -080055 }
milind-u92195982022-01-22 20:29:31 -080056
Milind Upadhyayf61e1482022-02-11 20:42:55 -080057 return tape_points;
58}
milind-u92195982022-01-22 20:29:31 -080059
Milind Upadhyay8f38ad82022-03-03 10:06:18 -080060std::array<cv::Point3d, 4> ComputeMiddleTapePiecePoints() {
61 std::array<cv::Point3d, 4> tape_piece_points;
62
63 // Angle that each piece of tape occupies on the hub
64 constexpr double kTapePieceAngle =
65 (kTapePieceWidth / (2.0 * M_PI * kUpperHubRadius)) * (2.0 * M_PI);
66
67 constexpr double kThetaTapeLeft = -kTapePieceAngle / 2.0;
68 constexpr double kThetaTapeRight = kTapePieceAngle / 2.0;
69
70 constexpr double kTapeTopHeight =
71 kTapeCenterHeight + (kTapePieceHeight / 2.0);
72 constexpr double kTapeBottomHeight =
73 kTapeCenterHeight - (kTapePieceHeight / 2.0);
74
75 tape_piece_points[0] = {kUpperHubRadius * std::cos(kThetaTapeLeft),
76 kUpperHubRadius * std::sin(kThetaTapeLeft),
77 kTapeTopHeight};
78 tape_piece_points[1] = {kUpperHubRadius * std::cos(kThetaTapeRight),
79 kUpperHubRadius * std::sin(kThetaTapeRight),
80 kTapeTopHeight};
81
82 tape_piece_points[2] = {kUpperHubRadius * std::cos(kThetaTapeRight),
83 kUpperHubRadius * std::sin(kThetaTapeRight),
84 kTapeBottomHeight};
85 tape_piece_points[3] = {kUpperHubRadius * std::cos(kThetaTapeLeft),
86 kUpperHubRadius * std::sin(kThetaTapeLeft),
87 kTapeBottomHeight};
88
89 return tape_piece_points;
90}
91
92} // namespace
93
Milind Upadhyayf61e1482022-02-11 20:42:55 -080094const std::vector<cv::Point3d> TargetEstimator::kTapePoints =
95 ComputeTapePoints();
Milind Upadhyay8f38ad82022-03-03 10:06:18 -080096const std::array<cv::Point3d, 4> TargetEstimator::kMiddleTapePiecePoints =
97 ComputeMiddleTapePiecePoints();
Milind Upadhyayf61e1482022-02-11 20:42:55 -080098
99TargetEstimator::TargetEstimator(cv::Mat intrinsics, cv::Mat extrinsics)
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800100 : blob_stats_(),
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800101 image_(std::nullopt),
102 roll_(0.0),
103 pitch_(0.0),
104 yaw_(M_PI),
105 distance_(3.0),
106 angle_to_camera_(0.0),
milind-ucafdd5d2022-03-01 19:58:57 -0800107 // Seed camera height
108 camera_height_(extrinsics.at<double>(2, 3) +
109 constants::Values::kImuHeight()) {
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800110 cv::cv2eigen(intrinsics, intrinsics_);
111 cv::cv2eigen(extrinsics, extrinsics_);
112}
113
114namespace {
115void SetBoundsOrFreeze(double *param, bool freeze, double min, double max,
116 ceres::Problem *problem) {
117 if (freeze) {
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800118 problem->SetParameterBlockConstant(param);
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800119 } else {
120 problem->SetParameterLowerBound(param, 0, min);
121 problem->SetParameterUpperBound(param, 0, max);
122 }
123}
milind-ucafdd5d2022-03-01 19:58:57 -0800124
125// With X, Y, Z being hub axes and x, y, z being camera axes,
126// x = -Y, y = -Z, z = X
127const Eigen::Matrix3d kHubToCameraAxes =
128 (Eigen::Matrix3d() << 0.0, -1.0, 0.0, 0.0, 0.0, -1.0, 1.0, 0.0, 0.0)
129 .finished();
130
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800131} // namespace
132
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800133void TargetEstimator::Solve(
134 const std::vector<BlobDetector::BlobStats> &blob_stats,
135 std::optional<cv::Mat> image) {
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800136 auto start = aos::monotonic_clock::now();
137
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800138 blob_stats_ = blob_stats;
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800139 image_ = image;
140
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800141 // Do nothing if no blobs were detected
142 if (blob_stats_.size() == 0) {
143 confidence_ = 0.0;
144 return;
145 }
146
147 CHECK_GE(blob_stats_.size(), 3) << "Expected at least 3 blobs";
148
149 const auto circle =
150 Circle::Fit({blob_stats_[0].centroid, blob_stats_[1].centroid,
151 blob_stats_[2].centroid});
152 CHECK(circle.has_value());
153
154 // Find the middle blob, which is the one with the angle closest to the
155 // average
156 double theta_avg = 0.0;
157 for (const auto &stats : blob_stats_) {
158 theta_avg += circle->AngleOf(stats.centroid);
159 }
160 theta_avg /= blob_stats_.size();
161
162 double min_diff = std::numeric_limits<double>::infinity();
163 for (auto it = blob_stats_.begin(); it < blob_stats_.end(); it++) {
164 const double diff = std::abs(circle->AngleOf(it->centroid) - theta_avg);
165 if (diff < min_diff) {
166 min_diff = diff;
167 middle_blob_index_ = it - blob_stats_.begin();
168 }
169 }
170
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800171 ceres::Problem problem;
172
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800173 // x and y differences between projected centroids and blob centroids, as well
174 // as width and height differences between middle projected piece and the
175 // detected blob
176 const size_t num_residuals = (blob_stats_.size() * 2) + 2;
177
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800178 // Set up the only cost function (also known as residual). This uses
179 // auto-differentiation to obtain the derivative (jacobian).
180 ceres::CostFunction *cost_function =
181 new ceres::AutoDiffCostFunction<TargetEstimator, ceres::DYNAMIC, 1, 1, 1,
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800182 1, 1, 1>(this, num_residuals,
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800183 ceres::DO_NOT_TAKE_OWNERSHIP);
184
185 // TODO(milind): add loss function when we get more noisy data
186 problem.AddResidualBlock(cost_function, nullptr, &roll_, &pitch_, &yaw_,
187 &distance_, &angle_to_camera_, &camera_height_);
188
milind-ucafdd5d2022-03-01 19:58:57 -0800189 // Compute the estimated rotation of the camera using the robot rotation.
Milind Upadhyayda042bb2022-03-26 16:01:45 -0700190 const Eigen::Matrix3d extrinsics_rot =
191 Eigen::Affine3d(extrinsics_).rotation() * kHubToCameraAxes;
192 // asin returns a pitch in [-pi/2, pi/2] so this will be the correct euler
193 // angles.
194 const double pitch_seed = -std::asin(extrinsics_rot(2, 0));
195 const double roll_seed =
196 std::atan2(extrinsics_rot(2, 1) / std::cos(pitch_seed),
197 extrinsics_rot(2, 2) / std::cos(pitch_seed));
198
milind-ucafdd5d2022-03-01 19:58:57 -0800199 // TODO(milind): seed with localizer output as well
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800200
milind-ucafdd5d2022-03-01 19:58:57 -0800201 // Constrain the rotation to be around the localizer's, otherwise there can be
202 // multiple solutions. There shouldn't be too much roll or pitch
203 constexpr double kMaxRollDelta = 0.1;
204 SetBoundsOrFreeze(&roll_, FLAGS_freeze_roll, roll_seed - kMaxRollDelta,
205 roll_seed + kMaxRollDelta, &problem);
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800206
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800207 constexpr double kMaxPitchDelta = 0.15;
milind-ucafdd5d2022-03-01 19:58:57 -0800208 SetBoundsOrFreeze(&pitch_, FLAGS_freeze_pitch, pitch_seed - kMaxPitchDelta,
209 pitch_seed + kMaxPitchDelta, &problem);
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800210 // Constrain the yaw to where the target would be visible
211 constexpr double kMaxYawDelta = M_PI / 4.0;
212 SetBoundsOrFreeze(&yaw_, FLAGS_freeze_yaw, M_PI - kMaxYawDelta,
213 M_PI + kMaxYawDelta, &problem);
214
215 constexpr double kMaxHeightDelta = 0.1;
216 SetBoundsOrFreeze(&camera_height_, FLAGS_freeze_camera_height,
217 camera_height_ - kMaxHeightDelta,
218 camera_height_ + kMaxHeightDelta, &problem);
219
220 // Distances shouldn't be too close to the target or too far
221 constexpr double kMinDistance = 1.0;
222 constexpr double kMaxDistance = 10.0;
223 SetBoundsOrFreeze(&distance_, false, kMinDistance, kMaxDistance, &problem);
224
225 // Keep the angle between +/- half of the angle between piece of tape
226 constexpr double kMaxAngle = ((2.0 * M_PI) / kNumPiecesOfTape) / 2.0;
227 SetBoundsOrFreeze(&angle_to_camera_, FLAGS_freeze_angle_to_camera, -kMaxAngle,
228 kMaxAngle, &problem);
229
230 ceres::Solver::Options options;
231 options.minimizer_progress_to_stdout = FLAGS_solver_output;
232 options.gradient_tolerance = 1e-12;
233 options.function_tolerance = 1e-16;
234 options.parameter_tolerance = 1e-12;
235 options.max_num_iterations = FLAGS_max_num_iterations;
236 ceres::Solver::Summary summary;
237 ceres::Solve(options, &problem, &summary);
238
239 auto end = aos::monotonic_clock::now();
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800240 VLOG(1) << "Target estimation elapsed time: "
241 << std::chrono::duration<double, std::milli>(end - start).count()
242 << " ms";
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800243
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800244 // For computing the confidence, find the standard deviation in pixels
245 std::vector<double> residual(num_residuals);
246 (*this)(&roll_, &pitch_, &yaw_, &distance_, &angle_to_camera_,
247 &camera_height_, residual.data());
248 double std_dev = 0.0;
249 for (auto it = residual.begin(); it < residual.end() - 2; it++) {
250 std_dev += std::pow(*it, 2);
Milind Upadhyay14279de2022-02-26 16:07:53 -0800251 }
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800252 std_dev /= num_residuals - 2;
253 std_dev = std::sqrt(std_dev);
254
255 // Use a sigmoid to convert the deviation into a confidence for the
256 // localizer. Fit a sigmoid to the points of (0, 1) and two other
257 // reasonable deviation-confidence combinations using
258 // https://www.desmos.com/calculator/try0pgx1qw
259 constexpr double kSigmoidCapacity = 1.045;
260 // Stretch the sigmoid out correctly.
261 // Currently, good estimates have deviations of around 2 pixels.
262 constexpr double kSigmoidScalar = 0.04452;
263 constexpr double kSigmoidGrowthRate = -0.4021;
264 confidence_ =
265 kSigmoidCapacity /
266 (1.0 + kSigmoidScalar * std::exp(-kSigmoidGrowthRate * std_dev));
Milind Upadhyay14279de2022-02-26 16:07:53 -0800267
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800268 if (FLAGS_solver_output) {
269 LOG(INFO) << summary.FullReport();
270
271 LOG(INFO) << "roll: " << roll_;
272 LOG(INFO) << "pitch: " << pitch_;
273 LOG(INFO) << "yaw: " << yaw_;
274 LOG(INFO) << "angle to target (based on yaw): " << angle_to_target();
275 LOG(INFO) << "angle to camera (polar): " << angle_to_camera_;
276 LOG(INFO) << "distance (polar): " << distance_;
277 LOG(INFO) << "camera height: " << camera_height_;
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800278 LOG(INFO) << "standard deviation (px): " << std_dev;
Milind Upadhyay14279de2022-02-26 16:07:53 -0800279 LOG(INFO) << "confidence: " << confidence_;
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800280 }
281}
282
283namespace {
284// Hacks to extract a double from a scalar, which is either a ceres jet or a
285// double. Only used for debugging and displaying.
286template <typename S>
287double ScalarToDouble(S s) {
288 const double *ptr = reinterpret_cast<double *>(&s);
289 return *ptr;
290}
291
292template <typename S>
293cv::Point2d ScalarPointToDouble(cv::Point_<S> p) {
294 return cv::Point2d(ScalarToDouble(p.x), ScalarToDouble(p.y));
295}
296} // namespace
297
298template <typename S>
299bool TargetEstimator::operator()(const S *const roll, const S *const pitch,
300 const S *const yaw, const S *const distance,
301 const S *const theta,
302 const S *const camera_height,
303 S *residual) const {
304 using Vector3s = Eigen::Matrix<S, 3, 1>;
305 using Affine3s = Eigen::Transform<S, 3, Eigen::Affine>;
306
307 Eigen::AngleAxis<S> roll_angle(*roll, Vector3s::UnitX());
308 Eigen::AngleAxis<S> pitch_angle(*pitch, Vector3s::UnitY());
309 Eigen::AngleAxis<S> yaw_angle(*yaw, Vector3s::UnitZ());
310 // Construct the rotation and translation of the camera in the hub's frame
311 Eigen::Quaternion<S> R_camera_hub = yaw_angle * pitch_angle * roll_angle;
312 Vector3s T_camera_hub(*distance * ceres::cos(*theta),
313 *distance * ceres::sin(*theta), *camera_height);
314
315 Affine3s H_camera_hub = Eigen::Translation<S, 3>(T_camera_hub) * R_camera_hub;
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800316 Affine3s H_hub_camera = H_camera_hub.inverse();
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800317
318 std::vector<cv::Point_<S>> tape_points_proj;
319 for (cv::Point3d tape_point_hub : kTapePoints) {
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800320 tape_points_proj.emplace_back(ProjectToImage(tape_point_hub, H_hub_camera));
321 VLOG(2) << "Projected tape point: "
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800322 << ScalarPointToDouble(
323 tape_points_proj[tape_points_proj.size() - 1]);
324 }
325
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800326 // Find the rectangle bounding the projected piece of tape
327 std::array<cv::Point_<S>, 4> middle_tape_piece_points_proj;
328 for (auto tape_piece_it = kMiddleTapePiecePoints.begin();
329 tape_piece_it < kMiddleTapePiecePoints.end(); tape_piece_it++) {
330 middle_tape_piece_points_proj[tape_piece_it -
331 kMiddleTapePiecePoints.begin()] =
332 ProjectToImage(*tape_piece_it, H_hub_camera);
333 }
334
335 for (size_t i = 0; i < blob_stats_.size(); i++) {
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800336 const auto distance = DistanceFromTape(i, tape_points_proj);
337 // Set the residual to the (x, y) distance of the centroid from the
338 // nearest projected piece of tape
339 residual[i * 2] = distance.x;
340 residual[(i * 2) + 1] = distance.y;
341 }
342
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800343 // Penalize based on the difference between the size of the projected piece of
344 // tape and that of the detected blobs. Use the squared size to avoid taking a
345 // norm, which ceres can't handle well
346 const S middle_tape_piece_width_squared =
347 ceres::pow(middle_tape_piece_points_proj[2].x -
348 middle_tape_piece_points_proj[3].x,
349 2) +
350 ceres::pow(middle_tape_piece_points_proj[2].y -
351 middle_tape_piece_points_proj[3].y,
352 2);
353 const S middle_tape_piece_height_squared =
354 ceres::pow(middle_tape_piece_points_proj[1].x -
355 middle_tape_piece_points_proj[2].x,
356 2) +
357 ceres::pow(middle_tape_piece_points_proj[1].y -
358 middle_tape_piece_points_proj[2].y,
359 2);
360
361 residual[blob_stats_.size() * 2] =
362 middle_tape_piece_width_squared -
363 std::pow(blob_stats_[middle_blob_index_].size.width, 2);
364 residual[(blob_stats_.size() * 2) + 1] =
365 middle_tape_piece_height_squared -
366 std::pow(blob_stats_[middle_blob_index_].size.height, 2);
367
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800368 if (image_.has_value()) {
369 // Draw the current stage of the solving
370 cv::Mat image = image_->clone();
371 for (size_t i = 0; i < tape_points_proj.size() - 1; i++) {
372 cv::line(image, ScalarPointToDouble(tape_points_proj[i]),
373 ScalarPointToDouble(tape_points_proj[i + 1]),
374 cv::Scalar(255, 255, 255));
375 cv::circle(image, ScalarPointToDouble(tape_points_proj[i]), 2,
376 cv::Scalar(255, 20, 147), cv::FILLED);
377 cv::circle(image, ScalarPointToDouble(tape_points_proj[i + 1]), 2,
378 cv::Scalar(255, 20, 147), cv::FILLED);
379 }
380 cv::imshow("image", image);
381 cv::waitKey(10);
382 }
383
384 return true;
385}
386
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800387template <typename S>
388cv::Point_<S> TargetEstimator::ProjectToImage(
389 cv::Point3d tape_point_hub,
390 Eigen::Transform<S, 3, Eigen::Affine> &H_hub_camera) const {
391 using Vector3s = Eigen::Matrix<S, 3, 1>;
392
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800393 const Vector3s tape_point_hub_eigen =
394 Vector3s(S(tape_point_hub.x), S(tape_point_hub.y), S(tape_point_hub.z));
395 // Project the 3d tape point onto the image using the transformation and
396 // intrinsics
397 const Vector3s tape_point_proj =
milind-ucafdd5d2022-03-01 19:58:57 -0800398 intrinsics_ * (kHubToCameraAxes * (H_hub_camera * tape_point_hub_eigen));
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800399
400 // Normalize the projected point
401 return {tape_point_proj.x() / tape_point_proj.z(),
402 tape_point_proj.y() / tape_point_proj.z()};
403}
404
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800405namespace {
406template <typename S>
407cv::Point_<S> Distance(cv::Point p, cv::Point_<S> q) {
408 return cv::Point_<S>(S(p.x) - q.x, S(p.y) - q.y);
409}
410
411template <typename S>
412bool Less(cv::Point_<S> distance_1, cv::Point_<S> distance_2) {
413 return (ceres::pow(distance_1.x, 2) + ceres::pow(distance_1.y, 2) <
414 ceres::pow(distance_2.x, 2) + ceres::pow(distance_2.y, 2));
415}
416} // namespace
417
418template <typename S>
419cv::Point_<S> TargetEstimator::DistanceFromTape(
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800420 size_t blob_index, const std::vector<cv::Point_<S>> &tape_points) const {
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800421 auto distance = cv::Point_<S>(std::numeric_limits<S>::infinity(),
422 std::numeric_limits<S>::infinity());
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800423 if (blob_index == middle_blob_index_) {
424 // Fix the middle blob so the solver can't go too far off
425 distance = Distance(blob_stats_[middle_blob_index_].centroid,
426 tape_points[tape_points.size() / 2]);
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800427 } else {
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800428 // Give the other blob_stats some freedom in case some are split into pieces
429 for (auto it = tape_points.begin(); it < tape_points.end(); it++) {
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800430 const auto current_distance =
Milind Upadhyay8f38ad82022-03-03 10:06:18 -0800431 Distance(blob_stats_[blob_index].centroid, *it);
432 if ((it != tape_points.begin() + (tape_points.size() / 2)) &&
433 Less(current_distance, distance)) {
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800434 distance = current_distance;
435 }
436 }
437 }
438
439 return distance;
440}
441
442namespace {
443void DrawEstimateValues(double distance, double angle_to_target,
444 double angle_to_camera, double roll, double pitch,
Milind Upadhyay14279de2022-02-26 16:07:53 -0800445 double yaw, double confidence, cv::Mat view_image) {
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800446 constexpr int kTextX = 10;
Milind Upadhyay2da80bb2022-03-12 22:54:35 -0800447 int text_y = 0;
448 constexpr int kTextSpacing = 25;
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800449
450 const auto kTextColor = cv::Scalar(0, 255, 255);
Milind Upadhyay2da80bb2022-03-12 22:54:35 -0800451 constexpr double kFontScale = 0.6;
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800452
453 cv::putText(view_image, absl::StrFormat("Distance: %.3f", distance),
454 cv::Point(kTextX, text_y += kTextSpacing),
455 cv::FONT_HERSHEY_DUPLEX, kFontScale, kTextColor, 2);
456 cv::putText(view_image,
457 absl::StrFormat("Angle to target: %.3f", angle_to_target),
458 cv::Point(kTextX, text_y += kTextSpacing),
459 cv::FONT_HERSHEY_DUPLEX, kFontScale, kTextColor, 2);
460 cv::putText(view_image,
461 absl::StrFormat("Angle to camera: %.3f", angle_to_camera),
462 cv::Point(kTextX, text_y += kTextSpacing),
463 cv::FONT_HERSHEY_DUPLEX, kFontScale, kTextColor, 2);
464
465 cv::putText(
466 view_image,
467 absl::StrFormat("Roll: %.3f, pitch: %.3f, yaw: %.3f", roll, pitch, yaw),
468 cv::Point(kTextX, text_y += kTextSpacing), cv::FONT_HERSHEY_DUPLEX,
469 kFontScale, kTextColor, 2);
Milind Upadhyay14279de2022-02-26 16:07:53 -0800470
471 cv::putText(view_image, absl::StrFormat("Confidence: %.3f", confidence),
472 cv::Point(kTextX, text_y += kTextSpacing),
473 cv::FONT_HERSHEY_DUPLEX, kFontScale, kTextColor, 2);
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800474}
475} // namespace
476
477void TargetEstimator::DrawEstimate(const TargetEstimate &target_estimate,
478 cv::Mat view_image) {
479 DrawEstimateValues(target_estimate.distance(),
480 target_estimate.angle_to_target(),
481 target_estimate.angle_to_camera(),
482 target_estimate.rotation_camera_hub()->roll(),
483 target_estimate.rotation_camera_hub()->pitch(),
Milind Upadhyay14279de2022-02-26 16:07:53 -0800484 target_estimate.rotation_camera_hub()->yaw(),
485 target_estimate.confidence(), view_image);
Milind Upadhyayf61e1482022-02-11 20:42:55 -0800486}
487
488void TargetEstimator::DrawEstimate(cv::Mat view_image) const {
489 DrawEstimateValues(distance_, angle_to_target(), angle_to_camera_, roll_,
Milind Upadhyay14279de2022-02-26 16:07:53 -0800490 pitch_, yaw_, confidence_, view_image);
milind-u92195982022-01-22 20:29:31 -0800491}
492
493} // namespace y2022::vision