Conversion 2: 1 panorama équirectangulaire en Carte cube

je travaille actuellement sur un simple visualiseur de panorama 3D pour un site web. Pour des raisons de performance mobile j'utilise le three.js CSS3 render . Cela nécessite une carte cubique, 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 2:1 equirectangular. Je redimensionne ensuite et les convertit en un cubemap avec ce site web: http://gonchar.me/panorama / (Flash)

préférablement, je voudrais faire la conversion moi-même , soit à la volée dans trois.js, si c'est possible, ou dans Photoshop. J'ai trouvé les actions Photoshop D'Andrew Hazelden, et elles semblent proches, mais aucune conversion directe n'est disponible. Est-il un moyen mathématique de convertir ces, ou une sorte de script qui est-il? J'aimerais éviter de passer par une application 3D comme Blender, si possible.

C'est peut-être peu probable, mais j'ai pensé demander. J'ai une bonne expérience avec javascript, mais je suis plutôt nouveau dans three.js . J'hésite également à me fier à la fonctionnalité WebGL, car elle semble lente ou buggée sur les appareils mobiles. Le soutien est encore inégale.

37
demandé sur WestLangley 2015-04-16 17:55:31

11 réponses

si vous voulez le faire côté serveur il y a beaucoup d'options. http://www.imagemagick.org / a un tas d'outils en ligne de commande qui pourrait découper votre image en morceaux. Vous pouvez mettre la commande pour faire ceci dans un script et juste l'exécuter chaque fois que vous avez une nouvelle image.

il est difficile de dire tout à fait quel algorithme est utilisé dans le programme. Nous pouvons essayer d'inverser ce qui se passe en insérant une grille carrée dans le programme. J'ai utilisé une grille de wikipedia

64 by 64 grid

qui donne projected grid cela nous donne un indice sur la façon dont la boîte est construite.

sphère D'imagerie avec des lignes de latitude et de longitude un il, et un cube l'entourant. Maintenant le projet du point au centre de la sphère produit une grille déformée sur le cube.

mathématiquement prendre polaire coordonnées r, θ, ø, pour la sphère r=1, 0 < θ < π, π/4 < ø < 7π/4

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

centralement projeter ceux-ci au cube. Nous avons d'abord diviser en quatre régions par la latitude -π/4 < ø < π/4, π/4 < ø < 3π/4, 3π/4 < ø < 5π/4, 5π/4 < ø < 7π/4. Ces va soit le projet à l'un des quatre côtés, le haut ou le 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 x=1 l'avion

  • a sin θ cos ø = 1

donc

  • a = 1 / (sin θ cos ø)

et le point prévu est

  • (1, tan ø, lit bébé θ / cos ø)

Si | lit bébé θ / 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 haut utilise le fait que la valeur minimale de cos ø sera cos π/4 = 1/√2, de sorte que le point projeté est toujours sur le dessus si cot θ / (1/√2) > 1 ou tan θ < 1/√2. Cela signifie que θ < 35º ou 0,615 radians.

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. Le cubeToImg prend les coords (x,y,z) et les traduit en coords image de sortie.

l'algorithme ci-dessus semble obtenir la bonne géométrie en utilisant une image de buckingham palace nous obtenons cube map of buckingham palace Cela semble obtenir la plupart des lignes dans le pavage à droite.

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

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 ce sont Using the inverse transformation

69
répondu Salix alba 2015-06-17 18:54:01

étant donné l'excellente réponse acceptée, je voulais ajouter mon correspondant C++ implementation , basé sur OpenCV .

pour ceux qui ne connaissent pas OpenCV, pensez à Mat comme une image. Nous construisons d'abord deux cartes qui se refont de l'image équirectangulaire à notre Face cubemap correspondante. Ensuite, nous effectuons le levage lourd (i.e. remapping avec interpolation) en utilisant OpenCV.

le code peut être rendue plus compacte, si la lisibilité n'est pas une préoccupation.

// 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));
}

étant donné l'entrée suivante:

enter image description here

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

enter image description here

image courtoisie de Optonaut .

11
répondu Emiswelt 2016-01-11 11:42:38

