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

Yet*_*eti 11 camera projection image-processing coordinates coordinate-systems

简化的问题

如何将球面坐标(θ,φ)转换为等距矩形投影(也称为"地理投影")的位置(x,y )?

其中:

  • x是经度,水平位置,从-180到180度.
  • y是纬度,垂直位置,从-90到90度.
  • θ是θ,以度为单位的水平角度,从(0,0,0)到球体表面上的点的矢量.
  • φ是phi,垂直角度,从(0,0,0)到球面上的点的矢量.

下面你会找到原来的问题,当我不能很好地理解这个问题时,我认为它仍然有助于展示这个解决方案的实际应用.

上下文

编辑:原始问题标题是:如何以给定角度转换照片成为全景照片的一部分?

如果我想以任何给定角度拍摄照片,以便我可以将得到的(扭曲/变形的)图像放置在等距矩形投影,立方体贴图上的相应特定位置,任何人都可以帮助我采取哪些步骤或任何全景照片投影?

无论哪种投影最容易做到都足够好,因为有很多资源可用于如何在不同的投影之间进行转换.我只是不知道如何从实际照片到这样的投影.

可以安全地假设摄像机将停留在固定位置,并可以从那里向任何方向旋转.我认为需要这样做的数据可能是这样的:

  • 物理相机的水平角度[-180, +180](例如+ 140deg).
  • 物理相机的垂直角度[-90, +90](例如-30度).
  • 照片分辨率w x h(例如1280x720像素).
  • 照片的水平角度(例如70度).
  • 照片的垂直角度(例如40度).
  • 镜头校正a,b,c参数(见下文).

我有这些数据,我想第一步是进行镜头校正,以便所有应该笔直的线条实际上是直的.而这是可以做到用imagemagick桶形畸变的 A,B和C:,在其中您只需要填写三个参数.应用于图像以纠正此问题的转换非常简单.

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

编辑:我想,也许一个数字将有助于更好地解释:)

问题是给定的照片红色不能在没有变换的情况下放入等角矩形投影中.下图说明了这个问题.

带有

所以,我有红色,我需要把它变成绿色.蓝色显示转换的差异,但这取决于水平/垂直角度.

Yet*_*eti 5

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

地平线案例

如果您正在看水平线(赤道),则垂直像素占纬度,水平像素占经度。对于地平线的简单全景照片,没有太大问题:

从(0,0,0)看地理球的线框

在这里,我们大致看一下世界的地平线。也就是说,相机具有角度va = ~0。这非常简单,因为如果我们知道照片的宽度为70度,高度为40度,那么我们也知道经度范围约为70度,纬度范围约为40度。

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

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
Run Code Online (Sandbox Code Playgroud)

问题

但是,当我们更垂直地移动相机时,这种近似完全不起作用:

从(0,0,0)看地理球的线框

那么,如何将像素从图片转换为给定拍摄照片的垂直/水平角(x, y)(longitude, latitude)坐标(va,ha)

为我解决问题的重要思想是:您基本上有两个领域

  • 光球与相机的中心。
  • 具有经度/纬度坐标的地理球(正四角投影球)。

您知道光球上某个点的球面坐标,并且想知道此点在具有不同摄影机角度的地理球上的位置。

真正的问题

我们必须意识到,仅使用球坐标很难在两个球之间进行任何计算笛卡尔坐标系的数学要简单得多。在笛卡尔坐标系中,我们可以使用旋转矩阵乘以坐标向量轻松地绕任何轴旋转,以将旋转的坐标返回。[x,y,z]

警告:在此非常重要的是要知道,关于-axis,-axis和-axis 的含义有不同的约定。不确定哪个轴是垂直轴,哪个轴指向何处。您只需要为自己做一张图纸并决定就可以了。如果结果是错误的,则可能是因为它们混淆了。这同样适用于在和球形坐标。xyzthetaphi

真正的解决方案

因此,诀窍是从光球转换为笛卡尔,然后应用旋转,然后回到球坐标

  1. 拍摄照片上的任何像素,并计算它在水平和垂直方向上偏离照片中心的相对角度。
  2. 将光球的球面坐标转换为笛卡尔坐标([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());
        }
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

注意,这只是某种伪代码。建议使用矩阵库来处理矩阵和向量的乘法和旋转。