diff --git a/cpp/Makefile b/cpp/Makefile index 3651f30..fde43c3 100644 --- a/cpp/Makefile +++ b/cpp/Makefile @@ -1,5 +1,5 @@ CXX = g++ -CXXFLAGS = -std=c++17 -Wall -Wextra -pedantic -g +CXXFLAGS = -std=c++17 -Wall -Wextra -pedantic -O3 INCLUDES = -I./include -ID:/Users/Jerome/Documents/Ingenierie/Programmation/eigen-3.4.0 -I./stb-master -ID:/Users/Jerome/Documents/Ingenierie/Programmation/cmd_line_parser-master/include LDFLAGS = -fopenmp diff --git a/cpp/include/Camera.hpp b/cpp/include/Camera.hpp index 58af01d..2d0b150 100644 --- a/cpp/include/Camera.hpp +++ b/cpp/include/Camera.hpp @@ -37,6 +37,9 @@ class Camera { /// @brief Orients the camera according to the provided Euler angles. The position of the camera is (0,0,0). Camera & SetEulerAngles(double yaw, double pitch, double roll, bool radians = false); + /// @brief Returns the Euler angles of the camera (technically the Tait-Bryan ZYX angles). + Eigen::Vector3d GetEulerAngles() const; + /// @brief Computes the ray direction in camera frame for a point (x,y) on the surface of the camera's sensor. p_norm is in normalized coordinates [-1;1]. /// @param p_norm Normalized coordinates of the point on the sensor. /// @return The ray direction in camera frame, cartesian coordinates. diff --git a/cpp/include/Image.hpp b/cpp/include/Image.hpp index 786ee18..9a4517b 100644 --- a/cpp/include/Image.hpp +++ b/cpp/include/Image.hpp @@ -159,6 +159,13 @@ class Image { /// @return Reference to the image. Image Colorized(unsigned char const* colormap) const; + /// @brief Resizes the image using the given interpolation method and returns the result. The original image is unchanged. + /// @param width Width of the resized image. + /// @param height Height of the resized image. + /// @param interp_method Interpolation method to use. + /// @return The resized image. + Image Resized(int new_width, int new_height, InterpMethod const& interp_method = InterpMethod::BILINEAR) const; + std::tuple ComputeMinMax() const; std::tuple ComputeMinMaxAvg() const; diff --git a/cpp/src/Camera.cpp b/cpp/src/Camera.cpp index 04f266d..d901076 100644 --- a/cpp/src/Camera.cpp +++ b/cpp/src/Camera.cpp @@ -75,23 +75,60 @@ Camera & Camera::SetEulerAngles(double yaw, double pitch, double roll, bool radi roll *= DEG2RAD; } - // Calculate sin and cos values - const double s_roll = std::sin(roll); - const double c_roll = std::cos(roll); - const double s_pitch = std::sin(pitch); - const double c_pitch = std::cos(pitch); - const double s_yaw = std::sin(yaw); - const double c_yaw = std::cos(yaw); + // Compute sin and cos values for the angles + const double sy = sin(yaw); + const double cy = cos(yaw); + const double sp = sin(pitch); + const double cp = cos(pitch); + const double sr = sin(roll); + const double cr = cos(roll); + + // Compute the rotation matrix + R(0,0) = cy*cp; + R(0,1) = cy*sp*sr - sy*cr; + R(0,2) = cy*sp*cr + sy*sr; + R(1,0) = sy*cp; + R(1,1) = sy*sp*sr + cy*cr; + R(1,2) = sy*sp*cr - cy*sr; + R(2,0) = -sp; + R(2,1) = cp*sr; + R(2,2) = cp*cr; + + // Align the axes so that -Z points forward, Y points up, and X points to the right + Eigen::Matrix3d R_Cam_Alignment; R_Cam_Alignment << 0., 0., -1., + -1., 0., 0., + 0., 1., 0; + R = R*R_Cam_Alignment; - // Build rotation matrix - R << c_pitch * c_yaw, -c_pitch * s_yaw, s_pitch, - s_roll * s_pitch * c_yaw + c_roll * s_yaw, -s_roll * s_pitch * s_yaw + c_roll * c_yaw, -s_roll * c_pitch, - -c_roll * s_pitch * c_yaw + s_roll * s_yaw, c_roll * s_pitch * s_yaw + s_roll * c_yaw, c_roll * c_pitch; - R_T = R.transpose(); return *this; } +Eigen::Vector3d Camera::GetEulerAngles() const +{ + // Align the axes so that -Z points forward, Y points up, and X points to the right + Eigen::Matrix3d R_Cam_Alignment; R_Cam_Alignment << 0., 0., -1., + -1., 0., 0., + 0., 1., 0; + Eigen::Matrix3d _R = R*R_Cam_Alignment.transpose(); + + double sy = sqrt(_R(0,0)*_R(0,0) + _R(1,0)*_R(1,0)); + + double yaw, pitch, roll; + if (sy > 1e-6) { + yaw = atan2(_R(1,0), _R(0,0)); + pitch = atan2(-_R(2,0), sy); + roll = atan2(_R(2,1), _R(2,2)); + } + else { + yaw = atan2(-_R(0,1), _R(1,1)); + pitch = atan2(-_R(2,0), sy); + roll = 0.0; + } + + return Eigen::Vector3d(yaw*RAD2DEG, pitch*RAD2DEG, roll*RAD2DEG); +} + Eigen::Vector3d Camera::ComputeRayDirInCameraFrame(Eigen::Vector2d const& p_norm) const { return Eigen::Vector3d(p_norm(0)*tanFOV2, p_norm(1)*tanFOV2, -1.0).normalized(); } diff --git a/cpp/src/Image.cpp b/cpp/src/Image.cpp index 0a48911..0bb0e02 100644 --- a/cpp/src/Image.cpp +++ b/cpp/src/Image.cpp @@ -222,6 +222,16 @@ Image Image::Colorized(unsigned char const* colormap) const { return colorized; } +Image Image::Resized(int new_width, int new_height, InterpMethod const& interp_method) const { + Image resized(new_width, new_height, depth); + double resize_factor = (double)new_width / (double)width; + #pragma omp parallel for + for(int i = 0 ; i < resized.GetHeight() ; i++) + for(int j = 0 ; j < resized.GetWidth() ; j++) + resized.SetPixel(i, j, GetPixelInterp(Eigen::Vector2d(i / resize_factor, j / resize_factor), interp_method)); + return resized; +} + std::tuple Image::ComputeMinMax() const { unsigned char min_val = 255, max_val = 0; #pragma omp parallel for reduction(min:min_val) reduction(max:max_val) @@ -278,4 +288,4 @@ std::vector Image::CumulativeHistogram(std::vector const& hi for(unsigned int i = 1 ; i < histogram.size() ; i++) cumul_hist[i] = cumul_hist[i - 1] + histogram[i]; return cumul_hist; -} \ No newline at end of file +} diff --git a/cpp/src/main.cpp b/cpp/src/main.cpp index 4a46c37..9c41525 100644 --- a/cpp/src/main.cpp +++ b/cpp/src/main.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include @@ -121,13 +120,12 @@ void configure_parser(cmd_line_parser::parser & parser) { parser.add("output_folder", "Output folder containing the projected images.", "-o", false); parser.add("size", "Size of generated images, in pixels. By default, 1000x1000 px.", "-s", false); parser.add("interpolation", "Interpolation mode when projecting the equirectangular (360 degree) image : 'nearest' (fast but blocky), 'bilinear' (slow but better). By default, 'bilinear'.", "--interp", false); - parser.add("output_format", "Output format among the following : jpg, png, bmp, tga. By default, jpg.", "-o", false); + parser.add("output_format", "Output format among the following : jpg, png, bmp, tga. By default, jpg.", "-f", false); parser.add("coverage", "Generate an colorized image depicting the coverage of the cameras over the whole sphere. The image produced is in equirectangular coordinates.", "--cov", false, true); parser.add("normalize", "If set, the images are histogram-normalized before being converted. This equalizes the utilization of all intensity values available. It might help the photogrammetry process to normalize the images.", "-n", false, true); } int main(int argc, char** argv) { - // std::locale::global(std::locale("en_US.utf8"));// DEBUG std::cout.precision(16); // Default configuration @@ -149,6 +147,12 @@ int main(int argc, char** argv) { // Print the configuration print_program_configuration(program_config); + // Create the output folder if it does not exist + if(!fs::exists(program_config.output_folder)) { + Logger::log("Creating output folder \"" + program_config.output_folder + "\"..."); + fs::create_directory(program_config.output_folder); + } + // Load the cameras std::vector cameras; { @@ -161,11 +165,10 @@ int main(int argc, char** argv) { Logger::error("No cameras have been found, aborting now."); return 1; } - } - - // DEBUG - for(unsigned int i = 0; i < cameras.size(); ++i) { - std::cout << "Camera " << i << " :\nR =\n" << cameras[i].GetR() << "\nFOV = " << cameras[i].GetFOV() << std::endl; + for(unsigned int i = 0; i < cameras.size(); ++i) { + Eigen::Vector3d euler_angles = cameras[i].GetEulerAngles(); + Logger::log("Camera " + std::to_string(i) + " : Euler angles (yaw, pitch, roll) = " + std::to_string(euler_angles[0]) + " " + std::to_string(euler_angles[1]) + " " + std::to_string(euler_angles[2]) + " FOV = " + std::to_string(cameras[i].GetFOV()*RAD2DEG)); + } } // Build the slicer @@ -183,8 +186,7 @@ int main(int argc, char** argv) { auto [min_cov, max_cov, avg_cov] = coverage_image.ComputeMinMaxAvg(); Logger::log("Coverage statistics (per pixel) :\tmin = " + std::to_string(int(min_cov)) + "\tmax = " + std::to_string(int(max_cov)) + "\tavg = " + std::to_string(avg_cov)); Logger::log("Exporting coverage image..."); - coverage_image.Normalized().Colorized(Image::Colormap::PARULA); - coverage_image.Save(program_config.output_folder + "/coverage." + program_config.output_format); + coverage_image.Normalized().Colorized(Image::Colormap::PARULA).Save(program_config.output_folder + "/coverage." + program_config.output_format); } // Generate the perspective images @@ -215,7 +217,7 @@ int main(int argc, char** argv) { } Logger::log(progress_timer.increment()); } - Logger::log(std::to_string(image_files.size()) + " perspective images generated."); + Logger::log(std::to_string(image_files.size()*slicer.cameras.size()) + " perspective images generated from " + std::to_string(image_files.size()) + " input images."); } return 0;