coord_utils.py 1.8 KB

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