Skip to content

Commit 4df9cf9

Browse files
committed
free projection functions
1 parent 381ca06 commit 4df9cf9

File tree

4 files changed

+122
-0
lines changed

4 files changed

+122
-0
lines changed

vortex_utils/cpp_test/test_math.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,4 +459,62 @@ TEST(enu_ned_rotation, test_symmetry) {
459459
1e-12));
460460
}
461461

462+
TEST(project_point, test_project_point_basic) {
463+
Eigen::Matrix3d K;
464+
K << 300.0, 0.0, 150.0, 0.0, 400.0, 200.0, 0.0, 0.0, 1.0;
465+
466+
Eigen::Vector3d point(2.0, 4.0, 2.0);
467+
468+
Eigen::Vector2d pixel = project_point(K, point);
469+
470+
const double x_norm = 2.0 / 2.0;
471+
const double y_norm = 4.0 / 2.0;
472+
473+
EXPECT_NEAR(pixel.x(), 300.0 * x_norm + 150.0, 1e-12);
474+
EXPECT_NEAR(pixel.y(), 400.0 * y_norm + 200.0, 1e-12);
475+
}
476+
477+
TEST(project_point, test_project_point_with_skew) {
478+
Eigen::Matrix3d K;
479+
K << 300.0, 50.0, 100.0, 0.0, 300.0, 100.0, 0.0, 0.0, 1.0;
480+
481+
Eigen::Vector3d point(2.0, 1.0, 1.0);
482+
483+
Eigen::Vector2d pixel = project_point(K, point);
484+
485+
EXPECT_NEAR(pixel.x(), 300.0 * 2.0 + 50.0 * 1.0 + 100.0, 1e-12);
486+
EXPECT_NEAR(pixel.y(), 300.0 * 1.0 + 100.0, 1e-12);
487+
}
488+
489+
TEST(backproject_ray, test_backproject_ray_explicit_values) {
490+
Eigen::Matrix3d K_inv;
491+
K_inv << 1.0 / 200.0, -20.0 / (200.0 * 400.0),
492+
(20.0 * 50.0 - 100.0 * 400.0) / (200.0 * 400.0), 0.0, 1.0 / 400.0,
493+
-50.0 / 400.0, 0.0, 0.0, 1.0;
494+
495+
Eigen::Vector2d pixel(300.0, 250.0);
496+
497+
Eigen::Vector3d ray = backproject_ray(K_inv, pixel);
498+
499+
EXPECT_NEAR(ray.x(), 0.95, 1e-12);
500+
EXPECT_NEAR(ray.y(), 0.5, 1e-12);
501+
EXPECT_NEAR(ray.z(), 1.0, 1e-12);
502+
}
503+
504+
TEST(backproject_point, test_backproject_point_explicit_values) {
505+
Eigen::Matrix3d K_inv;
506+
K_inv << 1.0 / 200.0, -20.0 / (200.0 * 400.0),
507+
(20.0 * 50.0 - 100.0 * 400.0) / (200.0 * 400.0), 0.0, 1.0 / 400.0,
508+
-50.0 / 400.0, 0.0, 0.0, 1.0;
509+
510+
Eigen::Vector2d pixel(300.0, 250.0);
511+
const double depth = 4.0;
512+
513+
Eigen::Vector3d point = backproject_point(K_inv, pixel, depth);
514+
515+
EXPECT_NEAR(point.x(), 3.8, 1e-12);
516+
EXPECT_NEAR(point.y(), 2.0, 1e-12);
517+
EXPECT_NEAR(point.z(), 4.0, 1e-12);
518+
}
519+
462520
} // namespace vortex::utils::math

