pixel_to_lonlat.py 5.6 KB

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