Cubic to equirectangular projection algorithm

后端 未结 5 1956

I have a cube map texture which defines a surrounding, however I need to pass it to a program which only works with latitude/longitude maps. I am really at lost here on how to d

相关标签:
5条回答
  • 2021-02-03 16:21

    I think from your algorithm in Python you might have inverted x and y in the calculation of theta and phi.

    def spherical_coordinates(x, y):
        return (math.pi*((y/h) - 0.5), 2*math.pi*x/(2*h), 1.0)
    

    from Paul Bourke's website here

    theta = x pi phi = y pi / 2

    and in your code you are using y in the theta calculation and x in the phi calculation.

    Correct me if I am wrong.

    0 讨论(0)
  • 2021-02-03 16:29

    A general procedure for projecting raster images like this is:

    for each pixel of the destination image:
        calculate the corresponding unit vector in 3-dimensional space
        calculate the x,y coordinate for that vector in the source image
        sample the source image at that coordinate and assign the value to the destination pixel
    

    The last step is simply interpolation. We will focus on the other two steps.

    The unit vector for a given latitude and longitude is (+z towards the north pole, +x towards the prime meridian):

    x = cos(lat)*cos(lon)
    y = cos(lat)*sin(lon)
    z = sin(lat)
    

    Assume the cube is +/- 1 unit around the origin (i.e. 2x2x2 overall size). Once we have the unit vector, we can find the face of the cube it's on by looking at the element with the largest absolute value. For example, if our unit vector was <0.2099, -0.7289, 0.6516>, then the y element has the largest absolute value. It's negative, so the point will be found on the -y face of the cube. Normalize the other two coordinates by dividing by the y magnitude to get the location within that face. So, the point will be at x=0.2879, z=0.8939 on the -y face.

    0 讨论(0)
  • 2021-02-03 16:30

    So, I found a solution mixing this article on spherical coordinates from wikipedia and the Section 3.8.10 from the OpenGL 4.1 specification (plus some hacks to make it work). So, assuming that the cubic image has a height h_o and width w_o, the equirectangular will have a height h = w_o / 3 and a width w = 2 * h. Now for each pixel (x, y) 0 <= x <= w, 0 <= y <= h in the equirectangular projection, we want to find the corresponding pixel in the cubic projection, I solve it using the following code in python (I hope I didn't make mistakes while translating it from C)

    import math
    
    # from wikipedia
    def spherical_coordinates(x, y):
        return (math.pi*((y/h) - 0.5), 2*math.pi*x/(2*h), 1.0)
    
    # from wikipedia
    def texture_coordinates(theta, phi, rho):
        return (rho * math.sin(theta) * math.cos(phi),
                rho * math.sin(theta) * math.sin(phi),
                rho * math.cos(theta))
    
    FACE_X_POS = 0
    FACE_X_NEG = 1
    FACE_Y_POS = 2
    FACE_Y_NEG = 3
    FACE_Z_POS = 4
    FACE_Z_NEG = 5
    
    # from opengl specification
    def get_face(x, y, z):
        largest_magnitude = max(x, y, z)
        if largest_magnitude - abs(x) < 0.00001:
            return FACE_X_POS if x < 0 else FACE_X_NEG
        elif largest_magnitude - abs(y) < 0.00001:
            return FACE_Y_POS if y < 0 else FACE_Y_NEG
        elif largest_magnitude - abs(z) < 0.00001:
            return FACE_Z_POS if z < 0 else FACE_Z_NEG
    
    # from opengl specification
    def raw_face_coordinates(face, x, y, z):
        if face == FACE_X_POS:
            return (-z, -y, x)
        elif face == FACE_X_NEG:
            return (-z, y, -x)
        elif face == FACE_Y_POS:
            return (-x, -z, -y)
        elif face == FACE_Y_NEG:
            return (-x, z, -y)
        elif face == FACE_Z_POS:
            return (-x, y, -z)
        elif face == FACE_Z_NEG:
            return (-x, -y, z)
    
    # computes the topmost leftmost coordinate of the face in the cube map
    def face_origin_coordinates(face):
        if face == FACE_X_POS:
            return (2*h, h)
        elif face == FACE_X_NEG:
            return (0, 2*h)
        elif face == FACE_Y_POS:
            return (h, h)
        elif face == FACE_Y_NEG:
            return (h, 3*h)
        elif face == FACE_Z_POS:
            return (h, 0)
        elif face == FACE_Z_NEG:
            return (h, 2*h)
    
    # from opengl specification
    def raw_coordinates(xc, yc, ma):
        return ((xc/abs(ma) + 1) / 2, (yc/abs(ma) + 1) / 2)
    
    
    def normalized_coordinates(face, x, y):
        face_coords = face_origin_coordinates(face)
        normalized_x = int(math.floor(x * h + 0.5))
        normalized_y = int(math.floor(y * h + 0.5))
        # eliminates black pixels
        if normalized_x == h:
          --normalized_x
        if normalized_y == h:
          --normalized_y
        return (face_coords[0] + normalized_x, face_coords[1] + normalized_y)
    
    def find_corresponding_pixel(x, y):
        spherical = spherical_coordinates(x, y)
        texture_coords = texture_coordinates(spherical[0], spherical[1], spherical[2])
        face = get_face(texture_coords[0], texture_coords[1], texture_coords[2])
    
        raw_face_coords = raw_face_coordinates(face, texture_coords[0], texture_coords[1], texture_coords[2])
        cube_coords = raw_coordinates(raw_face_coords[0], raw_face_coords[1], raw_face_coords[2])
        # this fixes some faces being rotated 90°
        if face in [FACE_X_NEG, FACE_X_POS]:
          cube_coords = (cube_coords[1], cube_coords[0])
        return normalized_coordinates(face, cube_coords[0], cube_coords[1])    
    

    at the end we just call find_corresponding_pixel for each pixel in the equirectangular projection

    0 讨论(0)
  • 2021-02-03 16:32

    Project changed name to libcube2cyl. Same goodness, better working examples both in C and C++.


    Now also available in C.


    I happened to solve the exact same problem as you described.

    I wrote this tiny C++ lib called "Cube2Cyl", you can find the detailed explanation of algorithm here: Cube2Cyl

    Please find the source code from github: Cube2Cyl

    It is released under MIT licence, use it for free!

    0 讨论(0)
  • 2021-02-03 16:35

    I'd like to share my MATLAB implementation of this conversion. I also borrowed from the OpenGL 4.1 specification, Chapter 3.8.10 (found here), as well as Paul Bourke's website (found here). Make sure you look under the subheading: Converting to and from 6 cubic environment maps and a spherical map.

    I also used Sambatyon's post above as inspiration. It started off as a port from Python over to MATLAB, but I made the code so that it is completely vectorized (i.e. no for loops). I also take the cubic image and split it up into 6 separate images, as the application I'm building has the cubic image in this format. Also there is no error checking with the code, and that this assumes that all of the cubic images are of the same size (n x n). This also assumes that the images are in RGB format. If you'd like to do this for a monochromatic image, simply comment out those lines of code that require access to more than one channel. Here we go!

    function [out] = cubic2equi(top, bottom, left, right, front, back)
    
    % Height and width of equirectangular image
    height = size(top, 1);
    width = 2*height;
    
    % Flags to denote what side of the cube we are facing
    % Z-axis is coming out towards you
    % X-axis is going out to the right
    % Y-axis is going upwards
    % Assuming that the front of the cube is towards the
    % negative X-axis
    FACE_Z_POS = 1; % Left
    FACE_Z_NEG = 2; % Right
    FACE_Y_POS = 3; % Top
    FACE_Y_NEG = 4; % Bottom
    FACE_X_NEG = 5; % Front 
    FACE_X_POS = 6; % Back
    
    % Place in a cell array
    stackedImages{FACE_Z_POS} = left;
    stackedImages{FACE_Z_NEG} = right;
    stackedImages{FACE_Y_POS} = top;
    stackedImages{FACE_Y_NEG} = bottom;
    stackedImages{FACE_X_NEG} = front;
    stackedImages{FACE_X_POS} = back;
    
    % Place in 3 3D matrices - Each matrix corresponds to a colour channel
    imagesRed = uint8(zeros(height, height, 6));
    imagesGreen = uint8(zeros(height, height, 6));
    imagesBlue = uint8(zeros(height, height, 6));
    
    % Place each channel into their corresponding matrices
    for i = 1 : 6
        im = stackedImages{i};
        imagesRed(:,:,i) = im(:,:,1);
        imagesGreen(:,:,i) = im(:,:,2);
        imagesBlue(:,:,i) = im(:,:,3);
    end
    
    % For each co-ordinate in the normalized image...
    [X, Y] = meshgrid(1:width, 1:height);
    
    % Obtain the spherical co-ordinates
    Y = 2*Y/height - 1;
    X = 2*X/width - 1;
    sphereTheta = X*pi;
    spherePhi = (pi/2)*Y;
    
    texX = cos(spherePhi).*cos(sphereTheta);
    texY = sin(spherePhi);
    texZ = cos(spherePhi).*sin(sphereTheta);
    
    % Figure out which face we are facing for each co-ordinate
    % First figure out the greatest absolute magnitude for each point
    comp = cat(3, texX, texY, texZ);
    [~,ind] = max(abs(comp), [], 3);
    maxVal = zeros(size(ind));
    % Copy those values - signs and all
    maxVal(ind == 1) = texX(ind == 1);
    maxVal(ind == 2) = texY(ind == 2);
    maxVal(ind == 3) = texZ(ind == 3);
    
    % Set each location in our equirectangular image, figure out which
    % side we are facing
    getFace = -1*ones(size(maxVal));
    
    % Back
    ind = abs(maxVal - texX) < 0.00001 & texX < 0;
    getFace(ind) = FACE_X_POS;
    
    % Front
    ind = abs(maxVal - texX) < 0.00001 & texX >= 0;
    getFace(ind) = FACE_X_NEG;
    
    % Top
    ind = abs(maxVal - texY) < 0.00001 & texY < 0;
    getFace(ind) = FACE_Y_POS;
    
    % Bottom
    ind = abs(maxVal - texY) < 0.00001 & texY >= 0;
    getFace(ind) = FACE_Y_NEG;
    
    % Left
    ind = abs(maxVal - texZ) < 0.00001 & texZ < 0;
    getFace(ind) = FACE_Z_POS;
    
    % Right
    ind = abs(maxVal - texZ) < 0.00001 & texZ >= 0;
    getFace(ind) = FACE_Z_NEG;
    
    % Determine the co-ordinates along which image to sample
    % based on which side we are facing
    rawX = -1*ones(size(maxVal));
    rawY = rawX;
    rawZ = rawX;
    
    % Back
    ind = getFace == FACE_X_POS;
    rawX(ind) = -texZ(ind);
    rawY(ind) = texY(ind);
    rawZ(ind) = texX(ind);
    
    % Front
    ind = getFace == FACE_X_NEG;
    rawX(ind) = texZ(ind);
    rawY(ind) = texY(ind);
    rawZ(ind) = texX(ind);
    
    % Top
    ind = getFace == FACE_Y_POS;
    rawX(ind) = texZ(ind);
    rawY(ind) = texX(ind);
    rawZ(ind) = texY(ind);
    
    % Bottom
    ind = getFace == FACE_Y_NEG;
    rawX(ind) = texZ(ind);
    rawY(ind) = -texX(ind);
    rawZ(ind) = texY(ind);
    
    % Left
    ind = getFace == FACE_Z_POS;
    rawX(ind) = texX(ind);
    rawY(ind) = texY(ind);
    rawZ(ind) = texZ(ind);
    
    % Right
    ind = getFace == FACE_Z_NEG;
    rawX(ind) = -texX(ind);
    rawY(ind) = texY(ind);
    rawZ(ind) = texZ(ind);
    
    % Concatenate all for later
    rawCoords = cat(3, rawX, rawY, rawZ);
    
    % Finally determine co-ordinates (normalized)
    cubeCoordsX = ((rawCoords(:,:,1) ./ abs(rawCoords(:,:,3))) + 1) / 2;
    cubeCoordsY = ((rawCoords(:,:,2) ./ abs(rawCoords(:,:,3))) + 1) / 2;
    cubeCoords = cat(3, cubeCoordsX, cubeCoordsY);
    
    % Now obtain where we need to sample the image
    normalizedX = round(cubeCoords(:,:,1) * height);
    normalizedY = round(cubeCoords(:,:,2) * height);
    
    % Just in case.... cap between [1, height] to ensure
    % no out of bounds behaviour
    normalizedX(normalizedX < 1) = 1;
    normalizedX(normalizedX > height) = height;
    normalizedY(normalizedY < 1) = 1;
    normalizedY(normalizedY > height) = height;
    
    % Place into a stacked matrix
    normalizedCoords = cat(3, normalizedX, normalizedY);
    
    % Output image allocation
    out = uint8(zeros([size(maxVal) 3]));
    
    % Obtain column-major indices on where to sample from the
    % input images
    % getFace will contain which image we need to sample from
    % based on the co-ordinates within the equirectangular image
    ind = sub2ind([height height 6], normalizedCoords(:,:,2), ...
        normalizedCoords(:,:,1), getFace);
    
    % Do this for each channel
    out(:,:,1) = imagesRed(ind);
    out(:,:,2) = imagesGreen(ind);
    out(:,:,3) = imagesBlue(ind);
    

    I've also made the code publicly available through github and you can go here for it. Included is the main conversion script, a test script to show its use and a sample set of 6 cubic images pulled from Paul Bourke's website. I hope this is useful!

    0 讨论(0)
提交回复
热议问题