Преобразование 2: 1 равноугольной панорамы в кубическую карту
В настоящее время я работаю над простым 3D-просмотрщиком панорамы для веб-сайта. По соображениям мобильности я использую three.js
рендеринг CSS3. Для этого требуется кубическая карта, разбитая на 6 отдельных изображений.
Я записываю изображения на iPhone с помощью приложения Google Photosphere или аналогичных приложений, которые создают 2: 1 равноугольные панорамы. Затем я изменяю размер и конвертирую их в кубкет с этим сайтом: http://gonchar.me/panorama/ (Flash)
Предпочтительно, я хотел бы сделать преобразование самостоятельно, либо на лету в three.js, если это возможно, либо в Photoshop. Я нашел действия Эндрю Хазелдена в Photoshop, и они кажутся близкими, но прямого обращения нет. Есть ли математический способ их преобразования или какой-то script, который это делает? Я бы хотел, если это возможно, избежать использования 3D-приложения, такого как Blender.
Возможно, это длинный выстрел, но я думал, что спрошу. У меня хороший опыт работы с javascript, но я довольно новичок в three.js
. Я также не решаюсь полагаться на функциональность WebGL, поскольку на мобильных устройствах это кажется медленным или искаженным. Поддержка также по-прежнему пятнистая.
Ответы
Ответ 1
Если вы хотите сделать это на стороне сервера, есть много вариантов. http://www.imagemagick.org/ имеет кучу инструментов командной строки, которые могут нарезать изображение на куски. Вы можете поместить эту команду в script и просто запускать ее каждый раз, когда у вас есть новое изображение.
Трудно сказать, какой алгоритм используется в программе. Мы можем попытаться перестроить то, что происходит, путем подачи квадратной сетки в программу. Я использовал сетку из википедии
![64 by 64 grid]()
Что дает
Это дает нам представление о том, как создается окно.
Сфера визуализации с линиями широты и долготы, одним и рядом с ним. Теперь проект из точки в центре сферы создает искаженную сетку на кубе.
Математически возьмем полярные координаты r, θ, ø, для сферы r = 1, 0 < θ < π, -π/4 < ø < 7π/4
- x = r sin θ cos ø
- y = r sin θ sin ø
- z = r cos θ
централизованно проецируйте их в куб. Сначала мы делим на четыре области по широте -π/4 < ø < π/4, π/4 < ø < 3π/4, 3π/4 < ø < 5π/4, 5π/4 < ø < 7π/4. Они либо проецируются на одну из четырех сторон сверху или снизу.
Предположим, что мы находимся в первой стороне -π/4 < ø < π/4. Центральная проекция
(sin θ cos ø, sin θ sin ø, cosθ) будет (a sin θ cos ø, a sin θ sin ø, a cosθ), который попадает на плоскость x = 1, когда
так
а проецируемая точка
Если | кроватка θ/cos ø | < 1 это будет на лицевой стороне. В противном случае он будет проецироваться сверху или снизу, и для этого вам понадобится другая проекция. Лучшим тестом для вершины является тот факт, что минимальное значение cos ø будет cos π/4 = 1/√2, поэтому проецируемая точка всегда находится сверху, если cot θ/(1/√2) > 1 или tan θ < 1/√2. Это работает как θ < 35º или 0.615 радианов.
Поместите это вместе в 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()
Функция projection
принимает значения theta
и phi
и возвращает координаты в кубе от -1 до 1 в каждом направлении. CubeToImg принимает координаты (x, y, z) и переводит их в выходные координаты изображения.
Вышеприведенный алгоритм, похоже, имеет правильную геометрию, используя образ бакингемского дворца
Похоже, что большинство линий в прайсе убирается.
Мы получаем несколько артефактов изображения. Это связано с тем, что не имеет 1 - 1 карты пикселей. Нам нужно использовать обратное преобразование. Вместо того, чтобы прокручивать каждый пиксель в источнике и находить соответствующий пиксель в цели, мы просматриваем целевые изображения и находим ближайший соответствующий исходный пиксель.
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()
Результаты этого: ![Using the inverse transformation]()
Ответ 2
Учитывая отличный принятый ответ, я хотел добавить свою соответствующую реализацию С++ на основе OpenCV.
Для тех, кто не знаком с OpenCV, подумайте о Mat
как об изображении. Сначала мы построим две карты, которые перенаправляются из равноугольного изображения на нашу соответствующую грань куба. Затем мы выполняем тяжелый подъем (т.е. Переназначение с интерполяцией) с использованием OpenCV.
Код можно сделать более компактным, если читаемость не вызывает беспокойства.
// 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 remap
remap(in, face, mapx, mapy,
CV_INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0));
}
Учитывая следующий ввод:
![введите описание изображения здесь]()
Сгенерированы следующие грани:
![введите описание изображения здесь]()
Изображение предоставлено Optonaut.
Ответ 3
Я написал script, чтобы вырезать созданный cubemap в отдельные файлы (posx.png, negx.png, posy.png, negy.png, posz.png и negz.png). Он также упакует 6 файлов в ZIP файл.
Источник находится здесь: https://github.com/dankex/compv/blob/master/3d-graphics/skybox/cubemap-cut.py
Вы можете изменить массив для установки файлов изображений:
name_map = [ \
["", "", "posy", ""],
["negz", "negx", "posz", "posx"],
["", "", "negy", ""]]
Преобразованные файлы:
![введите описание изображения здесь]()
Ответ 4
Здесь (наивно) измененная версия Salix Alba абсолютно фантастический ответ, который преобразует одно лицо за раз, выплевывает шесть разных изображений и сохраняет исходный тип файла изображения.
Помимо того, что большинство случаев использования, вероятно, ожидают шесть отдельных изображений, основное преимущество преобразования одной стороны за раз в том, что она делает работу с огромными изображениями намного менее интенсивной в памяти.
#!/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])
Ответ 5
Нашел этот вопрос, и хотя ответы хорошие, я думаю, что все еще есть какая-то основа, поэтому вот мои два цента.
Во-первых: если вам действительно не нужно преобразовывать изображения самостоятельно (т.е. из-за какого-то определенного требования к программному обеспечению), не.
Причина в том, что, хотя существует очень простое сопоставление между равноугольной проекцией и кубической проекцией, сопоставление между областями не просто: когда вы устанавливаете соответствие между определенной точкой вашего целевого изображения и точкой в источник с элементарным вычислением, как только вы конвертируете обе точки в пиксели округлением, вы выполняете грубое приближение очень, которое не учитывает размер пикселей, а качество изображения связано быть низким.
Во-вторых: даже если вам нужно выполнить преобразование во время выполнения, уверены ли вы, что вам нужно выполнить преобразование вообще? Если не существует очень жесткой проблемы с производительностью, если вам просто нужен skybox, создайте очень большую сферу, нарисуйте на ней равноправную тексюру и уйдете. Насколько мне известно, три JS предоставляют сферу, -)
В-третьих: NASA предоставляет инструмент для преобразования между всеми мыслимыми проекциями (я только что узнал, протестировал его и работал как шарм). Вы можете найти его здесь:
G.Projector - Глобальный проектор карты
и я считаю разумным думать, что ребята знают, что они делают; -)
Надеюсь, что это поможет
ОБНОВЛЕНИЕ: выясняется, что "ребята" знают, что они делают до определенного момента: сгенерированный куб-карта имеет отвратительную границу, которая делает преобразование не так просто...
ОБНОВЛЕНИЕ 2: нашел окончательный инструмент для преобразования equangelangular в cubemap и назвал erect2cubic
.
Это небольшая утилита, которая генерирует script для hugin таким образом:
$ erect2cubic --erect=input.png --ptofile=cube.pto
$ nona -o cube_prefix cube.pto
(информация сифилирована из Страница Vinay Hacks)
и будет генерировать все 6 кубических граней. Я использую его для своего проекта, и он работает как шарм!
Единственным недостатком этого подхода является то, что script erect2cubit
он не находится в стандартном дистрибутиве Ubuntu (это то, что я использую), и мне пришлось прибегнуть к инструкциям по этой ссылке:
Блог, описывающий, как установить и использовать erect2cubic
чтобы узнать, как его установить.
Всего стоит!
Ответ 6
cmft Studio поддерживает conversion/filtering
различных прогнозов HDR/LDR
на cubemaps
.
https://github.com/dariomanesku/cmftStudio
Ответ 7
Существуют различные представления карт окружения. Вот хороший обзор.
Обзор - Панорамные изображения
Если вы используете Photosphere (или любое приложение панорамы, если на то пошло), скорее всего, у вас уже есть горизонтальное представление широта/долгота.
Затем вы можете просто нарисовать текстурированный файл three.js SphereGeometry. Вот учебник о том, как сделать землю.
Учебник - Как сделать Землю в WebGL?
Желаем удачи:).
Ответ 8
Очень простое приложение на С++, которое преобразует равноугольную панораму в карту куба на основе ответа Salix Alba = > https://github.com/denivip/panorama
Ответ 9
Здесь приведена версия JavaScript Benjamn Dobell. convertFace
необходимо передать два объекта ìmageData
и идентификатор лица (0-6).
Предоставленный код можно безопасно использовать в веб-работнике, так как он не имеет зависимостей.
// 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) {...}
Ответ 10
Я создал решение этой проблемы с помощью OpenGL и создал вокруг нее инструмент командной строки. Он работает как с изображениями, так и с видео, и это самый быстрый инструмент, который я обнаружил там.
Convert360 - Проект на GitHub.
OpenGL Shader - Фрагментный шейдер, используемый для повторной проекции.
Использование так же просто, как:
$ pip install convert360
$ convert360 -i ~/Pictures/Barcelona/sagrada-familia.jpg -o example.png -s 300 300
Чтобы получить что-то вроде этого:
![введите описание изображения здесь]()
Ответ 11
Возможно, мне что-то не хватает. Но кажется, что большинство, если не весь представленный код трансформации, могут быть несколько неправильными. Они занимают сферическую панораму (равноугольная - 360 градусов по горизонтали и 180 градусов по вертикали) и, по-видимому, преобразуются в грани куба с помощью декартовой цилиндрической трансформации. Если они не будут использовать декартово-сферическое преобразование. См. http://mathworld.wolfram.com/SphericalCoordinates.html
Я полагаю, что до тех пор, пока они отклонят вычисление, чтобы перейти от грани куба к панораме, тогда это должно сработать. Но образы кубических граней при использовании сферического преобразования могут несколько отличаться.
Если я начну с этой равноугольной (сферической панорамы):
![введите описание изображения здесь]()
Тогда, если я использую цилиндрическое преобразование (которое я не уверен на 100% верно в это время), я получаю этот результат:
![введите описание изображения здесь]()
Но если я использую сферическое преобразование, я получаю этот результат:
![введите описание изображения здесь]()
Они не совпадают. Но мой результат сферической трансформации, похоже, соответствует результату Данке Се, но его ссылка не показывает вид преобразования, который он использует, насколько я могу его прочитать.
Так что я не понимаю код, используемый многими участниками этой темы?