opencv 将立方体贴图转换为等矩形全景

qcbq4gxm  于 2023-04-07  发布在  其他
关注(0)|答案(3)|浏览(296)

我想从立方体贴图[图1]转换成等矩形全景图[图2]。
图1

图2

可以从球形到立方(通过以下方式):Convert 2:1 equirectangular panorama to cube map),但失去了如何扭转它。
图2将使用Unity渲染成一个球体。

dffbzjpn

dffbzjpn1#

假设输入图像为以下cubemap格式:

目标是将图像投影为等矩形格式,如下所示:

转换算法相当简单。为了计算等距矩形图像中每个像素的颜色的最佳估计,给定一个有6个面的立方体图:

  • 首先,计算球面图像中每个像素对应的极坐标。
  • 其次,使用极坐标形成向量,并确定向量位于立方体Map的哪个面以及该面的哪个像素;就像从立方体中心投射的光线会击中它的一个侧面和该侧面上的特定点一样。

请记住,在给定归一化坐标的情况下,有多种方法可以估计等距矩形图像中像素的颜色(u,v)。最基本的方法是一个非常原始的近似,为了简单起见,将在这个答案中使用,其他更高级的方法可以计算几个相邻像素的平均值。
算法的实现将根据上下文而有所不同。我在Unity3D C#中做了一个快速实现,展示了如何在真实的世界的场景中实现算法。它在CPU上运行,有很大的改进空间,但很容易理解。

using UnityEngine;

public static class CubemapConverter
{
    public static byte[] ConvertToEquirectangular(Texture2D sourceTexture, int outputWidth, int outputHeight)
    {
        Texture2D equiTexture = new Texture2D(outputWidth, outputHeight, TextureFormat.ARGB32, false);
        float u, v; //Normalised texture coordinates, from 0 to 1, starting at lower left corner
        float phi, theta; //Polar coordinates
        int cubeFaceWidth, cubeFaceHeight;

        cubeFaceWidth = sourceTexture.width / 4; //4 horizontal faces
        cubeFaceHeight = sourceTexture.height / 3; //3 vertical faces

        for (int j = 0; j < equiTexture.height; j++)
        {
            //Rows start from the bottom
            v = 1 - ((float)j / equiTexture.height);
            theta = v * Mathf.PI;

            for (int i = 0; i < equiTexture.width; i++)
            {
                //Columns start from the left
                u = ((float)i / equiTexture.width);
                phi = u * 2 * Mathf.PI;

                float x, y, z; //Unit vector
                x = Mathf.Sin(phi) * Mathf.Sin(theta) * -1;
                y = Mathf.Cos(theta);
                z = Mathf.Cos(phi) * Mathf.Sin(theta) * -1;

                float xa, ya, za;
                float a;

                a = Mathf.Max(new float[3] { Mathf.Abs(x), Mathf.Abs(y), Mathf.Abs(z) });

                //Vector Parallel to the unit vector that lies on one of the cube faces
                xa = x / a;
                ya = y / a;
                za = z / a;

                Color color;
                int xPixel, yPixel;
                int xOffset, yOffset;

                if (xa == 1)
                {
                    //Right
                    xPixel = (int)((((za + 1f) / 2f) - 1f) * cubeFaceWidth);
                    xOffset = 2 * cubeFaceWidth; //Offset
                    yPixel = (int)((((ya + 1f) / 2f)) * cubeFaceHeight);
                    yOffset = cubeFaceHeight; //Offset
                }
                else if (xa == -1)
                {
                    //Left
                    xPixel = (int)((((za + 1f) / 2f)) * cubeFaceWidth);
                    xOffset = 0;
                    yPixel = (int)((((ya + 1f) / 2f)) * cubeFaceHeight);
                    yOffset = cubeFaceHeight;
                }
                else if (ya == 1)
                {
                    //Up
                    xPixel = (int)((((xa + 1f) / 2f)) * cubeFaceWidth);
                    xOffset = cubeFaceWidth;
                    yPixel = (int)((((za + 1f) / 2f) - 1f) * cubeFaceHeight);
                    yOffset = 2 * cubeFaceHeight;
                }
                else if (ya == -1)
                {
                    //Down
                    xPixel = (int)((((xa + 1f) / 2f)) * cubeFaceWidth);
                    xOffset = cubeFaceWidth;
                    yPixel = (int)((((za + 1f) / 2f)) * cubeFaceHeight);
                    yOffset = 0;
                }
                else if (za == 1)
                {
                    //Front
                    xPixel = (int)((((xa + 1f) / 2f)) * cubeFaceWidth);
                    xOffset = cubeFaceWidth;
                    yPixel = (int)((((ya + 1f) / 2f)) * cubeFaceHeight);
                    yOffset = cubeFaceHeight;
                }
                else if (za == -1)
                {
                    //Back
                    xPixel = (int)((((xa + 1f) / 2f) - 1f) * cubeFaceWidth);
                    xOffset = 3 * cubeFaceWidth;
                    yPixel = (int)((((ya + 1f) / 2f)) * cubeFaceHeight);
                    yOffset = cubeFaceHeight;
                }
                else
                {
                    Debug.LogWarning("Unknown face, something went wrong");
                    xPixel = 0;
                    yPixel = 0;
                    xOffset = 0;
                    yOffset = 0;
                }

                xPixel = Mathf.Abs(xPixel);
                yPixel = Mathf.Abs(yPixel);

                xPixel += xOffset;
                yPixel += yOffset;

                color = sourceTexture.GetPixel(xPixel, yPixel);
                equiTexture.SetPixel(i, j, color);
            }
        }

        equiTexture.Apply();
        var bytes = equiTexture.EncodeToPNG();
        Object.DestroyImmediate(equiTexture);

        return bytes;
    }
}

