94 const Eigen::Matrix<T, Eigen::Dynamic, 1>& coeffs) -> Eigen::Matrix<T, 2, 1> {
95 if (coeffs.size() < 2) {
96 throw std::runtime_error(
"Insufficient distortion coefficients");
99 const int num_radial_coeffs =
static_cast<int>(coeffs.size()) - 2;
100 const T& x_coord = norm_xy.x();
101 const T& y_coord = norm_xy.y();
102 T r2_val = x_coord * x_coord + y_coord * y_coord;
105 for (
int i = 0; i < num_radial_coeffs; ++i) {
106 radial += T(coeffs[i]) * rpow;
109 T tangential1 = T(coeffs[num_radial_coeffs]);
110 T tangential2 = T(coeffs[num_radial_coeffs + 1]);
111 T x_distorted = x_coord * radial + T(2) * tangential1 * x_coord * y_coord +
112 tangential2 * (r2_val + T(2) * x_coord * x_coord);
113 T y_distorted = y_coord * radial + tangential1 * (r2_val + T(2) * y_coord * y_coord) +
114 T(2) * tangential2 * x_coord * y_coord;
115 return {x_distorted, y_distorted};
167 -> Eigen::Matrix<Scalar, Eigen::Dynamic, 1> {
168 if (forward.size() < 2) {
169 throw std::runtime_error(
"Insufficient distortion coefficients");
171 int num_radial =
static_cast<int>(forward.size()) - 2;
173 constexpr int grid = 21;
174 constexpr double lim = 1.0;
175 std::vector<Observation<Scalar>> obs;
176 obs.reserve(grid * grid);
177 constexpr Scalar k_two = Scalar(2.0);
178 for (
int i = 0; i < grid; ++i) {
180 -lim + k_two * lim *
static_cast<Scalar
>(i) /
static_cast<Scalar
>(grid - 1);
181 for (
int j = 0; j < grid; ++j) {
183 -lim + k_two * lim *
static_cast<Scalar
>(j) /
static_cast<Scalar
>(grid - 1);
184 Eigen::Matrix<Scalar, 2, 1> und(x_coord, y_coord);
186 obs.push_back({dst.x(), dst.y(), x_coord, y_coord});
191 if (inv_opt.has_value()) {
192 return inv_opt->distortion;
194 return Eigen::Matrix<Scalar, Eigen::Dynamic, 1>::Zero(forward.size());
233 std::span<const int> fixed_indices = {},
234 std::span<const T> fixed_values = {})
235 -> std::optional<DistortionWithResiduals<T>> {
236 constexpr int k_min_observations = 8;
237 if (observations.size() < k_min_observations) {
241 const int num_coeffs = num_radial + 2;
242 const int num_obs =
static_cast<int>(observations.size());
243 const int num_rows = num_obs * 2;
245 Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic> design_matrix(num_rows, num_coeffs);
246 Eigen::Matrix<T, Eigen::Dynamic, 1> rhs(num_rows);
248 design_matrix.setZero();
251 const int idx_p1 = num_radial;
252 const int idx_p2 = num_radial + 1;
254 for (
int obs_idx = 0; obs_idx < num_obs; ++obs_idx) {
255 const T x_coord = T(observations[obs_idx].x);
256 const T y_coord = T(observations[obs_idx].y);
257 const T r2_val = x_coord * x_coord + y_coord * y_coord;
259 const T undistorted_u = intrinsics.
fx * x_coord + intrinsics.
skew * y_coord + intrinsics.
cx;
260 const T undistorted_v = intrinsics.
fy * y_coord + intrinsics.
cy;
262 const T residual_u = T(observations[obs_idx].u) - undistorted_u;
263 const T residual_v = T(observations[obs_idx].v) - undistorted_v;
265 const int row_u = 2 * obs_idx;
266 const int row_v = row_u + 1;
270 for (
int j = 0; j < num_radial; ++j) {
271 design_matrix(row_u, j) =
272 intrinsics.
fx * x_coord * rpow + intrinsics.
skew * y_coord * rpow;
273 design_matrix(row_v, j) = intrinsics.
fy * y_coord * rpow;
278 design_matrix(row_u, idx_p1) = intrinsics.
fx * (T(2.0) * x_coord * y_coord) +
279 intrinsics.
skew * (r2_val + T(2.0) * y_coord * y_coord);
280 design_matrix(row_u, idx_p2) = intrinsics.
fx * (r2_val + T(2.0) * x_coord * x_coord) +
281 intrinsics.
skew * (T(2.0) * x_coord * y_coord);
282 design_matrix(row_v, idx_p1) = intrinsics.
fy * (r2_val + T(2.0) * y_coord * y_coord);
283 design_matrix(row_v, idx_p2) = intrinsics.
fy * (T(2.0) * x_coord * y_coord);
285 rhs(row_u) = residual_u;
286 rhs(row_v) = residual_v;
289 if (fixed_indices.empty()) {
290 Eigen::JacobiSVD<Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>> svd(
291 design_matrix, Eigen::ComputeThinU | Eigen::ComputeThinV);
292 auto alpha = svd.solve(rhs);
293 Eigen::Matrix<T, Eigen::Dynamic, 1> residuals = design_matrix * alpha - rhs;
294 return DistortionWithResiduals<T>{alpha, residuals};
297 std::vector<std::pair<int, T>> fixed_pairs;
298 fixed_pairs.reserve(fixed_indices.size());
299 for (
size_t i = 0; i < fixed_indices.size(); ++i) {
300 int idx = fixed_indices[i];
302 if (i < fixed_values.size()) {
303 value = fixed_values[i];
305 fixed_pairs.emplace_back(idx, value);
307 std::sort(fixed_pairs.begin(), fixed_pairs.end(),
308 [](
const auto& a,
const auto& b) { return a.first < b.first; });
309 fixed_pairs.erase(std::unique(fixed_pairs.begin(), fixed_pairs.end(),
310 [](
const auto& a,
const auto& b) { return a.first == b.first; }),
313 std::vector<int> fixed_only;
314 fixed_only.reserve(fixed_pairs.size());
315 for (
const auto& [idx, value] : fixed_pairs) {
316 if (idx < 0 || idx >= num_coeffs) {
317 throw std::invalid_argument(
"Fixed distortion index out of range");
319 fixed_only.push_back(idx);
322 Eigen::Matrix<T, Eigen::Dynamic, 1> alpha =
323 Eigen::Matrix<T, Eigen::Dynamic, 1>::Zero(num_coeffs);
324 for (
const auto& [idx, value] : fixed_pairs) {
328 Eigen::Matrix<T, Eigen::Dynamic, 1> rhs_adjusted = rhs;
329 for (
const auto& [idx, value] : fixed_pairs) {
330 rhs_adjusted -= design_matrix.col(idx) * value;
333 std::vector<int> free_indices;
334 free_indices.reserve(num_coeffs -
static_cast<int>(fixed_pairs.size()));
335 for (
int idx = 0; idx < num_coeffs; ++idx) {
336 if (!std::binary_search(fixed_only.begin(), fixed_only.end(), idx)) {
337 free_indices.push_back(idx);
341 Eigen::Matrix<T, Eigen::Dynamic, 1> residuals;
342 if (free_indices.empty()) {
343 residuals = design_matrix * alpha - rhs;
344 return DistortionWithResiduals<T>{alpha, residuals};
347 Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic> free_design(
348 design_matrix.rows(),
static_cast<int>(free_indices.size()));
349 for (
int col = 0; col < static_cast<int>(free_indices.size()); ++col) {
350 free_design.col(col) = design_matrix.col(free_indices[col]);
353 Eigen::JacobiSVD<Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>> svd(
354 free_design, Eigen::ComputeThinU | Eigen::ComputeThinV);
355 Eigen::Matrix<T, Eigen::Dynamic, 1> free_alpha = svd.solve(rhs_adjusted);
357 for (
int col = 0; col < static_cast<int>(free_indices.size()); ++col) {
358 alpha(free_indices[col]) = free_alpha(col);
361 residuals = design_matrix * alpha - rhs;
362 return DistortionWithResiduals<T>{std::move(alpha), std::move(residuals)};
375 std::span<const int> fixed_indices = {},
376 std::span<const double> fixed_values = {})
377 -> std::optional<DualDistortionWithResiduals> {
384 std::vector<Observation<double>> inv_observations;
385 inv_observations.reserve(observations.size());
386 for (
const auto& obs : observations) {
387 double y_dist = (obs.v - intrinsics.
cy) / intrinsics.
fy;
388 double x_dist = (obs.u - intrinsics.
cx - intrinsics.
skew * y_dist) / intrinsics.
fx;
389 double u_undist = intrinsics.
fx * obs.x + intrinsics.
skew * obs.y + intrinsics.
cx;
390 double v_undist = intrinsics.
fy * obs.y + intrinsics.
cy;
391 inv_observations.push_back({x_dist, y_dist, u_undist, v_undist});
395 fit_distortion_full(inv_observations, intrinsics, num_radial, fixed_indices, fixed_values);
400 DualDistortionWithResiduals out;
401 out.distortion.forward = forward->distortion;
402 out.distortion.inverse = inverse->distortion;
403 out.residuals = forward->residuals;