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.
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
qui donne 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 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
é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:
les faces suivantes sont générées:
image courtoisie de Optonaut .
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:
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])
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!
cmft Studio supports conversion/filtering
de diverses HDR/LDR
projections à cubemaps
.
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 :).
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
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) {...}
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:
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):
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:
mais si j'utilise une transformation sphérique, j'obtiens ce résultat:
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?