web-dev-qa-db-fra.com

Convertir un panorama équirectangulaire 2: 1 en carte de cube

Je travaille actuellement sur une simple visionneuse de panorama 3D pour un site Web. Pour des raisons de performances mobiles, j'utilise le three.jsrendu CSS3 . Cela nécessite une carte de cube, divisée en 6 images simples.

J'enregistre les images sur l'iPhone avec l'application Google Photosphere ou des applications similaires qui créent des panoramas équirectangulaires 2: 1. J'ai ensuite redimensionné et converti ceux-ci en cubemap avec ce site Web: http://gonchar.me/panorama/ (Flash)

De préférence, j'aimerais faire la conversion moi-même, soit à la volée en trois.js, si cela est possible, ou dans Photoshop. J'ai trouvé les actions Photoshop d'Andrew Hazelden et elles semblent assez proches, mais aucune conversion directe n'est disponible. Existe-t-il un moyen mathématique de convertir ceux-ci ou une sorte de script qui le fait? Si possible, j'aimerais éviter d'utiliser une application 3D telle que Blender.

Peut-être que c'est un long coup, mais je pensais que je demanderais. Je connais bien le javascript, mais je suis assez nouveau pour three.js. J’hésite également à faire confiance à la fonctionnalité WebGL, qui semble être lente ou défectueuse sur les appareils mobiles. Le support est également toujours irrégulier.

39
oelna

Si vous voulez le faire côté serveur, il y a beaucoup d'options. http://www.imagemagick.org/ propose de nombreux outils en ligne de commande permettant de découper votre image en morceaux. Vous pouvez mettre la commande à cet effet dans un script et l'exécuter à chaque fois que vous avez une nouvelle image.

Il est difficile de dire exactement quel algorithme est utilisé dans le programme. Nous pouvons essayer de faire de l'ingénierie inverse en intégrant une grille carrée dans le programme. J'ai utilisé un grid de wikipedia

64 by 64 grid

Qui donne projected gridCela nous donne un indice sur la construction de la boîte. 

Sphère imageuse avec lignes de latitude et de longitude et un cube l’entourant. Maintenant, projeter à partir du point situé au centre de la sphère produit une grille déformée sur le cube.

Prendre mathématiquement les coordonnées polaires r, θ, ø, pour la sphère r = 1, 0 <θ <π, -π/4 <ø <7π/4

  • x = r sin θ cos ø 
  • y = r sin θ sin ø 
  • z = r cos θ

projeter centralement ceux-ci sur le cube Nous divisons d'abord en quatre régions par la latitude -π/4 <ø <π/4, π/4 <ø <3π/4, 3π/4 <ø <5π/4, 5π/4 <ø <7π/4. Ceux-ci seront soit projeter à l'un des quatre côtés en haut ou en bas. 

Supposons que nous sommes dans le premier côté -π/4 <ø <π/4. La projection centrale de .__ (sin θ cos ø, sin θ sin ø, cos θ) sera (un sin θ cos ø, un sin θ sin ø, un cos θ) qui frappe le plan x = 1 lorsque

  • un sin θ cos ø = 1

alors

  • a = 1/(sin θ cos ø)

et le point projeté est

  • (1, tan ø, cot θ/cos ø)

Si | lit θ/cos ø | <1 ce sera sur la face avant. Sinon, il sera projeté en haut ou en bas et vous aurez besoin d'une projection différente pour cela. Un meilleur test pour le sommet utilise le fait que la valeur minimale de cos ø sera cos π/4 = 1/√2, de sorte que le point projeté est toujours au sommet si cot θ/(1/√2)> 1 ou tan θ <1/√2. Cela correspond à θ <35º ou 0,615 radian. 

Mettez cela ensemble en python

import sys
from PIL import Image
from math import pi,sin,cos,tan

def cot(angle):
    return 1/tan(angle)