j'ai écrit un script pour couper le cubemap généré en fichiers individuels (posx.png, negx.png, posy.png, négy.png, posz.png et negz.png). Il va aussi empaqueter 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 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
répondu Danke Xie 2015-08-22 05:49:26

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 préserve le type de fichier de l'image originale.

mis à part le fait que la plupart des cas d'utilisation attendent probablement six images séparées, le principal avantage de convertir un visage à la fois est qu'il rend le travail avec des images énormes beaucoup moins intensif 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
répondu Benjamin Dobell 2017-05-23 12:10:31

a trouvé cette question, et même si les réponses sont bonnes, je pense qu'il y a encore du terrain découvert, donc voici mes deux cents.

D'abord: sauf si vous devez vraiment convertir les images vous-même (c.-à-d. en raison d'une exigence de logiciel spécifique), ne pas .

la raison est que, même s'il ya une cartographie très simple entre la projection équirectangulaire et la projection cubique, la cartographie 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 dans la source avec un calcul élémentaire, dès que vous convertissez les deux points en pixels en arrondissant vous faites un très approximation brute qui ne tient pas compte de la taille des pixels, et la qualité de l'image est liée à être faible.

Deuxièmement: même si vous avez besoin de faire le de conversion au moment de l'exécution, êtes-vous sûr que vous avez besoin de faire la conversion? À moins qu'il n'y ait un problème de performance très strict, si vous avez juste besoin d'une skybox, créer une très grande sphère, piquer le texure équirectangulaire sur elle, et vous partez. Trois JS fournit la sphère déjà, comme je me souviens bien ;-)

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

G. Projector-Global Map Projector

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

Espérons que cette aide

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

mise à jour 2: trouvé l'outil définitif pour equirectangular à la conversion cubemap, et il est appelé erect2cubic .

c'est un petit utilitaire qui génère un script à transmettre à hugin, de cette façon:

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

(information siphonnée de Vinay's Hacks page)

et générera 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 ce n'est pas dans la distribution Ubuntu standard (qui est ce que j'utilise) et j'ai dû recourir aux instructions à ce lien:

Blog décrivant comment installer et utiliser erect2cubic

pour savoir comment l'installer.

ça vaut le coup!

6
répondu Rick77 2015-09-05 09:00:19

cmft Studio supports conversion/filtering de diverses HDR/LDR projections à cubemaps .

https://github.com/dariomanesku/cmftStudio

1
répondu planetboy 2016-04-28 20:21:03

il existe diverses représentations de cartes d'environnement. Voici un bel aperçu.

Vue D'Ensemble-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 texturé.js SphereGeometry . Voici un tutoriel sur la façon de rendre terre.

Tutoriel - Comment faire la terre dans WebGL?

bonne chance :).

0
répondu Tobias Gurdan 2015-07-26 20:54:00

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
répondu Denis Bulichenko 2017-05-23 12:03:02

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

le code fourni peut être utilisé en toute sécurité dans un travailleur 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
répondu knee-cola 2017-04-18 09:56:54

j'ai créé une solution pour ce problème en utilisant OpenGL et j'ai créé un outil en ligne de commande autour de celui-ci. Il 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'usage 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 ceci:

enter image description here

0
répondu Mateus Zitelli 2017-06-24 20:24:02

peut-être que je 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 (equirectangular - - 360 degrés horizontalement et 180 degrés verticalement) et semblent se convertir aux faces du cube en utilisant une transformation cartésienne <-> cylindrique. S'ils n'utilisent pas 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 il devrait fonctionner. Mais les images des faces du cube peuvent être légèrement différentes en utilisant la transformation sphérique.

si je commence par ce equirectangular (panorama sphérique):

enter image description here

puis si j'utilise une transformation cylindrique (que je ne suis pas sûr à 100% est correct à ce moment-là), je reçois ce résultat:

enter image description here

mais si j'utilise une transformation sphérique, j'obtiens ce résultat:

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 genre de transformation qu'il utilise, comme le meilleur que je puisse lire.

est-ce que je me méprends sur le code utilisé par de nombreux contributeurs à ce sujet?

0
répondu fmw42 2017-08-21 03:03:00