Robustified frame transformations. Fixed image projection. Added quick performance test comparing multithreading with single thread projection.
This commit is contained in:
parent
bd6948ecdc
commit
4c6de81a5f
10 changed files with 363 additions and 95 deletions
19
.vscode/launch.json
vendored
Normal file
19
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "360 to perspective",
|
||||||
|
"type": "cppdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/cpp/obj/360_to_perspective.exe",
|
||||||
|
"cwd": "${workspaceFolder}/cpp",
|
||||||
|
"environment": [],
|
||||||
|
"args": [],
|
||||||
|
"stopAtEntry": false,
|
||||||
|
"externalConsole": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
21
.vscode/tasks.json
vendored
Normal file
21
.vscode/tasks.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "build",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "D:/Programmes/Octave/Octave-7.3.0/usr/bin/make.exe",
|
||||||
|
"args": [
|
||||||
|
"-f",
|
||||||
|
"${workspaceFolder}/cpp/Makefile"
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"cwd": "${workspaceFolder}/cpp"
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
|
|
||||||
#include <Eigen/Dense>
|
#include <Eigen/Dense>
|
||||||
|
|
||||||
#define DEG2RAD 0.017453292519943295
|
#define DEG2RAD 0.01745329251994329576923690768488612713442871888541725456097191440171009114603449443682241569634509482
|
||||||
|
#define RAD2DEG 57.29577951308232087679815481410517033240547246656432154916024386120284714832155263244096899585111094
|
||||||
|
|
||||||
/// @brief A camera class to perform frame transformations and projections.
|
/// @brief A camera class to perform frame transformations and projections.
|
||||||
class Camera {
|
class Camera {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,38 @@
|
||||||
#include <Eigen/Dense>
|
#include <Eigen/Dense>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
#ifndef M_PI
|
||||||
|
#define M_PI 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace frame_conversions {
|
||||||
|
/// @brief Computes the modulo of a number with another number, ensuring that the results lies in the interval [0;b].
|
||||||
|
/// @param a The number to compute the modulo of.
|
||||||
|
/// @param b The modulo.
|
||||||
|
/// @return The modulo of a with b, ensuring that the result lies in the interval [0;b]
|
||||||
|
template <typename T>
|
||||||
|
inline T fmod_pos(T a, T b) {
|
||||||
|
return a - b*std::floor(a/b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Maps a value x from the range (a,b) to the range (c,d).
|
||||||
|
///
|
||||||
|
/// This function takes an input value x and maps it from the input range (a,b)
|
||||||
|
/// to the output range (c,d). The resulting mapped value is returned.
|
||||||
|
///
|
||||||
|
/// @param x The value to be mapped.
|
||||||
|
/// @param a The lower bound of the input range.
|
||||||
|
/// @param b The upper bound of the input range.
|
||||||
|
/// @param c The lower bound of the output range.
|
||||||
|
/// @param d The upper bound of the output range.
|
||||||
|
///
|
||||||
|
/// @return The mapped value of x in the output range.
|
||||||
|
///
|
||||||
|
/// @note Make sure that a != b to avoid a divide-by-zero error.
|
||||||
|
double map(double x, double a, double b, double c, double d);
|
||||||
|
|
||||||
|
} // namespace frame_conversions
|
||||||
|
|
||||||
/// @brief Converts a spherical vector [r, theta, phi] in ISO convention (theta=0 is Z+) to a cartesian vector.
|
/// @brief Converts a spherical vector [r, theta, phi] in ISO convention (theta=0 is Z+) to a cartesian vector.
|
||||||
Eigen::Vector3d sph2cart(const Eigen::Vector3d & sph);
|
Eigen::Vector3d sph2cart(const Eigen::Vector3d & sph);
|
||||||
|
|
||||||
|
|
@ -16,7 +48,7 @@ Eigen::Vector3d cart2sph(const Eigen::Vector3d & cart);
|
||||||
/// @param width Width of the equirectangular image.
|
/// @param width Width of the equirectangular image.
|
||||||
/// @param height Height of the equirectangular image.
|
/// @param height Height of the equirectangular image.
|
||||||
/// @return A spherical vector [r, theta, phi] in ISO convention (theta=0 is Z+).
|
/// @return A spherical vector [r, theta, phi] in ISO convention (theta=0 is Z+).
|
||||||
Eigen::Vector3d equirectangular2sph(unsigned int i, unsigned int j, unsigned int width, unsigned int height);
|
Eigen::Vector3d equirectangular2sph(int i, int j, unsigned int width, unsigned int height);
|
||||||
|
|
||||||
/// @brief Converts spherical coordinates to pixel coordinates of an equirectangular image.
|
/// @brief Converts spherical coordinates to pixel coordinates of an equirectangular image.
|
||||||
/// @param p_sph A spherical vector [r, theta, phi] in ISO convention (theta=0 is Z+).
|
/// @param p_sph A spherical vector [r, theta, phi] in ISO convention (theta=0 is Z+).
|
||||||
|
|
|
||||||
135
cpp/include/test_framework.hpp
Normal file
135
cpp/include/test_framework.hpp
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
#ifndef h_test_framework
|
||||||
|
#define h_test_framework
|
||||||
|
|
||||||
|
#ifndef TOL_EQ_TEST
|
||||||
|
#define TOL_EQ_TEST 1e-9
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static size_t num_tests = 0;
|
||||||
|
static size_t num_tests_ok = 0;
|
||||||
|
static size_t num_tests_ko = 0;
|
||||||
|
|
||||||
|
#define PRINT_VAR(x) std::cout << #x << "\t= " << (x) << std::endl
|
||||||
|
|
||||||
|
// EXPECT_EQ macro
|
||||||
|
#define EXPECT_EQ(actual, expected) EXPECT_EQ_IMPL(actual, expected, __FILE__, __LINE__)
|
||||||
|
#define EXPECT_EQ_IMPL(actual, expected, file, line) \
|
||||||
|
do { \
|
||||||
|
num_tests++; \
|
||||||
|
auto&& e = (expected); \
|
||||||
|
auto&& a = (actual); \
|
||||||
|
if (e != a) { \
|
||||||
|
std::cout << file << ":" << line << " : " << "Expected: " << (#expected) << " = " << e << ", actual: " << (#actual) << " = " << a << " " << std::endl; \
|
||||||
|
num_tests_ko++; \
|
||||||
|
} \
|
||||||
|
else { \
|
||||||
|
num_tests_ok++; \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
// EXPECT_DOUBLE_EQ macro
|
||||||
|
#define EXPECT_DOUBLE_EQ(actual, expected) EXPECT_DOUBLE_EQ_IMPL(actual, expected, __FILE__, __LINE__)
|
||||||
|
#define EXPECT_DOUBLE_EQ_IMPL(actual, expected, file, line) \
|
||||||
|
do { \
|
||||||
|
num_tests++; \
|
||||||
|
double e = (expected); \
|
||||||
|
double a = (actual); \
|
||||||
|
if (std::abs(e - a) > TOL_EQ_TEST) { \
|
||||||
|
std::cout << file << ":" << line << " : " << "Expected: " << (#expected) << " = " << e << ", actual: " << (#actual) << " = " << a << " " << std::endl; \
|
||||||
|
num_tests_ko++; \
|
||||||
|
} \
|
||||||
|
else { \
|
||||||
|
num_tests_ok++; \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
// EXPECT_LT macro
|
||||||
|
#define EXPECT_LT(actual, expected) EXPECT_LT_IMPL(actual, expected, __FILE__, __LINE__)
|
||||||
|
#define EXPECT_LT_IMPL(actual, expected, file, line) \
|
||||||
|
do { \
|
||||||
|
num_tests++; \
|
||||||
|
auto&& e = (expected); \
|
||||||
|
auto&& a = (actual); \
|
||||||
|
if (a < e) { \
|
||||||
|
num_tests_ok++; \
|
||||||
|
} \
|
||||||
|
else { \
|
||||||
|
std::cout << file << ":" << line << " : " << "Expected " << a << " < " << (#expected) << " = " << e << ", actual: " << (#actual) << std::endl; \
|
||||||
|
num_tests_ko++; \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
// EXPECT_LE macro
|
||||||
|
#define EXPECT_LE(actual, expected) EXPECT_LE_IMPL(actual, expected, __FILE__, __LINE__)
|
||||||
|
#define EXPECT_LE_IMPL(actual, expected, file, line) \
|
||||||
|
do { \
|
||||||
|
num_tests++; \
|
||||||
|
auto&& e = (expected); \
|
||||||
|
auto&& a = (actual); \
|
||||||
|
if (a <= e) { \
|
||||||
|
num_tests_ok++; \
|
||||||
|
} \
|
||||||
|
else { \
|
||||||
|
std::cout << file << ":" << line << " : " << "Expected " << a << " <= " << (#expected) << " = " << e << ", actual: " << (#actual) << std::endl; \
|
||||||
|
num_tests_ko++; \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
// EXPECT_GT macro
|
||||||
|
#define EXPECT_GT(actual, expected) EXPECT_GT_IMPL(actual, expected, __FILE__, __LINE__)
|
||||||
|
#define EXPECT_GT_IMPL(actual, expected, file, line) \
|
||||||
|
do { \
|
||||||
|
num_tests++; \
|
||||||
|
auto&& e = (expected); \
|
||||||
|
auto&& a = (actual); \
|
||||||
|
if (a > e) { \
|
||||||
|
num_tests_ok++; \
|
||||||
|
} \
|
||||||
|
else { \
|
||||||
|
std::cout << file << ":" << line << " : " << "Expected " << a << " > " << (#expected) << " = " << e << ", actual: " << (#actual) << std::endl; \
|
||||||
|
num_tests_ko++; \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
// EXPECT_GE macro
|
||||||
|
#define EXPECT_GE(actual, expected) EXPECT_GE_IMPL(actual, expected, __FILE__, __LINE__)
|
||||||
|
#define EXPECT_GE_IMPL(actual, expected, file, line) \
|
||||||
|
do { \
|
||||||
|
num_tests++; \
|
||||||
|
auto&& e = (expected); \
|
||||||
|
auto&& a = (actual); \
|
||||||
|
if (a >= e) { \
|
||||||
|
num_tests_ok++; \
|
||||||
|
} \
|
||||||
|
else { \
|
||||||
|
std::cout << file << ":" << line << " : " << "Expected " << a << " >= " << (#expected) << " = " << e << ", actual: " << (#actual) << std::endl; \
|
||||||
|
num_tests_ko++; \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
// EXPECT_VEC_EQ macro
|
||||||
|
#define EXPECT_VEC_EQ(actual, expected) EXPECT_VEC_EQ_IMPL(actual, expected, __FILE__, __LINE__)
|
||||||
|
#define EXPECT_VEC_EQ_IMPL(actual, expected, file, line) \
|
||||||
|
do { \
|
||||||
|
for(unsigned int i = 0 ; i < (expected).size() ; i++) { \
|
||||||
|
EXPECT_DOUBLE_EQ_IMPL((actual)[i], (expected)[i], file, line); \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
// EXPECT_MAT_EQ macro
|
||||||
|
#define EXPECT_MAT_EQ(actual, expected) EXPECT_MAT_EQ_IMPL(actual, expected, __FILE__, __LINE__)
|
||||||
|
#define EXPECT_MAT_EQ_IMPL(actual, expected, file, line) \
|
||||||
|
do { \
|
||||||
|
for(unsigned int i = 0 ; i < (expected).rows() ; i++) { \
|
||||||
|
for(unsigned int j = 0 ; j < (expected).rows() ; j++) { \
|
||||||
|
EXPECT_DOUBLE_EQ_IMPL((actual)(i,j), (expected)(i,j), file, line); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
#define PRINT_TESTS_SUMMARY() \
|
||||||
|
do { \
|
||||||
|
std::cout << "Tests summary: " << num_tests << " tests in total\n " << num_tests_ok << "/" << num_tests << " OK (" << ((100.0*(double)num_tests_ok)/num_tests) << " %)\n " << num_tests_ko << "/" << num_tests << " KO (" << ((100.0*(double)num_tests_ko)/num_tests) << " %)" << std::endl; \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
#include <Camera.hpp>
|
#include <Camera.hpp>
|
||||||
|
|
||||||
#define _USE_MATH_DEFINES
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
Camera::Camera()
|
Camera::Camera()
|
||||||
: fwd(Eigen::Vector3d::UnitX()), up(Eigen::Vector3d::UnitZ()), R(Eigen::Matrix3d::Identity()), R_T(Eigen::Matrix3d::Identity()), FOV(90.0*DEG2RAD), tanFOV2(tan(FOV/2.0))
|
: fwd(Eigen::Vector3d::UnitX()), up(Eigen::Vector3d::UnitZ()), R(Eigen::Matrix3d::Identity()), R_T(Eigen::Matrix3d::Identity()), FOV(90.0*DEG2RAD), tanFOV2(tan(FOV/2.0))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,9 @@ Eigen::Vector3i Image::GetPixelInterp(Eigen::Vector2d const& pos, InterpMethod c
|
||||||
int j = (int)std::floor(pos[1]);
|
int j = (int)std::floor(pos[1]);
|
||||||
double u = pos[0] - i;
|
double u = pos[0] - i;
|
||||||
double v = pos[1] - j;
|
double v = pos[1] - j;
|
||||||
Eigen::Vector3i rgb = ((1 - u) * (1 - v) * GetPixel(i, j).cast<double>() + u * (1 - v) * GetPixel(i + 1, j).cast<double>() + (1 - u) * v * GetPixel(i, j + 1).cast<double>() + u * v * GetPixel(i + 1, j + 1).cast<double>()).cast<int>();
|
int i_plus_1 = (i + 1) % height;
|
||||||
|
int j_plus_1 = (j + 1) % width;
|
||||||
|
Eigen::Vector3i rgb = ((1 - u) * (1 - v) * GetPixel(i, j).cast<double>() + u * (1 - v) * GetPixel(i_plus_1, j).cast<double>() + (1 - u) * v * GetPixel(i, j_plus_1).cast<double>() + u * v * GetPixel(i_plus_1, j_plus_1).cast<double>()).cast<int>();
|
||||||
return rgb;
|
return rgb;
|
||||||
}
|
}
|
||||||
else // By default, use nearest neighbor interpolation
|
else // By default, use nearest neighbor interpolation
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ Eigen::Vector3i Slicer360ToPerspective::ProjectToCameraPixel(Camera const& camer
|
||||||
// Compute ray direction from sensor coordinates
|
// Compute ray direction from sensor coordinates
|
||||||
Eigen::Vector3d ray_dir = camera.ComputeRayDirInInertialFrame(p_sensor);
|
Eigen::Vector3d ray_dir = camera.ComputeRayDirInInertialFrame(p_sensor);
|
||||||
Eigen::Vector3d ray_dir_sph = cart2sph(ray_dir);
|
Eigen::Vector3d ray_dir_sph = cart2sph(ray_dir);
|
||||||
Eigen::Vector2d p_equi = sph2equirectangular_d(ray_dir_sph, output_image_width, output_image_height);
|
Eigen::Vector2d p_equi = sph2equirectangular_d(ray_dir_sph, panorama_image.GetWidth(), panorama_image.GetHeight());
|
||||||
return panorama_image.GetPixelInterp(p_equi, interpolation_method);
|
return panorama_image.GetPixelInterp(p_equi, interpolation_method);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
#include <frame_conversions.hpp>
|
#include <frame_conversions.hpp>
|
||||||
|
|
||||||
#define M_PI 3.14159265358979323846
|
namespace frame_conversions {
|
||||||
|
|
||||||
|
double map(double x, double a, double b, double c, double d) {
|
||||||
|
return c + (x - a) * (d - c) / (b - a);
|
||||||
|
}
|
||||||
|
|
||||||
|
}// namespace frame_conversions
|
||||||
|
|
||||||
Eigen::Vector3d sph2cart(const Eigen::Vector3d & sph) {
|
Eigen::Vector3d sph2cart(const Eigen::Vector3d & sph) {
|
||||||
double r = sph(0);
|
double r = sph(0);
|
||||||
|
|
@ -18,19 +24,20 @@ Eigen::Vector3d cart2sph(const Eigen::Vector3d & cart) {
|
||||||
double z = cart(2);
|
double z = cart(2);
|
||||||
double r = sqrt(x*x + y*y + z*z);
|
double r = sqrt(x*x + y*y + z*z);
|
||||||
double theta = acos(z/r);
|
double theta = acos(z/r);
|
||||||
double phi = atan2(y,x);
|
double phi = frame_conversions::fmod_pos(atan2(y,x), 2.0*M_PI);
|
||||||
return Eigen::Vector3d(r,theta,phi);
|
return Eigen::Vector3d(r,theta,phi);
|
||||||
}
|
}
|
||||||
|
|
||||||
Eigen::Vector3d equirectangular2sph(unsigned int i, unsigned int j, unsigned int width, unsigned int height) {
|
Eigen::Vector3d equirectangular2sph(int i, int j, unsigned int width, unsigned int height) {
|
||||||
return Eigen::Vector3d(1.0, M_PI*(((double)i)/(height-1)), 2.0*M_PI*(((double)j)/width - 0.5));
|
return Eigen::Vector3d(1.0, std::max(0.0, std::min(M_PI*(((double)i)/(height-1)), M_PI)), frame_conversions::fmod_pos(2.0*M_PI*((double)j)/((double)width), 2.0*M_PI));
|
||||||
}
|
}
|
||||||
|
|
||||||
Eigen::Vector2d sph2equirectangular_d(Eigen::Vector3d const& p_sph, unsigned int width, unsigned int height) {
|
Eigen::Vector2d sph2equirectangular_d(Eigen::Vector3d const& p_sph, unsigned int width, unsigned int height) {
|
||||||
double theta = p_sph[1];
|
double theta = std::max(0.0, std::min(p_sph[1], M_PI));
|
||||||
double phi = p_sph[2];
|
double phi = frame_conversions::fmod_pos(p_sph[2], 2.0*M_PI);
|
||||||
double i = std::fmod(((height - 1)*theta)/M_PI, height);
|
double i = ((height - 1)*theta)/M_PI;
|
||||||
double j = std::fmod((width*(M_PI + phi))/(2.0*M_PI), width);
|
// double j = std::fmod((width*phi)/(2.0*M_PI), width);
|
||||||
|
double j = std::fmod(frame_conversions::map(phi, 0.0, 2.0*M_PI, 0.0, width), width);
|
||||||
return Eigen::Vector2d(i, j);
|
return Eigen::Vector2d(i, j);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,80 +2,98 @@
|
||||||
#include <Camera.hpp>
|
#include <Camera.hpp>
|
||||||
#include <Image.hpp>
|
#include <Image.hpp>
|
||||||
#include <Slicer360ToPerspective.hpp>
|
#include <Slicer360ToPerspective.hpp>
|
||||||
|
#include <frame_conversions.hpp>
|
||||||
|
|
||||||
#define _USE_MATH_DEFINES
|
#include <test_framework.hpp>
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
constexpr double TOL_EQ_TEST = 1e-9;
|
|
||||||
constexpr double M_PI = 3.14159265358979323846;
|
|
||||||
|
|
||||||
static size_t num_tests = 0;
|
|
||||||
static size_t num_tests_ok = 0;
|
|
||||||
static size_t num_tests_ko = 0;
|
|
||||||
|
|
||||||
#define PRINT_VAR(x) std::cout << #x << "\t= " << (x) << std::endl
|
|
||||||
|
|
||||||
// EXPECT_EQ macro
|
|
||||||
#define EXPECT_EQ(actual, expected) EXPECT_EQ_IMPL(actual, expected, __FILE__, __LINE__)
|
|
||||||
#define EXPECT_EQ_IMPL(actual, expected, file, line) \
|
|
||||||
do { \
|
|
||||||
num_tests++; \
|
|
||||||
auto&& e = (expected); \
|
|
||||||
auto&& a = (actual); \
|
|
||||||
if (e != a) { \
|
|
||||||
std::cout << file << ":" << line << " : " << "Expected: " << (#expected) << " = " << e << ", actual: " << (#actual) << " = " << a << " " << std::endl; \
|
|
||||||
num_tests_ko++; \
|
|
||||||
} \
|
|
||||||
else { \
|
|
||||||
num_tests_ok++; \
|
|
||||||
} \
|
|
||||||
} while (false)
|
|
||||||
|
|
||||||
// EXPECT_DOUBLE_EQ macro
|
|
||||||
#define EXPECT_DOUBLE_EQ(actual, expected) EXPECT_DOUBLE_EQ_IMPL(actual, expected, __FILE__, __LINE__)
|
|
||||||
#define EXPECT_DOUBLE_EQ_IMPL(actual, expected, file, line) \
|
|
||||||
do { \
|
|
||||||
num_tests++; \
|
|
||||||
double e = (expected); \
|
|
||||||
double a = (actual); \
|
|
||||||
if (std::abs(e - a) > TOL_EQ_TEST) { \
|
|
||||||
std::cout << file << ":" << line << " : " << "Expected: " << (#expected) << " = " << e << ", actual: " << (#actual) << " = " << a << " " << std::endl; \
|
|
||||||
num_tests_ko++; \
|
|
||||||
} \
|
|
||||||
else { \
|
|
||||||
num_tests_ok++; \
|
|
||||||
} \
|
|
||||||
} while (false)
|
|
||||||
|
|
||||||
// EXPECT_VEC_EQ macro
|
|
||||||
#define EXPECT_VEC_EQ(actual, expected) EXPECT_VEC_EQ_IMPL(actual, expected, __FILE__, __LINE__)
|
|
||||||
#define EXPECT_VEC_EQ_IMPL(actual, expected, file, line) \
|
|
||||||
do { \
|
|
||||||
for(unsigned int i = 0 ; i < (expected).size() ; i++) { \
|
|
||||||
EXPECT_DOUBLE_EQ_IMPL((actual)[i], (expected)[i], file, line); \
|
|
||||||
} \
|
|
||||||
} while (false)
|
|
||||||
|
|
||||||
// EXPECT_MAT_EQ macro
|
|
||||||
#define EXPECT_MAT_EQ(actual, expected) EXPECT_MAT_EQ_IMPL(actual, expected, __FILE__, __LINE__)
|
|
||||||
#define EXPECT_MAT_EQ_IMPL(actual, expected, file, line) \
|
|
||||||
do { \
|
|
||||||
for(unsigned int i = 0 ; i < (expected).rows() ; i++) { \
|
|
||||||
for(unsigned int j = 0 ; j < (expected).rows() ; j++) { \
|
|
||||||
EXPECT_DOUBLE_EQ_IMPL((actual)(i,j), (expected)(i,j), file, line); \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
} while (false)
|
|
||||||
|
|
||||||
#define PRINT_TESTS_SUMMARY() \
|
|
||||||
do { \
|
|
||||||
std::cout << "Tests summary: " << num_tests << " tests in total\n " << num_tests_ok << "/" << num_tests << " OK (" << ((100.0*(double)num_tests_ok)/num_tests) << " %)\n " << num_tests_ko << "/" << num_tests << " KO (" << ((100.0*(double)num_tests_ko)/num_tests) << " %)" << std::endl; \
|
|
||||||
} while (false)
|
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
std::cout.precision(16);
|
std::cout.precision(16);
|
||||||
std::cout << "Running Unit Tests\n";
|
std::cout << "Running Unit Tests\n";
|
||||||
|
|
||||||
|
if(0){// frame transforms
|
||||||
|
std::cout << "frame transforms\n";
|
||||||
|
// PRINT_VAR(sph2equirectangular_d(Eigen::Vector3d(1.0, 0., 0.), 100, 50).transpose());
|
||||||
|
// PRINT_VAR(sph2equirectangular_d(Eigen::Vector3d(1.0, 0., 1.), 100, 50).transpose());
|
||||||
|
// PRINT_VAR(sph2equirectangular_d(Eigen::Vector3d(1.0, M_PI/2.0, 0.), 100, 50).transpose());
|
||||||
|
// PRINT_VAR(sph2equirectangular_d(Eigen::Vector3d(1.0, M_PI/2.0, M_PI/2.0), 100, 50).transpose());
|
||||||
|
// PRINT_VAR(sph2equirectangular_d(Eigen::Vector3d(1.0, M_PI/2.0, M_PI), 100, 50).transpose());
|
||||||
|
// PRINT_VAR(sph2equirectangular_d(Eigen::Vector3d(1.0, M_PI/2.0, -M_PI/2.0), 100, 50).transpose());
|
||||||
|
|
||||||
|
// PRINT_VAR(sph2equirectangular_d(Eigen::Vector3d(1.0, -M_PI/2.0, 0.), 100, 50).transpose());
|
||||||
|
// PRINT_VAR(sph2equirectangular_d(Eigen::Vector3d(1.0, M_PI/2.0, -M_PI/2.0), 100, 50).transpose());
|
||||||
|
// PRINT_VAR(sph2equirectangular_d(Eigen::Vector3d(1.0, -M_PI/2.0, -M_PI), 100, 50).transpose());
|
||||||
|
// PRINT_VAR(sph2equirectangular_d(Eigen::Vector3d(1.0, M_PI/2.0, -2.5*M_PI), 100, 50).transpose());
|
||||||
|
|
||||||
|
std::cout << "Testing equirectangular coordinates.\n";
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
int j = 0;
|
||||||
|
int width = 10;
|
||||||
|
int height = 5;
|
||||||
|
|
||||||
|
if(1) { std::cout << "\nTesting equirectangular2sph.\n";
|
||||||
|
for(i = -2*height ; i < 2*height ; i++) {
|
||||||
|
for(j = -2*width ; j < 2*width ; j++) {
|
||||||
|
Eigen::Vector3d p_sph = equirectangular2sph(i, j, width, height);
|
||||||
|
// std::cout << "i: " << i << ", j: " << j << ", p_sph: " << p_sph.transpose() << "\n";
|
||||||
|
EXPECT_DOUBLE_EQ(p_sph[0], 1.0);
|
||||||
|
EXPECT_LT(p_sph[1], M_PI+TOL_EQ_TEST);
|
||||||
|
EXPECT_GE(p_sph[1], 0.0);
|
||||||
|
EXPECT_LT(p_sph[2], 2.0*M_PI);
|
||||||
|
EXPECT_GE(p_sph[2], 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(1) { std::cout << "\nTesting roundtrip.\n";
|
||||||
|
for(i = 0 ; i < height ; i++) {
|
||||||
|
for(j = 0 ; j < width ; j++) {
|
||||||
|
Eigen::Vector3d p_sph = equirectangular2sph(i, j, width, height);
|
||||||
|
Eigen::Vector2d p_equirectangular = sph2equirectangular_d(p_sph, width, height);
|
||||||
|
// std::cout << "i: " << i << ", j: " << j << ", p_sph: " << p_sph.transpose() << ", p_equirectangular: " << p_equirectangular.transpose() << ((p_equirectangular[0] > height) ? "I OUT OF BOUNDS " : "") << ((p_equirectangular[1] > width) ? "J OUT OF BOUNDS " : "") << "\n";
|
||||||
|
EXPECT_DOUBLE_EQ(p_equirectangular[0], i);
|
||||||
|
EXPECT_DOUBLE_EQ(p_equirectangular[1], j);
|
||||||
|
EXPECT_LT(p_equirectangular[0], height);
|
||||||
|
EXPECT_GE(p_equirectangular[0], 0.0);
|
||||||
|
EXPECT_LT(p_equirectangular[1], width);
|
||||||
|
EXPECT_GE(p_equirectangular[1], 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(1) { std::cout << "\nTesting sph2equirectangular_d out of bounds.\n";
|
||||||
|
width = 3600;
|
||||||
|
height = 1801;
|
||||||
|
double theta = 0.0;
|
||||||
|
double phi = M_PI/4.0;
|
||||||
|
|
||||||
|
std::cout << "\nTesting sph2equirectangular_d out of bounds in THETA.\n";
|
||||||
|
for(theta = -4.0*M_PI ; theta < 4.0*M_PI ; theta += M_PI/10.0) {
|
||||||
|
auto p_equi = sph2equirectangular_d(Eigen::Vector3d(1.0, theta, phi), width, height).transpose();
|
||||||
|
// std::cout << "theta: " << theta << ", phi: " << phi << ", p_equirectangular: " << p_equi << "\n";
|
||||||
|
EXPECT_EQ(p_equi[0] < 0, false);
|
||||||
|
EXPECT_EQ(p_equi[0] >= height, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\nTesting sph2equirectangular_d out of bounds in PHI.\n";
|
||||||
|
theta = M_PI/2.0;
|
||||||
|
for(phi = -4.0*M_PI ; phi < 4.0*M_PI ; phi += M_PI/10.0) {
|
||||||
|
auto p_equi = sph2equirectangular_d(Eigen::Vector3d(1.0, theta, phi), width, height).transpose();
|
||||||
|
// std::cout << "theta: " << theta << ", phi: " << phi << ",\tp_equirectangular: " << p_equi << "\n";
|
||||||
|
EXPECT_EQ(p_equi[1] < 0, false);
|
||||||
|
EXPECT_EQ(p_equi[1] >= width, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(1) { std::cout << "\nTesting fmod.\n";
|
||||||
|
for(double x = -4.0*M_PI ; x < 4.0*M_PI ; x += M_PI/10.0) {
|
||||||
|
// std::cout << x << "\t" << std::fmod(x, 2.0*M_PI) << "\t" << frame_conversions::fmod_pos(x, 2.0*M_PI) << "\n";
|
||||||
|
EXPECT_EQ(frame_conversions::fmod_pos(x, 2.0*M_PI) >= 0.0, true);
|
||||||
|
EXPECT_EQ(frame_conversions::fmod_pos(x, 2.0*M_PI) < 2.0*M_PI, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(0){// Camera
|
if(0){// Camera
|
||||||
std::cout << "Camera\n";
|
std::cout << "Camera\n";
|
||||||
{// Constructor, accessors and setters
|
{// Constructor, accessors and setters
|
||||||
|
|
@ -315,20 +333,56 @@ int main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
if(1){// Test Slicer360ToPerspective
|
||||||
std::cout << "Coverage analysis\n";
|
if(0) { std::cout << "Coverage analysis\n";
|
||||||
Slicer360ToPerspective slicer;
|
Slicer360ToPerspective slicer;
|
||||||
|
|
||||||
double fov = 90.0*DEG2RAD;
|
double fov = 90.0*DEG2RAD;
|
||||||
int width = 500;
|
int width = 500;
|
||||||
slicer.cameras.push_back(Camera().SetFOV(fov).SetTarget(Eigen::Vector3d(1., 0., 0.)));
|
slicer.cameras.push_back(Camera().SetFOV(fov).SetTarget(Eigen::Vector3d(1., 0., 0.)));
|
||||||
slicer.cameras.push_back(Camera().SetFOV(fov).SetTarget(Eigen::Vector3d(-1., -1., -1.)));
|
slicer.cameras.push_back(Camera().SetFOV(fov).SetTarget(Eigen::Vector3d(-1., -1., -1.)));
|
||||||
slicer.cameras.push_back(Camera().SetFOV(fov/2).SetTarget(Eigen::Vector3d(1., 0.2, -0.3)));
|
slicer.cameras.push_back(Camera().SetFOV(fov/2).SetTarget(Eigen::Vector3d(1., 0.2, -0.3)));
|
||||||
Image coverage_raw = slicer.ComputeCoverage(width, true);
|
Image coverage_raw = slicer.ComputeCoverage(width, true);
|
||||||
Image coverage_col = slicer.ComputeCoverage(width, false);
|
Image coverage_col = slicer.ComputeCoverage(width, false);
|
||||||
|
|
||||||
coverage_raw.Save("coverage_raw.png");
|
coverage_raw.Save("coverage_raw.png");
|
||||||
coverage_col.Save("coverage_col.png");
|
coverage_col.Save("coverage_col.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(1){ std::cout << "Perspective Projection\n";
|
||||||
|
double Mpx = 1e6; // Image size in Mpx
|
||||||
|
double ratio = 16./9.; // Image ratio
|
||||||
|
int width = sqrt(Mpx)*sqrt(ratio);
|
||||||
|
int height = sqrt(Mpx)/sqrt(ratio);
|
||||||
|
|
||||||
|
Slicer360ToPerspective slicer = Slicer360ToPerspective(Image("../venise.jpg")).SetOutputImageSize(width, height).SetInterpolationMethod(Image::InterpMethod::BILINEAR);
|
||||||
|
Camera camera = Camera().SetFOV(90.0*DEG2RAD).SetTarget(Eigen::Vector3d(1., 0., 0.));
|
||||||
|
slicer.cameras.push_back(camera);
|
||||||
|
|
||||||
|
slicer.ProjectToCamera(camera, true).Save("venise_proj_01.png");
|
||||||
|
|
||||||
|
// measure performance
|
||||||
|
double t0, t1;
|
||||||
|
t0 = omp_get_wtime();
|
||||||
|
slicer.ProjectToCamera(camera, false);
|
||||||
|
t1 = omp_get_wtime();
|
||||||
|
std::cout << "Projection time single-threaded BILINEAR : " << t1-t0 << " s\n";
|
||||||
|
t0 = omp_get_wtime();
|
||||||
|
slicer.ProjectToCamera(camera, true);
|
||||||
|
t1 = omp_get_wtime();
|
||||||
|
std::cout << "Projection time multi-threaded BILINEAR : " << t1-t0 << " s\n";
|
||||||
|
|
||||||
|
slicer.SetInterpolationMethod(Image::InterpMethod::NEAREST);
|
||||||
|
|
||||||
|
t0 = omp_get_wtime();
|
||||||
|
slicer.ProjectToCamera(camera, false);
|
||||||
|
t1 = omp_get_wtime();
|
||||||
|
std::cout << "Projection time single-threaded NEAREST : " << t1-t0 << " s\n";
|
||||||
|
t0 = omp_get_wtime();
|
||||||
|
slicer.ProjectToCamera(camera, true);
|
||||||
|
t1 = omp_get_wtime();
|
||||||
|
std::cout << "Projection time multi-threaded NEAREST : " << t1-t0 << " s\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PRINT_TESTS_SUMMARY();
|
PRINT_TESTS_SUMMARY();
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue