Convertir 2: 1 panorama équidélégial en cube

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

J'écris les images sur l'iPhone avec l'application Google Photosphere, ou des applications similaires qui créent des panoramas équitaxiaux 2: 1. Je redimensionne et les convertit en cubemap avec ce site: http://gonchar.me/panorama/ (Flash)

De préférence, je voudrais faire la conversion moimême , soit à la volée en trois.js, si cela est possible, ou dans Photoshop. J'ai trouvé les actions de Photoshop de Andrew Hazelden, et elles semblent plutôt proches, mais aucune conversion directe n'est disponible. Existe-t-il un moyen mathématique de les convertir, ou une sorte de script qui le fait? Je voudrais éviter de passer par une application 3D comme Blender, si possible.

Peut-être que c'est un long coup, mais j'ai pensé que je demanderais. J'ai bien l'expérience avec javascript, mais je suis assez nouveau pour three.js . Je hésite également à compter sur la fonctionnalité WebGL, car il semble ralenti ou buggy sur les appareils mobiles. Le support est également encore irrégulier.

Si vous voulez le faire côté serveur, il existe de nombreuses options. http://www.imagemagick.org/ dispose d'un tas d'outils de ligne de commande qui peuvent diviser votre image en morceaux. Vous pouvez mettre la commande pour faire ceci dans un script et exécutez que chaque fois que vous avez une nouvelle image.

Il est difficile de dire quel algorithme est utilisé dans le programme. Nous pouvons essayer d'inverser l'ingénierie tout ce qui se passe en alimentant une grille carrée dans le programme. J'ai utilisé une grille de wikipedia

64 par 64 grille

Qui donne Grille projetée Cela nous donne une idée de la façon dont la boîte est construite.

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

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

  • X = r sin θ cos ø
  • Y = r sin θ sin ø
  • Z = r cos θ

Centralement les projeter sur le cube. D'abord, nous divisons en quatre régions par la latitude -π / 4 <ø <π / 4, π / 4 <ø <3π / 4, 3π / 4 <ø <5π / 4, 5π / 4 <ø <7π / 4. Ceux-ci projeteront soit l'un des quatre côtés du haut ou du bas.

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

  • Un sin θ cos ø = 1

alors

  • A = 1 / (sin θ cos ø)

Et le point projeté est

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

Si | Lit 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, donc le point projeté est toujours en haut si cot θ / (1 / √2)> 1 ou Bronzage θ <1 / √2. Cela correspond à θ <35º ou 0,615 radians.

Mettez cela ensemble en python

 import sys from PIL import Image from math import pi,sin,cos,tan def cot(angle): return 1/tan(angle) # Project polar coordinates onto a surrounding cube # assume ranges theta is [0,pi] with 0 the north poll, pi south poll # phi is in range [0,2pi] def projection(theta,phi): if theta<0.615: return projectTop(theta,phi) elif theta>2.527: return projectBottom(theta,phi) elif phi <= pi/4 or phi > 7*pi/4: return projectLeft(theta,phi) elif phi > pi/4 and phi <= 3*pi/4: return projectFront(theta,phi) elif phi > 3*pi/4 and phi <= 5*pi/4: return projectRight(theta,phi) elif phi > 5*pi/4 and phi <= 7*pi/4: return projectBack(theta,phi) def projectLeft(theta,phi): x = 1 y = tan(phi) z = cot(theta) / cos(phi) if z < -1: return projectBottom(theta,phi) if z > 1: return projectTop(theta,phi) return ("Left",x,y,z) def projectFront(theta,phi): x = tan(phi-pi/2) y = 1 z = cot(theta) / cos(phi-pi/2) if z < -1: return projectBottom(theta,phi) if z > 1: return projectTop(theta,phi) return ("Front",x,y,z) def projectRight(theta,phi): x = -1 y = tan(phi) z = -cot(theta) / cos(phi) if z < -1: return projectBottom(theta,phi) if z > 1: return projectTop(theta,phi) return ("Right",x,-y,z) def projectBack(theta,phi): x = tan(phi-3*pi/2) y = -1 z = cot(theta) / cos(phi-3*pi/2) if z < -1: return projectBottom(theta,phi) if z > 1: return projectTop(theta,phi) return ("Back",-x,y,z) def projectTop(theta,phi): # (a sin θ cos ø, a sin θ sin ø, a cos θ) = (x,y,1) a = 1 / cos(theta) x = tan(theta) * cos(phi) y = tan(theta) * sin(phi) z = 1 return ("Top",x,y,z) def projectBottom(theta,phi): # (a sin θ cos ø, a sin θ sin ø, a cos θ) = (x,y,-1) a = -1 / cos(theta) x = -tan(theta) * cos(phi) y = -tan(theta) * sin(phi) z = -1 return ("Bottom",x,y,z) # Convert coords in cube to image coords # coords is a tuple with the side and x,y,z coords # edge is the length of an edge of the cube in pixels def cubeToImg(coords,edge): if coords[0]=="Left": (x,y) = (int(edge*(coords[2]+1)/2), int(edge*(3-coords[3])/2) ) elif coords[0]=="Front": (x,y) = (int(edge*(coords[1]+3)/2), int(edge*(3-coords[3])/2) ) elif coords[0]=="Right": (x,y) = (int(edge*(5-coords[2])/2), int(edge*(3-coords[3])/2) ) elif coords[0]=="Back": (x,y) = (int(edge*(7-coords[1])/2), int(edge*(3-coords[3])/2) ) elif coords[0]=="Top": (x,y) = (int(edge*(3-coords[1])/2), int(edge*(1+coords[2])/2) ) elif coords[0]=="Bottom": (x,y) = (int(edge*(3-coords[1])/2), int(edge*(5-coords[2])/2) ) return (x,y) # convert the in image to out image def convert(imgIn,imgOut): inSize = imgIn.size outSize = imgOut.size inPix = imgIn.load() outPix = imgOut.load() edge = inSize[0]/4 # the length of each edge in pixels for i in xrange(inSize[0]): for j in xrange(inSize[1]): pixel = inPix[i,j] phi = i * 2 * pi / inSize[0] theta = j * pi / inSize[1] res = projection(theta,phi) (x,y) = cubeToImg(res,edge) #if i % 100 == 0 and j % 100 == 0: # print i,j,phi,theta,res,x,y if x >= outSize[0]: #print "x out of range ",x,res x=outSize[0]-1 if y >= outSize[1]: #print "y out of range ",y,res y=outSize[1]-1 outPix[x,y] = pixel imgIn = Image.open(sys.argv[1]) inSize = imgIn.size imgOut = Image.new("RGB",(inSize[0],inSize[0]*3/4),"black") convert(imgIn,imgOut) imgOut.show() 

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

L'algorithme ci-dessus semble avoir la bonne géométrie en utilisant une image du palais buckingham que nous obtenons Carte cubique du palais de Buckingham Cela semble obtenir la plupart des lignes dans le pavé à droite.

