Ubah panorama persegi panjang 2:1 menjadi peta kubus

Saat ini saya sedang mengerjakan penampil panorama 3D sederhana untuk sebuah situs web. Untuk alasan kinerja seluler, saya menggunakan three.js perender CSS3. Ini memerlukan peta kubus, dibagi menjadi 6 gambar tunggal.

Saya merekam gambar di iPhone dengan aplikasi Google Fotosfer, atau aplikasi serupa yang membuat panorama persegi panjang 2:1. Saya kemudian mengubah ukuran dan mengonversinya menjadi peta kubus dengan situs web ini: http://gonchar.me/panorama/ (Flash)

Sebaiknya saya melakukan konversi sendiri, baik dengan cepat di three.js, jika memungkinkan, atau di Photoshop. Saya menemukan tindakan Photoshop Andrew Hazelden, dan sepertinya hampir sama, tetapi tidak ada konversi langsung yang tersedia. Apakah ada cara matematis untuk mengonversinya, atau semacam skrip yang dapat melakukannya? Saya ingin menghindari menggunakan aplikasi 3D seperti Blender, jika memungkinkan.

Mungkin ini keputusan yang sulit, tapi saya pikir saya akan bertanya. Saya memiliki pengalaman yang baik dengan javascript, tapi saya cukup baru di three.js. Saya juga ragu untuk mengandalkan fungsi WebGL, karena tampaknya lambat atau bermasalah pada perangkat seluler. Dukungan juga masih terbatas.


person oelna    schedule 16.04.2015    source sumber
comment
Ada cara untuk melakukan ini dalam javascript menggunakan CSS atau kanvas. Namun saya tidak yakin itu kompatibel dengan three.js. stackoverflow.com/questions/8912917 /   -  person Salix alba    schedule 16.04.2015
comment
Saya membuat skrip python untuk melakukan ini github.com/seiferteric/cubemap   -  person Eric Seifert    schedule 03.03.2017


Jawaban (12)


Jika Anda ingin melakukannya di sisi server ada banyak pilihan. http://www.imagemagick.org/ memiliki banyak alat baris perintah yang dapat mengiris gambar Anda menjadi beberapa bagian . Anda dapat memasukkan perintah untuk melakukan ini ke dalam skrip dan menjalankannya setiap kali Anda memiliki gambar baru.

Sulit untuk mengetahui algoritma apa yang digunakan dalam program ini. Kita dapat mencoba dan merekayasa balik apa yang terjadi dengan memasukkan kotak persegi ke dalam program. Saya telah menggunakan grid dari wikipedia

64 kali 64 kisi

Yang memberi projected gridIni memberi kita petunjuk tentang bagaimana kotak itu dibuat.

Pencitraan bola dengan garis lintang dan bujur di salah satunya, dan kubus yang mengelilinginya. Sekarang proyek dari titik di tengah bola menghasilkan grid terdistorsi pada kubus.

Secara matematis ambil koordinat kutub r, θ, ø, untuk bola r=1, 0 ‹ θ ‹ π, -π/4 ‹ ø ‹ 7π/4

  • x= r dosa θ cos ø
  • kamu= r dosa θ dosa ø
  • z= r karena θ

memproyeksikan ini secara terpusat ke kubus. Pertama kita bagi menjadi empat wilayah berdasarkan garis lintang -π/4 ‹ ø ‹ π/4, π/4 ‹ ø ‹ 3π/4, 3π/4 ‹ ø ‹ 5π/4, 5π/4 ‹ ø ‹ 7π/4. Ini akan memproyeksikan ke salah satu dari empat sisi atas atau bawah.

Asumsikan kita berada di sisi pertama -π/4 ‹ ø ‹ π/4. Proyeksi sentral dari (sin θ cos ø, sin θ sin ø, cos θ) adalah (a sin θ cos ø, a sin θ sin ø, a cos θ) yang mengenai bidang x=1 ketika

  • dosa θ cos ø = 1

so

  • a = 1 / (sin θ cos ø)

dan titik yang diproyeksikan adalah

  • (1, tan ø, tempat tidur θ / cos ø)

Jika | tempat tidur θ / cos ø | ‹ 1 ini akan berada di bagian depan. Jika tidak, itu akan diproyeksikan ke atas atau bawah dan Anda memerlukan proyeksi berbeda untuk itu. Pengujian yang lebih baik untuk bagian atas menggunakan fakta bahwa nilai minimum cos ø adalah cos π/4 = 1/√2, sehingga titik proyeksi selalu berada di atas jika cot θ / (1/√2) > 1 atau tan θ ‹ 1/√2. Hasilnya adalah θ ‹ 35º atau 0,615 radian.

