coord_utils.py 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
  1. """球面坐标与 PTZ pan/tilt 角度换算工具."""
  2. import math
  3. from typing import Tuple
  4. def pan_tilt_to_vector(pan: float, tilt: float) -> Tuple[float, float, float]:
  5. """把 pan/tilt(度)转成单位向量 (x, y, z)。
  6. 坐标系:x 向右,y 向上,z 向前;pan 从正前方 z 轴开始顺时针;
  7. tilt 0 为水平,+90 为天顶。
  8. """
  9. pan_rad = math.radians(pan)
  10. tilt_rad = math.radians(tilt)
  11. x = math.cos(tilt_rad) * math.sin(pan_rad)
  12. y = math.sin(tilt_rad)
  13. z = math.cos(tilt_rad) * math.cos(pan_rad)
  14. return x, y, z
  15. def spherical_to_pan_tilt(x: float, y: float, z: float) -> Tuple[float, float]:
  16. """把单位向量 (x, y, z) 转成 pan/tilt(度)。"""
  17. norm = math.sqrt(x * x + y * y + z * z)
  18. if norm < 1e-9:
  19. return 0.0, 0.0
  20. x, y, z = x / norm, y / norm, z / norm
  21. tilt = math.degrees(math.asin(max(-1.0, min(1.0, y))))
  22. pan = math.degrees(math.atan2(x, z))
  23. if pan < 0:
  24. pan += 360.0
  25. return pan, tilt
  26. def compute_sample_grid(
  27. pan_range: Tuple[float, float] = (0.0, 360.0),
  28. tilt_layers: Tuple[float, ...] = (-20.0, 0.0, 20.0),
  29. pan_step: float = 30.0,
  30. ) -> list[tuple[float, float]]:
  31. """生成扫描采样网格 [(pan, tilt), ...]。"""
  32. if pan_step <= 0:
  33. raise ValueError("pan_step must be positive")
  34. if not tilt_layers:
  35. raise ValueError("tilt_layers must not be empty")
  36. if pan_range[0] >= pan_range[1]:
  37. raise ValueError("pan_range start must be less than end")
  38. points = []
  39. pan_start, pan_end = pan_range
  40. pan = pan_start
  41. while pan < pan_end - 1e-6:
  42. for tilt in tilt_layers:
  43. points.append((round(pan, 2), round(tilt, 2)))
  44. pan += pan_step
  45. return points