2019独角兽企业重金招聘Python工程师标准>>>

提问:

@ WestLangley:

我正在给某网站开发一个简单的3D全景浏览功能。考虑到移动端的性能,我使用了three.js的CSS3 renderer。它需要一个由六张图组成功立方体贴图。

我用iPhone上的“Google Photosphere”(或其他类似的app)创建了一个2:1的柱形全景图,然后用这个网站(需要Flash)把柱形全景图转换成了立方体全景图。

但是我想自己完成这种转换,比如用three.js或者Photoshop。我发现Andrew Hazelden's好像用Photoshop做过类似的操作,但是没有那种直接转换的。有没有什么数学方法或者现成的js库可以做到的?尽量不要使用Blender这类3D软件来做。

也许实现会比较复杂,但是还是打算来问一下。本人js基础还阔以,但是用three.js没多久,因为WebGL在移动端好像跑起来有点慢,兼容性也一般,所以犹豫要不要用它的函数库。

提问下的回复:

@ Salix alba:可以通过在js中使用CSS或canvas做到,但是我不确定three.js是不是也可行:https://stackoverflow.com/questions/8912917/cutting-an-image-into-pieces-through-javascript

@ Eric Seifert: 我用一段python代码实现了:https://github.com/seiferteric/cubemap

最高票回答:

@ Salix alba

如果是要在服务端做的话选项有很多。http://www.imagemagick.org/里有一堆命令行工具可以对图片进行切割,你可以把这些命令放到你的代码里面,每次要转换的时候就执行。

很难说清楚这些命令里面用了哪些算法。我们可以通过一张正方形网格图片看看程序做了什么操作(译者注:有点拗口不好翻,大概就这意思)。我用维基上的一张网格图来示例。

转换后

通过这两张图可以看出立方体映射是如何映射的。

想象一下,有一个布满经纬线的球体被一个立方体包裹,然后从球的中心点投影到立方体上,产生一个扭曲的网格。

用数学来说明,有极坐标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θ 经过 中心投影到x=1的平面上后会变成a · sinθ · cosø、a · sinθ · sinø、a · cosθ。

因为 a·sinθ · cos ø = 1,所以 a = 1 / (sinθ · cosø)

因此投影后的点坐标为(1, tanø, cotθ / cosø)

如果 | cot θ / 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,tandef 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 = 1y = 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 = 1z = 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 = -1y = 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 = -1z = 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 = 1return ("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 = -1return ("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.sizeoutSize = imgOut.sizeinPix = imgIn.load()outPix = imgOut.load()edge = inSize[0]/4   # the length of each edge in pixelsfor 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,yif x >= outSize[0]:#print "x out of range ",x,resx=outSize[0]-1if y >= outSize[1]:#print "y out of range ",y,resy=outSize[1]-1outPix[x,y] = pixelimgIn = 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函数接收一个包含xyz坐标的元组和立方体边长输出到图片坐标系中

网上找了一张全景图,用上面的算法对其进行转换以后,得到了正确的形状:

好像只有大部分的线是铺对的,又一部分图像没有任何像素,这是因为像素投影没有一 一对应,我们需要再做一次逆转换。先在源图像上遍历每个像素,然后在目标图像上找到每个对应的点。接着遍历目标图像,在对应的原图像上找到最近的像素。

修改代码后:

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)/edgeb = 2.0*float(j)/edgeif 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.sizeoutSize = imgOut.sizeinPix = imgIn.load()outPix = imgOut.load()edge = inSize[0]/4   # the length of each edge in pixelsfor i in xrange(outSize[0]):face = int(i/edge) # 0 - back, 1 - left 2 - front, 3 - rightif face==2:rng = xrange(0,edge*3)else:rng = xrange(edge,edge*2)for j in rng:if j<edge:face2 = 4 # topelif j>=2*edge:face2 = 5 # bottomelse:face2 = face(x,y,z) = outImgToXYZ(i,j,face2,edge)theta = atan2(y,x) # range -pi to pir = hypot(x,y)phi = atan2(z,r) # range -pi/2 to pi/2# source img coordsuf = ( 2.0*edge*(theta + pi)/pi )vf = ( 2.0*edge * (pi/2 - phi)/pi)# Use bilinear interpolation between the four surrounding pixelsui = floor(uf)  # coord of pixel to bottom leftvi = floor(vf)u2 = ui+1       # coords of pixel to top rightv2 = vi+1mu = uf-ui      # fraction of way across pixelnu = vf-vi# Pixel values of four cornersA = 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()

重新生成后的结果:

英文好的可以看原文:https://stackoverflow.com/questions/29678510/convert-21-equirectangular-panorama-to-cube-map,有翻译上的问题欢迎指出。

