google example attribute javascript math three.js

javascript - attribute - google maps marker example



Convierta un panorama equirectangular 2: 1 al mapa del cubo (11)

Actualmente estoy trabajando en un simple visor de panorama 3D para un sitio web. Por motivos de rendimiento móvil, estoy usando el renderizador CSS3 de three.js . Esto requiere un mapa de cubo, dividido en 6 imágenes individuales.

Estoy grabando las imágenes en el iPhone con la aplicación Google Photosphere, o aplicaciones similares que crean panoramas equirectangulares 2: 1. Luego cambio el tamaño y los convierto en un mapa de cubos con este sitio web: http://gonchar.me/panorama/ (Flash)

Preferiblemente, me gustaría hacer la conversión yo mismo , ya sea sobre la marcha en three.js, si eso es posible, o en Photoshop. Encontré las acciones de Photoshop de Andrew Hazelden, y parecen estar cerca, pero no hay conversión directa disponible. ¿Hay una forma matemática para convertir estos, o algún tipo de script que lo hace? Me gustaría evitar pasar por una aplicación 3D como Blender, si es posible.

Quizás esta es una posibilidad remota, pero pensé que podría preguntar. Tengo una buena experiencia con javascript, pero soy bastante nuevo en three.js . También dudo en confiar en la funcionalidad WebGL, ya que parece lenta o con fallas en los dispositivos móviles. El soporte también es irregular.


Aquí hay una versión (ingenua) modificada de la respuesta absolutamente fantástica de Salix Alba que convierte una cara a la vez, escupe seis imágenes diferentes y conserva el tipo de archivo de la imagen original.

Aparte del hecho de que la mayoría de los casos de uso probablemente esperan seis imágenes separadas, la principal ventaja de convertir una cara a la vez es que hace que trabajar con imágenes enormes requiera menos memoria.

#!/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])


Aquí hay una versión de JavaScript del código de Benjamn Dobell. El convertFace necesita pasar dos objetos ìmageData y una ID de rostro (0-6).

El código proporcionado se puede usar de forma segura en un trabajador web, ya que no tiene dependencias.

// 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) {...}


Creé una solución para este problema usando OpenGL e hice una herramienta de línea de comandos a su alrededor. Funciona tanto con imágenes como con videos, y es la herramienta más rápida que encontré.

Convert360 - Proyecto en GitHub.

OpenGL Shader - El sombreador de fragmentos utilizado para la re-proyección.

El uso es tan simple como:

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

Para obtener algo como esto:


Dada la excelente respuesta aceptada, quería agregar mi implementación de c ++ correspondiente, basada en OpenCV .

Para aquellos que no estén familiarizados con OpenCV, piense en Mat como una imagen. Primero construimos dos mapas que reasignan de la imagen equirectangular a nuestra correspondiente cara cubemap. Luego, hacemos el trabajo pesado (es decir, la reasignación con interpolación) usando OpenCV.

El código se puede hacer más compacto, si la legibilidad no es motivo de preocupación.

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

Dada la siguiente entrada:

Se generan las siguientes caras:

Imagen cortesía de Optonaut .


Encontré esta pregunta, y aunque las respuestas son buenas, creo que todavía hay algo de terreno descubierto, así que aquí están mis dos centavos.

Primero: a menos que realmente tenga que convertir las imágenes usted mismo (es decir, debido a algún requisito específico del software), no lo haga .

La razón es que, aunque hay una asignación muy simple entre proyección equirrectangular y proyección cúbica, la correspondencia entre las áreas no es simple : cuando establece una correspondencia entre un punto específico de su imagen de destino y un punto en la fuente con un computación elemental, tan pronto como convierta ambos puntos en píxeles al redondear , está haciendo una aproximación muy cruda que no tiene en cuenta el tamaño de los píxeles, y la calidad de la imagen será baja.

Segundo: incluso si necesita hacer la conversión en tiempo de ejecución, ¿está seguro de que necesita hacer la conversión? A menos que exista un problema de rendimiento muy estricto, si solo necesitas un palco, crea una esfera muy grande, cose la texura equirectangular y listo. Three JS ya proporciona la esfera, por lo que recuerdo ;-)

Tercero: la NASA proporciona una herramienta para convertir todas las proyecciones imaginables (lo descubrí, lo probé y funciona como un amuleto). Lo puedes encontrar aquí:

G.Projector - Global Map Projector

y me parece razonable pensar que los muchachos saben lo que están haciendo ;-)

Espero que esto ayude

ACTUALIZACIÓN: resulta que los " chicos " saben lo que hacen hasta cierto punto: el mapa de cubos generado tiene un borde horrible que hace que la conversión no sea tan fácil ...

ACTUALIZACIÓN 2: se encuentra la herramienta definitiva para la conversión equirectangular a cubemap, y se llama erect2cubic .

Es una pequeña utilidad que genera una secuencia de comandos para alimentar a hugin, de esta manera:

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

(información extraída de la página Hacks de Vinay )

y generará las 6 caras del mapa de cubos. Lo estoy usando para mi proyecto y funciona como un encanto !

El único inconveniente de este enfoque es que el script erect2cubit no está en la distribución estándar de Ubuntu (que es lo que estoy usando) y tuve que recurrir a las instrucciones en este enlace:

