blob: 944fa767d7937a3a3eb58a99660cdb6b8cbe60d7 [file] [log] [blame]
Jim Ostrowskicb8b4082024-01-21 02:23:46 -08001#include "frc971/vision/vision_util_lib.h"
2
Jim Ostrowski6d242d52024-04-03 20:39:03 -07003#include <numeric>
4
Austin Schuh99f7c6a2024-06-25 22:07:44 -07005#include "absl/log/check.h"
6#include "absl/log/log.h"
Jim Ostrowski33208982024-03-02 15:01:45 -08007#include "absl/strings/str_format.h"
Jim Ostrowskicb8b4082024-01-21 02:23:46 -08008
Jim Ostrowski6d242d52024-04-03 20:39:03 -07009#include "aos/util/math.h"
10#include "frc971/control_loops/quaternion_utils.h"
11
Jim Ostrowskicb8b4082024-01-21 02:23:46 -080012namespace frc971::vision {
13
14std::optional<cv::Mat> CameraExtrinsics(
15 const frc971::vision::calibration::CameraCalibration *camera_calibration) {
16 CHECK(!camera_calibration->has_turret_extrinsics())
17 << "Turret not currently supported";
18
19 if (!camera_calibration->has_fixed_extrinsics()) {
20 return std::nullopt;
21 }
22 CHECK(camera_calibration->fixed_extrinsics()->has_data());
23 cv::Mat result(4, 4, CV_32F,
24 const_cast<void *>(static_cast<const void *>(
25 camera_calibration->fixed_extrinsics()->data()->data())));
26 result.convertTo(result, CV_64F);
27 CHECK_EQ(result.total(),
28 camera_calibration->fixed_extrinsics()->data()->size());
29
30 return result;
31}
32
33cv::Mat CameraIntrinsics(
34 const frc971::vision::calibration::CameraCalibration *camera_calibration) {
35 cv::Mat result(3, 3, CV_32F,
36 const_cast<void *>(static_cast<const void *>(
37 camera_calibration->intrinsics()->data())));
38 result.convertTo(result, CV_64F);
39 CHECK_EQ(result.total(), camera_calibration->intrinsics()->size());
40
41 return result;
42}
43
44cv::Mat CameraDistCoeffs(
45 const frc971::vision::calibration::CameraCalibration *camera_calibration) {
46 const cv::Mat result(5, 1, CV_32F,
47 const_cast<void *>(static_cast<const void *>(
48 camera_calibration->dist_coeffs()->data())));
49 CHECK_EQ(result.total(), camera_calibration->dist_coeffs()->size());
50 return result;
51}
52
Jim Ostrowskib974cca2024-01-28 15:07:50 -080053std::optional<uint16_t> CameraNumberFromChannel(std::string camera_channel) {
54 if (camera_channel.find("/camera") == std::string::npos) {
55 return std::nullopt;
56 }
57 // If the string doesn't end in /camera#, return nullopt
58 uint16_t cam_len = std::string("/camera").length();
59 if (camera_channel.length() != camera_channel.find("/camera") + cam_len + 1) {
60 return std::nullopt;
61 }
62
63 uint16_t camera_number = std::stoi(
64 camera_channel.substr(camera_channel.find("/camera") + cam_len, 1));
65 return camera_number;
66}
67
Jim Ostrowski33208982024-03-02 15:01:45 -080068std::string CalibrationFilename(std::string calibration_folder,
69 std::string node_name, int team_number,
70 int camera_number, std::string camera_id,
71 std::string timestamp) {
72 // Get rid of any fractional seconds-- we shouldn't need those and it makes
73 // the string unnecessarily longer
74 timestamp = timestamp.substr(0, timestamp.find("."));
75 std::string calibration_filename =
76 calibration_folder +
77 absl::StrFormat("/calibration_%s-%d-%d_cam-%s_%s.json", node_name.c_str(),
78 team_number, camera_number, camera_id.c_str(),
79 timestamp.c_str());
80 return calibration_filename;
81}
82
Jim Ostrowski6d242d52024-04-03 20:39:03 -070083Eigen::Affine3d PoseUtils::Pose3dToAffine3d(
84 const ceres::examples::Pose3d &pose3d) {
85 Eigen::Affine3d H_world_pose =
86 Eigen::Translation3d(pose3d.p(0), pose3d.p(1), pose3d.p(2)) * pose3d.q;
87 return H_world_pose;
88}
89
90ceres::examples::Pose3d PoseUtils::Affine3dToPose3d(const Eigen::Affine3d &H) {
91 return ceres::examples::Pose3d{.p = H.translation(),
92 .q = Eigen::Quaterniond(H.rotation())};
93}
94
95ceres::examples::Pose3d PoseUtils::ComputeRelativePose(
96 const ceres::examples::Pose3d &pose_1,
97 const ceres::examples::Pose3d &pose_2) {
98 Eigen::Affine3d H_world_1 = Pose3dToAffine3d(pose_1);
99 Eigen::Affine3d H_world_2 = Pose3dToAffine3d(pose_2);
100
101 // Get the location of 2 in the 1 frame
102 Eigen::Affine3d H_1_2 = H_world_1.inverse() * H_world_2;
103 return Affine3dToPose3d(H_1_2);
104}
105
106ceres::examples::Pose3d PoseUtils::ComputeOffsetPose(
107 const ceres::examples::Pose3d &pose_1,
108 const ceres::examples::Pose3d &pose_2_relative) {
109 auto H_world_1 = Pose3dToAffine3d(pose_1);
110 auto H_1_2 = Pose3dToAffine3d(pose_2_relative);
111 auto H_world_2 = H_world_1 * H_1_2;
112
113 return Affine3dToPose3d(H_world_2);
114}
115
116Eigen::Quaterniond PoseUtils::EulerAnglesToQuaternion(
117 const Eigen::Vector3d &rpy) {
118 Eigen::AngleAxisd roll_angle(rpy.x(), Eigen::Vector3d::UnitX());
119 Eigen::AngleAxisd pitch_angle(rpy.y(), Eigen::Vector3d::UnitY());
120 Eigen::AngleAxisd yaw_angle(rpy.z(), Eigen::Vector3d::UnitZ());
121
122 return yaw_angle * pitch_angle * roll_angle;
123}
124
125Eigen::Vector3d PoseUtils::QuaternionToEulerAngles(
126 const Eigen::Quaterniond &q) {
127 return RotationMatrixToEulerAngles(q.toRotationMatrix());
128}
129
130Eigen::Vector3d PoseUtils::RotationMatrixToEulerAngles(
131 const Eigen::Matrix3d &R) {
132 double roll = aos::math::NormalizeAngle(std::atan2(R(2, 1), R(2, 2)));
133 double pitch = aos::math::NormalizeAngle(-std::asin(R(2, 0)));
134 double yaw = aos::math::NormalizeAngle(std::atan2(R(1, 0), R(0, 0)));
135
136 return Eigen::Vector3d(roll, pitch, yaw);
137}
138
139// Compute the average pose given a list of translations (as
140// Eigen::Vector3d's) and rotations (as Eigen::Vector4d quaternions)
141// Returned as an Eigen::Affine3d pose
142// Also, compute the variance of each of these list of vectors
143
144// NOTE: variance for rotations can get dicey, so we've cheated a
145// little by doing two things:
146// 1) Computing variance relative to the mean, so that we're doing
147// this on small angles and don't have to deal with wrapping at
148// 180/360 degrees
149// 2) Returning the variance in Euler angles, since I'm not sure of a
150// better way to represent variance for rotations. (Maybe log of
151// rotations, in the Lie algebra?)
152
153Eigen::Affine3d ComputeAveragePose(
154 std::vector<Eigen::Vector3d> &translation_list,
155 std::vector<Eigen::Vector4d> &rotation_list,
156 Eigen::Vector3d *translation_variance, Eigen::Vector3d *rotation_variance) {
157 Eigen::Vector3d avg_translation =
158 std::accumulate(translation_list.begin(), translation_list.end(),
159 Eigen::Vector3d{0, 0, 0}) /
160 translation_list.size();
161
162 Eigen::Quaterniond avg_rotation_q(
163 frc971::controls::QuaternionMean(rotation_list));
164 Eigen::Affine3d average_pose =
165 Eigen::Translation3d(avg_translation) * avg_rotation_q;
166
167 CHECK_EQ(translation_list.size(), rotation_list.size());
168 if (translation_variance != nullptr) {
169 CHECK(rotation_variance != nullptr);
170 Eigen::Vector3d translation_variance_sum(0.0, 0.0, 0.0);
171 Eigen::Vector3d rotation_variance_sum(0.0, 0.0, 0.0);
172 for (uint i = 0; i < translation_list.size(); i++) {
173 Eigen::Quaterniond rotation_q(rotation_list[i]);
174 Eigen::Affine3d pose =
175 Eigen::Translation3d(translation_list[i]) * rotation_q;
176 Eigen::Affine3d delta_pose = average_pose * pose.inverse();
177 translation_variance_sum =
178 translation_variance_sum +
179 Eigen::Vector3d(delta_pose.translation().array().square());
180 rotation_variance_sum =
181 rotation_variance_sum +
182 Eigen::Vector3d(PoseUtils::RotationMatrixToEulerAngles(
183 delta_pose.rotation().matrix())
184 .array()
185 .square());
186 }
187 // Compute the variance on the translations (in m)
188 if (translation_variance != nullptr) {
189 CHECK(translation_list.size() > 1)
190 << "Have to have at least two translations to compute variance";
191 *translation_variance =
192 translation_variance_sum / translation_list.size();
193 }
194
195 // Compute the variance on the rotations (in euler angles, radians),
196 // referenced to the mean, to remove issues with Euler angles by
197 // keeping them near zero
198 if (rotation_variance != nullptr) {
199 CHECK(rotation_list.size() > 1)
200 << "Have to have at least two rotations to compute variance";
201 *rotation_variance = rotation_variance_sum / rotation_list.size();
202 }
203 }
204 return average_pose;
205}
206
207// Helper function to compute average pose when supplied with list
208// of Eigen::Affine3d's
209Eigen::Affine3d ComputeAveragePose(std::vector<Eigen::Affine3d> &pose_list,
210 Eigen::Vector3d *translation_variance,
211 Eigen::Vector3d *rotation_variance) {
212 std::vector<Eigen::Vector3d> translation_list;
213 std::vector<Eigen::Vector4d> rotation_list;
214
215 for (Eigen::Affine3d pose : pose_list) {
216 translation_list.push_back(pose.translation());
217 Eigen::Quaterniond quat(pose.rotation().matrix());
218 rotation_list.push_back(
219 Eigen::Vector4d(quat.x(), quat.y(), quat.z(), quat.w()));
220 }
221
222 return ComputeAveragePose(translation_list, rotation_list,
223 translation_variance, rotation_variance);
224}
225
Jim Ostrowskicb8b4082024-01-21 02:23:46 -0800226} // namespace frc971::vision