11#include " PositionSolver.h"
22
3+ #define USE_FOV // Use FOV correction for the camera matrix.
34
45PositionSolver::PositionSolver (
56 int width,
@@ -20,16 +21,16 @@ PositionSolver::PositionSolver(
2021{
2122 this ->prior_pitch = -1.57 ;
2223 this ->prior_yaw = -1.57 ;
23- this ->prior_distance = prior_distance * -2 .;
24+ this ->prior_distance = prior_distance * -2.0 ;
2425
2526 this ->rv [0 ] = this ->prior_pitch ;
2627 this ->rv [1 ] = this ->prior_yaw ;
2728 this ->rv [2 ] = -1.57 ;
2829 this ->tv [2 ] = this ->prior_distance ;
2930
3031 head3dScale = (cv::Mat_<double >(3 , 3 ) <<
31- x_scale , 0.0 , 0 ,
32- 0.0 , y_scale , 0 ,
32+ y_scale , 0.0 , 0 , // pitch is rv[0], pitch involves y-axis
33+ 0.0 , x_scale , 0 , // yaw is rv[1], yaw involves x-axis
3334 0.0 , 0.0 , z_scale
3435 );
3536
@@ -63,9 +64,9 @@ PositionSolver::PositionSolver(
6364 {
6465 contour_indices = { 0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 ,13 ,14 ,15 ,27 ,28 ,29 ,30 ,31 ,32 ,33 ,34 ,35 ,36 ,39 ,42 ,45 };
6566
66- landmark_points_buffer = cv::Mat (contour_indices.size (), 1 , CV_32FC2);
67+ landmark_points_buffer = cv::Mat (( int ) contour_indices.size (), 1 , CV_32FC2);
6768
68- mat3dcontour = (cv::Mat_<double >(contour_indices.size (), 3 ) <<
69+ mat3dcontour = (cv::Mat_<double >(( int ) contour_indices.size (), 3 ) <<
6970 0.45517698 , -0.30089578 , 0.76442945 ,
7071 0.44899884 , -0.16699584 , 0.76514298 ,
7172 0.43743154 , -0.02265548 , 0.73926717 ,
@@ -102,15 +103,44 @@ PositionSolver::PositionSolver(
102103 // https://github.com/opentrack/opentrack/blob/3cc3ef246ad71c463c8952bcc96984b25d85b516/tracker-aruco/ftnoir_tracker_aruco.cpp#L193
103104 // Taking into account the camera FOV instead of assuming raw image dims is more clever and
104105 // will make the solver more camera-agnostic.
105- float diag_fov = fov * TO_RAD;
106+ float diag_fov = ( float )( fov * TO_RAD) ;
106107
107108 // Get expressed in sensor-size units
108109
110+ #ifdef USE_FOV
111+ // field of view is a rectangular viewport with corners on a circular lens
112+ // the diagonal of the rectangle can be expressed as the angular field of view or pixels
113+ // the width of the rectangle can be expressed as either the field of view width or width in pixels
114+ // the height of the rectangle can be expressed as either the field of view height or height in pixes
115+ double width_squared = (double )width * width;
116+ double height_squared = (double )height * height;
117+ double diagonal_squared = width_squared + height_squared;
118+ double diagonal = sqrt (diagonal_squared); // hypotenuse of triangle
119+
120+ // set default focalLength for width and heigh if field of view is not set
121+ double focalLength_width = width;
122+ double focalLength_height = height;
123+ if (fov != 0.0 )
124+ {
125+ double fov_w = (double )diag_fov * width / diagonal;
126+ double fov_h = (double )diag_fov * height / diagonal;
127+
128+ focalLength_width = 0.5 * width / tan (0.5 * fov_w);
129+ focalLength_height = 0.5 * height / tan (0.5 * fov_h);
130+ }
131+
132+ camera_matrix = (cv::Mat_<double >(3 , 3 ) <<
133+ focalLength_height, 0 , height / 2 ,
134+ 0 , focalLength_width, width / 2 ,
135+ 0 , 0 , 1
136+ );
137+
138+ #else
109139 double fov_w = 2 . * atan (tan (diag_fov / 2 .) / sqrt (1 . + height / (double )width * height / (double )width));
110140 double fov_h = 2 . * atan (tan (diag_fov / 2 .) / sqrt (1 . + width / (double )height * width / (double )height));
111141
112- float i_height = . 5 * height / (tan (. 5 * fov_w));
113- float i_width = . 5 * width / (tan (. 5 * fov_h));
142+ float i_height = ( float )( 0 . 5f * height / (tan (0.5 * fov_w) ));
143+ float i_width = ( float )( 0 . 5f * width / (tan (0.5 * fov_h) ));
114144
115145 /* camera_matrix = (cv::Mat_<double>(3, 3) <<
116146 height, 0, height / 2,
@@ -122,7 +152,10 @@ PositionSolver::PositionSolver(
122152 i_width, 0 , height / 2 ,
123153 0 , i_height, width / 2 ,
124154 0 , 0 , 1
125- );
155+ );
156+
157+
158+ #endif
126159
127160 camera_distortion = (cv::Mat_<double >(4 , 1 ) << 0 , 0 , 0 , 0 );
128161
@@ -131,6 +164,7 @@ PositionSolver::PositionSolver(
131164 if (complex ) std::cout << " Using complex solver" << std::endl;
132165}
133166
167+
134168void PositionSolver::solve_rotation (FaceData* face_data)
135169{
136170 int contour_idx = 0 ;
@@ -139,8 +173,7 @@ void PositionSolver::solve_rotation(FaceData* face_data)
139173 for (int i = 0 ; i < contour_indices.size (); i++)
140174 {
141175 contour_idx = contour_indices[i];
142- landmark_points_buffer.at <float >(i, j) = (int )face_data->landmark_coords [2 * contour_idx + j];
143-
176+ landmark_points_buffer.at <float >(i, j) = (float )(int )face_data->landmark_coords [2 * contour_idx + j]; // fix complation warnings.
144177 }
145178 }
146179
@@ -164,15 +197,18 @@ void PositionSolver::solve_rotation(FaceData* face_data)
164197 for (int i = 0 ; i < 3 ; i++)
165198 {
166199 face_data->rotation [i] = rvec.at <double >(i, 0 );
167- face_data->translation [i] = tvec.at <double >(i, 0 ) * 10 ;
200+ face_data->translation [i] = tvec.at <double >(i, 0 ) * 10 ; // scale solvePnP coordinates to opentrack units in centimeters
168201 }
169202
170- // We dont want the Z axis oversaturated.
203+ // We dont want the Z axis oversaturated since opentrack has +/-600 centimeter range
171204 face_data->translation [2 ] /= 100 ;
172205
173- std::cout << face_data->to_string () << std::endl;
206+ #ifdef _DEBUG
207+ std::cout << face_data->to_string () << std::endl; // disable copy constructor and output to std::cout
208+ #endif
174209
175210 correct_rotation (*face_data);
211+ clip_rotations (*face_data);
176212
177213}
178214
@@ -218,14 +254,38 @@ void PositionSolver::get_euler(cv::Mat& rvec, cv::Mat& tvec)
218254
219255void PositionSolver::correct_rotation (FaceData& face_data)
220256{
221- float distance = -(face_data.translation [2 ]);
222- float lateral_offset = face_data.translation [1 ];
223- float verical_offset = face_data.translation [0 ];
257+ float distance = ( float ) -(face_data.translation [2 ]);
258+ float lateral_offset = ( float ) face_data.translation [1 ];
259+ float verical_offset = ( float ) face_data.translation [0 ];
224260
225- float correction_yaw = std::atan (std::tan ( lateral_offset / distance)) * TO_DEG;
226- float correction_pitch = std::atan (std::tan ( verical_offset / distance)) * TO_DEG;
261+ float correction_yaw = ( float )( std::atan (lateral_offset / distance) * TO_DEG); // (lateral_offset / distance) is already tangent, so only need atan to obtain radians
262+ float correction_pitch = ( float )( std::atan (verical_offset / distance) * TO_DEG); // (verical_offset / distance) is already tangent, so only need atan to obtain radians
227263
228264 face_data.rotation [1 ] += correction_yaw;
229265 face_data.rotation [0 ] += correction_pitch;
266+
267+ // Note: We could saturate pitch here, but its better to let the user do it via Opentrack.
268+ // The coefficient could be problematic for some users.
269+ // face_data.rotation[0] = face_data.rotation[0] * 1.5;
270+ }
271+
272+
273+ void PositionSolver::clip_rotations (FaceData& face_data)
274+ {
275+ // Limit yaw between -90.0 and +90.0 degrees
276+ if (face_data.rotation [1 ] >= 90.0 )
277+ face_data.rotation [1 ] = 90.0 ;
278+ else if (face_data.rotation [1 ] <= -90.0 )
279+ face_data.rotation [1 ] = -90.0 ;
280+ // Limit pitch between -90.0 and +90.0
281+ if (face_data.rotation [0 ] >= 90.0 )
282+ face_data.rotation [0 ] = 90.0 ;
283+ else if (face_data.rotation [0 ] <= -90.0 )
284+ face_data.rotation [0 ] = -90.0 ;
285+ // Limit roll between -90.0 and +90.0
286+ if (face_data.rotation [2 ] >= 90.0 )
287+ face_data.rotation [2 ] = 90.0 ;
288+ else if (face_data.rotation [2 ] <= -90.0 )
289+ face_data.rotation [2 ] = -90.0 ;
230290}
231291
0 commit comments