pixel_to_lonlat.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. # -*- coding: utf-8 -*-
  2. """
  3. 最终功能模块:从像素坐标到经纬度的转换
  4. 核心功能:
  5. - pixel_to_lonlat: 输入像素坐标(u, v)和摄像头状态,输出对应的经纬度。
  6. """
  7. import numpy as np
  8. from scipy.spatial.transform import Rotation as R
  9. # --- 核心函数 ---
  10. def pixel_to_lonlat(u, v, camera_params, ptz_params):
  11. """
  12. 正向投影:将像素坐标(u, v)转换为经纬度。
  13. Args:
  14. u (int): 像素横坐标
  15. v (int): 像素纵坐标
  16. camera_params (dict): 摄像头的固定物理参数 (通过标定获得)
  17. ptz_params (dict): 摄像头当前的云台参数
  18. Returns:
  19. tuple: (经度, 纬度) 或 (None, None) 如果计算失败
  20. """
  21. # --- 1. 解包所有已知参数 ---
  22. res_w, res_h = int(camera_params['resolution_w']), int(camera_params['resolution_h'])
  23. cam_lon, cam_lat = float(camera_params['cam_lon']), float(camera_params['cam_lat'])
  24. cam_height, hfov =float( camera_params['height']), float(camera_params['hfov'])
  25. north_p = float(camera_params['north_p_value'])
  26. p, t = float(ptz_params['pan']), float(ptz_params['tilt'])
  27. # --- 2. 相机内参模型:将像素(u,v)转换为相机坐标系下的方向向量 ---
  28. # (相机坐标系定义: x向右, y向下, z向前)
  29. f_x = res_w / (2 * np.tan(np.radians(hfov) / 2))
  30. f_y = f_x # 假设像素是正方形
  31. cx, cy = res_w / 2, res_h / 2
  32. # 计算方向向量并归一化
  33. vec_cam = np.array([(u - cx) / f_x, (v - cy) / f_y, 1.0])
  34. vec_cam /= np.linalg.norm(vec_cam)
  35. # --- 3. 旋转变换:将相机坐标系下的向量旋转到世界坐标系 ---
  36. # (世界坐标系定义: ENU, 即 x向东, y向北, z向上)
  37. r_base = R.from_euler('x', -90, degrees=True) # 基础旋转:将(相机前)对准(世界北)
  38. p_corrected = p - north_p # 校正P值,使其以正北为0度
  39. r_pan = R.from_euler('z', p_corrected, degrees=True) # 方位角旋转 (绕世界Z轴)
  40. r_tilt = R.from_euler('x', -t, degrees=True) # 俯仰角旋转 (绕世界X轴)
  41. # 合成总旋转
  42. total_rotation = r_pan * r_tilt * r_base
  43. vec_enu = total_rotation.apply(vec_cam)
  44. # --- 4. 几何解算:计算光线与地面的交点 ---
  45. # 如果光线向量的z分量是正数或零,说明光线朝向天空或水平,无法与地面相交
  46. if vec_enu[2] >= -1e-9:
  47. return None, None
  48. # 摄像头在世界坐标系中的位置
  49. cam_pos_enu = np.array([0, 0, cam_height])
  50. # 求解射线参数 s
  51. s = -cam_height / vec_enu[2]
  52. # 计算交点坐标 (米)
  53. intersection_enu = cam_pos_enu + s * vec_enu
  54. # --- 5. 坐标转换:将米制坐标转回经纬度 ---
  55. EARTH_RADIUS = 6378137.0
  56. delta_lat_rad = intersection_enu[1] / EARTH_RADIUS
  57. delta_lon_rad = intersection_enu[0] / (EARTH_RADIUS * np.cos(np.radians(cam_lat)))
  58. lon = cam_lon + np.degrees(delta_lon_rad)
  59. lat = cam_lat + np.degrees(delta_lat_rad)
  60. return {"lon":lon, "lat":lat}
  61. # ==============================================================================
  62. # 使用示例
  63. # ==============================================================================
  64. if __name__ == '__main__':
  65. # --- 1. 定义您最终标定好的、精确的摄像头参数 ---
  66. # 这些是固定的物理参数,您应该保存下来
  67. CAMERA_PARAMS = {
  68. "height": 28.0000,
  69. "hfov": 61.0000,
  70. "resolution_w": 1280,
  71. "resolution_h": 720,
  72. "cam_lon": 112.893799,
  73. "cam_lat": 28.221701,
  74. "north_p_value": 39.0
  75. }
  76. # --- 2. 定义摄像头当前的云台状态 ---
  77. # 在您的实际应用中,这些值应该是从摄像头API实时读取的
  78. CURRENT_PTZ_PARAMS = {
  79. "pan": 271.9,
  80. "tilt": 26.2
  81. }
  82. # --- 3. 指定您想要查询的屏幕像素坐标 ---
  83. pixel_u, pixel_v = 91, 276 # 使用您标定过的“黄金数据点”之一作为测试
  84. print(f"--- 正在将像素坐标 ({pixel_u}, {pixel_v}) 转换为经纬度 ---")
  85. # --- 4. 调用核心函数进行计算 ---
  86. predicted_lon, predicted_lat = pixel_to_lonlat(pixel_u, pixel_v, CAMERA_PARAMS, CURRENT_PTZ_PARAMS)
  87. # --- 5. 打印结果 ---
  88. if predicted_lon is not None:
  89. print("\n计算成功!")
  90. print(f" 预测经度: {predicted_lon:.8f}")
  91. print(f" 预测纬度: {predicted_lat:.8f}")
  92. # 与标定时的真实值对比 (可选,用于验证)
  93. true_lon, true_lat = 112.89461289520455, 28.221594983922852
  94. print("\n--- 与标定时的真实值对比 ---")
  95. print(f" 真实经度: {true_lon:.8f}")
  96. print(f" 真实纬度: {true_lat:.8f}")
  97. else:
  98. print("\n计算失败(可能该点指向天空)。")