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

时间:2022-07-08 21:14:15

Simplified question

How do you convert a spherical coordinate (θ, φ) into a position (x, y) on an equirectangular projection (also called 'geographic projection')?

你如何将球面坐标(θ,φ)转换成一个位置(x,y)equirectangular投影(也称为“地理投影”)?

In which:

中:

  • x is longitude, the horizontal position, from -180 to 180 degrees.
  • x是经度,水平位置,从-180度到180度。
  • y is latitude, the vertical position, from -90 to 90 degrees.
  • y是纬度,垂直位置,从-90度到90度。
  • θ is theta, the horizontal angle in degrees, a vector from (0,0,0) to a point on the surface of a sphere.
  • θ是θ,水平角的度,一个向量从(0,0,0)到一个点在一个球体的表面。
  • φ is phi, the vertical angle in degrees, a vector from (0,0,0) to a point on the surface of a sphere.
  • φ是φ,竖直角的度,一个向量从(0,0,0)到一个点在一个球体的表面。

Below you find the original question, back when I did not understand the problem well, but I think that it is still good for showing what is a practical application of this solution.

下面你找到了最初的问题,当我没有很好地理解这个问题的时候,但是我认为它仍然很好地展示了这个解决方案的实际应用。

Context

Edit: The original question title was: How to transform a photo at a given angle to become a part of a panorama photo?

编辑:最初的问题题目是:如何以给定的角度将照片转换成全景照片的一部分?

Can anybody help me with which steps I should take if I want to transform a photo taken at any given angle in such a way that I can place the resulting (distorted/transformed) image at the corresponding specific location on an equirectangular projection, cube map, or any panorama photo projection?

如果我想要以任何给定的角度对所拍摄的照片进行转换,使我能够将得到的(扭曲/变形的)图像放置在等边投影、立方体地图或任何全景照片投影的相应特定位置,有人能帮助我采取哪些步骤吗?

Whichever projection is easiest to do is good enough, because there are plenty of resources on how to convert between different projections. I just don't know how to do the step from an actual photo to such a projection.

任何最容易做的投影都足够好了,因为关于如何在不同的投影之间转换有大量的资源。我只是不知道如何从真实的照片到这样的投影。

It is safe to assume that the camera will stay at a fixed location, and can rotate in any direction from there. The data that I think is required to do this, is probably something like this:

可以肯定的是,相机将保持在一个固定的位置,并且可以从那里向任何方向旋转。我认为这需要的数据,大概是这样的:

  • Horizontal angle of physical camera [-180, +180] (e.g. +140deg).
  • 物理摄像头的水平角度[-180,+180](例如+140deg)。
  • Vertical angle of physical camera [-90, +90] (e.g. -30deg).
  • 物理相机的垂直角度[-90,+90](如-30deg)。
  • Resolution of photo w x h (e.g. 1280x720 pixels).
  • 照片wxh的分辨率(例如1280x720像素)。
  • Horizontal angle of photo (e.g. 70deg).
  • 照片的水平角(如70度)。
  • Vertical angle of photo (e.g. 40deg).
  • 照片的垂直角度(如40deg)。
  • Lens correction a, b, c parameters (see below).
  • 镜头校正a、b、c参数(见下文)。

I have this data, and I guess the first step is to do lens correction so that all lines that should be straight are in fact straight. And this can be done using imagemagick's Barrel Distortion, in which you only need to fill in three parameters: a, b, and c. The transformation that is applied to the image to correct this is straightforward.

我有这个数据,我想第一步是做透镜校正,这样所有直线都是直的。这可以使用imagemagick的桶形失真来实现,其中您只需填充三个参数:a、b和c。

I am stuck on the next step. Either I do not fully understand it, or search engines are not helping me, because most results are about converting between already given projections or use advanced applications to stitch photos intelligently together. These results did not help me with answering my question.

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

EDIT: I figured that maybe a figure will help explaining it better :)

