pixel_to_lonlat.py 6.0 KB

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