Nous obtenons quelques artefacts d'image. Ceci est dû à ne pas avoir une 1 à 1 carte de pixels. Ce qu'il faut faire, c'est utiliser une transformation inverse. Plutôt que de boucler chaque pixel dans la source et de trouver le pixel correspondant dans la cible, on parcourt les images cibles et on trouve le pixel source correspondant le plus proche.

 import sys from PIL import Image from math import pi,sin,cos,tan,atan2,hypot,floor from numpy import clip # get x,y,z coords from out image pixels coords # i,j are pixel coords # face is face number # edge is edge length def outImgToXYZ(i,j,face,edge): a = 2.0*float(i)/edge b = 2.0*float(j)/edge if face==0: # back (x,y,z) = (-1.0, 1.0-a, 3.0 - b) elif face==1: # left (x,y,z) = (a-3.0, -1.0, 3.0 - b) elif face==2: # front (x,y,z) = (1.0, a - 5.0, 3.0 - b) elif face==3: # right (x,y,z) = (7.0-a, 1.0, 3.0 - b) elif face==4: # top (x,y,z) = (b-1.0, a -5.0, 1.0) elif face==5: # bottom (x,y,z) = (5.0-b, a-5.0, -1.0) return (x,y,z) # convert using an inverse transformation def convertBack(imgIn,imgOut): inSize = imgIn.size outSize = imgOut.size inPix = imgIn.load() outPix = imgOut.load() edge = inSize[0]/4 # the length of each edge in pixels for i in xrange(outSize[0]): face = int(i/edge) # 0 - back, 1 - left 2 - front, 3 - right if face==2: rng = xrange(0,edge*3) else: rng = xrange(edge,edge*2) for j in rng: if j<edge: face2 = 4 # top elif j>=2*edge: face2 = 5 # bottom else: face2 = face (x,y,z) = outImgToXYZ(i,j,face2,edge) theta = atan2(y,x) # range -pi to pi r = hypot(x,y) phi = atan2(z,r) # range -pi/2 to pi/2 # source img coords uf = ( 2.0*edge*(theta + pi)/pi ) vf = ( 2.0*edge * (pi/2 - phi)/pi) # Use bilinear interpolation between the four surrounding pixels ui = floor(uf) # coord of pixel to bottom left vi = floor(vf) u2 = ui+1 # coords of pixel to top right v2 = vi+1 mu = uf-ui # fraction of way across pixel nu = vf-vi # Pixel values of four corners A = inPix[ui % inSize[0],clip(vi,0,inSize[1]-1)] B = inPix[u2 % inSize[0],clip(vi,0,inSize[1]-1)] C = inPix[ui % inSize[0],clip(v2,0,inSize[1]-1)] D = inPix[u2 % inSize[0],clip(v2,0,inSize[1]-1)] # interpolate (r,g,b) = ( A[0]*(1-mu)*(1-nu) + B[0]*(mu)*(1-nu) + C[0]*(1-mu)*nu+D[0]*mu*nu, A[1]*(1-mu)*(1-nu) + B[1]*(mu)*(1-nu) + C[1]*(1-mu)*nu+D[1]*mu*nu, A[2]*(1-mu)*(1-nu) + B[2]*(mu)*(1-nu) + C[2]*(1-mu)*nu+D[2]*mu*nu ) outPix[i,j] = (int(round(r)),int(round(g)),int(round(b))) imgIn = Image.open(sys.argv[1]) inSize = imgIn.size imgOut = Image.new("RGB",(inSize[0],inSize[0]*3/4),"black") convertBack(imgIn,imgOut) imgOut.save(sys.argv[1].split('.')[0]+"Out2.png") imgOut.show() 

Les résultats sont Utilisation de la transformation inverse

Compte tenu de l'excellente réponse acceptée, je voulais ajouter ma mise en œuvre c ++ correspondante, basée sur OpenCV .