编辑:我想也许有个数字能更好地解释它:)

The problem is that a given photo Red cannot be placed into the equirectangular projection without a transformation. The figure below illustrates the problem.

问题是,如果没有变换,给定的照片红色就不能放在等边投影中。下图说明了这个问题。

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

So, I have Red, and I need to transform it into Green. Blue shows the difference in transformation, but this depends on the horizontal/vertical angle.

我有红色,我需要把它变成绿色。蓝色表示变换的不同,但这取决于水平/垂直的角度。

2 个解决方案

#1


2  

If photos are taken from a fixed point, and the camera can only rotate its yaw and pitch around that point. Then we can consider a sphere of any radius (for the math, it is highly recommended to use a radius of 1). The photo will be a rectangular shape on this sphere (from perspective of the camera).

如果照片是从一个固定的点拍摄的,而相机只能围绕这个点旋转它的偏航和俯仰。然后我们可以考虑一个任何半径的球体(对于数学来说,强烈建议使用半径为1的球体)。

Horizon-case

If you are looking at the horizon (equator), then vertical pixels account for latitude, and horizontal pixels account for longitude. For a simple panorama photo of the horizon there is not much of a problem:

如果你看地平线(赤道),垂直像素代表纬度,水平像素代表经度。对于一个简单的全景照片的地平线没有什么问题:

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

Here we look at roughly the horizon of our world. That is, the camera has angle va = ~0. Then this is pretty straightforward, because if we know that the photo is 70 degrees wide and 40 degrees high, then we also know that the longitude range will be approximately 70 degrees and latitude range 40 degrees.

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

If we don't care about a slight distortion, then the formula to calculate the (longitude,latitude) from any pixel (x,y) from the photo would be easy:

