diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a12379f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 2.8) + +project( Sphere2Cube ) + +set (CMAKE_CXX_STANDARD 11) +include_directories( ${OpenCV_INCLUDE_DIRS} ) + +find_package( OpenCV 2 REQUIRED ) +add_executable( main main.cpp sphere2cube.cpp ) +target_link_libraries( main ${OpenCV_LIBS} ) diff --git a/README.md b/README.md index 27b90ef..7367d7c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,7 @@ # sphere2cube -sphere2cube c++ version + +Tools to convert equirectangular projection 360 panoramas in 6 faces of the cube. + +Migrate from a [python version](https://github.com/flash286/sphere2cube). + +## Usage diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..0e93eac --- /dev/null +++ b/main.cpp @@ -0,0 +1,26 @@ +#include +#include + +#include +#include "sphere2cube.h" + +int main(int argc, char** argv){ + Sphere2Cube s2c(540); + cv::Mat image = cv::imread(argv[1], CV_LOAD_IMAGE_COLOR); + Faces cube; + + auto t1 = std::chrono::steady_clock::now(); + s2c.transform(image, cube); + auto t2 = std::chrono::steady_clock::now(); + + printf("Cost %f s.\n", std::chrono::duration_cast >(t2 - t1).count()); + + cv::imwrite("up.jpg", cube.faces[0]); + cv::imwrite("front.jpg", cube.faces[1]); + cv::imwrite("right.jpg", cube.faces[2]); + cv::imwrite("back.jpg", cube.faces[3]); + cv::imwrite("left.jpg", cube.faces[4]); + cv::imwrite("down.jpg", cube.faces[5]); + + return 0; +} diff --git a/sphere2cube.cpp b/sphere2cube.cpp new file mode 100644 index 0000000..ef2d729 --- /dev/null +++ b/sphere2cube.cpp @@ -0,0 +1,150 @@ +#define _USE_MATH_DEFINES +#include +#include + +#include "sphere2cube.h" + +using std::tie; + +typedef std::pair vec2f; +typedef std::vector VF; +typedef std::vector VVF; + +const float pi = M_PI; +const float doub_pi = pi*2; +const float half_pi = pi/2.0; +const float inv_pi = 1/pi; + +Sphere2Cube::Sphere2Cube(int TILESIZE){ + + tile_size = TILESIZE; + half_size = (TILESIZE - 1.0) / 2; + inv_half_size = 1/half_size; + + puts("Sphere2Cube : Perform cache angles..."); + + cache_zp = VVF(TILESIZE, VF(TILESIZE)); + cache_zm = VVF(TILESIZE, VF(TILESIZE)); + cache_xypm = VVF(TILESIZE, VF(TILESIZE)); + cache_phi = VVF(TILESIZE, VF(TILESIZE)); + + for(int tile_y = 0; tile_y < tile_size; tile_y++){ + float y = tile_y*inv_half_size - 1; + for(int tile_x = 0; tile_x < tile_size; tile_x++){ + float x = tile_x*inv_half_size - 1; + float inv_r = 1/sqrt(x*x + y*y + 1); + cache_zp[tile_y][tile_x] = acos(inv_r); + cache_zm[tile_y][tile_x] = acos(-inv_r); + cache_xypm[tile_y][tile_x] = acos(y*inv_r); + if(x != 0) + cache_phi[tile_y][tile_x] = atan(y/x); + } + } + + puts("Sphere2Cube : Perform cache angles ok."); + + face_func[0] = &Sphere2Cube::func_up; + face_func[1] = &Sphere2Cube::func_front; + face_func[2] = &Sphere2Cube::func_right; + face_func[3] = &Sphere2Cube::func_back; + face_func[4] = &Sphere2Cube::func_left; + face_func[5] = &Sphere2Cube::func_down; + + return; +} + +void Sphere2Cube::transform(const cv::Mat& sphere_image, Faces& ret){ + + std::thread prc[6]; + + for(int lx = 0;lx < 6;lx++){ + prc[lx] = std::thread([this, lx, sphere_image, &ret](){ + int sphere_height = sphere_image.rows, sphere_width = sphere_image.cols; + ret.faces[lx].create(tile_size, tile_size, CV_8UC3); + for(int tile_y = 0; tile_y < tile_size; tile_y++){ + for(int tile_x = 0; tile_x < tile_size; tile_x++){ + float theta, phi; + tie(theta, phi) = (this->face_func[lx])(*this, tile_y, tile_x); + int sp_x = this->phi2width(sphere_width, phi); + int sp_y = this->theta2height(sphere_height, theta); + ret.faces[lx].at(tile_y, tile_x) = sphere_image.at(sp_y, sp_x); + } + } + }); + } + + for(int lx = 0;lx < 6;lx++) + prc[lx].join(); + + return; +} + +float Sphere2Cube::update_phi(float phi, int major_dir, int minor_dir, float major_m, float major_p, float minor_m, float minor_p) const{ + if(major_dir < half_size) + return phi + major_m; + else if(major_dir > half_size) + return phi + major_p; + else if(minor_dir < half_size) + return minor_m; + else + return minor_p; +} + +vec2f Sphere2Cube::func_up(int tile_y, int tile_x){ + float theta = cache_zp[tile_y][tile_x]; + float phi = cache_phi[tile_x][tile_y]; + phi = update_phi(phi, tile_y, tile_x, pi, 0, -half_pi, half_pi); + return vec2f(theta, phi); +} + +vec2f Sphere2Cube::func_front(int tile_y, int tile_x){ + float theta = cache_xypm[tile_size - tile_y - 1][tile_size - tile_x - 1]; + float phi = cache_phi[tile_x][tile_size - 1]; + phi = update_phi(phi, tile_y, tile_x, 0, 0, -half_pi, half_pi); + return vec2f(theta, phi); +} + +vec2f Sphere2Cube::func_right(int tile_y, int tile_x){ + float theta, phi; + tie(theta, phi) = func_front(tile_y, tile_x); + phi += half_pi; + if(phi > doub_pi) phi -= doub_pi; + return vec2f(theta, phi); +} + +vec2f Sphere2Cube::func_back(int tile_y, int tile_x){ + float theta, phi; + tie(theta, phi) = func_front(tile_y, tile_x); + phi += 2*half_pi; + if(phi > doub_pi) phi -= doub_pi; + return vec2f(theta, phi); +} + +vec2f Sphere2Cube::func_left(int tile_y, int tile_x){ + float theta, phi; + tie(theta, phi) = func_front(tile_y, tile_x); + phi += 3*half_pi; + if(phi > doub_pi) phi -= doub_pi; + return vec2f(theta, phi); +} + +vec2f Sphere2Cube::func_down(int tile_y, int tile_x){ + float theta = cache_zm[tile_y][tile_x]; + float phi = cache_phi[tile_x][tile_size - tile_y - 1]; + phi = update_phi(phi, tile_y, tile_x, 0, pi, -half_pi, half_pi); + return vec2f(theta, phi); +} + +float Sphere2Cube::phi2width(int width, float phi) const{ + float x = 0.5 * width*(phi*inv_pi + 1); + if(x < 1) + return x + width; + else if(x > width) + return x - width; + else + return x; +} + +float Sphere2Cube::theta2height(int height, float theta) const{ + return height * theta * inv_pi; +} diff --git a/sphere2cube.h b/sphere2cube.h new file mode 100644 index 0000000..6b2819b --- /dev/null +++ b/sphere2cube.h @@ -0,0 +1,45 @@ +#ifndef SPHERE2CUBE_HEADER +#define SPHERE2CUBE_HEADER + +#include +#include +#include + +#include + +struct Faces{ + cv::Mat faces[6]; +}; + +class Sphere2Cube{ +public: + Sphere2Cube(int TILESIZE); + + void transform(const cv::Mat& sphere_image, Faces& ret); + +private: + typedef std::pair vec2f; + typedef std::vector VF; + typedef std::vector VVF; + + int tile_size; + float half_size, inv_half_size; + + VVF cache_zp, cache_zm, cache_xypm, cache_phi; + + std::function face_func[6]; + + float update_phi(float phi, int major_dir, int minor_dir, float major_m, float major_p, float minor_m, float minor_p) const; + + vec2f func_up(int tile_y, int tile_x); + vec2f func_front(int tile_y, int tile_x); + vec2f func_right(int tile_y, int tile_x); + vec2f func_back(int tile_y, int tile_x); + vec2f func_left(int tile_y, int tile_x); + vec2f func_down(int tile_y, int tile_x); + + float phi2width(int width, float phi) const; + float theta2height(int height, float theta) const; +}; + +#endif