ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

二维坐标转三维:射线歧义、相机模型与工程落地方案

二维坐标转三维:射线歧义、相机模型与工程落地方案 做视觉的朋友应该都有过这种经历项目里需要把屏幕上某个像素点对应的实际物理位置算出来或者把一张二维图片里的目标映射到三维空间里。一开始你可能觉得这无非是多补一个Z坐标的事但真正上手就会发现问题远没那么简单——给定一个二维坐标 (u, v)对应到三维空间里并不是唯一确定的一个点而是一条射线上的无数个点。这就是二维坐标转三维坐标最核心的难点信息不够。二维坐标只有两个自由度三维坐标却有三个自由度数学上这是一个欠约束问题。想把它解出来就必须引入额外的假设——比如你提前知道相机的位置和朝向比如你假设目标落在某个固定平面上再比如你有深度传感器或者双目系统能直接测出距离。不同的假设对应不同的解法和精度也对应不同的工程成本。这篇文章我会把这些路线逐一拆开讲清楚。从针孔相机模型的数学原理到内参外参的作用再到平面假设、双目三角化、PnP姿态估计这些具体方案的取舍最后给出一段带数值的完整推导和我在实际项目里踩过的坑。不管你是在做机器人抓取、AR导航、还是桌面级的坐标映射看完应该都能对坐标转换这件事建立起一个整体认知至少不会再被为什么二维到三维会有歧义这类问题卡住。1. 这件事的本质二维到三维为什么是一条线而不是一个点1.1 一个映射缺失一个维度先从数学上把问题摆清楚。三维空间里的任意一点经过相机拍摄变成图像上的一个像素这本质是一个从三维到二维的投影过程。三维坐标有三个分量 (X, Y, Z)投影到图像上只剩下两个量 (u, v)这个过程里深度信息 Z 被压扁了。一台普通相机像人眼一样只能看到场景的亮度分布无法直接记录每个物体的远近。反过来说当你看到一张图像上的像素 (u, v) 时你其实只知道这个像素是从某条光线上的某一个点来的但到底是哪个点如果你没有额外的深度信息就无从判断。你问二维转三维在不知道深度的情况下答案不是唯一的点而是一条射线。这条射线从相机光心出发穿过这个像素一直延伸到无穷远处。这就好比你在一个房间里看到墙上的一个投影点你只知道它来自光源和物体之间的某条连线但同样的投影点可能是距离很近的小物体产生的也可能是很远的大物体产生的。没有深度信息你永远无法唯一确定物体的实际位置和大小。所以二维坐标转三维坐标的第一原则是你必须额外引入一个约束。这个约束可以是物理上的比如目标物体放在已知平面上可以是测量上的比如用深度相机直接测到距离也可以是几何上的比如用两台相机同时观察同一个点通过视差反算深度。理解这一点是理解后面所有方案的前提。1.2 两条主流技术路线几何投影与深度先验基于约束的引入方式不同工程上大致分成两类路线。第一类是几何投影模型路线。它先把相机标定好得到相机的内参和外参然后建立像素坐标与三维世界坐标之间的数学关系。已知某个目标落在一个固定平面上比如桌面、地面时直接把平面方程代入投影公式就能解出唯一的三维点。这种方法被广泛应用在桌面机械臂抓取、AGV导航、停车位检测等场景中我叫它平面假设法。它只需要一台普通单目相机成本低实现简单但前提是目标确实在假设的平面上一旦物体悬空或平面高度变了结果就会偏移。第二类是深度先验路线。它的核心思想是用额外的硬件或算法把缺失的深度补回来。典型代表是双目视觉两个相机像人的两只眼睛一样观察同一个点通过左右图像上的视差算出距离还有结构光、ToF飞行时间深度相机它们直接输出一张带深度值的图每个像素都对应一个真实距离。工业上用的 RealSense、Kinect 这类深度相机就属于这一路。它们能拿到更完整的空间信息但设备成本和数据量都更大而且深度图在某些材质玻璃、强反光表面上会失效。我在实际项目里的体会是能上平面假设就上平面假设除非你的场景真的有大量不在同一平面的目标否则没必要为了炫技上深度相机。后面我会详细说为什么平面假设在很多场景下已经足够用以及它的精度上限到底在哪里。2. 相机模型与坐标转换的数学骨架2.1 四个坐标系从世界到像素环环相扣要做坐标转换先得有坐标系。一个完整的成像过程要经过四个坐标系世界坐标系、相机坐标系、图像坐标系、像素坐标系。世界坐标系是你在真实世界里约定的基准比如你把桌子的某个角定为原点X 轴指向桌长方向Y 轴指向桌宽方向Z 轴指向天花板。这个坐标系完全由你说了算怎么方便怎么来。相机坐标系则固定在相机光心上它的 Z 轴一般沿着相机的光轴指向正前方。世界坐标系到相机坐标系的变换关系就是相机的外参由旋转矩阵 R 和平移向量 t 描述。图像坐标系的原点在相机光轴与成像平面的交点上单位是毫米表示物体在 CCD 芯片上的物理位置。像素坐标系才是我们在代码里直接接触的它的原点是图像左上角单位是像素X 轴向右Y 轴向下。像素坐标和图像坐标之间的换算靠的是内参矩阵里的 fx、fy、cx、cy。这一堆坐标系看着繁琐但工程上你越到后面越会发现很多坑都出在坐标系搞混上。比如 OpenCV 的像素原点在左上角而 OpenGL 的原点在左下角比如相机坐标系的 Y 轴通常向下而一些游戏引擎的 Y 轴向上。做坐标转换前先把四层坐标系的变换链画出来比看了十篇文章还管用。2.2 内参矩阵K与外参[R|t]到底在做什么把上面的变换链写成矩阵形式就是经典的相机投影方程s * [u, v, 1]^T K * [R | t] * [X_w, Y_w, Z_w, 1]^T这里有三个关键部分。K 是内参矩阵一般写成K [[f_x, 0, c_x], [0, f_y, c_y], [0, 0, 1]]其中 f_x、f_y 是焦距在像素单位下的表示c_x、c_y 是光心在像素坐标系中的位置。比如分辨率 1920x1080 的相机理想情况下 c_x960、c_y540但实际上因为装配误差光心往往会偏那么几个像素。你如果忽略 c_x、c_y 的偏移强行走中心假设边缘点的误差会相当可观。[R | t] 是外参矩阵它把世界坐标变换到相机坐标。R 是 3x3 的旋转矩阵描述两个坐标系之间的姿态差异t 是 3x1 的平移向量描述原点之间的位移。外参不是相机出厂就固定的它取决于你把相机摆在哪里、朝哪个方向只要相机一动外参就得重新标定。这是很多项目中为什么昨天还好好的今天坐标就偏了的最常见原因——用力碰了一下相机外参早变了。s 是尺度因子本质上是深度值。这个 s 非常重要它意味着同一个像素坐标在 s1 和 s2 时对应的三维点相距一倍的距离。很多人第一次写逆投影代码时这里就糊了最后算出来的结果要么是单位不对要么是方向对但尺度完全离谱。2.3 逆投影的本质一条射线的自由把相机模型反过来用就是我们想要的逆投影。已知像素坐标 (u, v) 和内参 K反解世界坐标的过程是这样的先把像素坐标转换到归一化相机平面x_n (u - c_x) / f_xy_n (v - c_y) / f_y先不急着加上深度。你会发现只要给定一个任意深度 λ就能得到一个点[X_c, Y_c, Z_c] λ * [x_n, y_n, 1]换句话说像素点 (u, v) 对应的是相机坐标系里一条沿方向 (x_n, y_n, 1) 的射线λ 可以取任意正数。你需要再做一次外参逆变换把射线上的点转到世界坐标系但它依然是一条射线。所以逆投影如果没带任何深度信息得到的是射线的参数方程而不是一个点。这个数学事实解释了为什么所有二维转三维的方案都在想办法确定 λ要么假设目标在某个平面上直接解出 λ要么用双目视差或者深度传感器测出 λ。理解这条射线你再看各种方案就会觉得它们其实都在做同一件事——想办法把这条射线砍一刀砍到某个具体的距离上。3. 深度从哪来三类工程方案的取舍3.1 平面假设一张图锁死一个自由度平面假设是最朴素也最容易落地的方案。它的思路是预先知道所有目标点都落在某个平面上比如工作的桌面、地面的停车位、传送带的平面。这样的话三维点的 Z 值就有一个约束方程平面方程限定住了射线的自由度为 0交点就是唯一解。具体做法通常是单应性矩阵路径先在平面上放一块标定板用相机拍一张照片通过四个以上角点的像素坐标和真实世界坐标求解一个 3x3 的单应矩阵 H。之后任意一个像素点乘以 H 的逆矩阵就能得到对应平面上的世界坐标。这个过程甚至不需要显式地把相机内外参拆开因为 H 已经组合了所有信息。这样做的好处是开发极快几行代码就能完成而且不需要复杂的标定流程。但这套方案有个硬伤它只对落在假设平面上的点成立。如果你抓取的物体有高度它的底面贴台面但顶面不在台面上或者物体悬空用单应矩阵算出来的位置就会偏。我见过不少机器人抓取项目调试的时候放平了没问题一换成竖着的瓶子就开始抓偏原因就在这。实际项目里要么加限制确保工件始终贴平面要么换深度相机解决高度变化的问题。3.2 双目视觉三角化用视差换深度如果你需要的三维点不局限于一个平面双目视觉是比较划算的折中方案。它用两个固定间距的相机同时拍摄同一场景像人的两只眼睛一样产生视差——同一个空间点在左右图像上的像素位置不一样距离越近视差越大。数学上两个相机的光心与空间点构成一个三角形已知基线的长度两个相机光心之间的距离和两条光线的方向就能通过三角测量公式求出空间点的深度。这个原理其实很古老但工程上有两个比较现实的问题。一是双目标定精度直接决定深度精度左右相机的相对位姿只要差那么一点点远处的误差就会放大得很难看。二是立体匹配的可靠性左右图像上怎么找到同一个点是核心难点光照变化、反光、遮挡、低纹理区域都会让匹配产生错误视差最终体现在三维点上就是一堆飞点。所以双目方案虽然没有深度相机贵但算法复杂度更高。它的精度和分辨率其实并不取决于哪个相机更好而是取决于基线长度和匹配准确度。你要测 10 米以外的目标基线得拉很开你要测 0.5 米以内的目标基线又不能太大。工程上选用双目时一定要先想清楚工作距离范围再定基线不要拿着实验室的样板机直接上产线。3.3 单目PnP与动态标定靠已知点恢复位姿PnPPerspective-n-Point问题是一个看起来很优雅的实际方案已知 n 个三维点和它们对应的 n 个二维像素点求解相机的外参。它本质上是一种取巧的思路——我不去直接估计某个点的深度而是先通过已知的 3D-2D 对应关系反推出相机当前在世界坐标系里的位姿。这个方案在天花板上的二维码定位、AR 场景里非常常见。比如你在天花板上贴了四个角点坐标已知的二维码相机拍到这个二维码后用 solvePnP 算出相机相对二维码的位姿那么相机中心在世界坐标系里的位置就拿到了。一旦你知道了相机位姿再结合平面假设或者测距就能完成像素到三维的转换。实际用起来要注意的是PnP 对 2D 点检测精度极其敏感。同样的三维点如果二维检测偏差波动 0.5 个像素反推出来的位姿在近距离还稳远距离直接跳变。所以我会额外建议用 solvePnP 时不要只给最小数量的 4 个点能用 8 个点甚至 12 个点就别省RANSAC 也尽量开上代价只是多十几毫秒的计算但位姿稳定性会明显改善。3.4 快速对比不同方案的精度、成本与适用场景做个表格总结一下方便你在选型时快速对照方案深度来源硬件成本典型精度适用场景主要限制平面假设单应矩阵平面约束低单目相机毫米级近距离桌面机械臂、地面导航、平面工件检测目标必须在固定平面上不能悬空双目视觉视差三角化中双目模组相对误差百分之几避障、三维重建、抓取有高度物体匹配算法复杂低纹理区域效果差深度相机结构光/ToF直接测距中高毫米到厘米级机器人抓取、人体姿态估计、室内建模强光下受影响玻璃/镜面失效单目 PnP 已知尺寸由已知 3D-2D 对应恢复位姿低单目相机受检测精度影响视觉定位、AR 追踪、室外导航需要场景中存在已知尺寸的标记物这张表里我标出的主要限制都是我在项目里真实撞到过的。做选型的时候不要只看精度表格里那些实验室数值要看你自己的场景最不能容忍哪类失效。悬空物体多的老老实实上深度相机或双目全是贴平物的单目平面假设完全够用还能省一大笔钱。4. 从公式到落地的完整推导与实操4.1 场景设定与内外参数为了让你看得更真切我给你一个完整数值例子。假设我们有一台相机内参矩阵是K [[800, 0, 640], [0, 800, 360], [0, 0, 1]]图像分辨率是 1280x720所以光心在像素正中心。假设相机光轴水平朝前外参中旋转矩阵为单位阵相机在世界坐标系中的平移为 (0, 0, 500)单位是毫米。简单来说相机在世界坐标里只是沿 Z 轴方向偏了 500mm方向和世界坐标系完全一致。这样外参逆变换非常直观做数值演示足够清楚了。实际项目里R 和 t 很少这么干净大概率是一些旋转又平移的矩阵但运算过程是一样的先算相机坐标再套旋转和平移转成世界坐标。你可以先把内参标定准再用标定板跑一次 solvePnP 拿到真实外参矩阵替换下面的数值即可。4.2 已知深度像素坐标到三维坐标的数值计算现在假设我们在图像上检测到了一个目标点像素坐标是 (u800, v360)同时通过某种方式比如深度相机或双目视差得知该点在相机坐标系下的深度 Z_c 1000mm。怎么求它的三维坐标第一步算归一化坐标x_n (800 - 640) / 800 0.2y_n (360 - 360) / 800 0第二步乘上深度得到相机坐标系下的三维点X_c 0.2 * 1000 200Y_c 0 * 1000 0Z_c 1000第三步用外参转成世界坐标。这里 R 是单位阵t(0,0,500)世界坐标等于相机坐标加 tX_w 200 0 200Y_w 0 0 0Z_w 1000 500 1500所以这个像素点对应的三维点是 (200, 0, 1500)单位毫米。换句话说目标位于相机前方 1.5 米、偏右 20 厘米的地方。整个过程你仔细看其实只有一次乘法和一次加法没有比这更简单的操作了。要是没有深度值怎么办那就只能换公式。用平面假设时比如我知道目标落在地面平面 Z_w 0 上就要把世界坐标的表达式代入平面方程反解深度。这个过程会涉及一行稍微复杂的线性代数实际工程中我通常直接用 cv2.solvePnP 或单应矩阵解决不手推。4.3 地面场景单应性矩阵的简便实用方案在很多地面或桌面场景里我更推荐直接用单应性矩阵而不是先标定内参外参。步骤很简单在地面放一张 Aruco 标定板或棋盘格记录四个以上角点的像素坐标和真实世界坐标然后用 opencv 的 cv2.findHomography 求出 H 矩阵。之后任意像素点乘 H 的逆就得到世界平面坐标。具体代码逻辑大概是H, _ cv2.findHomography(src_points, dst_points) # src 像素点dst 世界点world_points cv2.perspectiveTransform(pixel_points.reshape(-1, 1, 2), np.linalg.inv(H))这个做法的妙处在于它完全不关心相机内参外参具体是多少H 把它们全部打包了。但你必须保证标定板贴着实际工作平面放置且目标点也在这个平面上。只要平面是稳定的精度能做到相当高我在桌面项目中用这套方案几百毫米范围内重复精度能做到 1mm 左右。实际操作时注意标定板角点检测要尽量覆盖整个工作区域不光是中间一小块。因为单应矩阵本质是全局线性模型周围边缘区域的投影畸变更大。标定板的四个角要尽量靠近画面的四角这样 H 对全幅映射的约束才完整。4.4 少走弯路标定与坐标系对齐的建议再分享一个我在项目里踩了挺久才想明白的点坐标系对齐比标定精度还要重要。很多人内参标得很准外参也算得出来但三维坐标还是飘最后发现是世界坐标系的朝向约定和后续机械臂、运动控制的坐标系没有对齐。比如你的世界坐标系原点定在桌面左上角X 轴沿桌面长边但机械臂的基坐标系原点在机器人底座中心X 轴指向机器人正前方。中间差了刚体变换当然可以标定出来但如果你在标定的时候就把世界坐标系直接建立在机械臂基座坐标系上后面每一步转换都少一次误差累积。我的建议是做任何坐标转换项目之前先花半天时间把所有坐标系的定义、轴向、单位、原点位置画在一张纸上和团队里的人对齐清楚。坐标系本身没有对错但约定不一致一定会产生非常隐蔽的 bug。特别是在单位上有人用毫米有人用米转换的时候漏除一个 1000三维点直接跑到几十米开外排查起来极其痛苦。5. 我踩过的坑常见问题与排查清单5.1 像素坐标原点与行列顺序的0/1陷阱坐标转换最常见的坑之一就是像素坐标的原点和行列顺序。OpenCV 的像素坐标原点是图像左上角u 向右增加v 向下增加。但很多标定板检测库、深度相机 SDK或者某些自写的角点检测输出的坐标可能不一样。有的库习惯先输出行号再输出列号有的先输出列号再输出行号如果你直接拿 (row, col) 当成 (u, v) 去套公式就会翻转横纵轴三维点自然就偏了。我见过一个真实案例项目里把库输出的坐标顺序理解反了导致三维坐标里的 X 和 Y 全反了但表面上看好像没问题因为调试时用的目标本身就在一条对角线上对称误差被掩盖了。要避开这个问题最有效的办法是写一个可视化小工具在图像上画一个十字光标光标显示当前位置的 (u, v)移动鼠标对比真实坐标和图像位置。花十分钟验证一下能省后面几天排查时间。5.2 畸变校正不能省边缘点偏移最明显普通工业镜头和消费级摄像头都有明显的镜头畸变特别是广角镜头。畸变分成径向畸变和切向畸变径向畸变让直线变弯表现为图像边缘的像素往内或往外扭曲。如果你拿着没去畸变的像素坐标直接套针孔模型中心区域误差可能只有几个像素但越往边缘越离谱可能要偏出几十个像素。在三维坐标映射任务里几十个像素意味着毫米到厘米级别的误差这是完全不可接受的。正确的流程是先用棋盘格标定出畸变系数k1、k2、p1、p2有些还要 k3然后在获取每一帧图像后先调用 cv2.undistort 或者 cv2.undistortPoints 做校正再做坐标转换。一个判断标准是看你画面边缘的直线是不是直的——如果有明显弯曲畸变校正确实还没做到位。需要提醒的是undistort 是对整幅图像做重映射比较耗时帧率敏感的场景建议直接用 undistortPoints 只对需要的特征点做校正速度会快很多。5.3 单位与尺度漂移从毫米到米的换算混乱第二个让我记忆深刻的坑是单位不一致。相机标定的时候棋盘格的真实尺寸你用什么单位内参和所有后续计算就默认延续什么单位。有人用毫米有人用厘米还有人标定的时候随手填了个 1全程没在意单位最后三维坐标出来的数值和实际物理距离差了十倍百倍却找不到原因。我的习惯是项目一开始就统一用毫米作为世界坐标系的距离单位。标定板格子的实际尺寸也用毫米量测棋盘格外框多量几次取平均别直接信商家标的数值。如果你发现所有三维点都整齐地放大了一定的倍数比如 25.4 倍那大概率就是英制单位和公制单位混用了或者标定板尺寸填错了。这种问题排查起来并不难难的是你一开始没有建立所有数值都要带单位意识的习惯。我在代码里会习惯性地把所有几何量的单位写在变量名旁边比如Z_c_mm、X_w_mm虽然丑了一点但真的能救命。5.4 快速排查表与心得按经验整理一张排查速查表直接收藏吧症状可能原因排查方法三维坐标整体偏移很大外参没过拟合或世界系没对齐重新做外参标定检查坐标系定义中心准、边缘偏畸变未校正或畸变系数不准校正好畸变边缘直线检测验证X/Y 反了像素坐标按 (row, col) 用了可视化十字光标验证 u/v 方向数值整体放大/缩小固定倍数单位混用或标定板尺寸写错统一单位到毫米重测标定板结果随深度跳变深度噪声大或匹配错误检查深度图质量做时域滤波移动相机后全部不准外参没跟着更新加视觉定位或者重新标定外参我个人在做坐标转换项目时最常跟新人强调的一课是先把一条射线这个直觉建立起来再看任何方案都不慌。你拿着一个像素点去问深度从哪来意味着你已经开始抓住这个问题的核心了。剩下的不过是选一个最适合你场景的深度来源再负责把参数标好单位对齐。这一套流程走下来二维转三维这件事基本就不会再给你什么意外的惊喜了。
返回列表