# Project polar coordinates onto a surrounding cube
# assume ranges theta is [0,pi] with 0 the north poll, pi south poll
# phi is in range [0,2pi] 
def projection(theta,phi): 
        if theta<0.615:
            return projectTop(theta,phi)
        Elif theta>2.527:
            return projectBottom(theta,phi)
        Elif phi <= pi/4 or phi > 7*pi/4:
            return projectLeft(theta,phi)
        Elif phi > pi/4 and phi <= 3*pi/4:
            return projectFront(theta,phi)
        Elif phi > 3*pi/4 and phi <= 5*pi/4:
            return projectRight(theta,phi)
        Elif phi > 5*pi/4 and phi <= 7*pi/4:
            return projectBack(theta,phi)

def projectLeft(theta,phi):
        x = 1
        y = tan(phi)
        z = cot(theta) / cos(phi)
        if z < -1:
            return projectBottom(theta,phi)
        if z > 1:
            return projectTop(theta,phi)
        return ("Left",x,y,z)

def projectFront(theta,phi):
        x = tan(phi-pi/2)
        y = 1
        z = cot(theta) / cos(phi-pi/2)
        if z < -1:
            return projectBottom(theta,phi)
        if z > 1:
            return projectTop(theta,phi)
        return ("Front",x,y,z)

def projectRight(theta,phi):
        x = -1
        y = tan(phi)
        z = -cot(theta) / cos(phi)
        if z < -1:
            return projectBottom(theta,phi)
        if z > 1:
            return projectTop(theta,phi)
        return ("Right",x,-y,z)

def projectBack(theta,phi):
        x = tan(phi-3*pi/2)
        y = -1
        z = cot(theta) / cos(phi-3*pi/2)
        if z < -1:
            return projectBottom(theta,phi)
        if z > 1:
            return projectTop(theta,phi)
        return ("Back",-x,y,z)

def projectTop(theta,phi):
        # (a sin θ cos ø, a sin θ sin ø, a cos θ) = (x,y,1)
        a = 1 / cos(theta)
        x = tan(theta) * cos(phi)
        y = tan(theta) * sin(phi)
        z = 1
        return ("Top",x,y,z)

def projectBottom(theta,phi):
        # (a sin θ cos ø, a sin θ sin ø, a cos θ) = (x,y,-1)
        a = -1 / cos(theta)
        x = -tan(theta) * cos(phi)
        y = -tan(theta) * sin(phi)
        z = -1
        return ("Bottom",x,y,z)

# Convert coords in cube to image coords 
# coords is a Tuple with the side and x,y,z coords
# Edge is the length of an Edge of the cube in pixels
def cubeToImg(coords,Edge):
    if coords[0]=="Left":
        (x,y) = (int(Edge*(coords[2]+1)/2), int(Edge*(3-coords[3])/2) )
    Elif coords[0]=="Front":
        (x,y) = (int(Edge*(coords[1]+3)/2), int(Edge*(3-coords[3])/2) )
    Elif coords[0]=="Right":
        (x,y) = (int(Edge*(5-coords[2])/2), int(Edge*(3-coords[3])/2) )
    Elif coords[0]=="Back":
        (x,y) = (int(Edge*(7-coords[1])/2), int(Edge*(3-coords[3])/2) )
    Elif coords[0]=="Top":
        (x,y) = (int(Edge*(3-coords[1])/2), int(Edge*(1+coords[2])/2) )
    Elif coords[0]=="Bottom":
        (x,y) = (int(Edge*(3-coords[1])/2), int(Edge*(5-coords[2])/2) )
    return (x,y)

# convert the in image to out image
def convert(imgIn,imgOut):
    inSize = imgIn.size
    outSize = imgOut.size
    inPix = imgIn.load()
    outPix = imgOut.load()
    Edge = inSize[0]/4   # the length of each Edge in pixels
    for i in xrange(inSize[0]):
        for j in xrange(inSize[1]):
            pixel = inPix[i,j]
            phi = i * 2 * pi / inSize[0]
            theta = j * pi / inSize[1]
            res = projection(theta,phi)
            (x,y) = cubeToImg(res,Edge)
            #if i % 100 == 0 and j % 100 == 0:
            #   print i,j,phi,theta,res,x,y
            if x >= outSize[0]:
                #print "x out of range ",x,res
                x=outSize[0]-1
            if y >= outSize[1]:
                #print "y out of range ",y,res
                y=outSize[1]-1
            outPix[x,y] = pixel

