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
Una aplicación C ++ muy simple que convierte un panorama equirectangular en un mapa de cubo basado en la respuesta de Salix Alba => github.com/denivip/panorama
cmft Studio admite conversion/filtering
de varias proyecciones HDR/LDR
a cubemaps
.