The open source OpenXR runtime
at main 343 lines 10 kB view raw
1/* 2 * Copyright 2022 Jan Schmidt 3 * SPDX-License-Identifier: BSL-1.0 4 * 5 */ 6/*! 7 * @file 8 * @brief Driver code for Oculus Rift S headsets 9 * 10 * Utility functions for generating a stereo camera calibration, 11 * and converting the Rift S Fisheye62 distortion parameters into 12 * OpenCV-compatible Kannala-Brandt parameters 13 * 14 * @author Jan Schmidt <jan@centricular.com> 15 * @ingroup drv_rift_s 16 */ 17#include <stdbool.h> 18#include <math.h> 19 20#include "tinyceres/tiny_solver.hpp" 21#include "tinyceres/tiny_solver_autodiff_function.hpp" 22 23#include "rift_s_util.h" 24 25using ceres::TinySolver; 26using ceres::TinySolverAutoDiffFunction; 27 28const int N_KB4_DISTORT_PARAMS = 4; 29 30template <typename T> 31bool 32fisheye62_undistort_func(struct t_camera_calibration *calib, 33 const double *distortion_params, 34 const T point[2], 35 T *out_point) 36{ 37 const T x = point[0]; 38 const T y = point[1]; 39 40 const T r2 = x * x + y * y; 41 const T r = sqrt(r2); 42 43 const double fx = calib->intrinsics[0][0]; 44 const double fy = calib->intrinsics[1][1]; 45 46 const double cx = calib->intrinsics[0][2]; 47 const double cy = calib->intrinsics[1][2]; 48 49 if (r < 1e-8) { 50 out_point[0] = fx * x + cx; 51 out_point[1] = fy * y + cy; 52 return true; 53 } 54 55 const T theta = atan(r); 56 const T theta2 = theta * theta; 57 58 const T xp = x * theta / r; 59 const T yp = y * theta / r; 60 61 const double k1 = distortion_params[0]; 62 const double k2 = distortion_params[1]; 63 const double k3 = distortion_params[2]; 64 const double k4 = distortion_params[3]; 65 const double k5 = distortion_params[4]; 66 const double k6 = distortion_params[5]; 67 const double p1 = distortion_params[6]; 68 const double p2 = distortion_params[7]; 69 70 /* 1 + k1 * theta^2 + k2 * theta^4 + k3 * theta^6 + k4 * theta^8 + k5 * theta^10 + k6 * theta^12 */ 71 T r_theta = k6 * theta2; 72 r_theta += k5; 73 r_theta *= theta2; 74 r_theta += k4; 75 r_theta *= theta2; 76 r_theta += k3; 77 r_theta *= theta2; 78 r_theta += k2; 79 r_theta *= theta2; 80 r_theta += k1; 81 r_theta *= theta2; 82 r_theta += 1; 83 84 T delta_x = 2 * p1 * xp * yp + p2 * (theta2 + xp * xp * 2.0); 85 T delta_y = 2 * p2 * xp * yp + p1 * (theta2 + yp * yp * 2.0); 86 87 const T mx = xp * r_theta + delta_x; 88 const T my = yp * r_theta + delta_y; 89 90 out_point[0] = fx * mx + cx; 91 out_point[1] = fy * my + cy; 92 93 return true; 94} 95 96struct UndistortCostFunctor 97{ 98 UndistortCostFunctor(struct t_camera_calibration *calib, double *distortion_params, double point[2]) 99 : m_calib(calib), m_distortion_params(distortion_params) 100 { 101 m_point[0] = point[0]; 102 m_point[1] = point[1]; 103 } 104 105 struct t_camera_calibration *m_calib; 106 double *m_distortion_params; 107 double m_point[2]; 108 109 template <typename T> 110 bool 111 operator()(const T *const x, T *residual) const 112 { 113 T out_point[2]; 114 115 if (!fisheye62_undistort_func(m_calib, m_distortion_params, x, out_point)) 116 return false; 117 118 residual[0] = out_point[0] - m_point[0]; 119 residual[1] = out_point[1] - m_point[1]; 120 return true; 121 } 122}; 123 124template <typename T> 125bool 126kb4_distort_func(struct t_camera_calibration *calib, const T *distortion_params, const double point[2], T *out_point) 127{ 128 const double x = point[0]; 129 const double y = point[1]; 130 131 const double r2 = x * x + y * y; 132 const double r = sqrt(r2); 133 134 const double fx = calib->intrinsics[0][0]; 135 const double fy = calib->intrinsics[1][1]; 136 137 const double cx = calib->intrinsics[0][2]; 138 const double cy = calib->intrinsics[1][2]; 139 140 if (r < 1e-8) { 141 out_point[0] = T(fx * x + cx); 142 out_point[1] = T(fy * y + cy); 143 return true; 144 } 145 146 const double theta = atan(r); 147 const double theta2 = theta * theta; 148 149 const T k1 = distortion_params[0]; 150 const T k2 = distortion_params[1]; 151 const T k3 = distortion_params[2]; 152 const T k4 = distortion_params[3]; 153 154 T r_theta = k4 * theta2; 155 156 r_theta += k3; 157 r_theta *= theta2; 158 r_theta += k2; 159 r_theta *= theta2; 160 r_theta += k1; 161 r_theta *= theta2; 162 r_theta += 1; 163 r_theta *= theta; 164 165 const T mx = x * r_theta / r; 166 const T my = y * r_theta / r; 167 168 out_point[0] = fx * mx + cx; 169 out_point[1] = fy * my + cy; 170 171 return true; 172} 173 174struct TargetPoint 175{ 176 double point[2]; 177 double distorted[2]; 178}; 179 180struct DistortParamKB4CostFunctor 181{ 182 DistortParamKB4CostFunctor(struct t_camera_calibration *calib, int nSteps, TargetPoint *targetPointGrid) 183 : m_calib(calib), m_nSteps(nSteps), m_targetPointGrid(targetPointGrid) 184 {} 185 186 struct t_camera_calibration *m_calib; 187 int m_nSteps; 188 TargetPoint *m_targetPointGrid; 189 190 template <typename T> 191 bool 192 operator()(const T *const distort_params, T *residual) const 193 { 194 T out_point[2]; 195 196 for (int y_index = 0; y_index < m_nSteps; y_index++) { 197 for (int x_index = 0; x_index < m_nSteps; x_index++) { 198 int residual_index = 2 * (y_index * m_nSteps + x_index); 199 TargetPoint *p = &m_targetPointGrid[(y_index * m_nSteps) + x_index]; 200 201 if (!kb4_distort_func<T>(m_calib, distort_params, p->point, out_point)) 202 return false; 203 204 residual[residual_index + 0] = out_point[0] - p->distorted[0]; 205 residual[residual_index + 1] = out_point[1] - p->distorted[1]; 206 } 207 } 208 209 return true; 210 } 211}; 212 213#define STEPS 21 214struct t_camera_calibration 215rift_s_get_cam_calib(struct rift_s_camera_calibration_block *camera_calibration, enum rift_s_camera_id cam_id) 216{ 217 struct t_camera_calibration tcc; 218 219 struct rift_s_camera_calibration *rift_s_cam = &camera_calibration->cameras[cam_id]; 220 tcc.image_size_pixels.h = rift_s_cam->roi.extent.h; 221 tcc.image_size_pixels.w = rift_s_cam->roi.extent.w; 222 tcc.intrinsics[0][0] = rift_s_cam->projection.fx; 223 tcc.intrinsics[1][1] = rift_s_cam->projection.fy; 224 tcc.intrinsics[0][2] = rift_s_cam->projection.cx; 225 tcc.intrinsics[1][2] = rift_s_cam->projection.cy; 226 tcc.intrinsics[2][2] = 1.0; 227 tcc.distortion_model = T_DISTORTION_FISHEYE_KB4; 228 229 TargetPoint xy[STEPS * STEPS]; 230 231 /* Convert fisheye62 params to KB4: */ 232 double fisheye62_distort_params[8]; 233 for (int i = 0; i < 6; i++) { 234 fisheye62_distort_params[i] = rift_s_cam->distortion.k[i]; 235 } 236 fisheye62_distort_params[6] = rift_s_cam->distortion.p1; 237 fisheye62_distort_params[7] = rift_s_cam->distortion.p2; 238 239 /* Calculate Fisheye62 distortion grid by finding the viewplane coordinates that 240 * project onto the points of grid spaced across the pixel image plane */ 241 for (int y_index = 0; y_index < STEPS; y_index++) { 242 for (int x_index = 0; x_index < STEPS; x_index++) { 243 int x = x_index * (tcc.image_size_pixels.w - 1) / (STEPS - 1); 244 int y = y_index * (tcc.image_size_pixels.h - 1) / (STEPS - 1); 245 TargetPoint *p = &xy[(y_index * STEPS) + x_index]; 246 247 p->distorted[0] = x; 248 p->distorted[1] = y; 249 250 Eigen::Matrix<double, 2, 1> result(0, 0); 251 252 using AutoDiffUndistortFunction = TinySolverAutoDiffFunction<UndistortCostFunctor, 2, 2>; 253 UndistortCostFunctor undistort_functor(&tcc, fisheye62_distort_params, p->distorted); 254 AutoDiffUndistortFunction f(undistort_functor); 255 256 TinySolver<AutoDiffUndistortFunction> solver; 257 solver.Solve(f, &result); 258 259 p->point[0] = result[0]; 260 p->point[1] = result[1]; 261 } 262 } 263 264 /* Use the calculated distortion grid to solve for kb4 params */ 265 { 266 Eigen::Matrix<double, N_KB4_DISTORT_PARAMS, 1> kb4_distort_params{0, 0, 0, 0}; 267 268 using AutoDiffDistortParamKB4Function = 269 TinySolverAutoDiffFunction<DistortParamKB4CostFunctor, 2 * STEPS * STEPS, N_KB4_DISTORT_PARAMS>; 270 DistortParamKB4CostFunctor distort_param_kb4_functor(&tcc, STEPS, xy); 271 AutoDiffDistortParamKB4Function f(distort_param_kb4_functor); 272 273 TinySolver<AutoDiffDistortParamKB4Function> solver; 274 solver.Solve(f, &kb4_distort_params); 275 276 tcc.kb4.k1 = kb4_distort_params[0]; 277 tcc.kb4.k2 = kb4_distort_params[1]; 278 tcc.kb4.k3 = kb4_distort_params[2]; 279 tcc.kb4.k4 = kb4_distort_params[3]; 280 281 return tcc; 282 } 283} 284 285/*! 286 * Allocate and populate an OpenCV-compatible @ref t_stereo_camera_calibration pointer from 287 * the Rift S config. 288 * 289 * This requires fitting a KB4 fisheye polynomial to the 6 radial + 2 tangential 'Fisheye62' 290 * parameters provided by the Rift S. 291 * 292 */ 293struct t_stereo_camera_calibration * 294rift_s_create_stereo_camera_calib_rotated(struct rift_s_camera_calibration_block *camera_calibration) 295{ 296 struct t_stereo_camera_calibration *calib = NULL; 297 t_stereo_camera_calibration_alloc(&calib, T_DISTORTION_FISHEYE_KB4); 298 299 struct rift_s_camera_calibration *left = &camera_calibration->cameras[RIFT_S_CAMERA_FRONT_LEFT]; 300 struct rift_s_camera_calibration *right = &camera_calibration->cameras[RIFT_S_CAMERA_FRONT_RIGHT]; 301 302 // Intrinsics 303 for (int view = 0; view < 2; view++) { 304 enum rift_s_camera_id cam_id = view == 0 ? RIFT_S_CAMERA_FRONT_LEFT : RIFT_S_CAMERA_FRONT_RIGHT; 305 calib->view[view] = rift_s_get_cam_calib(camera_calibration, cam_id); 306 } 307 308 struct xrt_pose device_from_left, device_from_right; 309 struct xrt_pose right_from_device, right_from_left; 310 311 struct xrt_matrix_3x3 right_from_left_rot; 312 313 /* Compute the transform from the left eye to the right eye 314 * by using the config provided camera->device transform 315 */ 316 math_pose_from_isometry(&left->device_from_camera, &device_from_left); 317 math_pose_from_isometry(&right->device_from_camera, &device_from_right); 318 319 math_pose_invert(&device_from_right, &right_from_device); 320 math_pose_transform(&device_from_left, &right_from_device, &right_from_left); 321 322 math_matrix_3x3_from_quat(&right_from_left.orientation, &right_from_left_rot); 323 324 /* Rotate the position in the camera extrinsics 90° to 325 * compensate for the front cams being rotated. That means hand poses 326 * are detected and come out rotated too, so need correcting 327 * in the tracking override offset */ 328 calib->camera_translation[0] = -right_from_left.position.y; 329 calib->camera_translation[1] = right_from_left.position.x; 330 calib->camera_translation[2] = right_from_left.position.z; 331 332 calib->camera_rotation[0][0] = right_from_left_rot.v[0]; 333 calib->camera_rotation[0][1] = right_from_left_rot.v[1]; 334 calib->camera_rotation[0][2] = right_from_left_rot.v[2]; 335 calib->camera_rotation[1][0] = right_from_left_rot.v[3]; 336 calib->camera_rotation[1][1] = right_from_left_rot.v[4]; 337 calib->camera_rotation[1][2] = right_from_left_rot.v[5]; 338 calib->camera_rotation[2][0] = right_from_left_rot.v[6]; 339 calib->camera_rotation[2][1] = right_from_left_rot.v[7]; 340 calib->camera_rotation[2][2] = right_from_left_rot.v[8]; 341 342 return calib; 343}