如何将球面坐标转换为等角投影坐标?

How to convert spherical coordinates to equirectangular projection coordinates?

简化问题

如何转换 spherical coordinate (θ, φ) into a position (x, y) on an equirectangular projection(也称为 'geographic projection')?

其中:


下面是原始问题,当时我还没有很好地理解这个问题,但我认为它仍然可以很好地展示这个解决方案的实际应用。

上下文

编辑:原题为:如何将给定角度的照片变换成全景照片的一部分?

如果我想转换以任何给定角度拍摄的照片,以便我可以将生成的 (distorted/transformed) 图像放置在相应的特定位置,谁能帮助我采取哪些步骤在等距柱状投影、立方体贴图或任何全景照片投影上?

无论哪个投影最容易做就足够了,因为关于如何在不同投影之间进行转换的资源很多。我只是不知道如何从实际照片到这样的投影。

可以安全地假设相机将停留在固定位置,并且可以从那里向任何方向旋转。我认为执行此操作所需的数据可能是这样的:

我有这个数据,我想第一步是做镜头校正,这样所有应该是直的线实际上都是直的。而这可以使用imagemagickBarrel Distortion来完成,其中您只需要填写三个参数:a、b和c。应用于图像以纠正此问题的转换很简单。

我卡在下一步了。要么我不完全理解它,要么搜索引擎没有帮助我,因为大多数结果都是关于在已经给定的投影之间进行转换,或者使用高级应用程序将照片智能地拼接在一起。这些结果没有帮助我回答我的问题。

编辑:我想也许一个数字可以帮助更好地解释它:)

问题是给定的照片 Red 无法在不进行变换的情况下放入等距柱状投影。下图说明了这个问题。

所以,我有红色,我需要将它转换成绿色蓝色显示变换的不同,但这取决于horizontal/vertical角度。

嗯,我想也许你应该退后一步。 考虑您的相机角度(70 毫米左右)。 但是你的背景图像是 horizontal 的 360 度(但也是垂直的)。 考虑两种图片的透视变形。 对于背景图片,在垂直方向上只有horizon没有垂直变形。可悲的是,它只是一条细线。 随着失真的增加,您到达顶部或底部的次数越多。

它不像桶形失真那样恒定,而是取决于 horizon 的垂直距离。

我认为了解差异的最佳方法是从侧面观察这两种类型的相机和它们应该投射的目标,从那里它的三角学,数学。

请注意,对于 70mm 图片,您需要知道拍摄角度。 (或估计)

如果照片是从固定点拍摄的,相机只能围绕该点旋转偏航和俯仰。然后我们可以考虑任意半径的球体(对于数学,强烈建议使用半径 1)。照片将在这个 sphere 上呈矩形(从相机的角度来看)。

Horizon-case

如果您正在查看 horizon(赤道),则垂直像素代表纬度,horizon总像素代表经度。 horizon一张简单的全景照片问题不大:

这里我们粗略地看看我们这个世界的 horizon。也就是说,相机有角度va = ~0。那么这就很简单了,因为如果我们知道照片宽70度,高40度,那么我们也知道经度范围大约是70度,纬度范围大约是40度。

如果我们不关心轻微的失真,那么从照片的任何像素 (x,y) 计算 (longitude,latitude) 的公式将很简单:

photo_width_deg = 70
photo_height_deg = 30
photo_width_px = 1280
photo_height_px = 720
ha = 0
va = 0
longitude = photo_width_deg * (x - photo_width_px/2) / photo_width_px + ha
latitude = photo_height_deg * (y - photo_height_px/2) / photo_height_px + va

问题

但是当我们更垂直地移动相机时,这个近似值根本不起作用:

那么,在给定 vertical/horizontal 拍摄照片的角度 (va,ha) 的情况下,我们如何将 (x, y) 处的图片像素转换为 (longitude, latitude) 坐标?

解决方案

为我解决问题的重要想法是:你基本上有 两个球体:

  • 相机居中的photo-sphere
  • geo-sphere(等角投影球体),坐标longitude/latitude。