Pour ceux qui ne sont pas familiers avec OpenCV, pensez à Mat comme image. Nous construisons d'abord deux cartes qui remappent de l'image equirectangulaire à notre face de cubemap correspondante. Ensuite, nous faisons le travail lourd (c'est-à-dire le remappage avec interpolation) en utilisant OpenCV.

Le code peut être plus compact, si la lisibilité n'est pas préoccupante.

 // Define our six cube faces. // 0 - 3 are side faces, clockwise order // 4 and 5 are top and bottom, respectively float faceTransform[6][2] = { {0, 0}, {M_PI / 2, 0}, {M_PI, 0}, {-M_PI / 2, 0}, {0, -M_PI / 2}, {0, M_PI / 2} }; // Map a part of the equirectangular panorama (in) to a cube face // (face). The ID of the face is given by faceId. The desired // width and height are given by width and height. inline void createCubeMapFace(const Mat &in, Mat &face, int faceId = 0, const int width = -1, const int height = -1) { float inWidth = in.cols; float inHeight = in.rows; // Allocate map Mat mapx(height, width, CV_32F); Mat mapy(height, width, CV_32F); // Calculate adjacent (ak) and opposite (an) of the // triangle that is spanned from the sphere center //to our cube face. const float an = sin(M_PI / 4); const float ak = cos(M_PI / 4); const float ftu = faceTransform[faceId][0]; const float ftv = faceTransform[faceId][1]; // For each point in the target image, // calculate the corresponding source coordinates. for(int y = 0; y < height; y++) { for(int x = 0; x < width; x++) { // Map face pixel coordinates to [-1, 1] on plane float nx = (float)y / (float)height - 0.5f; float ny = (float)x / (float)width - 0.5f; nx *= 2; ny *= 2; // Map [-1, 1] plane coords to [-an, an] // thats the coordinates in respect to a unit sphere // that contains our box. nx *= an; ny *= an; float u, v; // Project from plane to sphere surface. if(ftv == 0) { // Center faces u = atan2(nx, ak); v = atan2(ny * cos(u), ak); u += ftu; } else if(ftv > 0) { // Bottom face float d = sqrt(nx * nx + ny * ny); v = M_PI / 2 - atan2(d, ak); u = atan2(ny, nx); } else { // Top face float d = sqrt(nx * nx + ny * ny); v = -M_PI / 2 + atan2(d, ak); u = atan2(-ny, nx); } // Map from angular coordinates to [-1, 1], respectively. u = u / (M_PI); v = v / (M_PI / 2); // Warp around, if our coordinates are out of bounds. while (v < -1) { v += 2; u += 1; } while (v > 1) { v -= 2; u += 1; } while(u < -1) { u += 2; } while(u > 1) { u -= 2; } // Map from [-1, 1] to in texture space u = u / 2.0f + 0.5f; v = v / 2.0f + 0.5f; u = u * (inWidth - 1); v = v * (inHeight - 1); // Save the result for this pixel in map mapx.at<float>(x, y) = u; mapy.at<float>(x, y) = v; } } // Recreate output image if it has wrong size or type. if(face.cols != width || face.rows != height || face.type() != in.type()) { face = Mat(width, height, in.type()); } // Do actual resampling using OpenCV's remap remap(in, face, mapx, mapy, CV_INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0)); } 

Compte tenu de la saisie suivante:

Entrez la description de l'image ici

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

Entrez la description de l'image ici

Image avec l'aimable autorisation d' Optonaut .

J'ai écrit un script pour couper le cubemap généré dans des fichiers individuels (posx.png, negx.png, posy.png, negy.png, posz.png et negz.png). Il emploiera également 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:

Entrez la description de l'image ici Entrez la description de l'image ici Entrez la description de l'image ici Entrez la description de l'image ici Entrez la description de l'image ici Entrez la description de l'image ici

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

Tout d'abord: à moins que vous ne deviez vraiment convertir les images vous-même (c'est-à-dire en raison de certaines exigences spécifiques du logiciel), ne le faites pas .

La raison en est que, bien qu'il existe un mappage très simple entre la projection équidégiale et la projection cubique, le mappage entre les zones n'est pas simple : lorsque vous établissez une correspondance entre un point spécifique de votre image de destination et un point dans la source avec un Calcul élémentaire, dès que vous avez converti les deux points en pixels en arrondissant, vous effectuez une approximation très brute qui ne tient pas compte de la taille des pixels et que la qualité de l'image est faible.

Deuxièmement: même si vous devez effectuer la conversion au moment de l'exécution, êtes-vous sûr que vous devez effectuer la conversion? À moins qu'il y ait un problème de performance très sévère, si vous avez juste besoin d'une Skybox, créez une très grande sphère, stitch la texture équitèrienne sur elle, et vous quittez. Trois JS fournit déjà la sphère, pour autant que je me souvienne 😉