imgIn = Image.open(sys.argv[1])
inSize = imgIn.size
imgOut = Image.new("RGB",(inSize[0],inSize[0]*3/4),"black")
convert(imgIn,imgOut)
imgOut.show()

La fonction projection prend les valeurs theta et phi et renvoie les coordonnées dans un cube de -1 à 1 dans chaque direction. CubeToImg prend les coordonnées (x, y, z) et les traduit en coordonnées d'image de sortie.

L'algorithme ci-dessus semble obtenir la géométrie correcte en utilisant un image de buckingham palace nous obtenons cube map of buckingham palace Cela semble obtenir la plupart des lignes dans le pavage droit. 

Nous obtenons quelques artefacts d’image. Cela est dû au fait de ne pas avoir une carte de pixels de 1 à 1. Ce que nous devons faire, c'est utiliser une transformation inverse. Plutôt que de parcourir en boucle chaque pixel de la source et de trouver le pixel correspondant dans la cible, nous parcourons en boucle les images cibles et trouvons le pixel source correspondant le plus proche. 

import sys
from PIL import Image
from math import pi,sin,cos,tan,atan2,hypot,floor
from numpy import clip

# get x,y,z coords from out image pixels coords
# i,j are pixel coords
# face is face number
# Edge is Edge length
def outImgToXYZ(i,j,face,Edge):
    a = 2.0*float(i)/Edge
    b = 2.0*float(j)/Edge
    if face==0: # back
        (x,y,z) = (-1.0, 1.0-a, 3.0 - b)
    Elif face==1: # left
        (x,y,z) = (a-3.0, -1.0, 3.0 - b)
    Elif face==2: # front
        (x,y,z) = (1.0, a - 5.0, 3.0 - b)
    Elif face==3: # right
        (x,y,z) = (7.0-a, 1.0, 3.0 - b)
    Elif face==4: # top
        (x,y,z) = (b-1.0, a -5.0, 1.0)
    Elif face==5: # bottom
        (x,y,z) = (5.0-b, a-5.0, -1.0)
    return (x,y,z)

# convert using an inverse transformation
def convertBack(imgIn,imgOut):
    inSize = imgIn.size
    outSize = imgOut.size
    inPix = imgIn.load()
    outPix = imgOut.load()
    Edge = inSize[0]/4   # the length of each Edge in pixels
    for i in xrange(outSize[0]):
        face = int(i/Edge) # 0 - back, 1 - left 2 - front, 3 - right
        if face==2:
            rng = xrange(0,Edge*3)
        else:
            rng = xrange(Edge,edge*2)

        for j in rng:
            if j<Edge:
                face2 = 4 # top
            Elif j>=2*Edge:
                face2 = 5 # bottom
            else:
                face2 = face

            (x,y,z) = outImgToXYZ(i,j,face2,Edge)
            theta = atan2(y,x) # range -pi to pi
            r = hypot(x,y)
            phi = atan2(z,r) # range -pi/2 to pi/2
            # source img coords
            uf = ( 2.0*Edge*(theta + pi)/pi )
            vf = ( 2.0*Edge * (pi/2 - phi)/pi)
            # Use bilinear interpolation between the four surrounding pixels
            ui = floor(uf)  # coord of pixel to bottom left
            vi = floor(vf)
            u2 = ui+1       # coords of pixel to top right
            v2 = vi+1
            mu = uf-ui      # fraction of way across pixel
            nu = vf-vi
            # Pixel values of four corners
            A = inPix[ui % inSize[0],clip(vi,0,inSize[1]-1)]
            B = inPix[u2 % inSize[0],clip(vi,0,inSize[1]-1)]
            C = inPix[ui % inSize[0],clip(v2,0,inSize[1]-1)]
            D = inPix[u2 % inSize[0],clip(v2,0,inSize[1]-1)]
            # interpolate
            (r,g,b) = (
              A[0]*(1-mu)*(1-nu) + B[0]*(mu)*(1-nu) + C[0]*(1-mu)*nu+D[0]*mu*nu,
              A[1]*(1-mu)*(1-nu) + B[1]*(mu)*(1-nu) + C[1]*(1-mu)*nu+D[1]*mu*nu,
              A[2]*(1-mu)*(1-nu) + B[2]*(mu)*(1-nu) + C[2]*(1-mu)*nu+D[2]*mu*nu )

            outPix[i,j] = (int(round(r)),int(round(g)),int(round(b)))

