From bd6948ecdc17b0bd618d083bcb8b12682edabcad Mon Sep 17 00:00:00 2001 From: Jerome Date: Mon, 1 May 2023 13:41:51 +0200 Subject: [PATCH] Added projection to perspective and OpenMP support. --- cpp/Makefile | 5 +- cpp/include/Image.hpp | 13 +++-- cpp/include/Slicer360ToPerspective.hpp | 58 +++++++++++++++++-- cpp/src/Image.cpp | 23 ++++++-- cpp/src/Slicer360ToPerspective.cpp | 77 ++++++++++++++++++++++++-- 5 files changed, 157 insertions(+), 19 deletions(-) diff --git a/cpp/Makefile b/cpp/Makefile index fb8ebb6..52a85fa 100644 --- a/cpp/Makefile +++ b/cpp/Makefile @@ -1,6 +1,7 @@ CXX = g++ CXXFLAGS = -std=c++17 -Wall -Wextra -pedantic -g INCLUDES = -I./include -ID:/Users/Jerome/Documents/Ingenierie/Programmation/eigen-3.4.0 -I./stb-master +LDFLAGS = -fopenmp SRC_DIR = src OBJ_DIR = obj @@ -14,10 +15,10 @@ EXEC = $(OBJ_DIR)/360_to_perspective all: $(EXEC) $(EXEC): $(OBJS) - $(CXX) $(CXXFLAGS) -o $@ $^ + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $^ $(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp | $(OBJ_DIR) - $(CXX) $(CXXFLAGS) $(INCLUDES) -c -o $@ $< + $(CXX) $(CXXFLAGS) $(INCLUDES) $(LDFLAGS) -c -o $@ $< $(OBJ_DIR): mkdir -p $@ diff --git a/cpp/include/Image.hpp b/cpp/include/Image.hpp index a4e2a92..3a8f362 100644 --- a/cpp/include/Image.hpp +++ b/cpp/include/Image.hpp @@ -31,19 +31,24 @@ class Image { /// @param width_ Width of the image. /// @param height_ Height of the image. /// @param depth_ Depth of the image. - Image(int width_, int height_, int depth_ = 1); + Image(int width_ = 1, int height_ = 1, int depth_ = 1); /// @brief Copies another image. Performs a deep copy. /// @param other The image to copy. Image(Image const& other); - /// @brief Frees the allocated memory of the image. - ~Image(); - /// @brief Loads an image from a file. /// @param filename Path to the image file. Image(std::string const& filename); + /// @brief Frees the allocated memory of the image. + ~Image(); + + /// @brief Copies the other image into this one. Performs a deep copy. + /// @param other Image to copy. + /// @return A reference to this image. + Image & operator=(Image const& other); + /// @brief Saves the image to a file. /// Supports the following formats : PNG, BMP, TGA, JPG. The format is determined from the file extension. /// @param filename Path to save the image to. diff --git a/cpp/include/Slicer360ToPerspective.hpp b/cpp/include/Slicer360ToPerspective.hpp index 9c2e328..71a0b4e 100644 --- a/cpp/include/Slicer360ToPerspective.hpp +++ b/cpp/include/Slicer360ToPerspective.hpp @@ -4,21 +4,71 @@ #include #include -/// @brief This class encapsulates the algorithms to convert a 360-degree image to a perspective image. +/// @brief This class encapsulates the algorithms to convert a 360-degree image to perspective images. +/// @details The 360-degree image is converted to perspective images by projecting the pixels of the 360-degree image using perspective projection and virtual cameras. +/// The virtual cameras are added to the slicer using the AddCamera() method. +/// The output images are saved in the folder specified by the SetOutputFolder() method. +/// By default, the ProjectToCameras and ProjectToAllCameras are parallelized using OpenMP. +/// If the DO_NOT_USE_OPENMP macro is defined, the OpenMP parallization is disabled and the computation is sequential regardless of the value of "parallel". class Slicer360ToPerspective { public: - Slicer360ToPerspective(); + /// @brief Builds a Slicer360ToPerspective object using a 360-degree image. + /// @param panorama_image The 360-degree image to be used as an input. + Slicer360ToPerspective(Image const& panorama_image_ = Image(1,1,3)); + + /// @brief Sets the width of the output images. + /// @param width Width of the output images in pixels. + /// @param height Height of the output images in pixels. By default = -1. If < 0, the height is set to the width to yield a square image. + /// @return A reference to the current object. + Slicer360ToPerspective & SetOutputImageSize(int width, int height = -1); + + /// @brief Sets the interpolation method used to project the 360-degree image to perspective images. See Image::InterpMethod. + /// @param interpolation_method_ Interpolation method used to project the 360-degree image to perspective images. + /// @return A reference to the current object. + Slicer360ToPerspective & SetInterpolationMethod(Image::InterpMethod const& interpolation_method_); + + /// @brief Sets the folder where the output images are saved. By default, the output images are saved in the current folder. + /// @param folder Folder where the output images are saved. + /// @return A reference to the current object. + Slicer360ToPerspective & SetOutputFolder(std::string const& folder); /// @brief Computes the coverage of the 360-degree image by the cameras added to the slicer. /// @param width Width of the generated 360-degree image containing the results of the analysis. /// @param raw If true, the output is the number of cameras that see the pixel of the 360 image (8-bit monochrome image). If false, the output is a colored RGB image. /// @return The coverage of the 360-degree image. - Image ComputeCoverage(int width, bool raw = false); + Image ComputeCoverage(int width, bool raw = false) const; + + /// @brief Projects the 360-degree image to a perspective image using the specified camera. + /// @param camera The camera used to project the 360-degree image. + /// @return The generated perspective image. + Image ProjectToCamera(Camera const& camera, bool parallel = true) const; + + /// @brief Projects the 360-degree image to perspective images using the specified cameras. + /// @param cameras A vector of cameras used to project the 360-degree image. + /// @param parallel If true, the projection is done in parallel using all the available cores. + /// @return A vector of generated perspective images. + std::vector ProjectToCameras(std::vector const& cameras_, bool parallel = true) const; + + /// @brief Projects the 360-degree image to perspective images using all the cameras added to the slicer. + /// @param parallel If true, the projection is done in parallel using all the available cores. + /// @return A vector of generated perspective images. + std::vector ProjectToAllCameras(bool parallel = true) const; std::vector cameras;//!< A vector of cameras that are used to convert the 360-degree image to perspective images. private: - + /// @brief Computes the projection of the 360-degree image to a perspective image using the specified camera on a single pixel of the output image. + /// @param camera The camera used to project the 360-degree image. + /// @param i The y-coordinate of the pixel of the output image (row). + /// @param j The x-coordinate of the pixel of the output image (column). + /// @return The color of the pixel of the output image. + Eigen::Vector3i ProjectToCameraPixel(Camera const& camera, int i, int j) const; + + Image panorama_image; //!< The 360-degree input image. + int output_image_width; //!< Width of the output images in pixels. + int output_image_height; //!< Height of the output images in pixels. + Image::InterpMethod interpolation_method; //!< Interpolation method used to project the 360-degree image to perspective images. + std::string output_folder; //!< Folder where the output images are saved. }; #endif diff --git a/cpp/src/Image.cpp b/cpp/src/Image.cpp index c56fae3..a494d04 100644 --- a/cpp/src/Image.cpp +++ b/cpp/src/Image.cpp @@ -9,10 +9,6 @@ Image::Image(Image const& other) : width(other.width), height(other.height), dep std::memcpy(data, other.data, width * height * depth); } -Image::~Image() { - delete[] data; -} - Image::Image(std::string const& filename) { // Load image data using stb_image.h unsigned char* image_data = stbi_load(filename.c_str(), &width, &height, &depth, 0); @@ -31,6 +27,25 @@ Image::Image(std::string const& filename) { } } +Image::~Image() { + delete[] data; +} + +Image & Image::operator=(Image const& other) { + // Delete current data + delete[] data; + + // Copy image attributes + width = other.width; + height = other.height; + depth = other.depth; + + // Allocate new data and copy the data from other.data. + data = new unsigned char[width * height * depth]; + std::memcpy(data, other.data, width * height * depth); + return *this; +} + int Image::Save(std::string const& filename) const { if(filename.size() > 4 && filename.substr(filename.size() - 4) == ".png") return stbi_write_png(filename.c_str(), width, height, depth, (const void *)data, 0); diff --git a/cpp/src/Slicer360ToPerspective.cpp b/cpp/src/Slicer360ToPerspective.cpp index 447487b..e790e4d 100644 --- a/cpp/src/Slicer360ToPerspective.cpp +++ b/cpp/src/Slicer360ToPerspective.cpp @@ -1,13 +1,30 @@ #include #include -Slicer360ToPerspective::Slicer360ToPerspective() -{ - +#ifndef DO_NOT_USE_OPENMP +#include +#endif + +Slicer360ToPerspective::Slicer360ToPerspective(Image const& panorama_image_) : panorama_image(panorama_image_), output_image_width(2), interpolation_method(Image::InterpMethod::BILINEAR), output_folder(".") +{} + +Slicer360ToPerspective & Slicer360ToPerspective::SetOutputImageSize(int width, int height) { + output_image_width = (width > 0) ? width : 1024; + output_image_height = (height > 0) ? height : output_image_width; + return *this; } -Image Slicer360ToPerspective::ComputeCoverage(int width, bool raw) -{ +Slicer360ToPerspective & Slicer360ToPerspective::SetInterpolationMethod(Image::InterpMethod const& interpolation_method_) { + interpolation_method = interpolation_method_; + return *this; +} + +Slicer360ToPerspective & Slicer360ToPerspective::SetOutputFolder(std::string const& folder) { + output_folder = folder; + return *this; +} + +Image Slicer360ToPerspective::ComputeCoverage(int width, bool raw) const { int height = width/2; Image coverage(width, height, 1); @@ -32,3 +49,53 @@ Image Slicer360ToPerspective::ComputeCoverage(int width, bool raw) else return coverage.Normalized().Colorized(Image::Colormap::PARULA); } + +Eigen::Vector3i Slicer360ToPerspective::ProjectToCameraPixel(Camera const& camera, int i, int j) const { + // Get the ray direction in inertial frame by projecting the pixel to the sphere + // Eigen::Vector2d Camera::PixelToNormalizedCoordinates(unsigned int i, unsigned int j, unsigned int width, unsigned int height) { + Eigen::Vector2d p_sensor = Camera::PixelToNormalizedCoordinates(i, j, output_image_width, output_image_height); + p_sensor[0] *= -1; + p_sensor[1] *= ((double)output_image_height)/((double)output_image_width);// Take aspect ratio into account. + + // Compute ray direction from sensor coordinates + Eigen::Vector3d ray_dir = camera.ComputeRayDirInInertialFrame(p_sensor); + Eigen::Vector3d ray_dir_sph = cart2sph(ray_dir); + Eigen::Vector2d p_equi = sph2equirectangular_d(ray_dir_sph, output_image_width, output_image_height); + return panorama_image.GetPixelInterp(p_equi, interpolation_method); +} + +Image Slicer360ToPerspective::ProjectToCamera(Camera const& camera, bool parallel) const { + Image image(output_image_width, output_image_height, 3); + if(parallel) { + #ifndef DO_NOT_USE_OPENMP + #pragma omp parallel for + #endif + for(int i = 0 ; i < output_image_height ; i++) + for(int j = 0 ; j < output_image_width ; j++) + image.SetPixel(i, j, ProjectToCameraPixel(camera, i, j)); + } else { + for(int i = 0 ; i < output_image_height ; i++) + for(int j = 0 ; j < output_image_width ; j++) + image.SetPixel(i, j, ProjectToCameraPixel(camera, i, j)); + } + return image; +} + +std::vector Slicer360ToPerspective::ProjectToCameras(std::vector const& cameras_, bool parallel) const { + std::vector images(cameras_.size()); + if(parallel) { + #ifndef DO_NOT_USE_OPENMP + #pragma omp parallel for + #endif + for(unsigned int i = 0 ; i < cameras_.size(); i++) + images[i] = ProjectToCamera(cameras_[i], false);// Parallelization is already done at the camera level. + } else { + for(unsigned int i = 0 ; i < cameras_.size(); i++) + images[i] = ProjectToCamera(cameras_[i], false);// Parallelization is already done at the camera level. + } + return images; +} + +std::vector Slicer360ToPerspective::ProjectToAllCameras(bool parallel) const { + return ProjectToCameras(cameras, parallel); +}