Skip to content

Commit f8bba6a

Browse files
authored
A few improvements (#27)
1 parent 51c1118 commit f8bba6a

File tree

68 files changed

+7694
-4521
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+7694
-4521
lines changed

CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ pybind11_add_module(
4747
)
4848

4949
target_compile_definitions(lensboy_bindings
50-
# PRIVATE SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_DEBUG
51-
PRIVATE SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_OFF
50+
PRIVATE SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_DEBUG
51+
# PRIVATE SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_OFF
5252
)
5353

5454
target_link_libraries(

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ target_points, frames, image_indices = lb.extract_frames_from_charuco(board, img
3333
result = lb.calibrate_camera(
3434
target_points, frames,
3535
camera_model_config=lb.OpenCVConfig(
36-
image_height=h, image_width=w, initial_focal_length=1000,
36+
image_height=h, image_width=w,
3737
),
3838
)
3939

@@ -46,7 +46,7 @@ Swap the config for a spline model — same API, more flexible:
4646
result = lb.calibrate_camera(
4747
target_points, frames,
4848
camera_model_config=lb.PinholeSplinedConfig(
49-
image_height=h, image_width=w, initial_focal_length=1000,
49+
image_height=h, image_width=w,
5050
),
5151
)
5252
```

cpp_src/calibrate.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ py::dict calibrate_opencv(
2525

2626
py::dict get_matching_spline_distortion_model(
2727
std::vector<double>& opencv_distortion_params,
28-
PinholeSplinedConfig& model_config
28+
PinholeSplinedConfig& model_config,
29+
double image_bound_x,
30+
double image_bound_y
2931
);
3032

3133
py::dict fine_tune_pinhole_splined(

cpp_src/calibrate_opencv.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,6 @@ py::dict calibrate_opencv(
184184
problem.SetParameterBlockConstant(pt.data());
185185
}
186186

187-
SPDLOG_DEBUG("Added parameter blocks");
188-
189187
size_t num_cameras = frames.size();
190188

191189
for (size_t camera_idx = 0; camera_idx < num_cameras; camera_idx++) {
@@ -218,8 +216,6 @@ py::dict calibrate_opencv(
218216
}
219217
}
220218

221-
SPDLOG_DEBUG("Added residual blocks");
222-
223219
ceres::Solver::Options options;
224220
options.num_threads = static_cast<int>(std::thread::hardware_concurrency());
225221
options.linear_solver_type = ceres::ITERATIVE_SCHUR;

cpp_src/cameramodels.hpp

Lines changed: 167 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
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

2225
struct 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+
164197
template <typename T>
165198
static 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+
274407
template <typename T>
275408
void 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

Comments
 (0)