imgIn = Image.open(sys.argv[1])
inSize = imgIn.size
imgOut = Image.new("RGB",(inSize[0],inSize[0]*3/4),"black")
convertBack(imgIn,imgOut)
imgOut.save(sys.argv[1].split('.')[0]+"Out2.png")
imgOut.show()

Les résultats de ceci sont Using the inverse transformation

70
Salix alba

Compte tenu de l'excellente réponse acceptée, je voulais ajouter mon implémentation c ++ correspondante, basée sur OpenCV

Pour ceux qui ne connaissent pas OpenCV, considérez Mat comme une image. Nous construisons d’abord deux cartes qui remappent de l’image équirectangulaire à notre face correspondante. Ensuite, nous faisons le gros du travail (c'est-à-dire un remappage avec interpolation) en utilisant OpenCV. 

Le code peut être rendu plus compact si la lisibilité n’est pas un problème. 

// Define our six cube faces. 
// 0 - 3 are side faces, clockwise order
// 4 and 5 are top and bottom, respectively
float faceTransform[6][2] = 
{ 
    {0, 0},
    {M_PI / 2, 0},
    {M_PI, 0},
    {-M_PI / 2, 0},
    {0, -M_PI / 2},
    {0, M_PI / 2}
};

// Map a part of the equirectangular panorama (in) to a cube face
// (face). The ID of the face is given by faceId. The desired
// width and height are given by width and height. 
inline void createCubeMapFace(const Mat &in, Mat &face, 
        int faceId = 0, const int width = -1, 
        const int height = -1) {

    float inWidth = in.cols;
    float inHeight = in.rows;

    // Allocate map
    Mat mapx(height, width, CV_32F);
    Mat mapy(height, width, CV_32F);

    // Calculate adjacent (ak) and opposite (an) of the
    // triangle that is spanned from the sphere center 
    //to our cube face.
    const float an = sin(M_PI / 4);
    const float ak = cos(M_PI / 4);

    const float ftu = faceTransform[faceId][0];
    const float ftv = faceTransform[faceId][1];

    // For each point in the target image, 
    // calculate the corresponding source coordinates. 
    for(int y = 0; y < height; y++) {
        for(int x = 0; x < width; x++) {

            // Map face pixel coordinates to [-1, 1] on plane
            float nx = (float)y / (float)height - 0.5f;
            float ny = (float)x / (float)width - 0.5f;

            nx *= 2;
            ny *= 2;

            // Map [-1, 1] plane coords to [-an, an]
            // thats the coordinates in respect to a unit sphere 
            // that contains our box. 
            nx *= an; 
            ny *= an; 

            float u, v;

            // Project from plane to sphere surface.
            if(ftv == 0) {
                // Center faces
                u = atan2(nx, ak);
                v = atan2(ny * cos(u), ak);
                u += ftu; 
            } else if(ftv > 0) { 
                // Bottom face 
                float d = sqrt(nx * nx + ny * ny);
                v = M_PI / 2 - atan2(d, ak);
                u = atan2(ny, nx);
            } else {
                // Top face
                float d = sqrt(nx * nx + ny * ny);
                v = -M_PI / 2 + atan2(d, ak);
                u = atan2(-ny, nx);
            }

            // Map from angular coordinates to [-1, 1], respectively.
            u = u / (M_PI); 
            v = v / (M_PI / 2);

            // Warp around, if our coordinates are out of bounds. 
            while (v < -1) {
                v += 2;
                u += 1;
            } 
            while (v > 1) {
                v -= 2;
                u += 1;
            } 

            while(u < -1) {
                u += 2;
            }
            while(u > 1) {
                u -= 2;
            }

            // Map from [-1, 1] to in texture space
            u = u / 2.0f + 0.5f;
            v = v / 2.0f + 0.5f;

            u = u * (inWidth - 1);
            v = v * (inHeight - 1);

            // Save the result for this pixel in map
            mapx.at<float>(x, y) = u;
            mapy.at<float>(x, y) = v; 
        }
    }

    // Recreate output image if it has wrong size or type. 
    if(face.cols != width || face.rows != height || 
        face.type() != in.type()) {
        face = Mat(width, height, in.type());
    }

    // Do actual resampling using OpenCV's remap
    remap(in, face, mapx, mapy, 
         CV_INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0));
}

