| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- #!/usr/bin/env python3
- """
- 根据 PTZ 扫描的 pan/tilt 角度直接生成校准文件。
- 假设全景图水平方向与 PTZ pan 角度成线性对应:
- x_ratio = (360 - pan) / 180 (pan=180 -> x=1, pan=360 -> x=0)
- 竖直方向使用 config/camera.py 中的 tilt 曲线反推 y_ratio。
- """
- import json
- import math
- from pathlib import Path
- from typing import List, Tuple, Dict, Any
- import numpy as np
- def tilt_to_y(tilt: float, y0: float = 13.0, y1: float = -5.0, power: float = 0.8) -> float:
- """根据 tilt 曲线反推 y_ratio"""
- denom = y0 - y1
- if abs(denom) < 1e-9:
- return 0.5
- if tilt >= y0:
- return 0.0
- if tilt <= y1:
- return 1.0
- return ((y0 - tilt) / denom) ** (1.0 / power)
- def main():
- base = Path('/Users/wenhongquan/Desktop/阿里云同步/项目/dnn/德胜河 AI/dsh/calibration_scan_180_360_z1')
- raw_path = base / 'mapping_raw.json'
- out_path = base / 'calibration_group1.json'
- with open(raw_path, 'r', encoding='utf-8') as f:
- raw = json.load(f)
- records = raw['records']
- pano_w = raw['panorama_size']['width']
- pano_h = raw['panorama_size']['height']
- points: List[Dict[str, float]] = []
- for r in records:
- pan = float(r['pan'])
- tilt = float(r['tilt'])
- # pan 180~360 线性映射到 x 1~0
- x_ratio = (360.0 - pan) / 180.0
- x_ratio = float(np.clip(x_ratio, 0.0, 1.0))
- # 根据配置 tilt 曲线反推 y
- y_ratio = tilt_to_y(tilt)
- y_ratio = float(np.clip(y_ratio, 0.0, 1.0))
- points.append({
- 'pan': pan,
- 'tilt': tilt,
- 'x_ratio': round(x_ratio, 4),
- 'y_ratio': round(y_ratio, 4),
- 'panorama_x': int(round(x_ratio * pano_w)),
- 'panorama_y': int(round(y_ratio * pano_h)),
- })
- # 拟合 pan = offset + scale_x * x + scale_y * y
- X = np.array([[1.0, p['x_ratio'], p['y_ratio']] for p in points])
- pans = np.array([p['pan'] for p in points])
- tilts = np.array([p['tilt'] for p in points])
- pan_params, _, _, _ = np.linalg.lstsq(X, pans, rcond=None)
- tilt_params, _, _, _ = np.linalg.lstsq(X, tilts, rcond=None)
- # 构建 pan_lookup:按 x 排序,同一 x 取平均 pan
- x_to_pans: Dict[float, List[float]] = {}
- for p in points:
- x_to_pans.setdefault(p['x_ratio'], []).append(p['pan'])
- pan_lookup = sorted(
- [[x, float(np.mean(ps))] for x, ps in x_to_pans.items()],
- key=lambda item: item[0]
- )
- # 构建 tilt_lookup:按 y 分桶取中位数
- grid = 0.05
- y_to_tilts: Dict[float, List[float]] = {}
- for p in points:
- key = round(p['y_ratio'] / grid) * grid
- y_to_tilts.setdefault(key, []).append(p['tilt'])
- tilt_lookup = sorted(
- [[y, float(np.median(ts))] for y, ts in y_to_tilts.items()],
- key=lambda item: item[0]
- )
- # 重叠区间:直接使用扫描范围
- overlap_ranges = [{
- 'pan_start': 180.0,
- 'pan_end': 360.0,
- 'tilt_start': -35.0,
- 'tilt_end': 45.0,
- 'match_count': len(points),
- }]
- calibration = {
- 'pan_offset': float(pan_params[0]),
- 'pan_scale_x': float(pan_params[1]),
- 'pan_scale_y': float(pan_params[2]),
- 'tilt_offset': float(tilt_params[0]),
- 'tilt_scale_x': float(tilt_params[1]),
- 'tilt_scale_y': float(tilt_params[2]),
- 'rms_error': 0.0,
- 'overlap_ranges': overlap_ranges,
- 'pan_lookup': pan_lookup,
- 'tilt_lookup': tilt_lookup,
- 'mount_type': 'wall',
- 'tilt_flip': False,
- 'pan_flip': False,
- 'generated_from': 'ptz_pan_angle_linear_mapping',
- 'note': 'x_ratio=(360-pan)/180, y_ratio 由 config tilt 曲线反推',
- }
- with open(out_path, 'w', encoding='utf-8') as f:
- json.dump(calibration, f, indent=2, ensure_ascii=False)
- print(f'Generated {out_path}')
- print(f' points: {len(points)}')
- print(f' pan fit: {pan_params[0]:.2f} + {pan_params[1]:.2f}*x + {pan_params[2]:.2f}*y')
- print(f' tilt fit: {tilt_params[0]:.2f} + {tilt_params[1]:.2f}*x + {tilt_params[2]:.2f}*y')
- print(f' pan_lookup entries: {len(pan_lookup)}')
- print(f' tilt_lookup entries: {len(tilt_lookup)}')
- # 保存 human readable CSV
- csv_path = base / 'calibration_group1_points.csv'
- with open(csv_path, 'w', encoding='utf-8') as f:
- f.write('pan,tilt,x_ratio,y_ratio,panorama_x,panorama_y\n')
- for p in points:
- f.write(f"{p['pan']},{p['tilt']},{p['x_ratio']},{p['y_ratio']},{p['panorama_x']},{p['panorama_y']}\n")
- print(f' CSV: {csv_path}')
- if __name__ == '__main__':
- main()
|