如果我们不关心轻微的失真,那么从照片中任意像素(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

Problem

But this approximation does not work at all when we move the camera much more vertically:

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

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

So how do we transform a pixel from the picture at (x, y) to a (longitude, latitude) coordinate given a vertical/horizontal angle at which the photo was taken (va,ha)?

那么,我们如何将一个像素从(x, y)的图像转换成(经度,纬度)的坐标呢?

Solution

The important idea that solved the problem for me is this: you basically have two spheres:

对我来说,解决这个问题的重要想法是:你基本上有两个领域:

  • The photo-sphere with the camera in the center.
  • 以照相机为中心的光球。
  • The geo-sphere (equirectangular projection sphere), with longitude/latitude coordinates.
  • 地球球(等直角投影球),经纬度坐标。

You know the spherical coordinate of a point on the photo-sphere, and you want to know where this point is on the geo-sphere with the different camera-angle.

你知道一个点在光球上的球面坐标,你想知道这个点在地球上的位置和不同的摄像机角度。

The real problem

We have to realize that it is difficult to do any calculations between the two spheres using just 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 that are multiplied with the coordinate vector [x,y,z] to get the rotated coordinate back.

我们必须意识到,用球面坐标计算这两个球是很困难的。笛卡尔坐标系的数学要简单得多。在笛卡尔坐标系中,我们可以很容易地绕任意轴旋转,用旋转矩阵乘以坐标向量[x,y,z]来得到旋转的坐标。

Warning: Here it is very important to know that there are different conventions with regard to the meaning of x-axis, y-axis, and z-axis. It is uncertain which axis is the vertical one, and which one points where to. You just have to make a drawing for yourself and decide on this. If the result is wrong, it's probably because these are mixed up. The same goes for the theta and phi for spherical coordinates.

警告:这里非常重要的一点是要知道关于x轴、y轴和z轴的含义有不同的约定。它不确定哪个轴是垂直的,哪个轴指向哪里。你只需要自己画一幅图然后决定。如果结果是错误的,那可能是因为它们混淆了。对于球坐标来说也是一样的。

The real solution

So the trick is to transform from photo-sphere to cartesian, then apply the rotations, and then go back to spherical coordinates:

关键在于从光球到笛卡尔坐标系的变换,然后应用旋转,然后回到球面坐标:

  1. Take any pixel on the photo, and calculate the relative degrees it is away from the center of the photo both horizontally and vertically.
  2. 取照片上的任何一个像素,水平和垂直地计算它离照片中心的相对度数。
  3. Transform the photo-sphere spherical coordinates into cartesian coordinates ([x,y,z] vectors).
  4. 将光球球面坐标转换为笛卡尔坐标([x,y,z]向量)。
  5. Apply rotation matrices to the coordinate just like the camera was rotated (ha,va).
  6. 将旋转矩阵应用到坐标上,就像摄像机旋转一样(ha,va)。
  7. Transform the cartesian coordinates back to spherical coordinates, and these will be your longitude and latitude.
  8. 将笛卡尔坐标转换回球面坐标,这些就是经度和纬度。

Example code

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

Note, this is just some kind of pseudo-code. It is advised to use a matrix-library that handles your multiplications and rotations of matrices and vectors.

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

#2


0  

hmm, i think maybe you should go one step back. Consider your camera angle (70mm ? or so). but your background image is a 360 degree in horizontal (but also vertical). Consider the perspective distortions on both type of pictures. For the background pict, in a vertical sense only the horizon is not vertically distorted. Sadly its only a thin line. As the distortion increases the more you get to the top or bottom.

嗯,我想你应该退后一步。考虑你的相机角度(70毫米?左右)。但是你的背景图像是水平的360度(也有垂直的)。考虑这两种类型的图片的透视扭曲。对于背景pict,在垂直意义上,只有地平线没有垂直扭曲。遗憾的是,这只是一条线。随着失真的增加,你越接近顶部或底部。

Its not constant as in barrel distortion, but it depends on vertical distance of horizon.

它不像桶形畸变那样是恒定的,而是取决于视界的垂直距离。

I think the best way to realize the difference is to take a side view of both type of camera's and the target they supposed to project upon, from there its trigonometry, math.

我认为实现这种差异的最好方法是,从侧面观察这两种相机,以及它们应该投射到的目标上的三角函数和数学。

Note that for the 70mm picture you need to know the angle it was shot. (or estimate it)

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

#1


2  

If photos are taken from a fixed point, and the camera can only rotate its yaw and pitch around that point. Then we can consider a sphere of any radius (for the math, it is highly recommended to use a radius of 1). The photo will be a rectangular shape on this sphere (from perspective of the camera).

如果照片是从一个固定的点拍摄的,而相机只能围绕这个点旋转它的偏航和俯仰。然后我们可以考虑一个任何半径的球体(对于数学来说,强烈建议使用半径为1的球体)。

Horizon-case

If you are looking at the horizon (equator), then vertical pixels account for latitude, and horizontal pixels account for longitude. For a simple panorama photo of the horizon there is not much of a problem:

如果你看地平线(赤道),垂直像素代表纬度,水平像素代表经度。对于一个简单的全景照片的地平线没有什么问题:

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

Here we look at roughly the horizon of our world. That is, the camera has angle va = ~0. Then this is pretty straightforward, because if we know that the photo is 70 degrees wide and 40 degrees high, then we also know that the longitude range will be approximately 70 degrees and latitude range 40 degrees.

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

If we don't care about a slight distortion, then the formula to calculate the (longitude,latitude) from any pixel (x,y) from the photo would be easy:

如果我们不关心轻微的失真,那么从照片中任意像素(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

Problem

But this approximation does not work at all when we move the camera much more vertically:

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

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

So how do we transform a pixel from the picture at (x, y) to a (longitude, latitude) coordinate given a vertical/horizontal angle at which the photo was taken (va,ha)?

那么,我们如何将一个像素从(x, y)的图像转换成(经度,纬度)的坐标呢?

Solution

The important idea that solved the problem for me is this: you basically have two spheres:

对我来说,解决这个问题的重要想法是:你基本上有两个领域:

  • The photo-sphere with the camera in the center.
  • 以照相机为中心的光球。
  • The geo-sphere (equirectangular projection sphere), with longitude/latitude coordinates.
  • 地球球(等直角投影球),经纬度坐标。

You know the spherical coordinate of a point on the photo-sphere, and you want to know where this point is on the geo-sphere with the different camera-angle.

你知道一个点在光球上的球面坐标,你想知道这个点在地球上的位置和不同的摄像机角度。

The real problem

We have to realize that it is difficult to do any calculations between the two spheres using just 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 that are multiplied with the coordinate vector [x,y,z] to get the rotated coordinate back.

我们必须意识到,用球面坐标计算这两个球是很困难的。笛卡尔坐标系的数学要简单得多。在笛卡尔坐标系中,我们可以很容易地绕任意轴旋转,用旋转矩阵乘以坐标向量[x,y,z]来得到旋转的坐标。

Warning: Here it is very important to know that there are different conventions with regard to the meaning of x-axis, y-axis, and z-axis. It is uncertain which axis is the vertical one, and which one points where to. You just have to make a drawing for yourself and decide on this. If the result is wrong, it's probably because these are mixed up. The same goes for the theta and phi for spherical coordinates.

警告:这里非常重要的一点是要知道关于x轴、y轴和z轴的含义有不同的约定。它不确定哪个轴是垂直的,哪个轴指向哪里。你只需要自己画一幅图然后决定。如果结果是错误的,那可能是因为它们混淆了。对于球坐标来说也是一样的。

The real solution

So the trick is to transform from photo-sphere to cartesian, then apply the rotations, and then go back to spherical coordinates:

关键在于从光球到笛卡尔坐标系的变换,然后应用旋转,然后回到球面坐标:

  1. Take any pixel on the photo, and calculate the relative degrees it is away from the center of the photo both horizontally and vertically.
  2. 取照片上的任何一个像素,水平和垂直地计算它离照片中心的相对度数。
  3. Transform the photo-sphere spherical coordinates into cartesian coordinates ([x,y,z] vectors).
  4. 将光球球面坐标转换为笛卡尔坐标([x,y,z]向量)。
  5. Apply rotation matrices to the coordinate just like the camera was rotated (ha,va).
  6. 将旋转矩阵应用到坐标上,就像摄像机旋转一样(ha,va)。
  7. Transform the cartesian coordinates back to spherical coordinates, and these will be your longitude and latitude.
  8. 将笛卡尔坐标转换回球面坐标,这些就是经度和纬度。

Example code

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

Note, this is just some kind of pseudo-code. It is advised to use a matrix-library that handles your multiplications and rotations of matrices and vectors.

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

#2


0  

hmm, i think maybe you should go one step back. Consider your camera angle (70mm ? or so). but your background image is a 360 degree in horizontal (but also vertical). Consider the perspective distortions on both type of pictures. For the background pict, in a vertical sense only the horizon is not vertically distorted. Sadly its only a thin line. As the distortion increases the more you get to the top or bottom.

嗯,我想你应该退后一步。考虑你的相机角度(70毫米?左右)。但是你的背景图像是水平的360度(也有垂直的)。考虑这两种类型的图片的透视扭曲。对于背景pict,在垂直意义上,只有地平线没有垂直扭曲。遗憾的是,这只是一条线。随着失真的增加,你越接近顶部或底部。

Its not constant as in barrel distortion, but it depends on vertical distance of horizon.

它不像桶形畸变那样是恒定的,而是取决于视界的垂直距离。

I think the best way to realize the difference is to take a side view of both type of camera's and the target they supposed to project upon, from there its trigonometry, math.

我认为实现这种差异的最好方法是,从侧面观察这两种相机,以及它们应该投射到的目标上的三角函数和数学。

Note that for the 70mm picture you need to know the angle it was shot. (or estimate it)

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