blob: ac1946c25dfe63e04b928bcbc9e26dc9a7f1974d [file] [log] [blame]
Austin Schuhdcb6b362022-02-25 18:06:21 -08001#include "frc971/vision/calibration_accumulator.h"
milind-u8c72d532021-12-11 15:02:42 -08002
3#include <opencv2/aruco/charuco.hpp>
4#include <opencv2/calib3d.hpp>
5#include <opencv2/features2d.hpp>
6#include <opencv2/highgui/highgui.hpp>
7#include <opencv2/imgproc.hpp>
8
9#include "Eigen/Dense"
10#include "aos/events/simulated_event_loop.h"
11#include "aos/time/time.h"
12#include "frc971/control_loops/quaternion_utils.h"
13#include "frc971/wpilib/imu_batch_generated.h"
Austin Schuhdcb6b362022-02-25 18:06:21 -080014#include "frc971/vision/charuco_lib.h"
milind-u8c72d532021-12-11 15:02:42 -080015
16DEFINE_bool(display_undistorted, false,
17 "If true, display the undistorted image.");
18
19namespace frc971 {
20namespace vision {
21using aos::distributed_clock;
22using aos::monotonic_clock;
23namespace chrono = std::chrono;
24
Austin Schuh5b379072021-12-26 16:01:04 -080025constexpr double kG = 9.807;
26
milind-u8c72d532021-12-11 15:02:42 -080027void CalibrationData::AddCameraPose(
28 distributed_clock::time_point distributed_now, Eigen::Vector3d rvec,
29 Eigen::Vector3d tvec) {
Austin Schuh5b379072021-12-26 16:01:04 -080030 // Always start with IMU reading...
31 if (!imu_points_.empty() && imu_points_[0].first < distributed_now) {
32 rot_trans_points_.emplace_back(distributed_now, std::make_pair(rvec, tvec));
33 }
milind-u8c72d532021-12-11 15:02:42 -080034}
35
36void CalibrationData::AddImu(distributed_clock::time_point distributed_now,
37 Eigen::Vector3d gyro, Eigen::Vector3d accel) {
38 imu_points_.emplace_back(distributed_now, std::make_pair(gyro, accel));
39}
40
Austin Schuh2895f4c2022-02-26 16:38:46 -080041void CalibrationData::AddTurret(
42 aos::distributed_clock::time_point distributed_now, Eigen::Vector2d state) {
43 // We want the turret to be known too when solving. But, we don't know if we
44 // are going to have a turret until we get the first reading. In that case,
45 // blow away any camera readings from before.
46 while (!rot_trans_points_.empty() &&
47 rot_trans_points_[0].first < distributed_now) {
48 rot_trans_points_.erase(rot_trans_points_.begin());
49 }
50 turret_points_.emplace_back(distributed_now, state);
51}
52
Austin Schuhdcb6b362022-02-25 18:06:21 -080053void CalibrationData::ReviewData(CalibrationDataObserver *observer) const {
milind-u8c72d532021-12-11 15:02:42 -080054 size_t next_imu_point = 0;
55 size_t next_camera_point = 0;
56 while (true) {
57 if (next_imu_point != imu_points_.size()) {
58 // There aren't that many combinations, so just brute force them all
59 // rather than being too clever.
60 if (next_camera_point != rot_trans_points_.size()) {
61 if (imu_points_[next_imu_point].first >
62 rot_trans_points_[next_camera_point].first) {
63 // Camera!
64 observer->UpdateCamera(rot_trans_points_[next_camera_point].first,
65 rot_trans_points_[next_camera_point].second);
66 ++next_camera_point;
67 } else {
68 // IMU!
69 observer->UpdateIMU(imu_points_[next_imu_point].first,
70 imu_points_[next_imu_point].second);
71 ++next_imu_point;
72 }
73 } else {
74 if (next_camera_point != rot_trans_points_.size()) {
75 // Camera!
76 observer->UpdateCamera(rot_trans_points_[next_camera_point].first,
77 rot_trans_points_[next_camera_point].second);
78 ++next_camera_point;
79 } else {
80 // Nothing left for either list of points, so we are done.
81 break;
82 }
83 }
84 }
85 }
86}
87
88Calibration::Calibration(aos::SimulatedEventLoopFactory *event_loop_factory,
89 aos::EventLoop *image_event_loop,
90 aos::EventLoop *imu_event_loop, std::string_view pi,
91 CalibrationData *data)
92 : image_event_loop_(image_event_loop),
93 image_factory_(event_loop_factory->GetNodeEventLoopFactory(
94 image_event_loop_->node())),
95 imu_event_loop_(imu_event_loop),
96 imu_factory_(
97 event_loop_factory->GetNodeEventLoopFactory(imu_event_loop_->node())),
98 charuco_extractor_(
99 image_event_loop_, pi,
100 [this](cv::Mat rgb_image, monotonic_clock::time_point eof,
101 std::vector<int> charuco_ids,
102 std::vector<cv::Point2f> charuco_corners, bool valid,
103 Eigen::Vector3d rvec_eigen, Eigen::Vector3d tvec_eigen) {
104 HandleCharuco(rgb_image, eof, charuco_ids, charuco_corners, valid,
105 rvec_eigen, tvec_eigen);
106 }),
107 data_(data) {
108 imu_factory_->OnShutdown([]() { cv::destroyAllWindows(); });
109
110 imu_event_loop_->MakeWatcher(
111 "/drivetrain", [this](const frc971::IMUValuesBatch &imu) {
112 if (!imu.has_readings()) {
113 return;
114 }
115 for (const frc971::IMUValues *value : *imu.readings()) {
116 HandleIMU(value);
117 }
118 });
119}
120
121void Calibration::HandleCharuco(cv::Mat rgb_image,
122 const monotonic_clock::time_point eof,
123 std::vector<int> /*charuco_ids*/,
124 std::vector<cv::Point2f> /*charuco_corners*/,
125 bool valid, Eigen::Vector3d rvec_eigen,
126 Eigen::Vector3d tvec_eigen) {
127 if (valid) {
128 data_->AddCameraPose(image_factory_->ToDistributedClock(eof), rvec_eigen,
129 tvec_eigen);
130
milind-u8c72d532021-12-11 15:02:42 -0800131 // Z -> up
132 // Y -> away from cameras 2 and 3
133 // X -> left
134 Eigen::Vector3d imu(last_value_.accelerometer_x,
135 last_value_.accelerometer_y,
136 last_value_.accelerometer_z);
137
138 Eigen::Quaternion<double> imu_to_camera(
139 Eigen::AngleAxisd(-0.5 * M_PI, Eigen::Vector3d::UnitX()));
140
141 Eigen::Quaternion<double> board_to_world(
142 Eigen::AngleAxisd(0.5 * M_PI, Eigen::Vector3d::UnitX()));
143
144 Eigen::IOFormat HeavyFmt(Eigen::FullPrecision, 0, ", ", ",\n", "[", "]",
145 "[", "]");
146
147 const double age_double =
148 std::chrono::duration_cast<std::chrono::duration<double>>(
149 image_event_loop_->monotonic_now() - eof)
150 .count();
151 LOG(INFO) << std::fixed << std::setprecision(6) << "Age: " << age_double
152 << ", Pose is R:" << rvec_eigen.transpose().format(HeavyFmt)
153 << " T:" << tvec_eigen.transpose().format(HeavyFmt);
154 }
155
156 cv::imshow("Display", rgb_image);
157
158 if (FLAGS_display_undistorted) {
159 const cv::Size image_size(rgb_image.cols, rgb_image.rows);
160 cv::Mat undistorted_rgb_image(image_size, CV_8UC3);
161 cv::undistort(rgb_image, undistorted_rgb_image,
162 charuco_extractor_.camera_matrix(),
163 charuco_extractor_.dist_coeffs());
164
165 cv::imshow("Display undist", undistorted_rgb_image);
166 }
167}
168
169void Calibration::HandleIMU(const frc971::IMUValues *imu) {
170 VLOG(1) << "IMU " << imu;
171 imu->UnPackTo(&last_value_);
172 Eigen::Vector3d gyro(last_value_.gyro_x, last_value_.gyro_y,
173 last_value_.gyro_z);
174 Eigen::Vector3d accel(last_value_.accelerometer_x,
175 last_value_.accelerometer_y,
176 last_value_.accelerometer_z);
177
178 data_->AddImu(imu_factory_->ToDistributedClock(monotonic_clock::time_point(
179 chrono::nanoseconds(imu->monotonic_timestamp_ns()))),
Austin Schuh5b379072021-12-26 16:01:04 -0800180 gyro, accel * kG);
milind-u8c72d532021-12-11 15:02:42 -0800181}
182
183} // namespace vision
184} // namespace frc971