blob: b0fa33b66a1844f4d9984936d73ea553bd38e75e [file] [log] [blame]
Jim Ostrowski9bf206a2024-01-26 23:31:58 -08001#include <numeric>
2
Austin Schuh99f7c6a2024-06-25 22:07:44 -07003#include "absl/flags/flag.h"
4
Jim Ostrowski9bf206a2024-01-26 23:31:58 -08005#include "aos/configuration.h"
6#include "aos/events/logging/log_reader.h"
7#include "aos/events/simulated_event_loop.h"
8#include "aos/init.h"
9#include "aos/util/mcap_logger.h"
10#include "frc971/control_loops/pose.h"
11#include "frc971/control_loops/quaternion_utils.h"
12#include "frc971/vision/calibration_generated.h"
13#include "frc971/vision/charuco_lib.h"
14#include "frc971/vision/extrinsics_calibration.h"
15#include "frc971/vision/target_mapper.h"
Jim Ostrowski33208982024-03-02 15:01:45 -080016#include "frc971/vision/vision_util_lib.h"
Jim Ostrowski9bf206a2024-01-26 23:31:58 -080017#include "frc971/vision/visualize_robot.h"
18// clang-format off
19// OpenCV eigen files must be included after Eigen includes
20#include "opencv2/aruco.hpp"
21#include "opencv2/calib3d.hpp"
22#include "opencv2/core/eigen.hpp"
23#include "opencv2/features2d.hpp"
24#include "opencv2/highgui.hpp"
25#include "opencv2/highgui/highgui.hpp"
26#include "opencv2/imgproc.hpp"
27// clang-format on
28#include "frc971/constants/constants_sender_lib.h"
29#include "frc971/vision/vision_util_lib.h"
30#include "y2024/constants/simulated_constants_sender.h"
31#include "y2024/vision/vision_util.h"
32
Austin Schuh99f7c6a2024-06-25 22:07:44 -070033ABSL_FLAG(bool, alt_view, false,
34 "If true, show visualization from field level, rather than above");
35ABSL_FLAG(std::string, config, "",
36 "If set, override the log's config file with this one.");
37ABSL_FLAG(std::string, constants_path, "y2024/constants/constants.json",
38 "Path to the constant file");
39ABSL_FLAG(double, max_pose_error, 5e-5,
40 "Throw out target poses with a higher pose error than this");
41ABSL_FLAG(double, max_pose_error_ratio, 0.4,
42 "Throw out target poses with a higher pose error ratio than this");
Jim Ostrowski6d242d52024-04-03 20:39:03 -070043ABSL_FLAG(bool, robot, false,
44 "If true we're calibrating extrinsics for the robot, use the "
45 "correct node path for the robot.");
Austin Schuh99f7c6a2024-06-25 22:07:44 -070046ABSL_FLAG(std::string, output_folder, "/tmp",
47 "Directory in which to store the updated calibration files");
48ABSL_FLAG(std::string, target_type, "charuco_diamond",
49 "Type of target being used [aruco, charuco, charuco_diamond]");
50ABSL_FLAG(int32_t, team_number, 0,
51 "Required: Use the calibration for a node with this team number");
52ABSL_FLAG(
53 uint64_t, wait_key, 1,
Jim Ostrowski9bf206a2024-01-26 23:31:58 -080054 "Time in ms to wait between images (0 to wait indefinitely until click)");
Maxwell Henderson9d7b1862024-03-02 11:00:29 -080055
Austin Schuh99f7c6a2024-06-25 22:07:44 -070056ABSL_DECLARE_FLAG(int32_t, min_target_id);
57ABSL_DECLARE_FLAG(int32_t, max_target_id);
Jim Ostrowski6d242d52024-04-03 20:39:03 -070058ABSL_DECLARE_FLAG(double, outlier_std_devs);
Jim Ostrowski9bf206a2024-01-26 23:31:58 -080059
60// Calibrate extrinsic relationship between cameras using two targets
61// seen jointly between cameras. Uses two types of information: 1)
62// when a single camera sees two targets, we estimate the pose between
63// targets, and 2) when two separate cameras each see a target, we can
64// use the pose between targets to estimate the pose between cameras.
65
66// We then create the extrinsics for the robot by starting with the
67// given extrinsic for camera 1 (between imu/robot and camera frames)
68// and then map each subsequent camera based on the data collected and
69// the extrinsic poses computed here.
70
Jim Ostrowskiddebdcb2024-02-29 22:25:36 -080071// TODO<Jim>: Not currently using estimate from first camera to last camera--
72// should do full estimation, and probably also include camera->imu extrinsics
73// from all cameras, not just first camera
Jim Ostrowski9bf206a2024-01-26 23:31:58 -080074
Jim Ostrowskiddebdcb2024-02-29 22:25:36 -080075namespace y2024::vision {
Jim Ostrowski9bf206a2024-01-26 23:31:58 -080076using frc971::vision::PoseUtils;
77using frc971::vision::TargetMap;
78using frc971::vision::TargetMapper;
79using frc971::vision::VisualizeRobot;
80namespace calibration = frc971::vision::calibration;
81
82static constexpr double kImagePeriodMs =
Maxwell Henderson9d7b1862024-03-02 11:00:29 -080083 1.0 / 60.0 * 1000.0; // Image capture period in ms
Jim Ostrowski9bf206a2024-01-26 23:31:58 -080084
Jim Ostrowski6d242d52024-04-03 20:39:03 -070085// Set up our camera naming data
Jim Ostrowski67726282024-03-24 14:39:33 -070086std::vector<CameraNode> node_list(y2024::vision::CreateNodeList());
Jim Ostrowski67726282024-03-24 14:39:33 -070087std::map<std::string, int> ordering_map(
88 y2024::vision::CreateOrderingMap(node_list));
89
Jim Ostrowski9bf206a2024-01-26 23:31:58 -080090// Change reference frame from camera to robot
91Eigen::Affine3d CameraToRobotDetection(Eigen::Affine3d H_camera_target,
92 Eigen::Affine3d extrinsics) {
93 const Eigen::Affine3d H_robot_camera = extrinsics;
94 const Eigen::Affine3d H_robot_target = H_robot_camera * H_camera_target;
95 return H_robot_target;
96}
97
Jim Ostrowski67726282024-03-24 14:39:33 -070098struct TimestampedCameraDetection {
Jim Ostrowski9bf206a2024-01-26 23:31:58 -080099 aos::distributed_clock::time_point time;
100 // Pose of target relative to robot
101 Eigen::Affine3d H_camera_target;
102 // name of pi
Jim Ostrowski67726282024-03-24 14:39:33 -0700103 std::string camera_name;
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800104 int board_id;
105};
106
Jim Ostrowski67726282024-03-24 14:39:33 -0700107TimestampedCameraDetection last_observation;
108std::vector<std::pair<TimestampedCameraDetection, TimestampedCameraDetection>>
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800109 detection_list;
Jim Ostrowski67726282024-03-24 14:39:33 -0700110std::vector<TimestampedCameraDetection> two_board_extrinsics_list;
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800111VisualizeRobot vis_robot_;
112
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700113// Helper function to compute average pose when supplied with list
114// of TimestampedCameraDetection's
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800115Eigen::Affine3d ComputeAveragePose(
Jim Ostrowski67726282024-03-24 14:39:33 -0700116 std::vector<TimestampedCameraDetection> &pose_list,
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800117 Eigen::Vector3d *translation_variance = nullptr,
118 Eigen::Vector3d *rotation_variance = nullptr) {
119 std::vector<Eigen::Vector3d> translation_list;
120 std::vector<Eigen::Vector4d> rotation_list;
121
Jim Ostrowski67726282024-03-24 14:39:33 -0700122 for (TimestampedCameraDetection pose : pose_list) {
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800123 translation_list.push_back(pose.H_camera_target.translation());
124 Eigen::Quaterniond quat(pose.H_camera_target.rotation().matrix());
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700125 // NOTE: Eigen initialies as (x,y,z,w) from Vector4d's
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800126 rotation_list.push_back(
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700127 Eigen::Vector4d(quat.x(), quat.y(), quat.z(), quat.w()));
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800128 }
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700129 return frc971::vision::ComputeAveragePose(
130 translation_list, rotation_list, translation_variance, rotation_variance);
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800131}
132
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700133// Do outlier rejection. Given a list of poses, compute the
134// mean and standard deviation, and throw out those more than
135// FLAGS_outlier_std_devs standard deviations away from the mean.
136// Repeat for the desired number of iterations or until we don't throw
137// out any more outliers
138void RemoveOutliers(std::vector<TimestampedCameraDetection> &pose_list,
139 int num_iterations = 1) {
140 for (int i = 1; i <= num_iterations; i++) {
141 Eigen::Vector3d translation_variance, rotation_variance;
142 Eigen::Affine3d avg_pose = ComputeAveragePose(
143 pose_list, &translation_variance, &rotation_variance);
144
145 size_t original_size = pose_list.size();
146 // Create a lambda to identify the outliers to be removed
147 auto remove_lambda = [translation_variance, rotation_variance,
148 avg_pose](const TimestampedCameraDetection &pose) {
149 Eigen::Affine3d H_delta = avg_pose * pose.H_camera_target.inverse();
150 // Compute the z-score for each dimension, and use the max to
151 // decide on outliers. This is similar to the Mahalanobis
152 // distance (scale by inverse of the covariance matrix), but we're
153 // treating each dimension independently
154 Eigen::Matrix3d translation_sigma = translation_variance.asDiagonal();
155 Eigen::Matrix3d rotation_sigma = rotation_variance.asDiagonal();
156 Eigen::Vector3d delta_rotation =
157 PoseUtils::RotationMatrixToEulerAngles(H_delta.rotation().matrix());
158 double max_translation_z_score = std::sqrt(
159 H_delta.translation()
160 .cwiseProduct(translation_sigma.inverse() * H_delta.translation())
161 .maxCoeff());
162 double max_rotation_z_score = std::sqrt(static_cast<double>(
163 (delta_rotation.array() *
164 (rotation_sigma.inverse() * delta_rotation).array())
165 .maxCoeff()));
166 double z_score = std::max(max_translation_z_score, max_rotation_z_score);
167 // Remove observations that vary significantly from the mean
168 if (z_score > absl::GetFlag(FLAGS_outlier_std_devs)) {
169 VLOG(1) << "Removing outlier with z_score " << z_score
170 << " relative to std dev = "
171 << absl::GetFlag(FLAGS_outlier_std_devs);
172 return true;
173 }
174 return false;
175 };
176 pose_list.erase(
177 std::remove_if(pose_list.begin(), pose_list.end(), remove_lambda),
178 pose_list.end());
179
180 VLOG(1) << "Iteration #" << i << ": removed "
181 << (original_size - pose_list.size())
182 << " outlier constraints out of " << original_size
183 << " total\nStd Dev's are: "
184 << translation_variance.array().sqrt().transpose() << "m and "
185 << rotation_variance.array().sqrt().transpose() * 180.0 / M_PI
186 << "deg";
187 if (original_size - pose_list.size() == 0) {
188 VLOG(1) << "At step " << i
189 << ", ending outlier rejection early due to convergence at "
190 << pose_list.size() << " elements.\nStd Dev's are: "
191 << translation_variance.array().sqrt().transpose() << "m and "
192 << rotation_variance.array().sqrt().transpose() * 180.0 / M_PI
193 << "deg";
194 break;
195 }
196 }
197}
198
199static std::map<std::string, aos::distributed_clock::time_point>
200 last_eofs_debug;
201int display_count = 1;
202
203// Take in list of poses from a camera observation and add to running list
204// One of two options:
205// 1) We see two boards in one view-- store this to get an estimate of
206// the offset between the two boards
207// 2) We see just one board-- save this and try to pair it with a previous
208// observation from another camera
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800209void HandlePoses(cv::Mat rgb_image,
210 std::vector<TargetMapper::TargetPose> target_poses,
211 aos::distributed_clock::time_point distributed_eof,
Jim Ostrowski67726282024-03-24 14:39:33 -0700212 std::string camera_name) {
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700213 // This (H_world_board) is used to transform points for visualization
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800214 // Assumes targets are aligned with x->right, y->up, z->out of board
215 Eigen::Affine3d H_world_board;
216 H_world_board = Eigen::Translation3d::Identity() *
217 Eigen::AngleAxisd(M_PI / 2.0, Eigen::Vector3d::UnitX());
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700218 if (absl::GetFlag(FLAGS_alt_view)) {
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800219 // Don't rotate -- this is like viewing from the side
220 H_world_board = Eigen::Translation3d(0.0, 0.0, 3.0);
221 }
222
223 bool draw_vis = false;
224 CHECK_LE(target_poses.size(), 2u)
225 << "Can't handle more than two tags in field of view";
226 if (target_poses.size() == 2) {
227 draw_vis = true;
Jim Ostrowski67726282024-03-24 14:39:33 -0700228 VLOG(1) << "Saw two boards in same view from " << camera_name;
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800229 int from_index = 0;
230 int to_index = 1;
231 // Handle when we see two boards at once
232 // We'll store them referenced to the lower id board
233 if (target_poses[from_index].id > target_poses[to_index].id) {
234 std::swap<int>(from_index, to_index);
235 }
236
237 // Create "from" (A) and "to" (B) transforms
238 Eigen::Affine3d H_camera_boardA =
239 PoseUtils::Pose3dToAffine3d(target_poses[from_index].pose);
240 Eigen::Affine3d H_camera_boardB =
241 PoseUtils::Pose3dToAffine3d(target_poses[to_index].pose);
242
243 Eigen::Affine3d H_boardA_boardB =
244 H_camera_boardA.inverse() * H_camera_boardB;
245
Jim Ostrowski67726282024-03-24 14:39:33 -0700246 TimestampedCameraDetection boardA_boardB{
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800247 .time = distributed_eof,
248 .H_camera_target = H_boardA_boardB,
Jim Ostrowski67726282024-03-24 14:39:33 -0700249 .camera_name = camera_name,
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800250 .board_id = target_poses[from_index].id};
251
Jim Ostrowski67726282024-03-24 14:39:33 -0700252 VLOG(1) << "Two boards seen by " << camera_name << ". Map from board "
253 << target_poses[from_index].id << " to "
254 << target_poses[to_index].id << " is\n"
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800255 << H_boardA_boardB.matrix();
256 // Store this observation of the transform between two boards
257 two_board_extrinsics_list.push_back(boardA_boardB);
258
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700259 // Also, store one of the observations, so we can potentially
260 // match against the next single-target observation that comes in
261 TimestampedCameraDetection new_observation{
262 .time = distributed_eof,
263 .H_camera_target = H_camera_boardA,
264 .camera_name = camera_name,
265 .board_id = target_poses[from_index].id};
266 last_observation = new_observation;
267
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700268 if (absl::GetFlag(FLAGS_visualize)) {
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800269 vis_robot_.DrawFrameAxes(
270 H_world_board,
Jim Ostrowski67726282024-03-24 14:39:33 -0700271 std::string("Board ") + std::to_string(target_poses[from_index].id),
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800272 cv::Scalar(0, 255, 0));
273 vis_robot_.DrawFrameAxes(
274 H_world_board * boardA_boardB.H_camera_target,
Jim Ostrowski67726282024-03-24 14:39:33 -0700275 std::string("Board ") + std::to_string(target_poses[to_index].id),
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800276 cv::Scalar(255, 0, 0));
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800277 vis_robot_.DrawRobotOutline(H_world_board * H_camera_boardA.inverse(),
Jim Ostrowski67726282024-03-24 14:39:33 -0700278 camera_name, kOrinColors.at(camera_name));
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800279 }
280 } else if (target_poses.size() == 1) {
Jim Ostrowski67726282024-03-24 14:39:33 -0700281 VLOG(1) << camera_name << " saw single board " << target_poses[0].id;
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800282 Eigen::Affine3d H_camera2_board2 =
283 PoseUtils::Pose3dToAffine3d(target_poses[0].pose);
Jim Ostrowski67726282024-03-24 14:39:33 -0700284 TimestampedCameraDetection new_observation{
285 .time = distributed_eof,
286 .H_camera_target = H_camera2_board2,
287 .camera_name = camera_name,
288 .board_id = target_poses[0].id};
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800289
Jim Ostrowski67726282024-03-24 14:39:33 -0700290 // Only take two observations if they're within half an image cycle of
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700291 // each other (i.e., as close in time as possible). And, if two
292 // consecutive observations are from the same camera, just replace with
293 // the newest one
Jim Ostrowski67726282024-03-24 14:39:33 -0700294 if ((new_observation.camera_name != last_observation.camera_name) &&
295 (std::abs((distributed_eof - last_observation.time).count()) <
296 kImagePeriodMs / 2.0 * 1000000.0)) {
297 // Sort by camera numbering, since this is how we will handle them
298 std::pair<TimestampedCameraDetection, TimestampedCameraDetection>
299 new_pair;
300 if (ordering_map.at(last_observation.camera_name) <
301 ordering_map.at(new_observation.camera_name)) {
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800302 new_pair = std::pair(last_observation, new_observation);
Jim Ostrowski67726282024-03-24 14:39:33 -0700303 } else if (ordering_map.at(last_observation.camera_name) >
304 ordering_map.at(new_observation.camera_name)) {
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800305 new_pair = std::pair(new_observation, last_observation);
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800306 }
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800307
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800308 detection_list.push_back(new_pair);
309
310 // This bit is just for visualization and checking purposes-- use the
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700311 // first two-board observation to figure out the current estimate
312 // between the two cameras (this could be incorrect, but it keeps it
313 // constant)
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700314 if (absl::GetFlag(FLAGS_visualize) &&
315 two_board_extrinsics_list.size() > 0) {
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800316 draw_vis = true;
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700317 TimestampedCameraDetection &first_two_board_ext =
318 two_board_extrinsics_list.front();
319 Eigen::Affine3d &H_boardA_boardB = first_two_board_ext.H_camera_target;
320 int boardA_id = first_two_board_ext.board_id;
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800321
Jim Ostrowski67726282024-03-24 14:39:33 -0700322 TimestampedCameraDetection camera1_boardA = new_pair.first;
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700323 TimestampedCameraDetection camera2_boardA = new_pair.second;
324 Eigen::Affine3d H_camera1_boardA = camera1_boardA.H_camera_target;
325 Eigen::Affine3d H_camera2_boardA = camera2_boardA.H_camera_target;
326 // If camera1_boardA doesn't currently point to boardA, then fix it
327 if (camera1_boardA.board_id != boardA_id) {
328 H_camera1_boardA = H_camera1_boardA * H_boardA_boardB.inverse();
329 }
330 // If camera2_boardA doesn't currently point to boardA, then fix it
331 if (camera2_boardA.board_id != boardA_id) {
332 H_camera2_boardA = H_camera2_boardA * H_boardA_boardB.inverse();
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800333 }
Jim Ostrowski67726282024-03-24 14:39:33 -0700334 VLOG(1) << "Camera " << camera1_boardA.camera_name << " seeing board "
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800335 << camera1_boardA.board_id << " and camera "
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700336 << camera2_boardA.camera_name << " seeing board "
337 << camera2_boardA.board_id;
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800338
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700339 // Draw the two poses of the cameras, and the locations of the
340 // boards We use "Board A" as the origin (with everything relative
341 // to H_world_board)
342 vis_robot_.DrawRobotOutline(H_world_board * H_camera1_boardA.inverse(),
343 camera1_boardA.camera_name,
344 kOrinColors.at(camera1_boardA.camera_name));
345 vis_robot_.DrawRobotOutline(H_world_board * H_camera2_boardA.inverse(),
346 camera2_boardA.camera_name,
347 kOrinColors.at(camera2_boardA.camera_name));
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800348 vis_robot_.DrawFrameAxes(
349 H_world_board,
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700350 std::string("Board ") +
351 std::to_string(first_two_board_ext.board_id),
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800352 cv::Scalar(0, 255, 0));
353 vis_robot_.DrawFrameAxes(H_world_board * H_boardA_boardB, "Board B",
354 cv::Scalar(255, 0, 0));
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700355
Jim Ostrowski67726282024-03-24 14:39:33 -0700356 VLOG(1) << "Storing observation between " << new_pair.first.camera_name
357 << ", target " << new_pair.first.board_id << " and "
358 << new_pair.second.camera_name << ", target "
359 << new_pair.second.board_id;
360 } else if (two_board_extrinsics_list.size() == 0) {
361 VLOG(1) << "Not drawing observation yet, since we don't have a two "
362 "board estimate";
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800363 }
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800364 } else {
Jim Ostrowski67726282024-03-24 14:39:33 -0700365 if (new_observation.camera_name == last_observation.camera_name) {
366 VLOG(2) << "Updating repeated observation for " << camera_name;
367 } else {
368 VLOG(1) << "Storing observation for " << camera_name << " at time "
369 << distributed_eof << " since last observation was "
370 << std::abs((distributed_eof - last_observation.time).count()) /
371 1000000.0
372 << "ms ago";
373 }
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800374 last_observation = new_observation;
375 }
376 }
377
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700378 if (absl::GetFlag(FLAGS_visualize)) {
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800379 if (!rgb_image.empty()) {
Jim Ostrowski67726282024-03-24 14:39:33 -0700380 std::string image_name = camera_name + " Image";
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800381 cv::Mat rgb_small;
382 cv::resize(rgb_image, rgb_small, cv::Size(), 0.5, 0.5);
383 cv::imshow(image_name, rgb_small);
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700384 cv::waitKey(absl::GetFlag(FLAGS_wait_key));
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800385 }
386
387 if (draw_vis) {
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700388 cv::putText(vis_robot_.image_,
389 "Display #" + std::to_string(display_count++),
390 cv::Point(600, 10), cv::FONT_HERSHEY_PLAIN, 1.0,
391 cv::Scalar(255, 255, 255));
Jim Ostrowski67726282024-03-24 14:39:33 -0700392 cv::imshow("Overhead View", vis_robot_.image_);
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700393 cv::waitKey(absl::GetFlag(FLAGS_wait_key));
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800394 vis_robot_.ClearImage();
395 }
396 }
397}
398
399void HandleTargetMap(const TargetMap &map,
400 aos::distributed_clock::time_point distributed_eof,
Jim Ostrowski67726282024-03-24 14:39:33 -0700401 std::string camera_name) {
402 VLOG(1) << "Got april tag map call from camera " << camera_name;
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800403 // Create empty RGB image in this case
404 cv::Mat rgb_image;
405 std::vector<TargetMapper::TargetPose> target_poses;
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700406 VLOG(1) << ": Diff since last image from " << camera_name << " is "
407 << (distributed_eof - last_eofs_debug.at(camera_name)).count() /
408 1000000.0
409 << "ms";
410
411 if (last_eofs_debug.find(camera_name) == last_eofs_debug.end()) {
412 last_eofs_debug[camera_name] = distributed_eof;
413 }
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800414
415 for (const auto *target_pose_fbs : *map.target_poses()) {
416 // Skip detections with invalid ids
417 if (static_cast<TargetMapper::TargetId>(target_pose_fbs->id()) <
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700418 absl::GetFlag(FLAGS_min_target_id) ||
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800419 static_cast<TargetMapper::TargetId>(target_pose_fbs->id()) >
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700420 absl::GetFlag(FLAGS_max_target_id)) {
Jim Ostrowski67726282024-03-24 14:39:33 -0700421 VLOG(1) << "Skipping tag from " << camera_name << " with invalid id of "
422 << target_pose_fbs->id();
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800423 continue;
424 }
425
426 // Skip detections with high pose errors
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700427 if (target_pose_fbs->pose_error() > absl::GetFlag(FLAGS_max_pose_error)) {
Jim Ostrowski67726282024-03-24 14:39:33 -0700428 LOG(INFO) << "Skipping tag from " << camera_name << " with id "
429 << target_pose_fbs->id() << " due to pose error of "
430 << target_pose_fbs->pose_error();
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800431 continue;
432 }
433 // Skip detections with high pose error ratios
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700434 if (target_pose_fbs->pose_error_ratio() >
435 absl::GetFlag(FLAGS_max_pose_error_ratio)) {
Jim Ostrowski67726282024-03-24 14:39:33 -0700436 LOG(INFO) << "Skipping tag from " << camera_name << " with id "
437 << target_pose_fbs->id() << " due to pose error ratio of "
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800438 << target_pose_fbs->pose_error_ratio();
439 continue;
440 }
441
442 const TargetMapper::TargetPose target_pose =
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700443 TargetMapper::TargetPoseFromFbs(*target_pose_fbs);
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800444
445 target_poses.emplace_back(target_pose);
446
447 Eigen::Affine3d H_camera_target =
448 PoseUtils::Pose3dToAffine3d(target_pose.pose);
Jim Ostrowski67726282024-03-24 14:39:33 -0700449 VLOG(1) << camera_name << " saw target " << target_pose.id
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800450 << " from TargetMap at timestamp " << distributed_eof
451 << " with pose = " << H_camera_target.matrix();
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700452 LOG(INFO) << "pose info for target " << target_pose_fbs->id()
453 << ": \nconfidence: " << target_pose_fbs->confidence()
454 << ", pose_error: " << target_pose_fbs->pose_error()
455 << ", pose_error_ratio: " << target_pose_fbs->pose_error_ratio()
456 << ", dist_factor: " << target_pose_fbs->distortion_factor();
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800457 }
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700458 last_eofs_debug[camera_name] = distributed_eof;
Jim Ostrowski67726282024-03-24 14:39:33 -0700459 HandlePoses(rgb_image, target_poses, distributed_eof, camera_name);
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800460}
461
462void HandleImage(aos::EventLoop *event_loop, cv::Mat rgb_image,
463 const aos::monotonic_clock::time_point eof,
464 aos::distributed_clock::time_point distributed_eof,
465 frc971::vision::CharucoExtractor &charuco_extractor,
Jim Ostrowski67726282024-03-24 14:39:33 -0700466 std::string camera_name) {
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800467 std::vector<cv::Vec4i> charuco_ids;
468 std::vector<std::vector<cv::Point2f>> charuco_corners;
469 bool valid = false;
470 std::vector<Eigen::Vector3d> rvecs_eigen;
471 std::vector<Eigen::Vector3d> tvecs_eigen;
472 // Why eof vs. distributed_eof?
473 charuco_extractor.ProcessImage(rgb_image, eof, event_loop->monotonic_now(),
474 charuco_ids, charuco_corners, valid,
475 rvecs_eigen, tvecs_eigen);
476 if (rvecs_eigen.size() > 0 && !valid) {
477 LOG(WARNING) << "Charuco extractor returned not valid";
478 return;
479 }
480
481 std::vector<TargetMapper::TargetPose> target_poses;
482 for (size_t i = 0; i < tvecs_eigen.size(); i++) {
483 Eigen::Quaterniond rotation(
484 frc971::controls::ToQuaternionFromRotationVector(rvecs_eigen[i]));
485 ceres::examples::Pose3d pose(Eigen::Vector3d(tvecs_eigen[i]), rotation);
486 TargetMapper::TargetPose target_pose{charuco_ids[i][0], pose};
487 target_poses.emplace_back(target_pose);
488
489 Eigen::Affine3d H_camera_target = PoseUtils::Pose3dToAffine3d(pose);
Jim Ostrowski67726282024-03-24 14:39:33 -0700490 VLOG(2) << camera_name << " saw target " << target_pose.id
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800491 << " from image at timestamp " << distributed_eof
492 << " with pose = " << H_camera_target.matrix();
493 }
Jim Ostrowski67726282024-03-24 14:39:33 -0700494 HandlePoses(rgb_image, target_poses, distributed_eof, camera_name);
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800495}
496
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700497void WriteExtrinsicFile(Eigen::Affine3d extrinsic, CameraNode camera_node,
498 const calibration::CameraCalibration *original_cal) {
499 // Write out this extrinsic to a file
500 flatbuffers::FlatBufferBuilder fbb;
501 flatbuffers::Offset<flatbuffers::Vector<float>> data_offset =
502 fbb.CreateVector(frc971::vision::MatrixToVector(extrinsic.matrix()));
503 calibration::TransformationMatrix::Builder matrix_builder(fbb);
504 matrix_builder.add_data(data_offset);
505 flatbuffers::Offset<calibration::TransformationMatrix>
506 extrinsic_calibration_offset = matrix_builder.Finish();
507
508 calibration::CameraCalibration::Builder calibration_builder(fbb);
509 calibration_builder.add_fixed_extrinsics(extrinsic_calibration_offset);
510 const aos::realtime_clock::time_point realtime_now =
511 aos::realtime_clock::now();
512 calibration_builder.add_calibration_timestamp(
513 realtime_now.time_since_epoch().count());
514 fbb.Finish(calibration_builder.Finish());
515 aos::FlatbufferDetachedBuffer<calibration::CameraCalibration>
516 solved_extrinsics = fbb.Release();
517
518 aos::FlatbufferDetachedBuffer<frc971::vision::calibration::CameraCalibration>
519 cal_copy = aos::RecursiveCopyFlatBuffer(original_cal);
520 cal_copy.mutable_message()->clear_fixed_extrinsics();
521 cal_copy.mutable_message()->clear_calibration_timestamp();
522 aos::FlatbufferDetachedBuffer<calibration::CameraCalibration>
523 merged_calibration = aos::MergeFlatBuffers(&cal_copy.message(),
524 &solved_extrinsics.message());
525
526 std::stringstream time_ss;
527 time_ss << realtime_now;
528
529 const std::string calibration_filename = frc971::vision::CalibrationFilename(
530 absl::GetFlag(FLAGS_output_folder), camera_node.node_name,
531 absl::GetFlag(FLAGS_team_number), camera_node.camera_number,
532 cal_copy.message().camera_id()->data(), time_ss.str());
533 LOG(INFO) << calibration_filename << " -> "
534 << aos::FlatbufferToJson(merged_calibration, {.multi_line = true});
535
536 aos::util::WriteStringToFileOrDie(
537 calibration_filename,
538 aos::FlatbufferToJson(merged_calibration, {.multi_line = true}));
539}
540
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800541void ExtrinsicsMain(int argc, char *argv[]) {
542 vis_robot_ = VisualizeRobot(cv::Size(1000, 1000));
543 vis_robot_.ClearImage();
544 const double kFocalLength = 1000.0;
545 const int kImageWidth = 1000;
546 vis_robot_.SetDefaultViewpoint(kImageWidth, kFocalLength);
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700547 LOG(INFO) << "COPYTHIS, count, camera_name, target_id, timestamp, mag_T, "
548 "mag_R_deg, "
549 "confidence, pose_error, pose_error_ratio, distortion_factor";
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800550
551 std::optional<aos::FlatbufferDetachedBuffer<aos::Configuration>> config =
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700552 (absl::GetFlag(FLAGS_config).empty()
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800553 ? std::nullopt
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700554 : std::make_optional(
555 aos::configuration::ReadConfig(absl::GetFlag(FLAGS_config))));
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800556
557 // open logfiles
558 aos::logger::LogReader reader(
559 aos::logger::SortParts(aos::logger::FindLogs(argc, argv)),
560 config.has_value() ? &config->message() : nullptr);
561
Jim Ostrowskiddebdcb2024-02-29 22:25:36 -0800562 reader.RemapLoggedChannel("/imu/constants", "y2024.Constants");
563 reader.RemapLoggedChannel("/orin1/constants", "y2024.Constants");
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700564 if (absl::GetFlag(FLAGS_robot)) {
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800565 reader.RemapLoggedChannel("/roborio/constants", "y2024.Constants");
566 }
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800567 reader.Register();
568
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700569 y2024::SendSimulationConstants(reader.event_loop_factory(),
570 absl::GetFlag(FLAGS_team_number),
571 absl::GetFlag(FLAGS_constants_path));
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800572
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700573 VLOG(1) << "Using target type " << absl::GetFlag(FLAGS_target_type);
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800574
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800575 std::vector<const calibration::CameraCalibration *> calibration_list;
576
577 std::vector<std::unique_ptr<aos::EventLoop>> detection_event_loops;
578 std::vector<frc971::vision::CharucoExtractor *> charuco_extractors;
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800579 std::vector<Eigen::Affine3d> default_extrinsics;
580
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800581 for (const CameraNode &camera_node : node_list) {
Jim Ostrowski67726282024-03-24 14:39:33 -0700582 const aos::Node *node = aos::configuration::GetNode(
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800583 reader.configuration(), camera_node.node_name.c_str());
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800584
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800585 detection_event_loops.emplace_back(
586 reader.event_loop_factory()->MakeEventLoop(
Jim Ostrowski67726282024-03-24 14:39:33 -0700587 (camera_node.camera_name() + "_detection").c_str(), node));
588
Maxwell Henderson9beb5692024-03-17 18:36:11 -0700589 aos::EventLoop *const event_loop = detection_event_loops.back().get();
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800590 frc971::constants::ConstantsFetcher<y2024::Constants> constants_fetcher(
Maxwell Henderson9beb5692024-03-17 18:36:11 -0700591 event_loop);
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800592 // Get the calibration for this orin/camera pair
593 const calibration::CameraCalibration *calibration =
594 y2024::vision::FindCameraCalibration(constants_fetcher.constants(),
595 camera_node.node_name,
596 camera_node.camera_number);
597 calibration_list.push_back(calibration);
Maxwell Henderson69ec45e2024-03-01 22:52:21 -0800598
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800599 // Extract the extrinsics from the calibration, and save as "defaults"
600 cv::Mat extrinsics_cv =
601 frc971::vision::CameraExtrinsics(calibration).value();
602 Eigen::Matrix4d extrinsics_matrix;
603 cv::cv2eigen(extrinsics_cv, extrinsics_matrix);
Jim Ostrowski67726282024-03-24 14:39:33 -0700604 const auto ext_H_robot_camera = Eigen::Affine3d(extrinsics_matrix);
605 default_extrinsics.emplace_back(ext_H_robot_camera);
Maxwell Henderson69ec45e2024-03-01 22:52:21 -0800606
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800607 VLOG(1) << "Got extrinsics for " << camera_node.camera_name() << " as\n"
608 << default_extrinsics.back().matrix();
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800609
Maxwell Henderson9beb5692024-03-17 18:36:11 -0700610 event_loop->MakeWatcher(
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800611 camera_node.camera_name(),
Maxwell Henderson9beb5692024-03-17 18:36:11 -0700612 [&reader, event_loop, camera_node](const TargetMap &map) {
Jim Ostrowski67726282024-03-24 14:39:33 -0700613 aos::distributed_clock::time_point camera_distributed_time =
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800614 reader.event_loop_factory()
Maxwell Henderson9beb5692024-03-17 18:36:11 -0700615 ->GetNodeEventLoopFactory(event_loop->node())
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800616 ->ToDistributedClock(aos::monotonic_clock::time_point(
617 aos::monotonic_clock::duration(
618 map.monotonic_timestamp_ns())));
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800619
Jim Ostrowski67726282024-03-24 14:39:33 -0700620 HandleTargetMap(map, camera_distributed_time,
621 camera_node.camera_name());
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800622 });
Jim Ostrowski33208982024-03-02 15:01:45 -0800623 VLOG(1) << "Created watcher for using the detection event loop for "
Jim Ostrowski67726282024-03-24 14:39:33 -0700624 << camera_node.camera_name();
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700625
626 // Display images, if they exist
627 std::string camera_name = camera_node.camera_name();
628 detection_event_loops.back()->MakeWatcher(
629 camera_name.c_str(),
630 [camera_name](const frc971::vision::CameraImage &image) {
631 cv::Mat image_color_mat(cv::Size(image.cols(), image.rows()), CV_8UC2,
632 (void *)image.data()->data());
633 cv::Mat bgr_image(cv::Size(image.cols(), image.rows()), CV_8UC3);
634 cv::cvtColor(image_color_mat, bgr_image, cv::COLOR_YUV2BGR_YUYV);
635 cv::resize(bgr_image, bgr_image, cv::Size(500, 500));
636 cv::imshow(camera_name.c_str(), bgr_image);
637 cv::waitKey(1);
638 });
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800639 }
640
641 reader.event_loop_factory()->Run();
642
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800643 CHECK_GT(two_board_extrinsics_list.size(), 0u)
644 << "Must have at least one view of both boards";
645 int base_target_id = two_board_extrinsics_list[0].board_id;
646 VLOG(1) << "Base id for two_board_extrinsics_list is " << base_target_id;
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800647
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700648 int pre_outlier_num = two_board_extrinsics_list.size();
649 RemoveOutliers(two_board_extrinsics_list);
650
651 LOG(INFO) << "Started with " << pre_outlier_num
652 << " observations. After OUTLIER rejection, "
653 << two_board_extrinsics_list.size() << " observations remaining";
654 Eigen::Affine3d H_boardA_boardB_avg =
655 ComputeAveragePose(two_board_extrinsics_list);
656 LOG(INFO) << "Estimate of two board pose using all nodes with "
657 << two_board_extrinsics_list.size() << " observations is:\n"
658 << H_boardA_boardB_avg.matrix() << "\nOr translation of "
659 << H_boardA_boardB_avg.translation().transpose()
660 << " (m) and rotation (r,p,y) of "
661 << PoseUtils::RotationMatrixToEulerAngles(
662 H_boardA_boardB_avg.rotation().matrix())
663 .transpose() *
664 180.0 / M_PI
665 << " (deg)";
666
667 // Do quick check to see what averaged two-board pose for
668 // each camera is individually, and compare with overall average
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800669 for (auto camera_node : node_list) {
Jim Ostrowski67726282024-03-24 14:39:33 -0700670 std::vector<TimestampedCameraDetection> pose_list;
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800671 for (auto ext : two_board_extrinsics_list) {
672 CHECK_EQ(base_target_id, ext.board_id)
673 << " All boards should have same reference id";
Jim Ostrowski67726282024-03-24 14:39:33 -0700674 if (ext.camera_name == camera_node.camera_name()) {
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800675 pose_list.push_back(ext);
676 }
677 }
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700678 CHECK(pose_list.size() > 0)
679 << "Didn't get any two_board extrinsics for camera "
680 << camera_node.camera_name();
681 Eigen::Vector3d translation_variance, rotation_variance;
682 Eigen::Affine3d avg_pose_from_camera = ComputeAveragePose(
683 pose_list, &translation_variance, &rotation_variance);
684
685 Eigen::Vector3d translation_std_dev = translation_variance.array().sqrt();
686 LOG(INFO) << camera_node.camera_name() << " has average pose from "
687 << pose_list.size() << " views of two targets of \n"
688 << avg_pose_from_camera.matrix()
689 << "\nTranslation standard deviation is "
690 << translation_std_dev.transpose();
691 double stdev_norm = translation_std_dev.norm();
692 double threshold = 0.03; // 3 cm threshold on translation variation
693 if (stdev_norm > threshold) {
694 LOG(INFO) << "WARNING: |STD_DEV| is " << stdev_norm * 100 << " > "
695 << threshold * 100 << " cm!!!!\nStd dev vector (in m) is "
696 << translation_std_dev.transpose();
697 }
698
699 Eigen::Vector3d rotation_std_dev = rotation_variance.array().sqrt();
700 LOG(INFO) << camera_node.camera_name()
701 << " with rotational standard deviation of: "
702 << rotation_std_dev.transpose() << " (radians)";
703 double rot_stdev_norm = rotation_std_dev.norm();
704 double rot_threshold = 3 * M_PI / 180.0; // Warn if more than 3 degrees
705 if (rot_stdev_norm > rot_threshold) {
706 LOG(INFO) << "WARNING: ROTATIONAL STD DEV is "
707 << rot_stdev_norm * 180.0 / M_PI << " > "
708 << rot_threshold * 180.0 / M_PI
709 << " degrees!!!!\nStd dev vector (in deg) is "
710 << (rotation_std_dev * 180.0 / M_PI).transpose();
711 }
712 // Check if a particular camera deviates significantly from the overall
713 // average Any of these factors could indicate a problem with that camera
714 Eigen::Affine3d delta_from_overall =
715 H_boardA_boardB_avg * avg_pose_from_camera.inverse();
716 LOG(INFO) << camera_node.camera_name()
717 << " had estimate different from pooled average of\n"
718 << "|dT| = " << delta_from_overall.translation().norm()
719 << "m and |dR| = "
720 << (PoseUtils::RotationMatrixToEulerAngles(
721 delta_from_overall.rotation().matrix()) *
722 180.0 / M_PI)
723 .norm()
724 << " deg";
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800725 }
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800726
727 // Next, compute the relative camera poses
728 LOG(INFO) << "Got " << detection_list.size() << " extrinsic observations";
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700729 std::vector<TimestampedCameraDetection> H_camera1_camera2_list;
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800730 std::vector<Eigen::Affine3d> updated_extrinsics;
731 // Use the first node's extrinsics as our base, and fix from there
732 updated_extrinsics.push_back(default_extrinsics[0]);
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800733 LOG(INFO) << "Default extrinsic for camera " << node_list.at(0).camera_name()
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700734 << " is\n"
735 << default_extrinsics[0].matrix();
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800736 for (uint i = 0; i < node_list.size() - 1; i++) {
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800737 H_camera1_camera2_list.clear();
Jim Ostrowski67726282024-03-24 14:39:33 -0700738 // Go through the list, and find successive pairs of
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700739 // cameras. We'll be calculating and writing the second
740 // of the pair's (the i+1'th camera) extrinsics
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800741 for (auto [pose1, pose2] : detection_list) {
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700742 // Note that this assumes our poses are ordered by the node_list
743 CHECK(!((pose1.camera_name == node_list.at(i + 1).camera_name()) &&
744 (pose2.camera_name == node_list.at(i).camera_name())))
745 << "The camera ordering on our detections is incorrect!";
Jim Ostrowski67726282024-03-24 14:39:33 -0700746 if ((pose1.camera_name == node_list.at(i).camera_name()) &&
747 (pose2.camera_name == node_list.at(i + 1).camera_name())) {
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800748 Eigen::Affine3d H_camera1_boardA = pose1.H_camera_target;
749 // If pose1 isn't referenced to base_target_id, correct that
750 if (pose1.board_id != base_target_id) {
751 // pose1.H_camera_target references boardB, so map back to boardA
752 H_camera1_boardA =
753 pose1.H_camera_target * H_boardA_boardB_avg.inverse();
754 }
755
756 // Now, get the camera2->boardA map (notice it's boardA, same as
Jim Ostrowski33208982024-03-02 15:01:45 -0800757 // camera1, so we can compute the difference between the cameras with
758 // both referenced to boardA)
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800759 Eigen::Affine3d H_camera2_boardA = pose2.H_camera_target;
Jim Ostrowski67726282024-03-24 14:39:33 -0700760 // If pose2 isn't referenced to boardA (base_target_id), correct that
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800761 if (pose2.board_id != base_target_id) {
762 // pose2.H_camera_target references boardB, so map back to boardA
763 H_camera2_boardA =
764 pose2.H_camera_target * H_boardA_boardB_avg.inverse();
765 }
766
767 // Compute camera1->camera2 map
768 Eigen::Affine3d H_camera1_camera2 =
769 H_camera1_boardA * H_camera2_boardA.inverse();
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700770 // Set the list up as TimestampedCameraDetection's to use the outlier
771 // removal function
772 TimestampedCameraDetection camera1_camera2{
773 .H_camera_target = H_camera1_camera2,
774 .camera_name = node_list.at(i + 1).camera_name()};
775 H_camera1_camera2_list.push_back(camera1_camera2);
Jim Ostrowski67726282024-03-24 14:39:33 -0700776 VLOG(1) << "Map from camera " << pose1.camera_name << " and tag "
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800777 << pose1.board_id << " with observation: \n"
778 << pose1.H_camera_target.matrix() << "\n to camera "
Jim Ostrowski67726282024-03-24 14:39:33 -0700779 << pose2.camera_name << " and tag " << pose2.board_id
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800780 << " with observation: \n"
781 << pose2.H_camera_target.matrix() << "\ngot map as\n"
782 << H_camera1_camera2.matrix();
783
784 Eigen::Affine3d H_world_board;
785 H_world_board = Eigen::Translation3d::Identity() *
786 Eigen::AngleAxisd(M_PI / 2.0, Eigen::Vector3d::UnitX());
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700787 if (absl::GetFlag(FLAGS_alt_view)) {
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800788 H_world_board = Eigen::Translation3d(0.0, 0.0, 3.0);
789 }
790
Jim Ostrowski67726282024-03-24 14:39:33 -0700791 VLOG(2) << "Camera1 " << pose1.camera_name << " in world frame is \n"
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800792 << (H_world_board * H_camera1_boardA.inverse()).matrix();
Jim Ostrowski67726282024-03-24 14:39:33 -0700793 VLOG(2) << "Camera2 " << pose2.camera_name << " in world frame is \n"
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800794 << (H_world_board * H_camera2_boardA.inverse()).matrix();
795 }
796 }
797 // TODO<Jim>: If we don't get any matches, we could just use default
798 // extrinsics
799 CHECK(H_camera1_camera2_list.size() > 0)
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800800 << "Failed with zero poses for node " << node_list.at(i).camera_name()
801 << " and " << node_list.at(i + 1).camera_name();
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800802 if (H_camera1_camera2_list.size() > 0) {
803 Eigen::Affine3d H_camera1_camera2_avg =
804 ComputeAveragePose(H_camera1_camera2_list);
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800805 LOG(INFO) << "From " << node_list.at(i).camera_name() << " to "
806 << node_list.at(i + 1).camera_name() << " found "
807 << H_camera1_camera2_list.size()
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800808 << " observations, and the average pose is:\n"
809 << H_camera1_camera2_avg.matrix();
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700810
811 RemoveOutliers(H_camera1_camera2_list);
812
813 H_camera1_camera2_avg = ComputeAveragePose(H_camera1_camera2_list);
814 LOG(INFO) << "After outlier rejection, from "
815 << node_list.at(i).camera_name() << " to "
816 << node_list.at(i + 1).camera_name() << " found "
817 << H_camera1_camera2_list.size()
818 << " observations, and the average pose is:\n"
819 << H_camera1_camera2_avg.matrix();
820
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800821 Eigen::Affine3d H_camera1_camera2_default =
822 default_extrinsics[i].inverse() * default_extrinsics[i + 1];
823 LOG(INFO) << "Compare this to that from default values:\n"
824 << H_camera1_camera2_default.matrix();
825 Eigen::Affine3d H_camera1_camera2_diff =
826 H_camera1_camera2_avg * H_camera1_camera2_default.inverse();
827 LOG(INFO)
828 << "Difference between averaged and default delta poses "
829 "has |T| = "
830 << H_camera1_camera2_diff.translation().norm() << "m and |R| = "
831 << Eigen::AngleAxisd(H_camera1_camera2_diff.rotation()).angle()
832 << " radians ("
833 << Eigen::AngleAxisd(H_camera1_camera2_diff.rotation()).angle() *
834 180.0 / M_PI
835 << " degrees)";
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700836
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800837 // Next extrinsic is just previous one * avg_delta_pose
838 Eigen::Affine3d next_extrinsic =
839 updated_extrinsics.back() * H_camera1_camera2_avg;
840 updated_extrinsics.push_back(next_extrinsic);
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800841 LOG(INFO) << "Default Extrinsic for " << node_list.at(i + 1).camera_name()
Jim Ostrowskiddebdcb2024-02-29 22:25:36 -0800842 << " is \n"
Maxwell Henderson9d7b1862024-03-02 11:00:29 -0800843 << default_extrinsics[i + 1].matrix();
844 LOG(INFO) << "--> Updated Extrinsic for "
845 << node_list.at(i + 1).camera_name() << " is \n"
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800846 << next_extrinsic.matrix();
847
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700848 WriteExtrinsicFile(next_extrinsic, node_list[i + 1],
849 calibration_list[i + 1]);
Jim Ostrowski67726282024-03-24 14:39:33 -0700850
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700851 if (absl::GetFlag(FLAGS_visualize)) {
Jim Ostrowski67726282024-03-24 14:39:33 -0700852 // Draw each of the updated extrinsic camera locations
853 vis_robot_.SetDefaultViewpoint(1000.0, 1500.0);
854 vis_robot_.DrawFrameAxes(
855 updated_extrinsics.back(), node_list.at(i + 1).camera_name(),
856 kOrinColors.at(node_list.at(i + 1).camera_name()));
857 }
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800858 }
859 }
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700860 if (absl::GetFlag(FLAGS_visualize)) {
Jim Ostrowski67726282024-03-24 14:39:33 -0700861 // And don't forget to draw the base camera location
862 vis_robot_.DrawFrameAxes(updated_extrinsics[0],
863 node_list.at(0).camera_name(),
864 kOrinColors.at(node_list.at(0).camera_name()));
865 cv::imshow("Extrinsic visualization", vis_robot_.image_);
866 // Add a bit extra time, so that we don't go right through the extrinsics
867 cv::waitKey(3000);
868 cv::waitKey(0);
869 }
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800870
871 // Cleanup
Jim Ostrowski6d242d52024-04-03 20:39:03 -0700872 for (uint i = 0; i < charuco_extractors.size(); i++) {
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800873 delete charuco_extractors[i];
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800874 }
875}
Jim Ostrowskiddebdcb2024-02-29 22:25:36 -0800876} // namespace y2024::vision
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800877
878int main(int argc, char **argv) {
879 aos::InitGoogle(&argc, &argv);
Jim Ostrowskiddebdcb2024-02-29 22:25:36 -0800880 y2024::vision::ExtrinsicsMain(argc, argv);
Jim Ostrowski9bf206a2024-01-26 23:31:58 -0800881}