11#pragma once
22#include < ceres/jet.h>
3+ #include < ceres/rotation.h>
34#include < fmt/format.h>
45#include < pybind11/numpy.h>
56#include < pybind11/pybind11.h>
67#include < stdint.h>
8+ #include < array>
79#include < cmath>
810#include " ./type_defs.hpp"
911
@@ -17,6 +19,7 @@ struct PinholeSplinedConfig {
1719 double fov_deg_y;
1820 uint32_t num_knots_x;
1921 uint32_t num_knots_y;
22+ double smoothness_lambda;
2023};
2124
2225struct PinholeSplinedIntrinsicsParameters {
@@ -161,6 +164,36 @@ inline int clamp_int(
161164 return v < lo ? lo : (v > hi ? hi : v);
162165}
163166
167+ // Convert normalized pinhole coordinates to stereographic projection.
168+ // Given p = (x, y) in normalized space:
169+ // r = |p|, theta = arctan(r), p_stereo = (p / r) * 2 * tan(theta / 2)
170+ template <typename T>
171+ static inline void normalized_to_stereographic (
172+ const T& x_normalized,
173+ const T& y_normalized,
174+ T& x_stereo,
175+ T& y_stereo
176+ ) {
177+ using std::atan;
178+ using std::sqrt;
179+ using std::tan;
180+
181+ const T r_sq = x_normalized * x_normalized + y_normalized * y_normalized;
182+ const T r = sqrt (r_sq + T (1e-30 )); // avoid division by zero
183+ const T theta = atan (r);
184+ const T scale = T (2 ) * tan (theta / T (2 )) / r;
185+ x_stereo = x_normalized * scale;
186+ y_stereo = y_normalized * scale;
187+ }
188+
189+ // Compute stereographic half-range from FOV in radians.
190+ // At the edge: theta = fov/2, so stereo_half = 2*tan(fov/4).
191+ inline double stereo_half_range (
192+ double fov_rad
193+ ) {
194+ return 2.0 * std::tan (fov_rad / 4.0 );
195+ }
196+
164197template <typename T>
165198static inline void cubic_bspline_basis_uniform (
166199 const T& u,
@@ -271,6 +304,106 @@ static inline T eval_bspline2d_uniform_cubic_clamped(
271304 return acc;
272305}
273306
307+ struct SplineMap {
308+ int Nx = 0 ;
309+ int Ny = 0 ;
310+ double half_x = 0.0 ;
311+ double half_y = 0.0 ;
312+ double x_scale = 0.0 ;
313+ double y_scale = 0.0 ;
314+
315+ explicit SplineMap (
316+ const PinholeSplinedConfig& cfg
317+ ) {
318+ this ->Nx = static_cast <int >(cfg.num_knots_x );
319+ this ->Ny = static_cast <int >(cfg.num_knots_y );
320+
321+ const double fov_rad_x = cfg.fov_deg_x * M_PI / 180.0 ;
322+ const double fov_rad_y = cfg.fov_deg_y * M_PI / 180.0 ;
323+ this ->half_x = stereo_half_range (fov_rad_x);
324+ this ->half_y = stereo_half_range (fov_rad_y);
325+
326+ this ->x_scale = (Nx - 3 ) / (2.0 * this ->half_x );
327+ this ->y_scale = (Ny - 3 ) / (2.0 * this ->half_y );
328+ }
329+
330+ template <typename T>
331+ inline void normalized_to_grid_coords (
332+ const T& x_n,
333+ const T& y_n,
334+ T& gx,
335+ T& gy
336+ ) const {
337+ T x_s, y_s;
338+ normalized_to_stereographic (x_n, y_n, x_s, y_s);
339+
340+ const T x_s_raw = T (1.0 ) + (x_s + T (this ->half_x )) * T (this ->x_scale );
341+ const T y_s_raw = T (1.0 ) + (y_s + T (this ->half_y )) * T (this ->y_scale );
342+
343+ constexpr double eps = 1e-12 ;
344+ gx = clamp_T (x_s_raw, T (0.0 ), T (Nx - 1.0 - eps));
345+ gy = clamp_T (y_s_raw, T (0.0 ), T (Ny - 1.0 - eps));
346+ }
347+
348+ inline void project_to_spline_coords (
349+ const double * cam6,
350+ const Vec3<double >& pw,
351+ double & gx,
352+ double & gy,
353+ double & x_n,
354+ double & y_n
355+ ) const {
356+ double pc[3 ];
357+ ceres::AngleAxisRotatePoint (cam6, pw.data (), pc);
358+ pc[0 ] += cam6[3 ];
359+ pc[1 ] += cam6[4 ];
360+ pc[2 ] += cam6[5 ];
361+
362+ const double inv_z = 1.0 / pc[2 ];
363+ x_n = pc[0 ] * inv_z;
364+ y_n = pc[1 ] * inv_z;
365+
366+ normalized_to_grid_coords (x_n, y_n, gx, gy);
367+ }
368+
369+ inline void cell_index (
370+ const double * cam6,
371+ const Vec3<double >& pw,
372+ int & ix,
373+ int & iy
374+ ) const {
375+ double gx, gy, xn, yn;
376+ project_to_spline_coords (cam6, pw, gx, gy, xn, yn);
377+ ix = static_cast <int >(gx);
378+ iy = static_cast <int >(gy);
379+ }
380+
381+ // / Check whether the 4x4 support patch for cell (ix, iy) has all
382+ // / unique knot indices. Near edges, clamping causes duplicates which
383+ // / Ceres forbids in a single residual block.
384+ inline bool is_inside_fov (
385+ int ix,
386+ int iy
387+ ) const {
388+ return ix >= 1 && ix <= Nx - 3 && iy >= 1 && iy <= Ny - 3 ;
389+ }
390+
391+ inline void support_indices_4x4 (
392+ int ix,
393+ int iy,
394+ std::array<int , 16 >& flat
395+ ) const {
396+ int idx = 0 ;
397+ for (int b = 0 ; b < 4 ; b++) {
398+ const int yy = clamp_int (iy + b - 1 , 0 , Ny - 1 );
399+ for (int a = 0 ; a < 4 ; a++) {
400+ const int xx = clamp_int (ix + a - 1 , 0 , Nx - 1 );
401+ flat[idx++] = yy * Nx + xx;
402+ }
403+ }
404+ }
405+ };
406+
274407template <typename T>
275408void project_pinhole_splined (
276409 PinholeSplinedConfig* config,
@@ -280,63 +413,57 @@ void project_pinhole_splined(
280413 const Vec3<T>& point_in_camera,
281414 Vec2<T>& result
282415) {
283- // --- pinhole normalized coords
284416 const T x_normalized = point_in_camera[0 ] / point_in_camera[2 ];
285417 const T y_normalized = point_in_camera[1 ] / point_in_camera[2 ];
286418
287- const int Nx = static_cast <int >(config->num_knots_x );
288- const int Ny = static_cast <int >(config->num_knots_y );
289-
290- const double fov_rad_x = config->fov_deg_x * M_PI / 180.0 ;
291- const double fov_rad_y = config->fov_deg_y * M_PI / 180.0 ;
292-
293- // We define the spline domain over the normalized pinhole plane such that
294- // x_normalized in [-tan(fov_x/2), +tan(fov_x/2)] maps across the interior
295- // of the spline grid (with clamping outside).
296- const double half_x_range = std::tan (fov_rad_x / 2.0 );
297- const double half_y_range = std::tan (fov_rad_y / 2.0 );
298-
299- const T x_range_start = T (-half_x_range);
300- const T x_range_end = T (+half_x_range);
301- const T y_range_start = T (-half_y_range);
302- const T y_range_end = T (+half_y_range);
303-
304- const T fx = pinhole_parameters[0 ];
305- const T fy = pinhole_parameters[1 ];
306- const T cx = pinhole_parameters[2 ];
307- const T cy = pinhole_parameters[3 ];
308-
309- const T inv_x_span = T (1 ) / (x_range_end - x_range_start);
310- const T inv_y_span = T (1 ) / (y_range_end - y_range_start);
419+ const SplineMap map (*config);
311420
312- const T x_spline =
313- T (1 ) + (x_normalized - x_range_start) * T (Nx - 3 ) * inv_x_span;
314- const T y_spline =
315- T (1 ) + (y_normalized - y_range_start) * T (Ny - 3 ) * inv_y_span;
421+ T x_spline, y_spline;
422+ map.normalized_to_grid_coords (
423+ x_normalized,
424+ y_normalized,
425+ x_spline,
426+ y_spline
427+ );
316428
317- // --- evaluate spline surfaces
318429 const T dx = eval_bspline2d_uniform_cubic_clamped (
319430 dx_grid,
320- Nx,
321- Ny,
431+ map. Nx ,
432+ map. Ny ,
322433 x_spline,
323434 y_spline
324435 );
325436 const T dy = eval_bspline2d_uniform_cubic_clamped (
326437 dy_grid,
327- Nx,
328- Ny,
438+ map. Nx ,
439+ map. Ny ,
329440 x_spline,
330441 y_spline
331442 );
332443
333- // --- apply distortion in normalized plane
334- const T x_distorted = x_normalized + dx;
335- const T y_distorted = y_normalized + dy;
444+ const T fx = pinhole_parameters[0 ];
445+ const T fy = pinhole_parameters[1 ];
446+ const T cx = pinhole_parameters[2 ];
447+ const T cy = pinhole_parameters[3 ];
336448
337- // --- intrinsics to pixels
338- result[0 ] = fx * x_distorted + cx;
339- result[1 ] = fy * y_distorted + cy;
449+ result[0 ] = fx * (x_normalized + dx) + cx;
450+ result[1 ] = fy * (y_normalized + dy) + cy;
340451}
341452
453+ struct KnotSmoothness {
454+ double s;
455+ template <typename T>
456+ bool operator ()(
457+ const T* const a,
458+ const T* const b,
459+ const T* const c,
460+ const T* const d,
461+ T* residuals
462+ ) const {
463+ // Third derivative: -a + 3b - 3c + d
464+ residuals[0 ] = T (s) * (-a[0 ] + T (3.0 ) * b[0 ] - T (3.0 ) * c[0 ] + d[0 ]);
465+ return true ;
466+ }
467+ };
468+
342469} // namespace lensboy
0 commit comments