Blog que describe cómo instalar y usar erect2cubic

para descubrir cómo instalarlo

¡Merece la pena!


Escribí un guión para cortar el mapa de cubos generado en archivos individuales (posx.png, negx.png, posy.png, negy.png, posz.png y negz.png). También empacará los 6 archivos en un archivo .zip.

La fuente está aquí: https://github.com/dankex/compv/blob/master/3d-graphics/skybox/cubemap-cut.py

Puede modificar la matriz para establecer los archivos de imagen:

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

Los archivos convertidos son:


Hay varias representaciones de mapas de entorno. Aquí hay una buena descripción.

Descripción general - Imágenes panorámicas

Si usa Photosphere (o cualquier aplicación de panorama para ese caso), lo más probable es que ya tenga la representación horizontal de latitud / longitud . A continuación, simplemente puede dibujar una esfera SphereGeometry textura SphereGeometry . Aquí hay un tutorial sobre cómo renderizar la tierra.

Tutorial - Cómo hacer que la Tierra en WebGL?

La mejor de las suertes :).


Quizás me estoy perdiendo algo aquí. Pero parece que la mayoría, si no todo, el código de transformación presentado puede ser algo incorrecto. Toman un panorama esférico (equirectangular --- 360 grados horizontalmente y 180 grados verticalmente) y parecen convertirse a las caras del cubo mediante una <-> transformación cilíndrica cartesiana. ¿No deberían usar una transformación esférica cartesiana <->? Ver http://mathworld.wolfram.com/SphericalCoordinates.html

Supongo que siempre que reviertan el cálculo para pasar de las caras del cubo al panorama, entonces debería funcionar. Pero las imágenes de las caras del cubo pueden ser ligeramente diferentes cuando se utiliza la transformación esférica.

Si empiezo con este equirectangular (panorama esférico):

Entonces, si utilizo una transformación cilíndrica (que no estoy 100% seguro de que sea correcta en este momento), obtengo este resultado:

Pero si utilizo una transformación esférica, obtengo este resultado:

Ellos no son los mismos. Pero el resultado de mi transformación esférica parece coincidir con el resultado de Danke Xie, pero su enlace no muestra el tipo de transformación que está utilizando, lo mejor que puedo leer.

Entonces, ¿entiendo mal el código utilizado por muchos de los contribuyentes a este tema?


Si quieres hacerlo desde el lado del servidor, hay muchas opciones. http://www.imagemagick.org/ tiene un montón de herramientas de línea de comandos que pueden dividir su imagen en pedazos. Podría poner el comando para hacer esto en un script y simplemente ejecutarlo cada vez que tenga una imagen nueva.

Es difícil decir qué algoritmo se usa en el programa. Podemos intentar realizar una ingeniería inversa de todo lo que sucede al alimentar una cuadrícula cuadrada en el programa. He usado una grilla de wikipedia

Lo que da Esto nos da una pista sobre cómo se construye la caja.

Esfera de imágenes con líneas de latitud y longitud, y un cubo que lo rodea. Ahora el proyecto desde el punto en el centro de la esfera produce una cuadrícula distorsionada en el cubo.

Tomar matemáticamente coordenadas polares r, θ, ø, para la esfera r = 1, 0 <θ <π, -π / 4 <ø <7π / 4

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

centralmente proyectar estos al cubo. Primero, lo dividimos en cuatro regiones por la latitud -π / 4 <ø <π / 4, π / 4 <ø <3π / 4, 3π / 4 <ø <5π / 4, 5π / 4 <ø <7π / 4. Estos se proyectarán a uno de los cuatro lados, arriba o abajo.

Supongamos que estamos en el primer lado -π / 4 <ø <π / 4. La proyección central de (sen θ cos ø, sen θ sin ø, cos θ) será (un sen θ cos ø, un sen θ sin ø, un cos θ) que golpea el plano x = 1 cuando

  • un pecado θ cos ø = 1

asi que

  • a = 1 / (sin θ cos ø)

y el punto proyectado es

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

Si | cot θ / cos ø | <1 esto estará en la cara frontal. De lo contrario, se proyectará en la parte superior o inferior y necesitará una proyección diferente para eso. Una mejor prueba para la parte superior utiliza el hecho de que el valor mínimo de cos ø será cos π / 4 = 1 / √2, por lo que el punto proyectado siempre estará en la parte superior si cot θ / (1 / √2)> 1 o tan θ <1 / √2. Esto funciona como θ <35º o 0.615 radianes.

Pon esto juntos 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 función de projection toma los valores theta y phi y devuelve las coordenadas en un cubo de -1 a 1 en cada dirección. CubeToImg toma los coords (x, y, z) y los traduce a coords de imagen de salida.

El algoritmo anterior parece obtener la geometría correcta usando una imagen del palacio buckingham que obtenemos Esto parece obtener la mayoría de las líneas en la pavimentación a la derecha.

Estamos obteniendo algunos artefactos de imagen. Esto se debe a que no tiene un mapa de píxeles de 1 a 1. Lo que tenemos que hacer es usar una transformación inversa. En lugar de recorrer cada píxel en la fuente y encontrar el píxel correspondiente en el objetivo, recorremos las imágenes objetivo y buscamos el píxel fuente correspondiente más cercano.

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()

Los resultados de esto son