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