blob: e775e3e5e1f6558c3126f4c449f3ee6c9f3a833f [file] [log] [blame]
Austin Schuhbb4aae72021-10-08 22:12:25 -07001#include <opencv2/aruco/charuco.hpp>
2#include <opencv2/calib3d.hpp>
3#include <opencv2/features2d.hpp>
4#include <opencv2/highgui/highgui.hpp>
5#include <opencv2/imgproc.hpp>
6#include "Eigen/Dense"
7#include "Eigen/Geometry"
8
9#include "frc971/wpilib/imu_batch_generated.h"
10#include "absl/strings/str_format.h"
11#include "frc971/control_loops/quaternion_utils.h"
12#include "y2020/vision/charuco_lib.h"
13#include "aos/events/logging/log_reader.h"
14#include "aos/events/shm_event_loop.h"
15#include "aos/init.h"
16#include "aos/network/team_number.h"
17#include "aos/time/time.h"
18#include "aos/util/file.h"
19#include "frc971/control_loops/drivetrain/improved_down_estimator.h"
20#include "y2020/vision/sift/sift_generated.h"
21#include "y2020/vision/sift/sift_training_generated.h"
22#include "y2020/vision/tools/python_code/sift_training_data.h"
23#include "y2020/vision/vision_generated.h"
24#include "y2020/vision/charuco_lib.h"
25
26DEFINE_string(config, "config.json", "Path to the config file to use.");
27DEFINE_string(pi, "pi-7971-2", "Pi name to calibrate.");
28DEFINE_bool(display_undistorted, false,
29 "If true, display the undistorted image.");
30
31namespace frc971 {
32namespace vision {
33namespace chrono = std::chrono;
34using aos::distributed_clock;
35using aos::monotonic_clock;
36
37// Class to both accumulate and replay camera and IMU data in time order.
38class CalibrationData {
39 public:
40 CalibrationData()
41 : x_hat_(Eigen::Matrix<double, 9, 1>::Zero()),
42 q_(Eigen::Matrix<double, 9, 9>::Zero()) {}
43
44 // Adds an IMU point to the list at the provided time.
45 void AddImu(distributed_clock::time_point distributed_now,
46 Eigen::Vector3d gyro, Eigen::Vector3d accel) {
47 imu_points_.emplace_back(distributed_now, std::make_pair(gyro, accel));
48 }
49
50 // Adds a camera/charuco detection to the list at the provided time.
51 void AddCharuco(distributed_clock::time_point distributed_now,
52 Eigen::Vector3d rvec, Eigen::Vector3d tvec) {
53 rot_trans_points_.emplace_back(distributed_now, std::make_pair(rvec, tvec));
54 }
55
56 // Processes the data points by calling UpdateCamera and UpdateIMU in time
57 // order.
58 void ReviewData() {
59 size_t next_imu_point = 0;
60 size_t next_camera_point = 0;
61 while (true) {
62 if (next_imu_point != imu_points_.size()) {
63 // There aren't that many combinations, so just brute force them all
64 // rather than being too clever.
65 if (next_camera_point != rot_trans_points_.size()) {
66 if (imu_points_[next_imu_point].first >
67 rot_trans_points_[next_camera_point].first) {
68 // Camera!
69 UpdateCamera(rot_trans_points_[next_camera_point].first,
70 rot_trans_points_[next_camera_point].second);
71 ++next_camera_point;
72 } else {
73 // IMU!
74 UpdateIMU(imu_points_[next_imu_point].first,
75 imu_points_[next_imu_point].second);
76 ++next_imu_point;
77 }
78 } else {
79 if (next_camera_point != rot_trans_points_.size()) {
80 // Camera!
81 UpdateCamera(rot_trans_points_[next_camera_point].first,
82 rot_trans_points_[next_camera_point].second);
83 ++next_camera_point;
84 } else {
85 // Nothing left for either list of points, so we are done.
86 break;
87 }
88 }
89 }
90 }
91 }
92
93 void UpdateCamera(distributed_clock::time_point t,
94 std::pair<Eigen::Vector3d, Eigen::Vector3d> rt) {
95 LOG(INFO) << t << " Camera " << rt.second.transpose();
96 }
97
98 void UpdateIMU(distributed_clock::time_point t,
99 std::pair<Eigen::Vector3d, Eigen::Vector3d> wa) {
100 LOG(INFO) << t << " IMU " << wa.first.transpose();
101 }
102
103 private:
104 // TODO(austin): Actually use these. Or make a new "callback" object which has these.
105 Eigen::Matrix<double, 9, 1> x_hat_;
106 Eigen::Matrix<double, 9, 9> q_;
107
108 // Proposed filter states:
109 // States:
110 // xyz position
111 // xyz velocity
112 // orientation rotation vector
113 //
114 // Inputs
115 // xyz accel
116 // angular rates
117 //
118 // Measurement:
119 // xyz position
120 // orientation rotation vector
121
122 std::vector<std::pair<distributed_clock::time_point,
123 std::pair<Eigen::Vector3d, Eigen::Vector3d>>>
124 imu_points_;
125
126 // Store pose samples as timestamp, along with
127 // pair of rotation, translation vectors
128 std::vector<std::pair<distributed_clock::time_point,
129 std::pair<Eigen::Vector3d, Eigen::Vector3d>>>
130 rot_trans_points_;
131};
132
133// Class to register image and IMU callbacks in AOS and route them to the
134// corresponding CalibrationData class.
135class Calibration {
136 public:
137 Calibration(aos::SimulatedEventLoopFactory *event_loop_factory,
138 aos::EventLoop *image_event_loop, aos::EventLoop *imu_event_loop,
139 std::string_view pi, CalibrationData *data)
140 : image_event_loop_(image_event_loop),
141 image_factory_(event_loop_factory->GetNodeEventLoopFactory(
142 image_event_loop_->node())),
143 imu_event_loop_(imu_event_loop),
144 imu_factory_(event_loop_factory->GetNodeEventLoopFactory(
145 imu_event_loop_->node())),
146 charuco_extractor_(
147 image_event_loop_, pi,
148 [this](cv::Mat rgb_image, monotonic_clock::time_point eof,
149 std::vector<int> charuco_ids,
150 std::vector<cv::Point2f> charuco_corners, bool valid,
151 Eigen::Vector3d rvec_eigen, Eigen::Vector3d tvec_eigen) {
152 HandleCharuco(rgb_image, eof, charuco_ids, charuco_corners, valid,
153 rvec_eigen, tvec_eigen);
154 }),
155 data_(data) {
156 imu_event_loop_->MakeWatcher(
157 "/drivetrain", [this](const frc971::IMUValuesBatch &imu) {
158 if (!imu.has_readings()) {
159 return;
160 }
161 for (const frc971::IMUValues *value : *imu.readings()) {
162 HandleIMU(value);
163 }
164 });
165 }
166
167 // Processes a charuco detection.
168 void HandleCharuco(cv::Mat rgb_image, const monotonic_clock::time_point eof,
169 std::vector<int> /*charuco_ids*/,
170 std::vector<cv::Point2f> /*charuco_corners*/, bool valid,
171 Eigen::Vector3d rvec_eigen, Eigen::Vector3d tvec_eigen) {
172 if (valid) {
173 Eigen::Quaternion<double> rotation(
174 frc971::controls::ToQuaternionFromRotationVector(rvec_eigen));
175 Eigen::Translation3d translation(tvec_eigen);
176
177 const Eigen::Affine3d board_to_camera = translation * rotation;
178 (void)board_to_camera;
179
180 // TODO(austin): Need a gravity vector input.
181 //
182 // TODO(austin): Need a state, covariance, and model.
183 //
184 // TODO(austin): Need to record all the values out of a log and run it
185 // as a batch run so we can feed it into ceres.
186
187 // LOG(INFO) << "rotation " << rotation.matrix();
188 // LOG(INFO) << "translation " << translation.matrix();
189 // Z -> up
190 // Y -> away from cameras 2 and 3
191 // X -> left
192 Eigen::Vector3d imu(last_value_.accelerometer_x,
193 last_value_.accelerometer_y,
194 last_value_.accelerometer_z);
195
196 // For cameras 2 and 3...
197 Eigen::Quaternion<double> imu_to_camera(
198 Eigen::AngleAxisd(-0.5 * M_PI, Eigen::Vector3d::UnitX()));
199
200 Eigen::Quaternion<double> board_to_world(
201 Eigen::AngleAxisd(0.5 * M_PI, Eigen::Vector3d::UnitX()));
202
203 Eigen::IOFormat HeavyFmt(Eigen::FullPrecision, 0, ", ", ",\n", "[", "]",
204 "[", "]");
205
206 LOG(INFO);
207 LOG(INFO) << "World Gravity "
208 << (board_to_world * rotation.inverse() * imu_to_camera * imu)
209 .transpose()
210 .format(HeavyFmt);
211 LOG(INFO) << "Board Gravity "
212 << (rotation.inverse() * imu_to_camera * imu)
213 .transpose()
214 .format(HeavyFmt);
215 LOG(INFO) << "Camera Gravity "
216 << (imu_to_camera * imu).transpose().format(HeavyFmt);
217 LOG(INFO) << "IMU Gravity " << imu.transpose().format(HeavyFmt);
218
219 const double age_double =
220 std::chrono::duration_cast<std::chrono::duration<double>>(
221 image_event_loop_->monotonic_now() - eof)
222 .count();
223 LOG(INFO) << std::fixed << std::setprecision(6) << "Age: " << age_double
224 << ", Pose is R:" << rvec_eigen.transpose().format(HeavyFmt)
225 << " T:" << tvec_eigen.transpose().format(HeavyFmt);
226
227 data_->AddCharuco(image_factory_->ToDistributedClock(eof), rvec_eigen,
228 tvec_eigen);
229 }
230
231 cv::imshow("Display", rgb_image);
232
233 if (FLAGS_display_undistorted) {
234 const cv::Size image_size(rgb_image.cols, rgb_image.rows);
235 cv::Mat undistorted_rgb_image(image_size, CV_8UC3);
236 cv::undistort(rgb_image, undistorted_rgb_image,
237 charuco_extractor_.camera_matrix(),
238 charuco_extractor_.dist_coeffs());
239
240 cv::imshow("Display undist", undistorted_rgb_image);
241 }
242
243 int keystroke = cv::waitKey(1);
244 if ((keystroke & 0xFF) == static_cast<int>('q')) {
245 // image_event_loop_->Exit();
246 }
247 }
248
249 // Processes an IMU reading.
250 void HandleIMU(const frc971::IMUValues *imu) {
251 VLOG(1) << "IMU " << imu;
252 imu->UnPackTo(&last_value_);
253 Eigen::Vector3d gyro(last_value_.gyro_x, last_value_.gyro_y,
254 last_value_.gyro_z);
255 Eigen::Vector3d accel(last_value_.accelerometer_x,
256 last_value_.accelerometer_y,
257 last_value_.accelerometer_z);
258
259 data_->AddImu(imu_factory_->ToDistributedClock(monotonic_clock::time_point(
260 chrono::nanoseconds(imu->monotonic_timestamp_ns()))),
261 gyro, accel);
262 }
263
264 frc971::IMUValuesT last_value_;
265
266 private:
267 aos::EventLoop *image_event_loop_;
268 aos::NodeEventLoopFactory *image_factory_;
269 aos::EventLoop *imu_event_loop_;
270 aos::NodeEventLoopFactory *imu_factory_;
271
272 CharucoExtractor charuco_extractor_;
273
274 CalibrationData *data_;
275};
276
277void Main(int argc, char **argv) {
278 CalibrationData data;
279
280 {
281 // Now, accumulate all the data into the data object.
282 aos::logger::LogReader reader(
283 aos::logger::SortParts(aos::logger::FindLogs(argc, argv)));
284
285 aos::SimulatedEventLoopFactory factory(reader.configuration());
286 reader.Register(&factory);
287
288 CHECK(aos::configuration::MultiNode(reader.configuration()));
289
290 // Find the nodes we care about.
291 const aos::Node *const roborio_node =
292 aos::configuration::GetNode(factory.configuration(), "roborio");
293
294 std::optional<uint16_t> pi_number = aos::network::ParsePiNumber(FLAGS_pi);
295 CHECK(pi_number);
296 LOG(INFO) << "Pi " << *pi_number;
297 const aos::Node *const pi_node = aos::configuration::GetNode(
298 factory.configuration(), absl::StrCat("pi", *pi_number));
299
300 LOG(INFO) << "roboRIO " << aos::FlatbufferToJson(roborio_node);
301 LOG(INFO) << "Pi " << aos::FlatbufferToJson(pi_node);
302
303 std::unique_ptr<aos::EventLoop> roborio_event_loop =
304 factory.MakeEventLoop("calibration", roborio_node);
305 std::unique_ptr<aos::EventLoop> pi_event_loop =
306 factory.MakeEventLoop("calibration", pi_node);
307
308 // Now, hook Calibration up to everything.
309 Calibration extractor(&factory, pi_event_loop.get(),
310 roborio_event_loop.get(), FLAGS_pi, &data);
311
312 factory.Run();
313
314 reader.Deregister();
315 }
316
317 LOG(INFO) << "Done with event_loop running";
318 // And now we have it, we can start processing it.
319 data.ReviewData();
320}
321
322} // namespace vision
323} // namespace frc971
324
325int main(int argc, char **argv) {
326 aos::InitGoogle(&argc, &argv);
327
328 frc971::vision::Main(argc, argv);
329}