1. 为什么必须理解坐标系
在视觉系统里,绝大多数 Bug 都不是模型本身的问题,而是“数据在不同空间里被误读”。
检测框漂移、跟踪抖动、3D 框姿态错误
多传感器融合失败(camera/lidar 时间或坐标不一致)
部署后精度骤降(resize/quantize 后未修正坐标)
- 调试困难(日志里都是 x,y,但不知道是米、像素还是索引)
2. 常用坐标系总览(World → Sensor → Image → Pixel → Tensor)
典型链路:
每个空间回答的问题不同:
- world/global:物体在真实场景中在哪里?
- ego:相对自车在哪里?
- sensor/camera/lidar:相对某传感器在哪里?
- image plane:3D 点投影到成像平面的几何位置是什么?
- pixel:在图像像素网格中落在哪个像素?
- tensor:在网络输入张量中对应哪个索引位置?
关键词区分(代码里最常见):
- global:全局地图/惯性参考系,通常长期稳定
- world:与 global 常近义,但也可能是局部重定位后的“世界系”
- ego:自车坐标系(车体中心或后轴中心)
- sensor:传感器本体系
- camera:相机坐标系(通常 z 朝前)
- image:理想成像平面连续坐标
- pixel:离散像素坐标(u,v)
- tensor:神经网络输入/特征图索引(h,w 或 y,x)
3. 世界坐标系(World / Global)
定义
描述物体在物理世界中的统一参考系。可选地图坐标、ENU、UTM 或任务自定义全局系。
坐标表示方式
- 点:Pw = [Xw, Yw, Zw]^T
- 位姿:Twb = [Rwb | twb](world 到 body/ego)
单位
- 位置通常是米(m)
- 姿态常用弧度(rad)或四元数
表达的语义
“这个目标在真实世界绝对/半绝对位置在哪里”。
存在的必要性
多帧时序融合必须有稳定锚点
地图关联、轨迹评估、重定位都依赖 global/world 一致性
典型应用场景
自动驾驶定位与地图对齐
多相机/多雷达跨时间融合
SLAM 后端优化
工程代码常见命名
- p_world, T_world_ego, pose_global
- global_xyz, world_frame, map_frame
4. 传感器坐标系(Sensor / Camera / LiDAR)
定义
每个传感器自身的本地坐标系,原点在传感器安装位置。
坐标表示方式
- 点:Ps = [Xs, Ys, Zs]^T
- 外参:Tws, Tsc, Tcl 等(注意方向)
单位
- 米(m)
表达的语义
“目标相对这个传感器的空间位置”。
存在的必要性
传感器观测是“本地”产生的,不先转到 sensor 系就无法做正确几何计算
多传感器融合本质是外参驱动的坐标统一
典型应用场景
相机投影、LiDAR 点云处理
Radar-Camera/LiDAR-Camera 对齐
自车运动补偿(ego-motion compensation)
工程代码常见命名
- p_cam, p_lidar, T_ego_cam, extrinsic, Rt
- cam0_frame, lidar_frame, sensor2ego
补充:
- 自动驾驶中 ego 与 sensor 都关键;视频纯视觉中常忽略 ego,但做稳像/SLAM 就必须引入。
5. 图像平面坐标系(Image Plane)
定义
3D 点通过针孔模型投影到理想成像平面的连续坐标(非离散像素)。
坐标表示方式
齐次形式:
单位
无量纲比例值(可理解为焦距归一化后的坐标)
表达的语义
“物体方向/视线在成像几何中的连续位置”。
存在的必要性
将透视几何与像素采样解耦
便于畸变校正、几何推导与标定
典型应用场景
相机标定与重投影误差计算
双目/多目三角化
PnP/SLAM 前端
工程代码常见命名
- x_norm, y_norm, bearing, normalized_plane
- undist_pt, cv::undistortPoints 输出常在该空间
6. 像素坐标系(Pixel)
定义
坐标表示方式
- 连续像素坐标:(u, v)(float)
- 离散索引:(col,row) 或 (x,y)(int)
单位
- 像素(px)
表达的语义
“这个点/框在当前图像数组里的位置”。
存在的必要性
图像处理算子(resize/crop/pad/warp)全部发生在像素空间
标注、检测框、关键点结果都先落在像素空间
典型应用场景
检测框后处理(NMS/解码)
图像增强
可视化绘制
工程代码常见命名
- u,v, x,y, cx,cy, bbox_xyxy, bbox_xywh
- img_w,img_h, principal_point
工程提醒:
- xyxy 与 xywh 必须显式标注,禁止“默认理解”。
- 像素中心是 0.5 偏移还是整数点,必须统一。
7. 张量索引坐标系(Tensor Index)
定义
神经网络输入或中间特征图在内存中的离散索引空间。
坐标表示方式
- 常见布局:NCHW 或 NHWC
- 空间索引:(h,w),通道:c
- 多尺度特征:P3/P4/P5 上的不同步长索引
单位
索引(index),不是像素,不是米
表达的语义
“数据在 tensor 网格上的位置与通道语义”。
存在的必要性
模型推理与部署只认识 tensor,不认识世界坐标
后处理要把 tensor 输出映射回 pixel/image/world
典型应用场景
anchor/grid 解码
heatmap 峰值定位
多任务头输出解析
工程代码常见命名
feat_h, feat_w, stride
- grid_x, grid_y, idx, anchor_idx
- input_tensor, output_blob
8. 不同坐标系之间的转换关系
8.1 标准链路与公式
world -> sensor(camera)
或齐次:
sensor(camera) -> image plane
image plane -> pixel
- pixel -> tensor(以 resize+pad 为例)
再根据步长映射到特征图:
8.2 转换为何必须做
- 算法目标空间和数据所在空间不同
例如:跟踪在 world 评估,但检测输出在 tensor。 - 多模块接口空间不同
例如:ISP 输出 pixel,感知融合需要 ego/world。 - 不同硬件链路对输入格式有约束
例如:NPU 只接受固定尺寸 tensor。
8.3 转换需要的数据
- 外参:R, t 或 T_ab
- 内参:K = [[fx,0,cx],[0,fy,cy],[0,0,1]]
- 畸变参数:k1,k2,p1,p2,...
- 图像预处理参数:resize scale, crop roi, padding, flip
- 网络参数:stride, anchor, grid definition
8.4 转换前后数据形式
- world/sensor: float32/float64,3D 点或位姿
- image/pixel: float 连续坐标 + int 像素索引
- tensor: int 索引 + quantized/int8 数值张量
9. 为什么必须做坐标转换
从工程角度,转换不是“数学洁癖”,是系统协同的必要条件:
时空统一:跨帧、跨传感器需要同一语义空间
精度可控:每一步变换都能度量误差来源
接口可维护:模块边界清晰,易于替换和部署
问题可定位:知道错误发生在几何、采样还是量化环节
不同场景的核心坐标关注点:
- 视频算法:pixel <-> tensor 最关键(预处理/后处理一致性)
- SLAM:camera/sensor <-> world 最关键(位姿与重投影)
- 自动驾驶:global/world/ego/sensor 全链路都关键(融合与规划)
- 部署推理:pixel -> tensor -> pixel 与量化参数最关键
10. 量化与坐标空间的关系
量化通常不直接改变“坐标值定义”,但会改变与坐标相关的数值精度、解码精度和稳定性。
10.1 INT8 量化基础
常见线性量化:
反量化:
10.2 对空间精度的影响
框回归输出量化后,最小可分辨位移增大,导致框抖动
深度/距离回归量化后,远距离误差放大
关键点 heatmap 峰值受量化噪声影响,亚像素定位变差
10.3 fixed-point / Q 格式
- 实值到定点:x_fixed = round(x * 2^n)
- 定点回实值:x = x_fixed / 2^n
工程含义:
- n 越大,分辨率越高,但动态范围越小
坐标变换链路里多次乘加容易溢出或截断误差累积
建议:
几何主链路(投影/反投影)尽量保持 FP32
仅在模型内部算子或末端后处理做定点化
对关键坐标量单独做误差预算(px 级、m 级)
11. 工程常见错误与踩坑总结
外参方向写反
右手系/左手系混淆
尤其在图形引擎、SLAM、自动驾驶模块拼接时。
- xy 与 row/col 混淆
resize 后忘记同步标注/内参
letterbox padding 未回退
后处理直接用网络输出还原原图,位置整体偏移。
crop ROI 坐标基准错误
ROI 内坐标当作全图坐标使用,框错位。
畸变处理顺序错误
未去畸变就直接几何计算,重投影误差异常。
时间戳对齐忽略
坐标正确但时刻不一致,融合后仍漂移。
量化参数漏传
单位不统一
上游米、下游毫米;角度弧度/度混用。
12. 完整工程示例串讲(world → sensor → image → pixel → tensor)
world -> camera
- 输入:Pw,Tcw=(Rcw, tcw)
- 输出:Pc=[Xc,Yc,Zc]^T (m)
camera -> image plane
- 输入:Pc
- 输出:(x,y)=(Xc/Zc, Yc/Zc)(无量纲)
image plane -> pixel
- 输入:(x,y),相机内参 K(fx,fy,cx,cy)
- 输出:(u,v)(像素坐标,float)
- pixel -> preprocessed pixel(resize + crop + padding)
- 输入:原图 (u,v),预处理参数
- 输出:网络输入图坐标 (u',v')
preprocessed pixel -> tensor index
- 输入:(u',v'),stride
- 输出:(grid_x, grid_y) 或 heatmap 索引 (h,w)
- tensor output -> pixel/world(反向恢复)
- 输入:网络输出、量化参数、预处理逆变换参数、K/R/t
输出:原图框/关键点,或 3D/world 结果
关键检查点(每一步都应记录):
坐标系名(字符串标签)
单位(m/px/index)
数据类型(fp32/int8/int)
时间戳与相机ID
13. 阅读代码快速判断坐标空间的方法
实战中最快的方法不是看注释,而是看“变量 + 变换 + 单位 + 调用点”。
13.1 四步法
看变量命名后缀
- _world/_global/_map:世界系
- _ego/_vehicle:自车系
- _cam/_lidar/_sensor:传感器系
- _uv/_xy:像素或图像平面(需结合类型判断)
- _grid/_feat/_idx:tensor 索引
- 看是否出现 K/R/t/T
- 出现 K:大概率 camera/image/pixel 转换
- 出现 R,t:大概率 world/ego/sensor 变换
- 出现 stride/anchor:大概率 pixel/tensor 转换
看单位与数值范围
10~100 量级常是米
0~W/H 常是像素
0~feature map 尺寸常是索引
-1~1 常是归一化坐标
看数组访问方式
- img[v][u]/img[row,col]:像素索引
- tensor[n,c,h,w]:张量索引
- 线代乘法 R@p+t:几何空间
13.2 快速排错清单
- 这个 x,y 的单位是什么?
当前坐标相对哪个原点?
是连续值还是离散索引?
是否经历过 resize/crop/pad/flip?
是否在量化域(int8)中做了几何运算?
14. 总结
真正可落地的空间认知体系不是“记住几个公式”,而是建立以下闭环:
明确每个坐标系回答的问题
明确每次转换的输入参数与输出数据形式
明确单位、精度与量化影响
明确预处理/后处理对坐标的扰动
明确代码命名与坐标语义的一一对应关系