Troisièmement: la NASA fournit un outil pour convertir toutes les projections envisageables (je viens de le découvrir, l'ai testé et ça a l'air d'être 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 😉

J'espère que cela t'aides

MISE À JOUR: il s'avère que les « gars » savent ce qu'ils font jusqu'à un certain point: le Cubemap généré a une frontière hideuse qui rend la conversion pas si facile …

UPDATE 2: a trouvé l'outil définitif pour la conversion équirectangulaire vers cubemap, et s'appelle erect2cubic .

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

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

(Information siphonnée à partir de la page Hacks de Vinay )

Et générera les 6 faces cubemap. Je l'utilise pour mon projet et ça 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 (c'est ce que j'utilise) et j'ai dû recourir aux instructions sur ce lien:

Blog décrivant comment installer et utiliser erect2cubic

Pour savoir comment l'installer.

Vraiment la peine!

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

Mis à part le fait que la plupart des cas d'utilisation prévoient vraisemblablement six images séparées, le principal avantage de convertir un visage à la fois est qu'il rend le travail avec d'énormes images beaucoup moins de mémoire.

 #!/usr/bin/env python import sys from PIL import Image from math import pi, sin, cos, tan, atan2, hypot, floor from numpy import clip # get x,y,z coords from out image pixels coords # i,j are pixel coords # faceIdx is face number # faceSize is edge length def outImgToXYZ(i, j, faceIdx, faceSize): a = 2.0 * float(i) / faceSize b = 2.0 * float(j) / faceSize if faceIdx == 0: # back (x,y,z) = (-1.0, 1.0 - a, 1.0 - b) elif faceIdx == 1: # left (x,y,z) = (a - 1.0, -1.0, 1.0 - b) elif faceIdx == 2: # front (x,y,z) = (1.0, a - 1.0, 1.0 - b) elif faceIdx == 3: # right (x,y,z) = (1.0 - a, 1.0, 1.0 - b) elif faceIdx == 4: # top (x,y,z) = (b - 1.0, a - 1.0, 1.0) elif faceIdx == 5: # bottom (x,y,z) = (1.0 - b, a - 1.0, -1.0) return (x, y, z) # convert using an inverse transformation def convertFace(imgIn, imgOut, faceIdx): inSize = imgIn.size outSize = imgOut.size inPix = imgIn.load() outPix = imgOut.load() faceSize = outSize[0] for xOut in xrange(faceSize): for yOut in xrange(faceSize): (x,y,z) = outImgToXYZ(xOut, yOut, faceIdx, faceSize) theta = atan2(y,x) # range -pi to pi r = hypot(x,y) phi = atan2(z,r) # range -pi/2 to pi/2 # source img coords uf = 0.5 * inSize[0] * (theta + pi) / pi vf = 0.5 * inSize[0] * (pi/2 - phi) / pi # Use bilinear interpolation between the four surrounding pixels ui = floor(uf) # coord of pixel to bottom left vi = floor(vf) u2 = ui+1 # coords of pixel to top right v2 = vi+1 mu = uf-ui # fraction of way across pixel nu = vf-vi # Pixel values of four corners A = inPix[ui % inSize[0], clip(vi, 0, inSize[1]-1)] B = inPix[u2 % inSize[0], clip(vi, 0, inSize[1]-1)] C = inPix[ui % inSize[0], clip(v2, 0, inSize[1]-1)] D = inPix[u2 % inSize[0], clip(v2, 0, inSize[1]-1)] # interpolate (r,g,b) = ( A[0]*(1-mu)*(1-nu) + B[0]*(mu)*(1-nu) + C[0]*(1-mu)*nu+D[0]*mu*nu, A[1]*(1-mu)*(1-nu) + B[1]*(mu)*(1-nu) + C[1]*(1-mu)*nu+D[1]*mu*nu, A[2]*(1-mu)*(1-nu) + B[2]*(mu)*(1-nu) + C[2]*(1-mu)*nu+D[2]*mu*nu ) outPix[xOut, yOut] = (int(round(r)), int(round(g)), int(round(b))) imgIn = Image.open(sys.argv[1]) inSize = imgIn.size faceSize = inSize[0] / 4 components = sys.argv[1].rsplit('.', 2) FACE_NAMES = { 0: 'back', 1: 'left', 2: 'front', 3: 'right', 4: 'top', 5: 'bottom' } for face in xrange(6): imgOut = Image.new("RGB", (faceSize, faceSize), "black") convertFace(imgIn, imgOut, face) imgOut.save(components[0] + "_" + FACE_NAMES[face] + "." + components[1]) 

Cmft Studio prend en charge la conversion/filtering de diverses projections HDR/LDR en cubemaps .

https://github.com/dariomanesku/cmftStudio

Il existe diverses représentations de cartes environnementales. Voici un bon aperçu.

Vue d'ensemble – Images panoramiques

Si vous utilisez Photosphere (ou toute application panoramique pour cette question), vous avez probablement la représentation horizontale de latitude / longitude . Vous pouvez simplement dessiner une structurée trois.js SphereGeometry . Voici un didacticiel sur la façon de rendre la terre.

Didacticiel – Comment faire de la Terre dans WebGL?

Bonne chance :).

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

Voici une version JavaScript du code Benjamn Dobell. convertFace doit passer deux objets " ìmageData et un ID de visage (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 à l'aide d'OpenGL et j'ai créé un outil de ligne de commande autour de lui. 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'utilisation est aussi simple que:

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

Pour obtenir quelque chose comme ceci:

Entrez la description de l'image ici