如果照片是从固定点拍摄的,相机只能在该点周围绕其偏航和俯仰旋转。那么我们可以考虑任意半径的球体(为了数学计算,强烈建议使用半径为1)。这张照片将成为该
球体上的矩形形状(从相机的视角来看)。
地平线情况
如果您正在观察地平线(赤道),则垂直像素代表纬度,而水平像素代表经度。对于地平线简单全景照片,问题不大:
这里我们大致看一下我们的世界地平线。也就是说,相机的角度为
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
问题
但是当我们将相机垂直移动得更多时,这种近似根本不起作用:
那么,如何在给定拍摄照片的垂直/水平角度(va, ha)的情况下,将图片中的一个像素从(x, y)转换为经纬度坐标(longitude, latitude)呢?
解决方案
对我有帮助的重要思路是:你基本上有两个球体:
1.以相机为中心的照片球体。
2.以经度/纬度坐标为基础的地理球体(等距投影球体)。
您知道照片球体上一点的球面坐标,并想知道该点在不同相机角度下在地理球体上的位置。
真正的问题
我们必须认识到,仅使用
球面坐标进行两个球体之间的任何计算是
困难的。
笛卡尔坐标系的数学要简单得多。在笛卡尔坐标系中,我们可以轻松地绕任意轴旋转,使用乘以坐标向量
[x,y,z]
的
旋转矩阵来获得旋转后的坐标。
警告: 这里非常重要的是要知道有关
x
轴、
y
轴和
z
轴含义的
不同约定。不确定哪个轴是垂直轴,哪个指向何处。你只需为自己画一个图并决定这一点。如果结果有误,可能是因为弄混了这些。对于球面坐标的
theta
和
phi
也是如此。
真正的解决方案
因此,关键在于将照片球形坐标系转换为笛卡尔坐标系,然后应用旋转,最后再回到球面坐标系:
- 取照片上的任意像素,并计算其相对于照片中心的水平和垂直角度。
- 将照片球面坐标系转换为笛卡尔坐标系(
[x,y,z]
向量)。
- 对坐标应用旋转矩阵,就像相机被旋转
(ha,va)
一样。
- 将笛卡尔坐标系转换回球面坐标系,这些将是您的经度和纬度。
示例代码
double img_w_px = 1280;
double img_h_px = 720;
double img_ha_deg = 70;
double img_va_deg = 40;
double hcam_deg = 230;
double vcam_deg = 60;
double hcam_rad = hcam_deg/180.0*PI;
double vcam_rad = vcam_rad/180.0*PI;
Matrix rot_y = {
cos(vcam_rad), 0, sin(vcam_rad),
0, 1, 0,
-sin(vcam_rad), 0, cos(vcam_rad)
};
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);
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;
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};
Vector p1 = rot_y * p0;
Vector p2 = rot_z * p1;
double theta = atan2(p2[1], p2[0]);
double phi = asin(p2[2]);
double longitude = theta / PI * 180.0;
double latitude = phi / PI * 180.0;
{
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());
}
{
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());
}
}
}
请注意,这只是某种伪代码。建议使用矩阵库来处理矩阵和向量的乘法和旋转。