Compte tenu de l'entrée suivante:

 enter image description here

Les faces suivantes sont générées: 

 enter image description here

Image reproduite avec l'aimable autorisation de Optonaut .

11
Emiswelt

J'ai écrit un script pour couper le cubemap généré en fichiers individuels (posx.png, negx.png, posy.png, negy.png, posz.png et negz.png). Il va également emballer les 6 fichiers dans un fichier .Zip.

La source est ici: https://github.com/dankex/compv/blob/master/3d-graphics/skybox/cubemap-cut.py

Vous pouvez modifier le tableau pour définir les fichiers d’image:

name_map = [ \
 ["", "", "posy", ""],
 ["negz", "negx", "posz", "posx"],
 ["", "", "negy", ""]]

Les fichiers convertis sont:

 enter image description here  enter image description here  enter image description here  enter image description here  enter image description here  enter image description here

9
Danke Xie

Voici une version (naïvement) modifiée de la réponse absolument fantastique de Salix Alba qui convertit un visage à la fois, crache six images différentes et conserve le type de fichier de l'image d'origine.

Mis à part le fait que la plupart des cas d'utilisation prévoient probablement six images distinctes, le principal avantage de la conversion d'une face à la fois est que le travail sur des images de grande taille nécessite beaucoup moins de mémoire. 

#!/usr/bin/env python
import sys
from PIL import Image
from math import pi, sin, cos, tan, atan2, hypot, floor
from numpy import clip

# get x,y,z coords from out image pixels coords
# i,j are pixel coords
# faceIdx is face number
# faceSize is Edge length
def outImgToXYZ(i, j, faceIdx, faceSize):
    a = 2.0 * float(i) / faceSize
    b = 2.0 * float(j) / faceSize

    if faceIdx == 0: # back
        (x,y,z) = (-1.0, 1.0 - a, 1.0 - b)
    Elif faceIdx == 1: # left
        (x,y,z) = (a - 1.0, -1.0, 1.0 - b)
    Elif faceIdx == 2: # front
        (x,y,z) = (1.0, a - 1.0, 1.0 - b)
    Elif faceIdx == 3: # right
        (x,y,z) = (1.0 - a, 1.0, 1.0 - b)
    Elif faceIdx == 4: # top
        (x,y,z) = (b - 1.0, a - 1.0, 1.0)
    Elif faceIdx == 5: # bottom
        (x,y,z) = (1.0 - b, a - 1.0, -1.0)

    return (x, y, z)