vortex_utils/include/vortex/utils/math.hpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,45 @@ Eigen::Quaterniond average_quaternions(
165165
*/
166166
Eigen::Quaterniond enu_ned_rotation(const Eigen::Quaterniond& quat);
167167

168+
/**
169+
* @brief Project a 3D point expressed in the camera coordinate frame
170+
* onto the image sensor, producing pixel coordinates.
171+
*
172+
* This function implements the pinhole camera model by:
173+
* 1) applying perspective division (Eq. 2.50 in Szeliski),
174+
* 2) mapping normalized image coordinates to pixel coordinates
175+
* using the camera intrinsics (Eq. 2.54 with K defined in Eq. 2.57).
176+
*
177+
* @param intrinsic_matrix Eigen::Matrix3d representing the camera matrix K.
178+
* @param point 3D point in camera coordinates (Xc, Yc, Zc), with Zc > 0.
179+
* @return 2D pixel coordinates (u, v).
180+
*/
181+
Eigen::Vector2d project_point(const Eigen::Matrix3d& intrinsic_matrix,
182+
const Eigen::Vector3d& point);
183+
/**
184+
* @brief Backproject a pixel using the inverse intrinsics matrix to produce
185+
* a ray through the pixel. The ray is in normalized image coordinates with
186+
* z = 1.0 and is not necessarily normalized.
187+
* @param intrinsic_matrix_inv Eigen::Matrix3d representing the inverse camera
188+
* matrix K_inv.
189+
* @param pixel Eigen::Vector2d representing the pixel to backproject.
190+
* @return ray in camera space (x, y, 1.0).
191+
*/
192+
Eigen::Vector3d backproject_ray(const Eigen::Matrix3d& intrinsic_matrix_inv,
193+
const Eigen::Vector2d& pixel);
194+
195+
/**
196+
* @brief Backproject a pixel using the inverse intrinsics matrix and a
197+
* depth value to compute the corresponding 3D point.
198+
* @param intrinsic_matrix_inv Eigen::Matrix3d representing the inverse camera
199+
* matrix K_inv.
200+
* @param pixel Eigen::Vector2d representing camera pixel coordinates.
201+
* @param depth Depth for the corresponding pixel.
202+
* @return 3D point in camera space
203+
*/
204+
Eigen::Vector3d backproject_point(const Eigen::Matrix3d& intrinsic_matrix_inv,
205+
const Eigen::Vector2d& pixel,
206+
double depth);
168207
} // namespace vortex::utils::math
169208

170209
#endif // VORTEX_UTILS_MATH_HPP

vortex_utils/include/vortex/utils/types.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,7 @@ struct CameraIntrinsics {
426426
* @brief Backproject a pixel using the inverse intrinsics matrix to produce
427427
* a ray through the pixel. The ray is in normalized image coordinates with
428428
* z = 1.0 and is not necessarily normalized.
429+
* @param pixel Eigen::Vector2d representing camera pixel coordinates.
429430
* @return ray in camera space (x, y, 1.0).
430431
*/
431432
Eigen::Vector3d backproject_ray(const Eigen::Vector2d& pixel) const {
@@ -442,10 +443,16 @@ struct CameraIntrinsics {
442443
/**
443444
* @brief Backproject a pixel using the inverse intrinsics matrix and a
444445
* depth value to compute the corresponding 3D point.
446+
* @param pixel Eigen::Vector2d representing camera pixel coordinates.
447+
* @param depth Depth for the corresponding pixel.
445448
* @return 3D point in camera space
446449
*/
447450
Eigen::Vector3d backproject_point(const Eigen::Vector2d& pixel,
448451
double depth) const {
452+
if (depth <= 0.0) {
453+
throw std::runtime_error(
454+
"Backprojection failed. Depth must be positive.");
455+
}
449456
return depth * backproject_ray(pixel);
450457
}
451458
};

vortex_utils/src/math.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,22 @@ Eigen::Quaterniond enu_ned_rotation(const Eigen::Quaterniond& quat) {
189189
return q_out.normalized();
190190
}
191191

192+
Eigen::Vector2d project_point(const Eigen::Matrix3d& intrinsic_matrix,
193+
const Eigen::Vector3d& point) {
194+
Eigen::Vector3d point_norm = point / point.z();
195+
return (intrinsic_matrix * point_norm).head(2);
196+
}
197+
198+
Eigen::Vector3d backproject_ray(const Eigen::Matrix3d& intrinsic_matrix_inv,
199+
const Eigen::Vector2d& pixel) {
200+
Eigen::Vector3d pixel_h(pixel.x(), pixel.y(), 1.0);
201+
return intrinsic_matrix_inv * pixel_h;
202+
}
203+
204+
Eigen::Vector3d backproject_point(const Eigen::Matrix3d& intrinsic_matrix_inv,
205+
const Eigen::Vector2d& pixel,
206+
double depth) {
207+
return backproject_ray(intrinsic_matrix_inv, pixel) * depth;
208+
}
209+
192210
} // namespace vortex::utils::math

0 commit comments

Comments
 (0)