Gabungkan ini dengan 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()

Fungsi projection mengambil nilai theta dan phi dan mengembalikan koordinat dalam kubus dari -1 hingga 1 di setiap arah. cubeToImg mengambil koordinat (x,y,z) dan menerjemahkannya ke koordinat gambar keluaran.

Algoritme di atas sepertinya mendapatkan geometri yang benar dengan menggunakan gambar istana buckingham kita mendapatkan < img src="https://i.stack.imgur.com/rfgE8.png" alt="peta kubus istana buckingham"> Tampaknya ini membuat sebagian besar garis di paving benar.

Kami mendapatkan beberapa artefak gambar. Hal ini disebabkan tidak memiliki peta piksel 1 banding 1. Yang perlu kita lakukan adalah menggunakan transformasi terbalik. Daripada mengulang setiap piksel di sumber dan menemukan piksel yang sesuai di target, kita mengulangi gambar target dan menemukan piksel sumber terdekat yang sesuai.

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()

Hasilnya adalah Menggunakan transformasi terbalik

person Salix alba    schedule 16.04.2015
comment
Ya, mengirisnya sebenarnya bukan masalah. Bagian yang sulit adalah kemiringan dan lengkungan proyeksi, sehingga gambar sesuai dengan peta kubus. - person oelna; 16.04.2015
comment
Terima kasih atas jawaban lengkapnya. Harus kuakui, ini agak berlebihan, tapi sepertinya ini mungkin berhasil. Saya akan memikirkannya sedikit dan jika berhasil, saya akan memposting pembaruan pada pertanyaan saya yang merinci hasilnya. Sementara itu, saya akan menerima jawaban Anda. Terima kasih lagi! - person oelna; 20.04.2015
comment
@Salixalba Bagaimana cara mendapatkan theta dan phi untuk piksel? - person Brans Ds; 16.06.2015
comment
@oelna Apakah Anda menguji ini? - person Brans Ds; 16.06.2015
comment
@BransDs Sayangnya, tidak. Seperti yang saya nyatakan, ini lebih rumit daripada yang dapat saya pahami saat ini. Saya pikir jawabannya mungkin masih membantu orang lain. Jika ada yang benar-benar menggunakan ini dan membuatnya berfungsi, saya ingin mendengar tanggapannya. - person oelna; 16.06.2015
comment
Theta dan phi pada dasarnya adalah garis lintang dan garis bujur. θ=0 adalah jajak pendapat utara, θ=π/2 di khatulistiwa, dan θ=π di jajak pendapat selatan. Jadi ambil koordinat y dari gambar masukan Anda, bagi dengan tinggi dan kalikan dengan π. Untuk ø ambil koordinat x bagi lebarnya, kalikan dengan 2π dan kurangi dengan π/4. Metode yang lebih sederhana adalah dengan mengambil potongan dari tengah gambar dan membaginya menjadi empat bagian yang sama besar. Ini akan menghasilkan empat dinding. Itu n=mungkin terdistorsi tetapi dapat diterima. - person Salix alba; 16.06.2015
comment
@Salixalba hanya lupa apa itu fungsi klip? apakah itu Clip(x, lo, hi) if (x ‹ lo) return lo; jika (x › hai) kembalikan hai; kembalikan x; - person Brans Ds; 17.06.2015
comment
Ya. Ini adalah fungsi numpy.clip(). Anda tidak memerlukan cek untuk batas bawah. - person Salix alba; 17.06.2015
comment
Anda harus mengemasnya dan membuangnya di github. - person freakTheMighty; 29.12.2015
comment
Saya mengganti numpy.clip dengan python murni dan mendapatkan kinerja yang lebih baik (130 detik hingga 50 detik) (numpy.clip untuk daftar angka) contoh : A = inPix[int(ui % inSize[0]),sorted([0 , vi, dalamUkuran[1]-1])[1]] - person Pooya Mobasher Behrooz; 16.02.2017
comment
Saat mengerjakan sebuah proyek, saya telah mengimplementasikan algoritme ini sebagai aplikasi C++ mandiri =› github.com/denivip/panorama - person Denis Bulichenko; 13.03.2017
comment
Hai teman-teman, saya menulis ulang ini dengan banyak trik numpy, dan menggunakan opencv untuk interpolasi piksel (tidak terlalu diperlukan..) dan berubah dari 244 detik menjadi 7 detik pada gambar 8000x4000. Dengan metode ini Anda dapat membuat pemetaan satu kali, dan menggunakannya pada banyak file dengan cepat (dengan remap cv2) pastebin.com/Eeki92Zv - person Eric; 17.03.2018