# convert using an inverse transformation
def convertFace(imgIn, imgOut, faceIdx):
    inSize = imgIn.size
    outSize = imgOut.size
    inPix = imgIn.load()
    outPix = imgOut.load()
    faceSize = outSize[0]

    for xOut in xrange(faceSize):
        for yOut in xrange(faceSize):
            (x,y,z) = outImgToXYZ(xOut, yOut, faceIdx, faceSize)
            theta = atan2(y,x) # range -pi to pi
            r = hypot(x,y)
            phi = atan2(z,r) # range -pi/2 to pi/2

            # source img coords
            uf = 0.5 * inSize[0] * (theta + pi) / pi
            vf = 0.5 * inSize[0] * (pi/2 - phi) / pi

            # Use bilinear interpolation between the four surrounding pixels
            ui = floor(uf)  # coord of pixel to bottom left
            vi = floor(vf)
            u2 = ui+1       # coords of pixel to top right
            v2 = vi+1
            mu = uf-ui      # fraction of way across pixel
            nu = vf-vi

            # Pixel values of four corners
            A = inPix[ui % inSize[0], clip(vi, 0, inSize[1]-1)]
            B = inPix[u2 % inSize[0], clip(vi, 0, inSize[1]-1)]
            C = inPix[ui % inSize[0], clip(v2, 0, inSize[1]-1)]
            D = inPix[u2 % inSize[0], clip(v2, 0, inSize[1]-1)]

            # interpolate
            (r,g,b) = (
              A[0]*(1-mu)*(1-nu) + B[0]*(mu)*(1-nu) + C[0]*(1-mu)*nu+D[0]*mu*nu,
              A[1]*(1-mu)*(1-nu) + B[1]*(mu)*(1-nu) + C[1]*(1-mu)*nu+D[1]*mu*nu,
              A[2]*(1-mu)*(1-nu) + B[2]*(mu)*(1-nu) + C[2]*(1-mu)*nu+D[2]*mu*nu )

            outPix[xOut, yOut] = (int(round(r)), int(round(g)), int(round(b)))

imgIn = Image.open(sys.argv[1])
inSize = imgIn.size
faceSize = inSize[0] / 4
components = sys.argv[1].rsplit('.', 2)

FACE_NAMES = {
  0: 'back',
  1: 'left',
  2: 'front',
  3: 'right',
  4: 'top',
  5: 'bottom'
}

for face in xrange(6):
  imgOut = Image.new("RGB", (faceSize, faceSize), "black")
  convertFace(imgIn, imgOut, face)
  imgOut.save(components[0] + "_" + FACE_NAMES[face] + "." + components[1])
7
Benjamin Dobell

Trouvé cette question, et même si les réponses sont bonnes, je pense qu'il reste encore du terrain à couvrir, alors voici mes deux sous.

Premièrement: à moins que vous n’ayez réellement à convertir les images vous-même (c’est-à-dire à cause de logiciels spécifiques), non.

La raison en est que, même s'il existe un mappage très simple entre projection équirectangulaire et projection cubique, le mappage entre les zones n'est pas simple: lorsque vous établissez une correspondance entre un point spécifique de votre image de destination et un point de la source avec un calcul élémentaire, dès que vous convertissez les deux points en pixels en arrondissant vous effectuez une approximation brute very _ qui ne prend pas en compte la taille des pixels et la qualité de l'image est forcément basse.

Deuxièmement: même si vous devez effectuer la conversion au moment de l'exécution, êtes-vous sûr de devoir effectuer la conversion? Sauf en cas de problème de performances très strict, si vous avez juste besoin d’une skybox, créez une très grande sphère, cousez-y la texture équirectangulaire, et c'est parti. Trois JS fournit déjà la sphère, autant que je m'en souvienne ;-)

Troisièmement: la NASA fournit un outil pour convertir toutes les projections imaginables (je viens de le découvrir, de le tester et de fonctionner comme un charme). Vous pouvez le trouver ici:

G.Projector - Projecteur de carte globale

et je trouve raisonnable de penser que les gars savent ce qu’ils font ;-)

J'espère que cela t'aides

(UPDATE:} _ il s'avère que les "gars" savent ce qu'ils font jusqu'à un certain point: le cubemap généré a une bordure hideuse qui rend la conversion difficile ...

