Monado OpenXR Runtime
t_camera_models.h
Go to the documentation of this file.
1// Copyright 2023, Collabora, Ltd.
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief Simple, untemplated, C, float-only, camera (un)projection functions for various camera models.
6 * @author Moshi Turner <moshiturner@protonmail.com>
7 * @ingroup aux_tracking
8 *
9 * Some notes:
10 * These functions should return exactly the same values as basalt-headers, down to floating point bits.
11 *
12 * They were mainly written as an expedient way to stop depending on OpenCV-based (un)projection code in Monado's hand
13 * tracking code, and to encourage compiler optimizations through inlining.
14 *
15 * Current users:
16 *
17 * * Mercury hand tracking
18 */
19
20#pragma once
21#include "math/m_vec2.h"
22#include "math/m_matrix_2x2.h"
23#include "math/m_mathinclude.h"
24
25#include "t_tracking.h"
26
27#include <assert.h>
28
29#ifdef __cplusplus
30extern "C" {
31#endif
32
33/*!
34 * Floating point parameters for @ref T_DISTORTION_FISHEYE_KB4
35 * @ingroup aux_tracking
36 */
38{
39 float k1, k2, k3, k4;
40};
41
42/*!
43 * Floating point parameters for @ref T_DISTORTION_OPENCV_RADTAN_8, also including
44 * @p metric_radius.
45 * @ingroup aux_tracking
46 */
48{
49 float k1, k2, p1, p2, k3, k4, k5, k6, metric_radius;
50};
51
52/*!
53 * Floating point calibration data for a single calibrated camera.
54 * @note This is basically @ref t_camera_calibration, just without some compatibility stuff and using single floats
55 * instead of doubles.
56 * @ingroup aux_tracking
57 */
59{
60 float fx, fy, cx, cy;
61 union {
64 };
65 // This model gets reinterpreted from values in the main t_camera_calibration struct to either
66 // * T_DISTORTION_FISHEYE_KB4
67 // * T_DISTORTION_OPENCV_RADTAN_8
69};
70
71
72static const float SQRT_EPSILON = 0.00316; // sqrt(1e-05)
73
74/*
75 * Functions for @ref T_DISTORTION_FISHEYE_KB4 (un)projections
76 */
77
78static inline float
79kb4_calc_r_theta(const struct t_camera_model_params *dist, //
80 const float theta, //
81 const float theta2)
82{
83 float r_theta = dist->fisheye.k4 * theta2;
84 r_theta += dist->fisheye.k3;
85 r_theta *= theta2;
86 r_theta += dist->fisheye.k2;
87 r_theta *= theta2;
88 r_theta += dist->fisheye.k1;
89 r_theta *= theta2;
90 r_theta += 1.0f;
91 r_theta *= theta;
92
93 return r_theta;
94}
95
96static inline bool
97kb4_project(const struct t_camera_model_params *dist, //
98 const float x, //
99 const float y, //
100 const float z, //
101 float *out_x, //
102 float *out_y)
103{
104 bool is_valid = true;
105 const float r2 = x * x + y * y;
106 const float r = sqrtf(r2);
107
108 if (r > SQRT_EPSILON) {
109
110
111 const float theta = atan2(r, z);
112 const float theta2 = theta * theta;
113
114 float r_theta = kb4_calc_r_theta(dist, theta, theta2);
115
116 const float mx = x * r_theta / r;
117 const float my = y * r_theta / r;
118
119 *out_x = dist->fx * mx + dist->cx;
120 *out_y = dist->fy * my + dist->cy;
121 } else {
122 // Check that the point is not cloze to zero norm
123 if (z < SQRT_EPSILON) {
124 is_valid = false;
125 }
126 *out_x = dist->fx * x / z + dist->cx;
127 *out_y = dist->fy * y / z + dist->cy;
128 }
129
130 return is_valid;
131}
132
133static inline float
134kb4_solve_theta(const struct t_camera_model_params *dist, const float *r_theta, float *d_func_d_theta)
135{
136 float theta = *r_theta;
137 for (int i = 4; i > 0; i--) {
138 float theta2 = theta * theta;
139
140 float func = dist->fisheye.k4 * theta2;
141 func += dist->fisheye.k3;
142 func *= theta2;
143 func += dist->fisheye.k2;
144 func *= theta2;
145 func += dist->fisheye.k1;
146 func *= theta2;
147 func += 1.0f;
148 func *= theta;
149
150 *d_func_d_theta = 9.0f * dist->fisheye.k4 * theta2;
151 *d_func_d_theta += 7.0f * dist->fisheye.k3;
152 *d_func_d_theta *= theta2;
153 *d_func_d_theta += 5.0f * dist->fisheye.k2;
154 *d_func_d_theta *= theta2;
155 *d_func_d_theta += 3.0f * dist->fisheye.k1;
156 *d_func_d_theta *= theta2;
157 *d_func_d_theta += 1.0f;
158
159 // Iteration of Newton method
160 theta += ((*r_theta) - func) / (*d_func_d_theta);
161 }
162
163 return theta;
164}
165
166static inline bool
168 const float x, //
169 const float y, //
170 float *out_x, //
171 float *out_y, //
172 float *out_z)
173{
174 const float mx = (x - dist->cx) / dist->fx;
175 const float my = (y - dist->cy) / dist->fy;
176
177 float theta = 0.0;
178 float sin_theta = 0.0;
179 float cos_theta = 1.0;
180 float thetad = sqrt(mx * mx + my * my);
181 float scaling = 1.0;
182 float d_func_d_theta = 0.0;
183
184 if (thetad > SQRT_EPSILON) {
185 theta = kb4_solve_theta(dist, &thetad, &d_func_d_theta);
186
187 sin_theta = sin(theta);
188 cos_theta = cos(theta);
189 scaling = sin_theta / thetad;
190 }
191
192 *out_x = mx * scaling;
193 *out_y = my * scaling;
194 *out_z = cos_theta;
195
196 //!@todo I'm not 100% sure if kb4 is always non-injective. basalt-headers always returns true here, so it might
197 //! be wrong too.
198 return true;
199}
200
201static inline void
202kb4_undistort(const struct t_camera_model_params *dist, const float x, const float y, float *out_x, float *out_y)
203{
204 float xp, yp, zp;
205
206 kb4_unproject(dist, x, y, &xp, &yp, &zp);
207
208 *out_x = xp / zp;
209 *out_y = yp / zp;
210}
211
212/*
213 * Functions for radial-tangential (un)projections
214 */
215
216static inline bool
217rt8_project(const struct t_camera_model_params *dist, //
218 const float x, //
219 const float y, //
220 const float z, //
221 float *out_x, //
222 float *out_y)
223{
224 const float xp = x / z;
225 const float yp = y / z;
226 const float rp2 = xp * xp + yp * yp;
227 const float cdist = (1.0f + rp2 * (dist->rt8.k1 + rp2 * (dist->rt8.k2 + rp2 * dist->rt8.k3))) /
228 (1.0f + rp2 * (dist->rt8.k4 + rp2 * (dist->rt8.k5 + rp2 * dist->rt8.k6)));
229 const float deltaX = 2.0f * dist->rt8.p1 * xp * yp + dist->rt8.p2 * (rp2 + 2.0f * xp * xp);
230 const float deltaY = 2.0f * dist->rt8.p2 * xp * yp + dist->rt8.p1 * (rp2 + 2.0f * yp * yp);
231 const float xpp = xp * cdist + deltaX;
232 const float ypp = yp * cdist + deltaY;
233 const float u = dist->fx * xpp + dist->cx;
234 const float v = dist->fy * ypp + dist->cy;
235
236 *out_x = u;
237 *out_y = v;
238
239 const float rpmax = dist->rt8.metric_radius;
240 bool positive_z = z >= SQRT_EPSILON; // Sophus::Constants<Scalar>::epsilonSqrt();
241 bool in_injective_area = rpmax == 0.0f ? true : rp2 <= rpmax * rpmax;
242 bool is_valid = positive_z && in_injective_area;
243 return is_valid;
244}
245
246static inline void
247rt8_distort(const struct t_camera_model_params *params,
248 const struct xrt_vec2 *undist,
249 struct xrt_vec2 *dist,
250 struct xrt_matrix_2x2 *d_dist_d_undist)
251{
252 const float k1 = params->rt8.k1;
253 const float k2 = params->rt8.k2;
254 const float p1 = params->rt8.p1;
255 const float p2 = params->rt8.p2;
256 const float k3 = params->rt8.k3;
257 const float k4 = params->rt8.k4;
258 const float k5 = params->rt8.k5;
259 const float k6 = params->rt8.k6;
260
261 const float xp = undist->x;
262 const float yp = undist->y;
263 const float rp2 = xp * xp + yp * yp;
264 const float cdist = (1.0f + rp2 * (k1 + rp2 * (k2 + rp2 * k3))) / (1.0f + rp2 * (k4 + rp2 * (k5 + rp2 * k6)));
265 const float deltaX = 2.0f * p1 * xp * yp + p2 * (rp2 + 2.0f * xp * xp);
266 const float deltaY = 2.0f * p2 * xp * yp + p1 * (rp2 + 2.0f * yp * yp);
267 const float xpp = xp * cdist + deltaX;
268 const float ypp = yp * cdist + deltaY;
269 dist->x = xpp;
270 dist->y = ypp;
271
272 // Jacobian part!
273 // Expressions derived with sympy
274 const float v0 = xp * xp;
275 const float v1 = yp * yp;
276 const float v2 = v0 + v1;
277 const float v3 = k6 * v2;
278 const float v4 = k4 + v2 * (k5 + v3);
279 const float v5 = v2 * v4 + 1.0f;
280 const float v6 = v5 * v5;
281 const float v7 = 1.0f / v6;
282 const float v8 = p1 * yp;
283 const float v9 = p2 * xp;
284 const float v10 = 2.0f * v6;
285 const float v11 = k3 * v2;
286 const float v12 = k1 + v2 * (k2 + v11);
287 const float v13 = v12 * v2 + 1.0f;
288 const float v14 = v13 * (v2 * (k5 + 2.0f * v3) + v4);
289 const float v15 = 2.0f * v14;
290 const float v16 = v12 + v2 * (k2 + 2.0f * v11);
291 const float v17 = 2.0f * v16;
292 const float v18 = xp * yp;
293 const float v19 = 2.0f * v7 * (-v14 * v18 + v16 * v18 * v5 + v6 * (p1 * xp + p2 * yp));
294
295 const float dxpp_dxp = v7 * (-v0 * v15 + v10 * (v8 + 3.0f * v9) + v5 * (v0 * v17 + v13));
296 const float dxpp_dyp = v19;
297 const float dypp_dxp = v19;
298 const float dypp_dyp = v7 * (-v1 * v15 + v10 * (3.0f * v8 + v9) + v5 * (v1 * v17 + v13));
299
300 d_dist_d_undist->v[0] = dxpp_dxp;
301 d_dist_d_undist->v[1] = dxpp_dyp;
302 d_dist_d_undist->v[2] = dypp_dxp;
303 d_dist_d_undist->v[3] = dypp_dyp;
304}
305
306static inline void
307rt8_undistort(const struct t_camera_model_params *hg_dist, const float u, const float v, float *out_x, float *out_y)
308{
309 const float x0 = (u - hg_dist->cx) / hg_dist->fx;
310 const float y0 = (v - hg_dist->cy) / hg_dist->fy;
311
312 //! @todo Decide if besides rpmax, it could be useful to have an rppmax
313 //! field. A good starting point to having this would be using the sqrt of
314 //! the max rpp2 value computed in the optimization of `computeRpmax()`.
315
316 // Newton solver
317 struct xrt_vec2 dist = {x0, y0};
318 struct xrt_vec2 undist = dist;
319
320 const int N = 5; // Max iterations
321 for (int i = 0; i < N; i++) {
322 struct xrt_matrix_2x2 J;
323 struct xrt_vec2 fundist;
324
325 rt8_distort(hg_dist, &undist, &fundist, &J);
326 struct xrt_vec2 residual = m_vec2_sub(fundist, dist);
327
328 // fundist - dist;
329 struct xrt_matrix_2x2 J_inverse;
330
331 m_mat2x2_invert(&J, &J_inverse);
332
333 struct xrt_vec2 undist_sub;
334
335 m_mat2x2_transform_vec2(&J_inverse, &residual, &undist_sub);
336
337 undist = m_vec2_sub(undist, undist_sub);
338 if (m_vec2_len(residual) < SQRT_EPSILON) {
339 break;
340 }
341 }
342 *out_x = undist.x;
343 *out_y = undist.y;
344}
345
346static inline bool
347rt8_unproject(
348 const struct t_camera_model_params *hg_dist, const float u, const float v, float *out_x, float *out_y, float *out_z)
349{
350 float xp, yp;
351
352 rt8_undistort(hg_dist, u, v, &xp, &yp);
353
354 const float norm_inv = 1.0f / sqrt(xp * xp + yp * yp + 1.0f);
355 *out_x = xp * norm_inv;
356 *out_y = yp * norm_inv;
357 *out_z = norm_inv;
358
359 const float rp2 = xp * xp + yp * yp;
360 bool in_injective_area =
361 hg_dist->rt8.metric_radius == 0.0f ? true : rp2 <= hg_dist->rt8.metric_radius * hg_dist->rt8.metric_radius;
362 bool is_valid = in_injective_area;
363
364 return is_valid;
365}
366
367#if 0
368static inline bool
369zero_distortion_pinhole_project(const struct t_camera_model_params *dist, //
370 const float x, //
371 const float y, //
372 const float z, //
373 float *out_x, //
374 float *out_y)
375{
376 *out_x = ((dist->fx * x / z) + dist->cx);
377 *out_y = ((dist->fy * y / z) + dist->cy);
378
379 bool is_valid = z >= SQRT_EPSILON;
380 return is_valid;
381}
382static inline bool
383zero_distortion_pinhole_unproject(const struct t_camera_model_params *dist, //
384 const float x, //
385 const float y, //
386 float *out_x, //
387 float *out_y, //
388 float *out_z)
389{
390 const float mx = (x - dist->cx) / dist->fx;
391 const float my = (y - dist->cy) / dist->fy;
392
393 const float r2 = mx * mx + my * my;
394
395 const float norm = sqrtf(1.0f + r2);
396
397 const float norm_inv = 1.0f / norm;
398
399 *out_x = mx * norm_inv;
400 *out_y = my * norm_inv;
401 *out_z = norm_inv;
402
403 // Pinhole unprojection is always valid :)
404 return true;
405}
406#endif
407
408/*
409 * Misc functions.
410 */
411
412static inline void
413interpret_as_rt8(const struct t_camera_calibration *cc, struct t_camera_model_params *out_params)
414{
415 // Make a temporary buffer that definitely has zeros in it.
416 double distortion_tmp[XRT_DISTORTION_MAX_DIM] = {0};
417
419 U_LOG_W("Reinterpreting %s distortion as %s", t_stringify_camera_distortion_model(cc->distortion_model),
421 }
422
424
425 for (size_t i = 0; i < dist_num; i++) {
426 // Copy only the valid values over. The high indices will be zero, which means that rt4 and rt5
427 // calibrations will work correctly.
428 distortion_tmp[i] = cc->distortion_parameters_as_array[i];
429 }
430
431 int acc_idx = 0;
432 out_params->rt8.k1 = distortion_tmp[acc_idx++];
433 out_params->rt8.k2 = distortion_tmp[acc_idx++];
434 out_params->rt8.p1 = distortion_tmp[acc_idx++];
435 out_params->rt8.p2 = distortion_tmp[acc_idx++];
436 out_params->rt8.k3 = distortion_tmp[acc_idx++];
437 out_params->rt8.k4 = distortion_tmp[acc_idx++];
438 out_params->rt8.k5 = distortion_tmp[acc_idx++];
439 out_params->rt8.k6 = distortion_tmp[acc_idx++];
440
441
442
444 out_params->rt8.metric_radius = cc->wmr.rpmax;
445 } else {
446 out_params->rt8.metric_radius = 0;
447 }
448 out_params->model = T_DISTORTION_OPENCV_RADTAN_8;
449}
450
451/*
452 * "Exported" functions.
453 */
454
455/*!
456 * Takes a @ref t_camera_calibration through \p cc, and returns a @ref t_camera_model_params that shadows \p cc
457 * 's parameters through \p out_params
458 */
459static inline void
461 struct t_camera_model_params *out_params)
462{
463 // Paranoia.
464 U_ZERO(out_params);
465
466 // First row, first column.
467 out_params->fx = (float)cc->intrinsics[0][0];
468 // Second row, second column.
469 out_params->fy = (float)cc->intrinsics[1][1];
470 // First row, third column.
471 out_params->cx = (float)cc->intrinsics[0][2];
472 // Second row, third column.
473 out_params->cy = (float)cc->intrinsics[1][2];
474
475 out_params->model = cc->distortion_model;
476
477
478
479 switch (cc->distortion_model) {
481 out_params->fisheye.k1 = (float)cc->kb4.k1;
482 out_params->fisheye.k2 = (float)cc->kb4.k2;
483 out_params->fisheye.k3 = (float)cc->kb4.k3;
484 out_params->fisheye.k4 = (float)cc->kb4.k4;
485 } break;
489 case T_DISTORTION_WMR: interpret_as_rt8(cc, out_params); break;
490 default:
491 U_LOG_E("t_camera_un_projections doesn't support camera model %s yet!",
493 break;
494 }
495}
496
497/*!
498 * Takes a 2D image-space point through \p x and \p y, unprojects it to a normalized 3D direction, and returns
499 * the result through \p out_x, \p out_y and \p out_z
500 */
501static inline bool
503 const struct t_camera_model_params *dist, const float x, const float y, float *out_x, float *out_y, float *out_z)
504{
505 switch (dist->model) {
507 return rt8_unproject(dist, x, y, out_x, out_y, out_z);
508 }; break;
510 return kb4_unproject(dist, x, y, out_x, out_y, out_z);
511 }; break;
512 // Return false so we don't get warnings on Release builds.
513 default: assert(false); return false;
514 }
515}
516
517/*!
518 * Takes a 2D image-space point through \p x and \p y, unprojects it to a normalized 3D direction, flips its Y
519 * and Z values (performing a coordinate space transform from +Z forward -Y up to -Z forward +Y up) and returns the
520 * result through \p out_x, \p out_y and \p out_z
521 */
522static inline bool
524 const struct t_camera_model_params *dist, const float x, const float y, float *out_x, float *out_y, float *out_z)
525{
526 bool ret = t_camera_models_unproject(dist, x, y, out_x, out_y, out_z);
527
528 *out_z *= -1;
529 *out_y *= -1;
530 return ret;
531}
532
533/*!
534 * Takes a distorted 2D point through \p x and \p y and computes the undistorted point into
535 * \p out_x and \p out_y
536 */
537static inline void
539 const struct t_camera_model_params *dist, const float x, const float y, float *out_x, float *out_y)
540{
541 switch (dist->model) {
543 rt8_undistort(dist, x, y, out_x, out_y);
544 }; break;
546 kb4_undistort(dist, x, y, out_x, out_y);
547 }; break;
548 // Return false so we don't get warnings on Release builds.
549 default: assert(false);
550 }
551}
552
553
554/*!
555 * Takes a 3D point through \p x, \p y, and \p z, projects it into image space, and returns the result
556 * through \p out_x and \p out_y
557 */
558static inline bool
560 const float x, //
561 const float y, //
562 const float z, //
563 float *out_x, //
564 float *out_y)
565{
566 switch (dist->model) {
568 return rt8_project(dist, x, y, z, out_x, out_y);
569 }; break;
571 return kb4_project(dist, x, y, z, out_x, out_y);
572 }; break;
573 // Return false so we don't get warnings on Release builds.
574 default: assert(false); return false;
575 }
576}
577
578/*!
579 * Takes a 3D point through \p x, \p y, and \p z, flips its Y and Z values (performing a coordinate space
580 * transform from -Z forward +Y up to +Z forward -Y up), projects it into image space, and returns the result through
581 * \p out_x and \p out_y
582 */
583static inline bool
585 const float x, //
586 const float y, //
587 const float z, //
588 float *out_x, //
589 float *out_y)
590{
591 float _y = y * -1;
592 float _z = z * -1;
593
594 return t_camera_models_project(dist, x, _y, _z, out_x, out_y);
595}
596
597#ifdef __cplusplus
598}
599#endif
#define U_LOG_E(...)
Log a message at U_LOGGING_ERROR level, conditional on the global log level.
Definition: u_logging.h:442
#define U_LOG_W(...)
Log a message at U_LOGGING_WARN level, conditional on the global log level.
Definition: u_logging.h:439
static const char * t_stringify_camera_distortion_model(const enum t_camera_distortion_model model)
Stringifies a t_camera_distortion_model.
Definition: t_tracking.h:141
#define XRT_DISTORTION_MAX_DIM
Maximum size of rectilinear distortion coefficient array.
Definition: t_tracking.h:54
static size_t t_num_params_from_distortion_model(const enum t_camera_distortion_model model)
Returns the number of parameters needed for this t_camera_distortion_model to be held by an OpenCV Ma...
Definition: t_tracking.h:161
t_camera_distortion_model
The distortion model this camera calibration falls under.
Definition: t_tracking.h:63
@ T_DISTORTION_OPENCV_RADTAN_8
OpenCV's radial-tangential distortion model.
Definition: t_tracking.h:82
@ T_DISTORTION_OPENCV_RADTAN_5
OpenCV's radial-tangential distortion model.
Definition: t_tracking.h:73
@ T_DISTORTION_FISHEYE_KB4
Juho Kannalla and Sami Sebastian Brandt's fisheye distortion model.
Definition: t_tracking.h:116
@ T_DISTORTION_WMR
Windows Mixed Reality headsets' camera model.
Definition: t_tracking.h:131
@ T_DISTORTION_OPENCV_RADTAN_14
OpenCV's radial-tangential distortion model.
Definition: t_tracking.h:101
#define U_ZERO(PTR)
Zeroes the correct amount of memory based on the type pointed-to by the argument.
Definition: u_misc.h:68
Wrapper header for <math.h> to ensure pi-related math constants are defined.
C matrix_2x2 math library.
C vec2 math library.
Floating point parameters for T_DISTORTION_FISHEYE_KB4.
Definition: t_camera_models.h:38
Floating point parameters for T_DISTORTION_OPENCV_RADTAN_8, also including metric_radius.
Definition: t_camera_models.h:48
Essential calibration data for a single camera, or single lens/sensor of a stereo camera.
Definition: t_tracking.h:236
double intrinsics[3][3]
Camera intrinsics matrix.
Definition: t_tracking.h:241
enum t_camera_distortion_model distortion_model
Distortion model that this camera uses.
Definition: t_tracking.h:254
Floating point calibration data for a single calibrated camera.
Definition: t_camera_models.h:59
A tightly packed 2x2 matrix of floats.
Definition: xrt_defines.h:526
A 2 element vector with single floats.
Definition: xrt_defines.h:268
static bool t_camera_models_flip_and_project(const struct t_camera_model_params *dist, const float x, const float y, const float z, float *out_x, float *out_y)
Takes a 3D point through x, y, and z, flips its Y and Z values (performing a coordinate space transfo...
Definition: t_camera_models.h:584
static bool t_camera_models_project(const struct t_camera_model_params *dist, const float x, const float y, const float z, float *out_x, float *out_y)
Takes a 3D point through x, y, and z, projects it into image space, and returns the result through ou...
Definition: t_camera_models.h:559
static bool t_camera_models_unproject(const struct t_camera_model_params *dist, const float x, const float y, float *out_x, float *out_y, float *out_z)
Takes a 2D image-space point through x and y, unprojects it to a normalized 3D direction,...
Definition: t_camera_models.h:502
static void t_camera_models_undistort(const struct t_camera_model_params *dist, const float x, const float y, float *out_x, float *out_y)
Takes a distorted 2D point through x and y and computes the undistorted point into out_x and out_y.
Definition: t_camera_models.h:538
static void rt8_undistort(const struct t_camera_model_params *hg_dist, const float u, const float v, float *out_x, float *out_y)
Definition: t_camera_models.h:307
static void t_camera_model_params_from_t_camera_calibration(const struct t_camera_calibration *cc, struct t_camera_model_params *out_params)
Takes a t_camera_calibration through cc, and returns a t_camera_model_params that shadows cc 's param...
Definition: t_camera_models.h:460
static bool t_camera_models_unproject_and_flip(const struct t_camera_model_params *dist, const float x, const float y, float *out_x, float *out_y, float *out_z)
Takes a 2D image-space point through x and y, unprojects it to a normalized 3D direction,...
Definition: t_camera_models.h:523
static bool kb4_unproject(const struct t_camera_model_params *dist, const float x, const float y, float *out_x, float *out_y, float *out_z)
Definition: t_camera_models.h:167
Tracking API interface.