Mengingat jawaban diterima yang sangat baik, saya ingin menambahkan implementasi c++ saya yang sesuai, berdasarkan OpenCV.

Bagi mereka yang belum familiar dengan OpenCV, anggap Mat sebagai gambar. Pertama-tama kita membuat dua peta yang dipetakan ulang dari gambar persegi panjang ke permukaan peta kubus yang sesuai. Kemudian, kami melakukan pekerjaan berat (yaitu memetakan ulang dengan interpolasi) menggunakan OpenCV.

Kode dapat dibuat lebih ringkas, jika keterbacaan tidak menjadi perhatian.

// 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));
}

Dengan diberikan masukan sebagai berikut:

masukkan deskripsi gambar di sini

Wajah-wajah berikut dihasilkan:

masukkan deskripsi gambar di sini

Gambar milik Optonaut.

person Emiswelt    schedule 11.01.2016
comment
Hai! Program Anda benar, tetapi di OpenCV, x dan y di .at(x,y) terbalik bukan? Seharusnya mapx.at<float>(y, x) = u; mapy.at<float>(y, x) = v; - person Raph Schim; 13.02.2018
comment
tidak masalah jika lebar = tinggi tetapi dapat menyebabkan kesalahan.. Tetapi jika Anda mengganti x dengan y, gambar Anda akan diputar :) Masih berfungsi dengan baik, terima kasih! - person Raph Schim; 13.02.2018
comment
ini peta kubus -› silinder, bukan persegi panjang.. makanya ada lingkaran hitam di kutub atas dan bawah peta kubus.. karena proyeksi silinder tidak memiliki data atas dan bawah - person David Jeske; 01.07.2020
comment
@DavidJeske Ini pasti berbentuk persegi panjang. Panorama masukan (dari koleksi saya) hanya kehilangan bagian atas dan bawah. Jika Anda menjalankan kode dengan panorama penuh, tidak akan ada lubang. - person Emiswelt; 05.07.2020

PEMBARUAN 2: Sepertinya ada orang lain yang sudah membuat aplikasi web daripada milik saya. Konversinya berjalan di sisi klien, jadi tidak ada unggahan/unduhan yang perlu dikhawatirkan.

Saya kira jika Anda membenci JS karena alasan tertentu, atau mencoba melakukan ini di ponsel Anda, maka aplikasi web saya di bawah ini baik-baik saja.

PEMBARUAN: Saya telah memublikasikan aplikasi web sederhana tempat Anda dapat mengunggah panorama dan mengembalikan 6 gambar skybox dalam zip.

Sumber adalah implementasi ulang yang telah dibersihkan dari apa yang ada di bawah, dan tersedia di Github.

Aplikasi saat ini berjalan pada satu dyno Heroku tingkat gratis, mohon jangan mencoba menggunakannya sebagai API. Jika Anda menginginkan otomatisasi, buat penerapan Anda sendiri; tersedia satu klik Terapkan ke Heroku.

ASLI: Ini adalah versi modifikasi (naif) dari Jawaban Salix Alba yang benar-benar fantastis yang mengubah satu wajah pada satu waktu, mengeluarkan enam gambar berbeda dan mempertahankan jenis file gambar asli.

Selain fakta bahwa sebagian besar kasus penggunaan mungkin mengharapkan enam gambar terpisah, keuntungan utama mengonversi satu wajah pada satu waktu adalah membuat bekerja dengan gambar berukuran besar menjadi lebih hemat memori.

#!/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])
person Benjamin Dobell    schedule 02.05.2016

Saya menulis skrip untuk memotong peta kubus yang dihasilkan menjadi file individual (posx.png, negx.png, posy.png, negy.png, posz.png, dan negz.png). Itu juga akan mengemas 6 file ke dalam file .zip.

Sumbernya ada di sini: https://github.com/dankex/compv/blob/master/3d-graphics/skybox/cubemap-cut.py

Anda dapat memodifikasi array untuk mengatur file gambar:

name_map = [ \
 ["", "", "posy", ""],
 ["negz", "negx", "posz", "posx"],
 ["", "", "negy", ""]]

File yang dikonversi adalah:

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini < img src="https://i.stack.imgur.com/I3qMC.png" alt="masukkan deskripsi gambar di sini"> masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

person Danke Xie    schedule 22.08.2015

Menemukan pertanyaan ini, dan meskipun jawabannya bagus, saya pikir masih ada beberapa hal yang belum terungkap, jadi inilah dua sen saya.

Pertama: kecuali Anda benar-benar harus mengonversi gambar itu sendiri (yaitu karena beberapa persyaratan perangkat lunak tertentu), jangan.

Alasannya adalah, meskipun terdapat pemetaan yang sangat sederhana antara proyeksi segi empat dan proyeksi kubik, pemetaan antar area tidaklah sederhana: ketika Anda membuat korespondensi antara titik tertentu pada gambar tujuan Anda dan sebuah titik di sumber dengan perhitungan dasar, segera setelah Anda mengonversi kedua titik menjadi piksel dengan membulatkan Anda melakukan perkiraan yang sangat mentah yang tidak mempertimbangkan ukuran pikselnya, dan kualitas gambarnya pasti rendah.

Kedua: meskipun Anda perlu melakukan konversi saat runtime, apakah Anda yakin perlu melakukan konversi sama sekali? Kecuali ada masalah kinerja yang sangat ketat, jika Anda hanya memerlukan skybox, buatlah bola yang sangat besar, jahit tekstur persegi panjang di atasnya, dan berangkatlah. Sejauh yang saya ingat, tiga JS sudah menyediakan ruang tersebut ;-)

Ketiga: NASA menyediakan alat untuk mengkonversi semua proyeksi yang mungkin (saya baru mengetahuinya, mengujinya, dan bekerja dengan sangat baik). Anda dapat menemukannya di sini:

G.Projector — Proyektor Peta Global

dan menurut saya masuk akal untuk berpikir bahwa orang-orang itu tahu apa yang mereka lakukan ;-)

Semoga ini membantu

PEMBARUAN: ternyata "orang-orang" tahu apa yang mereka lakukan sampai titik tertentu: peta kubus yang dihasilkan memiliki batas yang mengerikan yang membuat konversi tidak semudah itu...

PEMBARUAN 2: menemukan alat definitif untuk konversi persegi panjang ke peta kubus, dan disebut erect2cubic.

Ini adalah utilitas kecil yang menghasilkan skrip untuk dimasukkan ke pelukan, dengan cara ini:

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

(informasi diambil dari halaman Vinay's Hacks)

dan akan menghasilkan semua 6 wajah peta kubus. Saya menggunakannya untuk proyek saya dan berfungsi dengan sangat baik!

Satu-satunya kelemahan dari pendekatan ini adalah skripnya erect2cubit itu tidak ada dalam distribusi standar Ubuntu (yang saya gunakan) dan saya harus menggunakan instruksi di tautan ini:

Blog yang menjelaskan cara memasang dan menggunakan erect2cubic

untuk mengetahui cara menginstalnya.

Benar-benar layak!

person Rick77    schedule 28.08.2015
comment
Saya tidak percaya betapa buruknya erect2cubic dan nona. Keduanya akan gagal tanpa kesalahan jika tidak dapat membaca file sumber apa pun. erect2cubic tidak mungkin dijalankan di Windows, dan masih memerlukan tutorial untuk menjalankannya bahkan di Linux. nona tidak akan melakukan apa pun tanpa formulir atau pesan atau kesalahan apa pun karena ada saluran tambahan di file sumber. - person Alexander Gräf; 26.09.2020

cmft Studio mendukung conversion/filtering dari berbagai HDR/LDR proyeksi hingga cubemaps.

https://github.com/dariomanesku/cmftStudio

person planetboy    schedule 28.04.2016

Berikut adalah versi JavaScript dari kode Benjamn Dobell. convertFace harus melewati dua objek ìmageData dan ID wajah (0-6).

Kode yang disediakan dapat digunakan dengan aman di pekerja web, karena tidak memiliki ketergantungan.

        // 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) {...}
person knee-cola    schedule 18.04.2017

Ada berbagai macam representasi peta lingkungan. Berikut ini ikhtisar yang bagus.

Ikhtisar - Gambar Panorama

Jika Anda menggunakan Fotosfer (atau aplikasi panorama apa pun), kemungkinan besar Anda sudah memiliki garis lintang / representasi garis bujur. Anda kemudian dapat menggambar three.js SphereGeometry bertekstur. Berikut ini tutorial cara merender bumi.

Tutorial - Cara Membuat Bumi di WebGL?

Semoga berhasil :).