你知道photo-sphere上一点的球坐标,你想知道这个点在geo-sphere上的什么位置,不同的是camera-angle.

真正的问题

我们必须认识到,仅使用与坐标向量 [x,y,z] 相乘的 spherical coordinates. The math for the cartesian coordinate system is much simpler. In the cartesian coordinate system we can easily rotate around any axis using rotation matrices很难在两个球体之间进行任何计算获取旋转后的坐标。

警告:这里重要的是要知道关于x的含义有不同的约定轴、y 轴和 z 轴。不确定哪个轴是垂直的,哪个指向哪里。你只需要为自己画一幅画并做出决定。如果结果是错误的,那可能是因为这些混淆了。球坐标的 thetaphi 也是如此。

真正的解决方案

所以 诀窍是从 photo-sphere 转换为笛卡尔坐标系,然后应用旋转,然后返回到球坐标系:

  1. 取照片上的任意像素,并计算它与照片中心的相对度数 horizon计数和垂直。
  2. 将 photo-sphere 球坐标转换为笛卡尔坐标([x,y,z] 向量)。
  3. 像旋转相机一样将旋转矩阵应用于坐标 (ha,va)
  4. 将笛卡尔坐标转换回球坐标,这些将是您的经度和纬度。

示例代码

// Photo resolution
double img_w_px = 1280;
double img_h_px = 720;
// Camera field-of-view angles
double img_ha_deg = 70;
double img_va_deg = 40;
// Camera rotation angles
double hcam_deg = 230;
double vcam_deg = 60;
// Camera rotation angles in radians
double hcam_rad = hcam_deg/180.0*PI;
double vcam_rad = vcam_rad/180.0*PI;
// Rotation around y-axis for vertical rotation of camera
Matrix rot_y = {
    cos(vcam_rad), 0, sin(vcam_rad),
    0, 1, 0,
    -sin(vcam_rad), 0, cos(vcam_rad)
};
// Rotation around z-axis for horizontal rotation of camera
Matrix rot_z = {
    cos(hcam_rad), -sin(hcam_rad), 0,
    sin(hcam_rad), cos(hcam_rad), 0,
    0, 0, 1
};

Image img = load('something.png');
for(int i=0;i<img_h_px;++i)
{
    for(int j=0;j<img_w_px;++j)
    {
        Pixel p = img.getPixelAt(i, j);

        // Calculate relative position to center in degrees
        double p_theta = (j - img_w_px / 2.0) / img_w_px * img_w_deg / 180.0 * PI;
        double p_phi = -(i - img_h_px / 2.0) / img_h_px * img_h_deg / 180.0 * PI;

        // Transform into cartesian coordinates
        double p_x = cos(p_phi) * cos(p_theta);
        double p_y = cos(p_phi) * sin(p_theta);
        double p_z = sin(p_phi);
        Vector p0 = {p_x, p_y, p_z};

        // Apply rotation matrices (note, z-axis is the vertical one)
        // First vertically
        Vector p1 = rot_y * p0;
        Vector p2 = rot_z * p1;

        // Transform back into spherical coordinates
        double theta = atan2(p2[1], p2[0]);
        double phi = asin(p2[2]);

        // Retrieve longitude,latitude
        double longitude = theta / PI * 180.0;
        double latitude = phi / PI * 180.0;

        // Now we can use longitude,latitude coordinates in many different projections, such as:
        // Polar projection
        {
            int polar_x_px = (0.5*PI + phi)*0.5 * cos(theta) /PI*180.0 * polar_w;
            int polar_y_px = (0.5*PI + phi)*0.5 * sin(theta) /PI*180.0 * polar_h;
            polar.setPixel(polar_x_px, polar_y_px, p.getRGB());
        }
        // Geographical (=equirectangular) projection
        {
            int geo_x_px = (longitude + 180) * geo_w;
            int geo_y_px = (latitude + 90) * geo_h;
            geo.setPixel(geo_x_px, geo_y_px, p.getRGB());
        }
        // ...
    }
}

注意,这只是某种 pseudo-code。建议使用 matrix-library 来处理矩阵和向量的乘法和旋转。