(UPDATE 2: _ a trouvé l'outil de conversion ultime équirectangulaire en cubemap, appelé erect2cubic.

C'est un petit utilitaire qui génère un script à envoyer à Hugin, de la manière suivante:

$ erect2cubic --erect=input.png --ptofile=cube.pto
$ nona -o cube_prefix cube.pto 

(informations extraites de Vinay's Hacks page)

et générera toutes les 6 faces cubemap. Je l'utilise pour mon projet et il fonctionne comme un charme!

Le seul inconvénient de cette approche est que le script erect2cubit n'est pas dans la distribution standard d'Ubuntu (c'est ce que j'utilise) et j'ai dû suivre les instructions à ce lien:

Blog décrivant comment installer et utiliser erect2cubic

pour savoir comment l'installer.

Vraiment la peine! 

6
Rick77

(cmft} _ Studio prend en charge conversion/filtering de diverses projections HDR/LDR à cubemaps.

https://github.com/dariomanesku/cmftStudio

1
planetboy

Peut-être me manque quelque chose ici. Mais il semble que la plupart sinon la totalité du code de transformation présenté peut être quelque peu incorrect. Ils prennent un panorama sphérique (équirectangulaire - 360 degrés horizontalement et 180 degrés verticalement) et semblent se convertir en faces de cube en utilisant une transformation cartésienne <-> cylindrique. Ne devraient-ils pas utiliser une transformation cartésienne <-> sphérique. Voir http://mathworld.wolfram.com/SphericalCoordinates.html

Je suppose que tant qu'ils inversent le calcul pour passer des faces du cube au panorama, alors cela devrait fonctionner. Mais les images des faces du cube peuvent être légèrement différentes lorsque vous utilisez la transformation sphérique.

Si je commence par ce équirectangulaire (panorama sphérique):

 enter image description here

Ensuite, si j'utilise une transformation cylindrique (ce dont je ne suis pas sûr à 100% est correct à ce stade), j'obtiens le résultat suivant:

 enter image description here

Mais si j'utilise une transformation sphérique, j'obtiens le résultat suivant:

 enter image description here

Ils ne sont pas les mêmes. Mais mon résultat de transformation sphérique semble correspondre au résultat de Danke Xie, mais son lien ne montre pas le type de transformation qu'il utilise, autant que je puisse le lire.

Alors, est-ce que je comprends mal le code utilisé par beaucoup de contributeurs à ce sujet?

0
fmw42

J'ai créé une solution à ce problème en utilisant OpenGL et créé un outil de ligne de commande autour de ce problème. Cela fonctionne à la fois avec des images et des vidéos, et c'est l'outil le plus rapide que j'ai trouvé là-bas.

Convert360 - Projet sur GitHub.

OpenGL Shader - Le fragment shader utilisé pour la re-projection.

L'utilisation est aussi simple que:

$ pip install convert360
$ convert360 -i ~/Pictures/Barcelona/sagrada-familia.jpg -o example.png -s 300 300

Pour obtenir quelque chose comme ça:

 enter image description here

0
Mateus Zitelli

Une application C++ très simple qui convertit un panorama équirectangulaire en carte cube basée sur la réponse de Salix Alba => https://github.com/denivip/panorama

0
Denis Bulichenko

Voici une version JavaScript du code de Benjamn Dobell. convertFace doit recevoir deux objets ìmageData et un ID de visage (0-6).

Le code fourni peut être utilisé en toute sécurité dans un outil Web, car il n’a pas de dépendances.

        // convert using an inverse transformation
        function convertFace(imgIn, imgOut, faceIdx) {
            var inPix = shimImgData(imgIn),
                        outPix = shimImgData(imgOut),
                        faceSize = imgOut.width,
                        pi = Math.PI,
                        pi_2 = pi/2;

            for(var xOut=0;xOut<faceSize;xOut++) {
                    for(var yOut=0;yOut<faceSize;yOut++) {

                    var xyz = outImgToXYZ(xOut, yOut, faceIdx, faceSize);
                    var theta = Math.atan2(xyz.y, xyz.x); // range -pi to pi
                    var r = Math.hypot(xyz.x,xyz.y);
                    var phi = Math.atan2(xyz.z,r); // range -pi/2 to pi/2

                    // source img coords
                    var uf = 0.5 * imgIn.width * (theta + pi) / pi;
                    var vf = 0.5 * imgIn.width * (pi_2 - phi) / pi;

                    // Use bilinear interpolation between the four surrounding pixels
                    var ui = Math.floor(uf);  // coord of pixel to bottom left
                    var vi = Math.floor(vf);
                    var u2 = ui+1;       // coords of pixel to top right
                    var v2 = vi+1;
                    var mu = uf-ui;      // fraction of way across pixel
                    var nu = vf-vi;

                    // Pixel values of four corners
                    var A = inPix.getPx(ui % imgIn.width, clip(vi, 0, imgIn.height-1));
                    var B = inPix.getPx(u2 % imgIn.width, clip(vi, 0, imgIn.height-1));
                    var C = inPix.getPx(ui % imgIn.width, clip(v2, 0, imgIn.height-1));
                    var D = inPix.getPx(u2 % imgIn.width, clip(v2, 0, imgIn.height-1));

                    // interpolate
                    var rgb = {
                      r:A[0]*(1-mu)*(1-nu) + B[0]*(mu)*(1-nu) + C[0]*(1-mu)*nu+D[0]*mu*nu,
                      g:A[1]*(1-mu)*(1-nu) + B[1]*(mu)*(1-nu) + C[1]*(1-mu)*nu+D[1]*mu*nu,
                      b:A[2]*(1-mu)*(1-nu) + B[2]*(mu)*(1-nu) + C[2]*(1-mu)*nu+D[2]*mu*nu
                    };

                    rgb.r=Math.round(rgb.r);
                    rgb.g=Math.round(rgb.g);
                    rgb.b=Math.round(rgb.b);

                    outPix.setPx(xOut, yOut, rgb);

                } // for(var yOut=0;yOut<faceSize;yOut++) {...}
             } // for(var xOut=0;xOut<faceSize;xOut++) {...}
        } // function convertFace(imgIn, imgOut, faceIdx) {...}

        // get x,y,z coords from out image pixels coords
        // i,j are pixel coords
        // faceIdx is face number
        // faceSize is Edge length
        function outImgToXYZ(i, j, faceIdx, faceSize) {
            var a = 2 * i / faceSize,
                    b = 2 * j / faceSize;

            switch(faceIdx) {
                case 0: // back
                return({x:-1, y:1-a, z:1-b});
            case 1: // left
                return({x:a-1, y:-1, z:1-b});
            case 2: // front
                return({x: 1, y:a-1, z:1-b});
            case 3: // right
                return({x:1-a, y:1, z:1-b});
            case 4: // top
                return({x:b-1, y:a-1, z:1});
            case 5: // bottom
                return({x:1-b, y:a-1, z:-1});

            }
        } // function outImgToXYZ(i, j, faceIdx, faceSize) {...}

        function clip(val, min, max) {
            return(val<min?min:(val>max?max:val));
        }

        function shimImgData(imgData) {
            var w=imgData.width*4,
                    d=imgData.data;

            return({
                getPx:function(x,y) {
                    x=x*4+y*w;
                    return([ d[x], d[x+1], d[x+2] ]);
                },
                setPx:function(x,y,rgb) {
                    x=x*4+y*w;
                    d[x]=rgb.r;
                    d[x+1]=rgb.g;
                    d[x+2]=rgb.b;
                    d[x+3]=255; // alpha
                }
            });
        } // function shimImgData(imgData) {...}
0
knee-cola

Il existe différentes représentations de cartes d'environnement. Voici un bel aperçu.

Aperçu - Images panoramiques

Si vous utilisez Photosphere (ou n'importe quelle application de panorama), vous avez probablement déjà la représentation horizontale latitude/longitude . Vous pouvez alors simplement dessiner un trois.js SphereGeometry . Voici un tutoriel sur la façon de rendre la terre.

Tutoriel - Comment créer la Terre dans WebGL?

Bonne chance :).

0
Tobias Gurdan