person Tobias Gurdan    schedule 26.07.2015

Aplikasi C++ yang sangat sederhana yang mengubah panorama persegi panjang menjadi peta kubus berdasarkan jawaban oleh Salix Alba => https://github.com/denivip/panorama

person Denis Bulichenko    schedule 13.03.2017
comment
Ada masalah dengan satu gambar wajah. silakan periksa masalah itu di git github.com/denivip/panorama/issues/6 - person gadlol; 22.12.2018
comment
@gadlol ya, lihat notifikasinya. Terima kasih telah memperhatikan! Saya akan menjaganya setelah Natal :-) - person Denis Bulichenko; 23.12.2018

Saya membuat solusi untuk masalah ini menggunakan OpenGL dan membuat alat baris perintah di sekitarnya. Ia berfungsi baik dengan gambar maupun video, dan ini adalah alat tercepat yang saya temukan di luar sana.

Convert360 - Proyek di GitHub.

OpenGL Shader - Shader fragmen yang digunakan untuk re -proyeksi.

Penggunaannya sesederhana:

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

Untuk mendapatkan sesuatu seperti ini:

masukkan deskripsi gambar di sini

person Mateus Zitelli    schedule 24.06.2017

Mungkin saya melewatkan sesuatu di sini. Namun tampaknya sebagian besar, jika tidak semua, kode transformasi yang disajikan mungkin salah. Mereka mengambil panorama bola (persegi panjang --- 360 derajat secara horizontal dan 180 derajat secara vertikal) dan tampaknya mengkonversi ke permukaan kubus menggunakan transformasi silinder kartesius ‹->. Seharusnya mereka tidak menggunakan transformasi bola ‹-> kartesius. Lihat http://mathworld.wolfram.com/SphericalCoordinates.html

Saya kira selama mereka membalik perhitungan untuk beralih dari permukaan kubus ke panorama, maka itu akan berhasil. Namun gambar permukaan kubus mungkin sedikit berbeda saat menggunakan transformasi bola.

Jika saya memulai dengan persegi panjang ini (panorama bola):

masukkan deskripsi gambar di sini

Kemudian jika saya menggunakan transformasi silinder (yang saat ini saya belum yakin 100% benar), saya mendapatkan hasil ini:

masukkan deskripsi gambar di sini

Tetapi jika saya menggunakan transformasi bola, saya mendapatkan hasil ini:

masukkan deskripsi gambar di sini

Mereka tidak sama. Namun hasil transformasi bola saya sepertinya cocok dengan hasil Danke Xie, namun tautannya tidak menunjukkan jenis transformasi yang dia gunakan, sejauh yang saya bisa membacanya.

Jadi, apakah saya salah memahami kode yang digunakan oleh banyak kontributor topik ini?

person fmw42    schedule 20.08.2017
comment
Saya memiliki masalah yang sama dan bertanya-tanya apakah itu gambar sumbernya. Namun, gambar yang bermasalah tersebut dapat diproses dengan benar oleh scope2cube misalnya. Apakah Anda membuat kemajuan lebih lanjut dalam hal ini? - person Longmang; 24.02.2020
comment
@ Longmang. Tidak, saya memiliki skrip yang melakukan transformasi bola dan sepertinya berhasil. Saya masih belum paham dengan referensi yang mengklaim menggunakan silinder dan menyebutnya bola. - person fmw42; 24.02.2020
comment
maukah kamu mengizinkan aku mengambil naskahmu? Pengetahuan saya tentang OpenCV dll lemah sehingga kemampuan saya berada pada batas yang kurang baik - person Longmang; 25.02.2020
comment
Skrip saya adalah skrip bash Unix shell yang menjalankan ImageMagick, bukan OpenCV. Ini tersedia untuk penggunaan non-komersial tanpa lisensi di fmwconcepts.com/imagemagick - person fmw42; 25.02.2020

kubi dapat mengonversi gambar persegi panjang menjadi permukaan kubus. Saya telah menulisnya agar cepat dan fleksibel. Ini memberikan opsi untuk memilih tata letak keluaran (6 gambar terpisah adalah default) dan memutuskan metode pengambilan sampel ulang.

person indus    schedule 18.06.2021