后面的回复还有不少干货,有空我会继续翻译

转载于:https://my.oschina.net/codingDog/blog/1489965

(译)stackoverflow上关于柱形全景与立方体全景转换的讨论相关推荐

  1. R语言在直方图条(柱形)上添加计数(count)或者百分比(percent)标签

    R语言在直方图条(柱形)上添加计数(count)或者百分比(percent)标签 目录

  2. 用python绘制柱状图标题-使用Python绘制柱形竞赛图

    我们经常看到的Bar Chart Race(柱形竞赛图),可以看到数据的呈现非常的直观.今天就一起来学习下如何生成和上面一样的柱形竞赛图. 1.导入Python库 Python 1 2 3 4 5im ...

  3. angular面试题及答案_关于最流行的Angular问题的StackOverflow上的48个答案

    angular面试题及答案 by Shlomi Levi 通过Shlomi Levi 关于最流行的Angular问题的StackOverflow上的48个答案 (48 answers on Stack ...

  4. python 分布图_python数据分布型图表柱形分布图系列带误差线的柱形图

    柱形分布图系列 柱形分布图系列 使用柱形图的方式展示数据的分布规律: 可以借助误差线或散点图: 带误差线的柱形图就是使用每个类别的均值作为柱形的高度: 再根据每个类别的标准差绘制误差线: 缺点:无法显 ...

  5. glide加载gif图不显示动画_用Python绘制会动的柱形竞赛图

    我们经常看到的Bar Chart Race(柱形竞赛图),可以看到数据的呈现非常的直观.今天就一起来学习下如何生成和上面一样的柱形竞赛图. 1.导入Python库 2.加载数据集 这里使用的是城市人口 ...

  6. stackoverflow上一个最会举例子的专家

    https://stackoverflow.com/ Premraj是stackoverflow上一个一个最会举例子的专家,我特意收集了他的一些有趣的举例: Java弱引用最精彩的解释 https:/ ...

  7. 如何在StackOverflow上获得第一个标签徽章-以及为什么它很重要。

    by Angelos Chalaris 通过安吉洛斯·查拉利斯(Angelos Chalaris) 如何在StackOverflow上获得第一个标签徽章-以及为什么它很重要. (How to get ...

  8. 为什么 StackOverflow 上的代码片段会摧毁你的项目?

    昨天公司里碰到一件令人哑然失笑的事情.帮朋友公司做的一个项目,做SIT测试的时候发现一些bug,仔细查了下原因,原来是因为当初觉得这个项目比较简单,交给了几个新入职的新丁,也算是给他们练练手,结果其中 ...

  9. stackoverflow上Java相关回答整理翻译

    原文链接:https://github.com/giantray/stackoverflow-java-top-qa stackoverflow-Java-top-qa 对stackoverflow上 ...

最新文章

  1. 一文详解Inception家族的前世今生(从InceptionV1-V4、Xception)附全部代码实现
  2. 如何用Dart写一个单例
  3. 【HDOJ】2209 翻纸牌游戏
  4. vsscode beego 没有提示_轻松搭建基于 Serverless 的 Go 应用(Gin、Beego 举例)
  5. VTK:Points之PowercrustExtractSurface
  6. Python ConfigParser的使用
  7. java修改默认字符编码_设置默认的Java字符编码?
  8. Django---启动admin的报no such table: auth_user错误
  9. python3openpyxl,python3和openpyxl,在写入fi时格式化日期列表
  10. kubeadm安装k8s测试环境
  11. 眼图测试(硬件测试、信号完整性测试)
  12. 密码学(五):数字签名
  13. 家谱系统php,家谱信息管理系统
  14. php 导出word怎么分页,php 导出Word怎么分页
  15. uni-app在华为应用市场上架审核无法通过,涉及个人信息:IMEI用户数据收集问题
  16. Docker中Swarm集群部署
  17. [洛谷P1024]python一元三次方程求解
  18. java实现图片分辨率压缩、图片软化、jpg质量压缩
  19. 孩子,你在家乡还好妈
  20. 终于弄懂 CRC 循环冗余校验 辽

热门文章

  1. 大数据公司面试题准备
  2. 三星9300 Kies 升级包 存储
  3. 算法 | 布朗运动与醉汉 赌徒的关系
  4. Linux centos7 docker部署gitlab私有服务器
  5. 怎样写robots.txt实例
  6. RC电路的充放过程C语言实现,RC串联电路的暂态过程基本原理介绍
  7. Hi3518E_V200 SDK编译笔记 第二季
  8. 计算机机考试卷分析,在线考试系统 ——试卷分析
  9. zookeeper 链接报错 KeeperErrorCode = NodeExists for
  10. 揭密让程序猿谈外包而色变的原因