尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

点投影系数法实现前方交会:测试程序原理、实现与精度验证

点投影系数法实现前方交会:测试程序原理、实现与精度验证 简介前方交会测试程序基于点投影系数法实现面向测绘、GIS及相关专业学生与开发者解决由多个已知控制点及观测方向推算目标坐标的定位问题。程序覆盖从观测角输入、向量运算到坐标求解的完整流程适合用于教学验证、算法原型测试以及工程测量、无人机航拍等场景。资源包共20个文件以cpp/h源码、exe可执行程序及txt说明文档为主体同时包含pdb、obj等编译调试中间文件便于直接运行和回溯修改。包体仅321KB轻量易用。目前已有274人学习下载适合希望理解前方交会几何原理并掌握实际编码实现的读者。通过源程序可学习点投影系数法的向量计算步骤借助示例数据和结果文件可对照检核输出快速上手并拓展到自己的测量任务中。 写这个测试程序之前我其实被一个问题卡了几天手头有一套框幅式相机拍摄的立体像对内方位元素、外方位元素都有人给了同名像点也在影像上量出来了可我需要的只是几十个地面点的坐标。上光束法平差没有必要商业软件又犯不上为这点活折腾。翻摄影测量教材最顺手的就是前方交会里的点投影系数法——不用迭代、不用矩阵求逆公式简洁写一个测试程序就能把坐标跑出来。这篇文章把原理、实现、实测数据和踩坑过程完整记下来适合正在做摄影测量课程设计、近景摄影测量实验或者想在OpenCV之外自己实现三角化验证工具的读者。1. 绕不开的前方交会为什么测试程序偏选点投影系数法1.1 前方交会到底在解决什么问题前方交会是摄影测量里最基础的定位手段之一。简单说一个地面点A同时被两张像片拍到左片上有像点a1右片上有像点a2如果我知道两张像片拍摄时的位置和姿态就能沿a1和摄影中心S1引一条射线沿a2和S2引另一条射线两条射线相交的位置就是A的三维坐标。你可以把摄影测量相机想象成两只眼睛左眼和右眼各自“看”过去视线交汇处就是目标距离和位置。这个思路和OpenCV里的双目三角化本质是一样的区别只是测绘领域习惯带上内方位元素、外方位元素、旋转矩阵这些完整参数。为什么要强调“已知位置和姿态”因为这里有一个前提单张像片只能确定一条射线无法确定射线上哪个点对应物方所以必须至少两张像片。像片之间的位置关系越稳、交会角度越好深度方向的结果就越可靠。1.2 点投影系数法和共线方程法怎么选前方交会的解法可以大致分成两类。一类是共线方程法把x、y两个观测方程写出来对未知的地面点坐标线性化用最小二乘迭代求解。另一类是点投影系数法先求出每条光线从投影中心到物点的“投影系数”再乘上方向向量直接得到物点坐标。点投影系数法看似多了一步实际上避开了迭代和初值问题。两个摄影中心之间的基线向量固定两条光线的方向向量也固定它们满足一个简单的线性关系S1 N1 D1 S2 N2 D2。解出N1和N2地面点坐标就落到两条光线形成的交点上。对于测试程序来说这个方法的最大优点是每步中间量都可以打印出来比对教材算例出错了很容易定位。方案核心思路优点局限性共线方程法对观测方程线性化迭代解算便于扩展多片、多余观测平差需要初值代码逻辑略重点投影系数法先求投影系数再求物点坐标无迭代、直观、适合快速验证通常只用于两片交会多余观测处理不直接1.3 测试程序应该做到什么程度写测试程序不是写生产工具目标很具体验证算法正确性、能处理一组或几组数据、能直观输出中间量。所以我没有一上来就写界面、写多片平差而是用一套清晰的数据文本来驱动核心逻辑压缩成几个独立函数哪怕以后要接到工程项目里也能直接搬过去当几何模块用。2. 把公式揉碎点投影系数法的推导和坐标转换2.1 坐标系关系数字影像上的像点坐标通常是仪器量测得到的像平面坐标(x, y)加上内方位元素主距f和像主点(x0, y0)后可以转到以投影中心为原点的像空间坐标系坐标是(x-x0, y-y0, -f)。注意这里z取-f是因为像平面在投影中心后方这是摄影测量里的标准约定。真正参与前方交会计算的对象是像空间辅助坐标系里的方向向量。这个坐标系的原点还是投影中心但三个轴和物方坐标系平行。从像空间坐标系转到像空间辅助坐标系靠的是外方位角元素构成的旋转矩阵R。这里我要提醒一句不同教材对R的定义方向不一样有的写成R有的写成R的转置最开始写测试程序时极易搞反。我按大多数中文摄影测量教材的习惯用R表示从像空间辅助坐标系到像空间坐标系的旋转矩阵那么反变换就是R的转置。计算时先由角元素phi、omega、kappa构造R再对像点向量左乘R的转置。2.2 投影方向向量的取法很多教材直接把像点向量(x-x0, y-y0, -f)当方向向量往下算数学上没错但投影系数会是负的调试时看着别扭还容易怀疑自己算错。我的做法是定义投影方向向量D为D (x0-x, y0-y, f)。这样D的z分量是正的方向就是“从投影中心指向物点”的方向投影系数N也成为有明确物理意义的正数。实测下来这样调试舒服很多。2.3 投影系数公式推导设左片投影中心为S1方向向量D1右片投影中心为S2方向向量D2物点为P。P同时落在两条射线上所以存在N1、N2使得P S1 N1 D1 S2 N2 D2把S2 - S1记为基线B整理得到N1 D1 - N2 D2 B写成分量形式取X和Z分量N1 D1x - N2 D2x Bx N1 D1z - N2 D2z Bz这组二元线性方程的解是N1 (Bx * D2z - Bz * D2x) / (D1x * D2z - D2x * D1z)N2 (Bx * D1z - Bz * D1x) / (D1x * D2z - D2x * D1z)分母就是两条方向向量在X-Z平面上的叉积相关项等于0时两条光线平行也就是交会角为0前方交会失效。得到N1、N2之后地面点坐标可以分别从左右片计算P1 S1 N1 * D1P2 S2 N2 * D2理论上P1和P2应当重合实际上因为像点量测误差和参数误差会有微小差异。取平均作为最终结果用差值做检核。2.4 为什么Y分量没有参与解算同一个平面内两条相交直线确定一个点位置是唯一的但三个坐标方向的等式并不是完全独立的。实际编程时选择几何意义最直观的X和Z分量来解N1、N2Y分量不参加解算而是作为检核使用。测出来的Y方向差值如果接近零说明左右两条光线在垂直方向上也对得上如果差好几米那大概率是数据或代码有问题。3. 程序怎么落地输入、核心代码与输出设计3.1 输入文件格式设计程序没有做界面数据全部走文本文件因为测试阶段要反复改参数文本最直观。我的格式这样组织第一行内方位元素 f、x0、y0 第二行左片外方位元素 Xs、Ys、Zs、phi、omega、kappa 第三行右片外方位元素 Xs、Ys、Zs、phi、omega、kappa 第四行起每行一对同名点 x1、y1、x2、y2示例18.0 0.0 0.0 100.0 0.0 10.0 0.0 0.0 0.0 200.0 0.0 12.0 0.0 0.0 0.0 -1.834 -0.183 1.844 -0.184这套格式只适合固定像对批量处理。如果有多组像片建议在外方位行前面加上像片编号或者改成一行一个观测记录的方式按自己的场景改读取逻辑即可。3.2 旋转矩阵与方向向量的实现核心是三个独立函数构造旋转矩阵、把像点转成方向向量、前方交会。旋转矩阵部分我按常用的phi、omega、kappa转角顺序实现#include Eigen/Dense #include cmath // 由角元素构造旋转矩阵R像空间辅助坐标系 - 像空间坐标系 Eigen::Matrix3d buildR(double phi, double omega, double kappa) { double p phi, w omega, k kappa; double c1 cos(p), c2 cos(w), c3 cos(k); double s1 sin(p), s2 sin(w), s3 sin(k); Eigen::Matrix3d R; R(0,0) c1*c3 - s1*s2*s3; R(0,1) -c1*s3 - s1*s2*c3; R(0,2) -s1*c2; R(1,0) c2*s3; R(1,1) c2*c3; R(1,2) -s2; R(2,0) s1*c3 c1*s2*s3; R(2,1) -s1*s3 c1*s2*c3; R(2,2) c1*c2; return R; }这个R的写法对应很多课程的符号约定但正式项目里务必先对一遍自己教材的旋转矩阵元素别直接抄。接下来是把像点坐标转成方向向量// 像点(x, y) - 从投影中心指向物点的方向向量D // 像点坐标和f、x0、y0单位一致通常是毫米这里统一成米 Eigen::Vector3d imageToDir(double x, double y, double f, double x0, double y0, const Eigen::Matrix3d R) { Eigen::Vector3d d(x0 - x, y0 - y, f); d / 1000.0; // 毫米转米与基线单位保持一致 return R.transpose() * d; // 转到像空间辅助坐标系 }这里有个非常重要的细节方向向量的单位必须和基线向量一致。像点坐标通常给毫米外方位线元素通常给米如果忘记除以1000N值会凭空大三个数量级结果完全不能看。3.3 前方交会主函数有了方向向量和基线核心交会代码就非常短了struct Point3D { double X, Y, Z; }; struct IntersectionResult { Point3D P; double N1, N2; double dY; // Y方向检核差 }; IntersectionResult forwardIntersection( const Point3D S1, const Point3D S2, const Eigen::Matrix3d R1, const Eigen::Matrix3d R2, double x1, double y1, double x2, double y2, double f, double x0, double y0) { Eigen::Vector3d D1 imageToDir(x1, y1, f, x0, y0, R1); Eigen::Vector3d D2 imageToDir(x2, y2, f, x0, y0, R2); Eigen::Vector3d B(S2.X - S1.X, S2.Y - S1.Y, S2.Z - S1.Z); double denom D1.x() * D2.z() - D2.x() * D1.z(); double N1 (B.x() * D2.z() - B.z() * D2.x()) / denom; double N2 (B.x() * D1.z() - B.z() * D1.x()) / denom; Eigen::Vector3d P1, P2; P1 S1.X N1 * D1.x(), S1.Y N1 * D1.y(), S1.Z N1 * D1.z(); P2 S2.X N2 * D2.x(), S2.Y N2 * D2.y(), S2.Z N2 * D2.z(); IntersectionResult res; res.P.X 0.5 * (P1.x() P2.x()); res.P.Y 0.5 * (P1.y() P2.y()); res.P.Z 0.5 * (P1.z() P2.z()); res.N1 N1; res.N2 N2; res.dY P1.y() - P2.y(); return res; }3.4 中间量输出和自检程序里每一组同名点计算完可以把D1、D2、B、denom、N1、N2、P1、P2、dY全部打印出来。这些中间量不是为了炫技而是调试时对照教材算例的关键。遇到结果不对先把D1、D2和旋转矩阵打出来一般能看出是方向错了还是单位错了。4. 用一组模拟数据跑一遍看结果和精度4.1 模拟数据怎么构造测试程序最怕没有标准答案。我的做法是先在地面上选一个已知点P再给定左右外方位元素用共线条件反算出左右像点坐标然后用这些像点坐标去跑前方交会看能不能回到P。这样每一环节都可以控制误差也是已知的。下面这组数据是我用来演示的地面点P(150.0, 5.0, 500.0)左摄站S1(100.0, 0.0, 10.0)右摄站S2(200.0, 0.0, 12.0)姿态角都为0焦距18.0mm像主点0。按共线公式反算出来的左右像点约等于参数左片右片x-1.8341.844y-0.183-0.184反算时用了毫米单位四舍五入到3位小数所以流程验证中会引入约分米级的舍入误差实际工程中量测精度会比这个高。4.2 程序输出把数据喂给程序输出大致是项目数值D1米(0.001834, 0.000183, 0.018)D2米(-0.001844, 0.000184, 0.018)B米(100.0, 0.0, 2.0)N127244.53N227133.30P米(149.97, 4.99, 500.40)dY米0.01地面点真值是(150.0, 5.0, 500.0)X和Y方向都很接近Z方向差了0.4米。这个偏差不是程序算错而是像点坐标只保留到0.001mm级别同时这组数据的交会角只有大约11.7度深度方向误差被几何关系放大了。如果把像点坐标保留到6位小数结果会向真值收敛。4.3 精度随交会角的变化交会角是左右两条投影方向向量之间的夹角。交会角接近90度时深度方向误差最小交会角太小哪怕像点量测只有微米级误差Z方向也会被放大到米级。这个测试程序里我没有做完整的误差传播统计但可以做一个简单试验把左右摄站基线拉长到200米其他条件不变Z方向偏差会明显下降。这也是为什么实际航摄任务要保证足够的重叠度和基线高比不是随便拍一拍就能高精度定位。5. 调程序时实际踩过的坑和定位方法5.1 角度制与弧度制的混用第一次跑外方位元素里phi、omega、kappa我直接用度sin、cos直接用结果R矩阵的元素模离谱正交性检查就过不了。定位方法很简单程序里加一段自检输出R乘以R的转置理论上应该是单位阵。如果对角线元素不是1首先怀疑角度单位。改过来之后R就正常了。5.2 旋转矩阵转置方向的坑跟着教材写旋转矩阵公式抄对了但向前方交会里套时像点方向向量我左乘了R而不是R的转置。现象是地面点坐标的X和Z完全不对称地面点从右侧飞到了左侧。排查时我打印了同一对点在左右片的方向向量发现在辅助坐标系里指向同一个物点时方向不一致。后来把教材里的共线方程和坐标转换顺序又过了一遍确定像空间坐标到辅助坐标应该左乘R的转置。这个坑很容易踩因为它纯粹是符号约定问题不结合算例很难发现。5.3 像主点改正完全忘掉内方位元素表里x0、y0不是零我却在代码里直接用x、y当方向向量没有减x0、y0。结果所有点位整体偏移而且偏移量随像点位置变化不像系统差那样固定。后来把x、y、x0、y0打出来一对比才意识到像主点改正漏了。严格说x、y可以是相对像主点的坐标但很多原始数据给的是像素坐标或框标坐标系下的坐标不是天然相对像主点的必须显式先减x0、y0。5.4 同名点顺序和单位问题还有一次结果N1突然出现负数检核差也大。检查后发现我把同名点录入顺序弄反了左片的x1写到了右片。方向向量一旦交叉两条光线就指向了错误方向交会点位置自然错乱。另外单位问题也很隐蔽像点坐标是毫米基线和外方位线元素是米一开始没除以1000N值大了三个数量级看起来像数据爆炸。所有距离单位统一成米之后再跑结果就稳了。6. 测试程序验证通过之后还可以怎么演进6.1 用OpenCV结果做交叉验证同一组同名点可以用OpenCV的triangulatePoints接口算一遍获得三维点后与这个测试程序的结果对比。两者的数学基础相通但一个基于投影矩阵的DLT一个基于点投影系数交叉验证能帮助发现代码里的隐性错误。我实际验证过在像点坐标精度足够的情况下两类方法的结果能吻合到毫米级。6.2 加入多余观测和最小二乘测试程序只有左右两条光线每一点只有一次交会无法估计点位精度。工程中通常用多片前方交会同一个地面点被三张以上像片拍到列出多余观测方程用最小二乘求最终坐标同时给出点位中误差。这个升级逻辑也很清晰把D1到Dn、S1到Sn摆好构误差方程解算即可。点投影系数法得到的坐标可以直接作为光束法平差的初始值省去初值估计的麻烦。6.3 与后方交会结合形成完整的单像定向流程点投影系数法前方交会要求外方位元素已知。实际项目中外方位元素往往要靠控制点反算那就是另一个经典流程后方交会。如果手上正好有控制点可以先写一个单像后方交会求出外方位元素再用这套前方交会求未知点。两个模块串起来就是一个最小可用的测量计算工具。我后来把这个测试程序里的几何模块抽出来封装成一个很小的库再接到数据处理脚本里比反复改一个main函数舒服得多。本文还有配套的精品资源点击获取
返回列表