为了利用GPU,我创建了一个着色器来进行相同的转换。它比在CPU上逐像素运行转换要快得多,但不幸的是Unity对立方体贴图施加了分辨率限制,因此它的用处在使用高分辨率输入图像的情况下受到限制。

Shader "Conversion/CubemapToEquirectangular" {
  Properties {
        _MainTex ("Cubemap (RGB)", CUBE) = "" {}
    }

    Subshader {
        Pass {
            ZTest Always Cull Off ZWrite Off
            Fog { Mode off }      

            CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #pragma fragmentoption ARB_precision_hint_fastest
                //#pragma fragmentoption ARB_precision_hint_nicest
                #include "UnityCG.cginc"

                #define PI    3.141592653589793
                #define TWOPI 6.283185307179587

                struct v2f {
                    float4 pos : POSITION;
                    float2 uv : TEXCOORD0;
                };

                samplerCUBE _MainTex;

                v2f vert( appdata_img v )
                {
                    v2f o;
                    o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                    o.uv = v.texcoord.xy * float2(TWOPI, PI);
                    return o;
                }

                fixed4 frag(v2f i) : COLOR 
                {
                    float theta = i.uv.y;
                    float phi = i.uv.x;
                    float3 unit = float3(0,0,0);

                    unit.x = sin(phi) * sin(theta) * -1;
                    unit.y = cos(theta) * -1;
                    unit.z = cos(phi) * sin(theta) * -1;

                    return texCUBE(_MainTex, unit);
                }
            ENDCG
        }
    }
    Fallback Off
}

通过在转换期间采用更复杂的方法来估计像素的颜色或通过对结果图像进行后处理(或实际上两者),可以大大提高结果图像的质量。例如,可以生成较大尺寸的图像以应用模糊滤波器,然后将其下采样到所需的尺寸。
我创建了一个简单的Unity项目,其中包含两个编辑器向导,展示了如何正确使用C#代码或上面显示的着色器。https://github.com/Mapiarz/CubemapToEquirectangular
请记住在Unity中为输入图像设置正确的导入设置:

  • 点滤波
  • 真彩色格式
  • 禁用mipmaps
  • 非2的幂:无(仅适用于2DTextures)
  • 启用读/写(仅适用于2DTextures)
kq0g1dla

kq0g1dla2#

cube2sphere自动执行整个过程。示例:

$ cube2sphere front.jpg back.jpg right.jpg left.jpg top.jpg bottom.jpg -r 2048 1024 -fTGA -ostitched
jei2mxaa

jei2mxaa3#

Bartosz的答案是可行的,但是当编译器优化将单位向量除法转换为逆乘法时,他的代码中存在数值不稳定性。该操作的结果可能不完全是1.0,导致IF失败(众所周知,浮点数不喜欢在数学运算后使用==)。
此问题的修复方法是将IF更改为与+/- a进行比较。

else if (xa == -1) // don't do this
else if (vx == -a) // do this

此外,对于优化,sin(theta)cos(theta)可以在内循环外部预先计算。
另一个问题是,这段代码不做插值。我建议实现它,以便有更好的图像质量。我使用双三次插值,除了边缘的瓷砖。工作得很好。

相关问题