blob: 59c27e68aebe908264f090d1f75e737463b68d64 [file] [log] [blame]
James Kuszmauld6199be2023-02-11 19:56:28 -08001#include "Eigen/Dense"
2#include "Eigen/Geometry"
3#include "absl/strings/str_format.h"
Philipp Schrader790cb542023-07-05 21:06:52 -07004#include <opencv2/core/eigen.hpp>
5
James Kuszmauld6199be2023-02-11 19:56:28 -08006#include "aos/events/logging/log_reader.h"
7#include "aos/events/logging/log_writer.h"
8#include "aos/init.h"
9#include "aos/network/team_number.h"
10#include "aos/time/time.h"
11#include "aos/util/file.h"
James Kuszmauld6199be2023-02-11 19:56:28 -080012#include "frc971/constants/constants_sender_lib.h"
Jim Ostrowski86203682023-02-22 19:43:10 -080013#include "frc971/control_loops/quaternion_utils.h"
James Kuszmauld6199be2023-02-11 19:56:28 -080014#include "frc971/vision/extrinsics_calibration.h"
15#include "frc971/vision/vision_generated.h"
16#include "frc971/wpilib/imu_batch_generated.h"
17#include "y2023/constants/constants_generated.h"
18#include "y2023/vision/vision_util.h"
19
20DEFINE_string(pi, "pi-7971-2", "Pi name to calibrate.");
21DEFINE_bool(plot, false, "Whether to plot the resulting data.");
Jim Ostrowski86203682023-02-22 19:43:10 -080022DEFINE_string(target_type, "charuco_diamond",
James Kuszmauld6199be2023-02-11 19:56:28 -080023 "Type of target: aruco|charuco|charuco_diamond");
24DEFINE_string(image_channel, "/camera", "Channel to listen for images on");
25DEFINE_string(output_logs, "/tmp/calibration/",
26 "Output folder for visualization logs.");
Jim Ostrowskie9085d42023-02-23 21:14:06 -080027DEFINE_string(base_calibration, "",
28 "Intrinsics (and optionally extrinsics) to use for extrinsics "
29 "calibration.");
James Kuszmauld6199be2023-02-11 19:56:28 -080030DEFINE_string(output_calibration, "",
Jim Ostrowskie9085d42023-02-23 21:14:06 -080031 "Output json file to use for the resulting calibration "
32 "(intrinsics and extrinsics).");
James Kuszmauld6199be2023-02-11 19:56:28 -080033
Stephan Pleinesf63bde82024-01-13 15:59:33 -080034namespace frc971::vision {
James Kuszmauld6199be2023-02-11 19:56:28 -080035namespace chrono = std::chrono;
36using aos::distributed_clock;
37using aos::monotonic_clock;
38
39struct CalibrationAccumulatorState {
40 CalibrationAccumulatorState(
41 aos::SimulatedEventLoopFactory *factory, CalibrationData *data,
42 aos::FlatbufferDetachedBuffer<calibration::CameraCalibration> *intrinsics)
43 : factory(factory), data(data), intrinsics(intrinsics) {}
44 aos::SimulatedEventLoopFactory *factory;
45 CalibrationData *data;
46 aos::FlatbufferDetachedBuffer<calibration::CameraCalibration> *intrinsics;
47 std::unique_ptr<aos::EventLoop> imu_event_loop;
48 std::unique_ptr<aos::EventLoop> pi_event_loop;
49 std::unique_ptr<aos::EventLoop> logger_event_loop;
50 std::unique_ptr<aos::logger::Logger> logger;
51 std::unique_ptr<Calibration> extractor;
52 void MaybeMakeExtractor() {
53 if (imu_event_loop && pi_event_loop) {
54 TargetType target_type = TargetTypeFromString(FLAGS_target_type);
55 std::unique_ptr<aos::EventLoop> constants_event_loop =
56 factory->MakeEventLoop("constants_fetcher", pi_event_loop->node());
Jim Ostrowskie9085d42023-02-23 21:14:06 -080057 if (FLAGS_base_calibration.empty()) {
Jim Ostrowski86203682023-02-22 19:43:10 -080058 frc971::constants::ConstantsFetcher<y2023::Constants> constants_fetcher(
59 constants_event_loop.get());
60 *intrinsics =
61 aos::RecursiveCopyFlatBuffer(y2023::vision::FindCameraCalibration(
62 constants_fetcher.constants(),
63 pi_event_loop->node()->name()->string_view()));
64 } else {
65 *intrinsics = aos::JsonFileToFlatbuffer<calibration::CameraCalibration>(
Jim Ostrowskie9085d42023-02-23 21:14:06 -080066 FLAGS_base_calibration);
Jim Ostrowski86203682023-02-22 19:43:10 -080067 }
James Kuszmauld6199be2023-02-11 19:56:28 -080068 extractor = std::make_unique<Calibration>(
69 factory, pi_event_loop.get(), imu_event_loop.get(), FLAGS_pi,
70 &intrinsics->message(), target_type, FLAGS_image_channel, data);
71 }
72 }
73};
74
75void Main(int argc, char **argv) {
76 CalibrationData data;
Jim Ostrowskicb8b4082024-01-21 02:23:46 -080077 std::optional<uint16_t> pi_number =
78 aos::network::ParsePiOrOrinNumber(FLAGS_pi);
James Kuszmauld6199be2023-02-11 19:56:28 -080079 CHECK(pi_number);
80 const std::string pi_name = absl::StrCat("pi", *pi_number);
81
Jim Ostrowskie9085d42023-02-23 21:14:06 -080082 aos::FlatbufferDetachedBuffer<calibration::CameraCalibration> calibration =
James Kuszmauld6199be2023-02-11 19:56:28 -080083 aos::FlatbufferDetachedBuffer<calibration::CameraCalibration>::Empty();
84 {
85 // Now, accumulate all the data into the data object.
86 aos::logger::LogReader reader(
87 aos::logger::SortParts(aos::logger::FindLogs(argc, argv)));
Jim Ostrowski86203682023-02-22 19:43:10 -080088 reader.RemapLoggedChannel<foxglove::CompressedImage>("/pi1/camera");
89 reader.RemapLoggedChannel<foxglove::CompressedImage>("/pi2/camera");
90 reader.RemapLoggedChannel<foxglove::CompressedImage>("/pi3/camera");
91 reader.RemapLoggedChannel<foxglove::CompressedImage>("/pi4/camera");
92 reader.RemapLoggedChannel<foxglove::ImageAnnotations>("/pi1/camera");
93 reader.RemapLoggedChannel<foxglove::ImageAnnotations>("/pi2/camera");
94 reader.RemapLoggedChannel<foxglove::ImageAnnotations>("/pi3/camera");
95 reader.RemapLoggedChannel<foxglove::ImageAnnotations>("/pi4/camera");
James Kuszmauld6199be2023-02-11 19:56:28 -080096
97 aos::SimulatedEventLoopFactory factory(reader.configuration());
98 reader.RegisterWithoutStarting(&factory);
99
100 CHECK(aos::configuration::MultiNode(reader.configuration()));
101
102 // Find the nodes we care about.
103 const aos::Node *const imu_node =
104 aos::configuration::GetNode(factory.configuration(), "imu");
105
106 const aos::Node *const pi_node =
107 aos::configuration::GetNode(factory.configuration(), pi_name);
108
Jim Ostrowskie9085d42023-02-23 21:14:06 -0800109 CalibrationAccumulatorState accumulator_state(&factory, &data,
110 &calibration);
James Kuszmauld6199be2023-02-11 19:56:28 -0800111
112 reader.OnStart(imu_node, [&accumulator_state, imu_node, &factory]() {
113 accumulator_state.imu_event_loop =
114 factory.MakeEventLoop("calibration", imu_node);
115 accumulator_state.MaybeMakeExtractor();
116 });
117
118 reader.OnStart(imu_node, [&accumulator_state, pi_node, &factory]() {
119 accumulator_state.pi_event_loop =
120 factory.MakeEventLoop("logger", pi_node);
121 accumulator_state.logger_event_loop =
122 factory.MakeEventLoop("logger", pi_node);
123 accumulator_state.logger = std::make_unique<aos::logger::Logger>(
124 accumulator_state.logger_event_loop.get());
125 accumulator_state.logger->StartLoggingOnRun(FLAGS_output_logs);
126 accumulator_state.MaybeMakeExtractor();
127 });
128
129 factory.Run();
130
131 reader.Deregister();
132 }
133
134 LOG(INFO) << "Done with event_loop running";
135 CHECK(data.imu_samples_size() > 0) << "Didn't get any IMU data";
136 CHECK(data.camera_samples_size() > 0) << "Didn't get any camera observations";
137
138 // And now we have it, we can start processing it.
Jim Ostrowski86203682023-02-22 19:43:10 -0800139 // NOTE: For y2023, with no turret, pivot == imu
140
141 // Define the mapping that takes imu frame (with z up) to camera frame (with z
142 // pointing out)
143 const Eigen::Quaterniond R_precam_cam(
144 Eigen::AngleAxisd(-0.5 * M_PI, Eigen::Vector3d::UnitX()) *
145 Eigen::AngleAxisd(-0.5 * M_PI, Eigen::Vector3d::UnitZ()));
146 // Set up initial conditions for the pis that are reasonable
147 Eigen::Quaternion<double> nominal_initial_orientation(
148 Eigen::AngleAxisd(0.0, Eigen::Vector3d::UnitZ()));
149 Eigen::Quaternion<double> nominal_pivot_to_camera(
150 Eigen::AngleAxisd(0.5 * M_PI, Eigen::Vector3d::UnitX()) *
151 Eigen::AngleAxisd(-0.75 * M_PI, Eigen::Vector3d::UnitY()));
152 Eigen::Vector3d nominal_pivot_to_camera_translation(8.0, 8.0, 0.0);
153 Eigen::Quaternion<double> nominal_board_to_world(
James Kuszmauld6199be2023-02-11 19:56:28 -0800154 Eigen::AngleAxisd(-0.5 * M_PI, Eigen::Vector3d::UnitX()));
James Kuszmauld6199be2023-02-11 19:56:28 -0800155 Eigen::Matrix<double, 6, 1> nominal_initial_state =
156 Eigen::Matrix<double, 6, 1>::Zero();
Jim Ostrowski86203682023-02-22 19:43:10 -0800157 // Set y value to -1 m (approx distance from imu to the board/world)
158 nominal_initial_state(1, 0) = 1.0;
James Kuszmauld6199be2023-02-11 19:56:28 -0800159
Jim Ostrowskie9085d42023-02-23 21:14:06 -0800160 // If available, pull extrinsics from the calibration file
161 const calibration::CameraCalibration *calib_msg = &calibration.message();
162 if (calib_msg->has_fixed_extrinsics()) {
163 LOG(INFO) << "Using extrinsiscs from calibration file as initial condition";
164 const cv::Mat extrinsics_cv(
165 4, 4, CV_32F,
166 const_cast<void *>(static_cast<const void *>(
167 calib_msg->fixed_extrinsics()->data()->data())));
168 CHECK_EQ(extrinsics_cv.total(),
169 calib_msg->fixed_extrinsics()->data()->size());
170 Eigen::Matrix4d ext_mat;
171 cv::cv2eigen(extrinsics_cv, ext_mat);
172 Eigen::Affine3d extrinsics(ext_mat);
173 Eigen::Affine3d H_camera_imu = extrinsics.inverse();
174 nominal_pivot_to_camera = H_camera_imu.rotation();
175 nominal_pivot_to_camera_translation = H_camera_imu.translation();
176 }
177
James Kuszmauld6199be2023-02-11 19:56:28 -0800178 CalibrationParameters calibration_parameters;
179 calibration_parameters.initial_orientation = nominal_initial_orientation;
180 calibration_parameters.pivot_to_camera = nominal_pivot_to_camera;
Jim Ostrowski86203682023-02-22 19:43:10 -0800181 calibration_parameters.pivot_to_camera_translation =
182 nominal_pivot_to_camera_translation;
183 // Board to world rotation
James Kuszmauld6199be2023-02-11 19:56:28 -0800184 calibration_parameters.board_to_world = nominal_board_to_world;
Jim Ostrowski86203682023-02-22 19:43:10 -0800185 // Initial imu location (and velocity)
James Kuszmauld6199be2023-02-11 19:56:28 -0800186 calibration_parameters.initial_state = nominal_initial_state;
187 calibration_parameters.has_pivot = false;
188
Jim Ostrowski86203682023-02-22 19:43:10 -0800189 // Show the inverse of pivot_to_camera, since camera_to_pivot tells where
190 // the camera is with respect to the pivot frame
James Kuszmauld6199be2023-02-11 19:56:28 -0800191 const Eigen::Affine3d nominal_affine_pivot_to_camera =
192 Eigen::Translation3d(calibration_parameters.pivot_to_camera_translation) *
193 nominal_pivot_to_camera;
194 const Eigen::Quaterniond nominal_camera_to_pivot_rotation(
195 nominal_affine_pivot_to_camera.inverse().rotation());
196 const Eigen::Vector3d nominal_camera_to_pivot_translation(
197 nominal_affine_pivot_to_camera.inverse().translation());
198
199 LOG(INFO) << "Initial Conditions for solver. Assumes:\n"
200 << "1) board origin is same as world, but rotated pi/2 about "
201 "x-axis, so z points out\n"
Jim Ostrowski86203682023-02-22 19:43:10 -0800202 << "2) pivot origin matches imu origin (since there's no turret)\n"
James Kuszmauld6199be2023-02-11 19:56:28 -0800203 << "3) camera is offset from pivot (depends on which camera)";
204
Jim Ostrowski86203682023-02-22 19:43:10 -0800205 LOG(INFO) << "Nominal initial_orientation of imu w.r.t. world "
206 "(angle-axis vector): "
207 << frc971::controls::ToRotationVectorFromQuaternion(
208 nominal_initial_orientation)
209 .transpose();
James Kuszmauld6199be2023-02-11 19:56:28 -0800210 LOG(INFO) << "Nominal initial_state: \n"
211 << "Position: "
212 << nominal_initial_state.block<3, 1>(0, 0).transpose() << "\n"
213 << "Velocity: "
214 << nominal_initial_state.block<3, 1>(3, 0).transpose();
Jim Ostrowski86203682023-02-22 19:43:10 -0800215 LOG(INFO) << "Nominal camera_to_pivot rotation (angle-axis vector): "
James Kuszmauld6199be2023-02-11 19:56:28 -0800216 << frc971::controls::ToRotationVectorFromQuaternion(
217 nominal_camera_to_pivot_rotation)
218 .transpose();
219 LOG(INFO) << "Nominal camera_to_pivot translation: "
220 << nominal_camera_to_pivot_translation.transpose();
221
222 aos::FlatbufferDetachedBuffer<calibration::CameraCalibration>
223 solved_extrinsics = Solve(data, &calibration_parameters);
Jim Ostrowskie9085d42023-02-23 21:14:06 -0800224 calibration.mutable_message()->clear_fixed_extrinsics();
225 calibration.mutable_message()->clear_turret_extrinsics();
James Kuszmauld6199be2023-02-11 19:56:28 -0800226 aos::FlatbufferDetachedBuffer<calibration::CameraCalibration>
Jim Ostrowskie9085d42023-02-23 21:14:06 -0800227 merged_calibration = aos::MergeFlatBuffers(&calibration.message(),
James Kuszmauld6199be2023-02-11 19:56:28 -0800228 &solved_extrinsics.message());
229
230 if (!FLAGS_output_calibration.empty()) {
231 aos::WriteFlatbufferToJson(FLAGS_output_calibration, merged_calibration);
Jim Ostrowski86203682023-02-22 19:43:10 -0800232 } else {
233 LOG(WARNING) << "Calibration filename not provided, so not saving it";
James Kuszmauld6199be2023-02-11 19:56:28 -0800234 }
235
236 LOG(INFO) << "RESULTS OF CALIBRATION SOLVER:";
237 std::cout << aos::FlatbufferToJson(&merged_calibration.message())
238 << std::endl;
Jim Ostrowski86203682023-02-22 19:43:10 -0800239
James Kuszmauld6199be2023-02-11 19:56:28 -0800240 LOG(INFO) << "initial_orientation of imu w.r.t. world (angle-axis vector): "
241 << frc971::controls::ToRotationVectorFromQuaternion(
242 calibration_parameters.initial_orientation)
243 .transpose();
244 LOG(INFO)
Jim Ostrowski86203682023-02-22 19:43:10 -0800245 << "initial_state (imu): \n"
James Kuszmauld6199be2023-02-11 19:56:28 -0800246 << "Position: "
247 << calibration_parameters.initial_state.block<3, 1>(0, 0).transpose()
248 << "\n"
249 << "Velocity: "
250 << calibration_parameters.initial_state.block<3, 1>(3, 0).transpose();
251
252 const Eigen::Affine3d affine_pivot_to_camera =
253 Eigen::Translation3d(calibration_parameters.pivot_to_camera_translation) *
254 calibration_parameters.pivot_to_camera;
Jim Ostrowski86203682023-02-22 19:43:10 -0800255 const Eigen::Affine3d affine_camera_to_pivot =
256 affine_pivot_to_camera.inverse();
James Kuszmauld6199be2023-02-11 19:56:28 -0800257 const Eigen::Quaterniond camera_to_pivot_rotation(
Jim Ostrowski86203682023-02-22 19:43:10 -0800258 affine_camera_to_pivot.rotation());
James Kuszmauld6199be2023-02-11 19:56:28 -0800259 const Eigen::Vector3d camera_to_pivot_translation(
Jim Ostrowski86203682023-02-22 19:43:10 -0800260 affine_camera_to_pivot.translation());
261 LOG(INFO) << "camera to pivot(imu) rotation (angle-axis vec): "
James Kuszmauld6199be2023-02-11 19:56:28 -0800262 << frc971::controls::ToRotationVectorFromQuaternion(
263 camera_to_pivot_rotation)
264 .transpose();
Jim Ostrowski86203682023-02-22 19:43:10 -0800265 LOG(INFO) << "camera to pivot(imu) translation: "
James Kuszmauld6199be2023-02-11 19:56:28 -0800266 << camera_to_pivot_translation.transpose();
267 LOG(INFO) << "board_to_world (rotation) "
268 << frc971::controls::ToRotationVectorFromQuaternion(
269 calibration_parameters.board_to_world)
270 .transpose();
271 LOG(INFO) << "accelerometer bias "
272 << calibration_parameters.accelerometer_bias.transpose();
273 LOG(INFO) << "gyro_bias " << calibration_parameters.gyro_bias.transpose();
274 LOG(INFO) << "gravity " << 9.81 * calibration_parameters.gravity_scalar;
275
Jim Ostrowski86203682023-02-22 19:43:10 -0800276 LOG(INFO) << "Checking delta from nominal (initial condition) to solved "
277 "values:";
278 LOG(INFO) << "nominal_pivot_to_camera rotation: "
279 << frc971::controls::ToRotationVectorFromQuaternion(
280 R_precam_cam * nominal_pivot_to_camera)
281 .transpose();
282 LOG(INFO) << "solved pivot_to_camera rotation: "
283 << frc971::controls::ToRotationVectorFromQuaternion(
284 R_precam_cam * calibration_parameters.pivot_to_camera)
285 .transpose();
286
287 LOG(INFO) << "pivot_to_camera rotation delta (zero if the IC's match the "
288 "solved value) "
James Kuszmauld6199be2023-02-11 19:56:28 -0800289 << frc971::controls::ToRotationVectorFromQuaternion(
290 calibration_parameters.pivot_to_camera *
291 nominal_pivot_to_camera.inverse())
292 .transpose();
Jim Ostrowski86203682023-02-22 19:43:10 -0800293 LOG(INFO) << "board_to_world rotation change "
James Kuszmauld6199be2023-02-11 19:56:28 -0800294 << frc971::controls::ToRotationVectorFromQuaternion(
295 calibration_parameters.board_to_world *
296 nominal_board_to_world.inverse())
297 .transpose();
298
299 if (FLAGS_visualize) {
300 LOG(INFO) << "Showing visualization";
301 Visualize(data, calibration_parameters);
302 }
303
304 if (FLAGS_plot) {
305 Plot(data, calibration_parameters);
306 }
307} // namespace vision
308
Stephan Pleinesf63bde82024-01-13 15:59:33 -0800309} // namespace frc971::vision
James Kuszmauld6199be2023-02-11 19:56:28 -0800310
311int main(int argc, char **argv) {
312 aos::InitGoogle(&argc, &argv);
313
314 frc971::vision::Main(argc, argv);
315}