简介findEllIntersect.m 是面向无源雷达与被动雷达定位场景的 MATLAB 程序解决多站椭圆法目标位置求解问题。程序基于观测站获取的 TDOA/FDOA 信息构造椭圆通过解非线性方程组获取椭圆交点从而估计无源目标位置适用于雷达信号处理、电子对抗与无源定位算法研究等方向。资源包内仅涉及 1 个 m 文件压缩包整体约 1KB体积非常精简便于快速阅读、修改和嵌入式调用。目前已有 346 人学习下载说明其实用性获得一定认可。程序主体包含了椭圆参数构造、非线性方程迭代求解以及交点判定等核心步骤可直接运行或集成到更大的无源定位系统中。在此基础上使用者还能结合最小二乘、牛顿法或概率分析进行扩展用于定位精度评估、多站布站优化或融合卡尔曼滤波实现目标跟踪。对希望理解椭圆法原理并快速搭建定位原型的开发者而言这是一份轻量且可直接上手的小工具。1. findEllIntersect 在无源定位里到底解什么问题两个椭圆的交点接到一个无源定位任务最常被问的一句话是目标在椭圆上然后呢在被动雷达定位场景里所谓椭圆法就是用双/多基地的到达时间差构造成距离和把无源目标约束在某个椭圆上换一个收发组合又得到另一个椭圆两个椭圆一交叉目标位置就被锁定了。findEllIntersect 就是我在 MATLAB 和 Python 里都留着的一个小函数输入两个椭圆的焦点和半长轴输出候选交点坐标。这里把从时差到椭圆、从椭圆到交点的完整链路拆开讲包括参数设置、多站融合和我在实际无源雷达解算中踩过的坑。适合正在做无源目标定位、被动雷达定位算法验证的人。2. 把时差测量写成椭圆方程无源定位椭圆法的几何与退化前提在被动雷达定位里最常见的观测量是到达时间而不是角度。椭圆法的核心是把“发射站到目标再到接收站”的距离和构造成一个椭圆发射站和接收站就是两个焦点距离和的一半就是半长轴。只要拿到两个以上收发组合的椭圆目标位置就是这些椭圆的公共交点。写代码之前先把几何前提和符号约定讲清楚否则后面的 findEllIntersect 很容易因为参数不对返回来一堆 nan。2.1 双基地距离和为什么目标是椭圆而不是圆单站雷达测距得到的是目标到站点的距离画出来是一个以站点为圆心的圆。椭圆法不一样它利用的是一个照射源和一个接收站组成的双基地基线。设发射站位置为 tx接收站位置为 rx目标位置为 p。目标对发射信号形成反射或再辐射后接收站收到的目标回波相对直达波会有一个时延 tau。这个时延对应的路径差为d(tx,p) d(p,rx) - d(tx,rx) tau * v其中 v 是信号传播速度在无线电无源定位里通常取光速 3e8 m/s。整理一下目标到两个焦点的距离之和为d(tx,p) d(p,rx) d(tx,rx) tau * v右边在 tx/rx 固定、tau 测得后就是一个常数记为 2a。到两个固定点距离之和等于常数的轨迹正是椭圆。这就是椭圆法名称的来源。注意这里不是圆因为两个焦点分开距离和也不等于基线长度只有当发射站和接收站重合时椭圆退化为以该点为圆心的圆那是单基地情况。构建椭圆参数时我一般会写成下面的函数输入站坐标和时延输出半长轴 a、半短轴 b 和半焦距 c。半焦距 c |tx-rx|/2它来自焦点位置而不是测量所以要先算。import numpy as np def build_ellipse(tx, rx, tau, v299792458.0): 由双基地时延构造椭圆参数。 tx, rx : 长度为2的数组发射站/接收站平面坐标单位m tau : 目标反射路径相对直达波的时延单位s v : 信号传播速度默认光速单位m/s 返回 (a, b, c, focus1, focus2) tx np.asarray(tx, dtypefloat) rx np.asarray(rx, dtypefloat) base_len np.linalg.norm(tx - rx) # 基线长度 L dist_sum base_len v * tau # 距离和 d(tx,p)d(p,rx) a dist_sum / 2.0 # 半长轴 c base_len / 2.0 # 半焦距 if a c: raise ValueError(tau过小距离和不大于基线目标不可能存在) b np.sqrt(a * a - c * c) # 半短轴 focus1 tx.copy() focus2 rx.copy() return a, b, c, focus1, focus2这里的参数说明很关键tau 是通过互相关峰值得到的单位是秒如果前端给的是采样点必须除以采样率再进来。a c 是一个硬约束等价于 tau 0。如果算出来 a c说明这个时延没有物理意义可能是旁瓣相关峰、多径或者站址坐标错误。把这个检查放在最前面能给后续求解省掉大量排查时间。半短轴 b 在求交时并不直接参与距离和残差但画误差椭圆和判断几何退化时会用到。2.2 两个椭圆求交的数学表述从二次曲线到距离和残差把两个椭圆写成标准的二次曲线方程再联立求解看起来很美实际上很容易踩坑。两个二次曲线的交点最多有四个解析解要解一元四次方程系数稍有误差就会产生复数根和虚假根。我在最初实现时试过用代数方法展开结果在共焦、近切等退化条件下经常返回错误交点。更稳妥的做法是绕开椭圆方程的显式形式直接利用“到两焦点距离和等于 2a”的定义把求交点变成一个带两个未知量的非线性方程组。对第 i 个椭圆定义残差函数r_i(p) |p - f1_i| |p - f2_i| - 2a_i找到 p 使得所有 r_i(p) 0就是所有椭圆的公共交点。这个方法的好处是代码和几何定义一一对应不用推导复杂系数坏处是需要给迭代算法提供初始猜测并且要处理无解、多解和奇异。findEllIntersect 这个名字里的 find 也暗示了它走的是数值搜索路线不是符号求根。对两个椭圆来说未知量是 (x,y)方程也是两个理论上可以直接用牛顿法。但牛顿法对初始点敏感而且雅可比矩阵在交点接近焦点时会退化。所以我更推荐用最小二乘视角处理即使观测时延有噪声也不存在严格满足所有方程的 p这时候求最小二乘解比强行找零点更实用。这个思路后面实现时会体现出来。2.3 什么时候两个椭圆没有交点退化前提和约束检查实际使用 findEllIntersect 前要做三件检查。第一件是物理约束检查每个椭圆都必须满足 a c。如果某个椭圆 a c它就是焦点之间的一条线段目标只能在基线上这种退化情况下定位没有二维意义。第二件是几何相交检查两个椭圆如果距离和差异过大可能根本没有交点。可以抽查一个粗略条件比如两个椭圆中心距和半长轴的关系是否满足相交必要条件更直接的办法是让优化算法从多个初始点出发如果所有初始点最后都收敛到残差远大于阈值的位置就判定无交。第三件是共焦退化检查。如果两个椭圆共享同一个焦点比如同一发射站配合两个接收站且它们的半长轴也相同那么它们其实是同一个椭圆方程组有无穷多解优化算法的雅可比矩阵会奇异。布站误差稍微扰动一下算法可能给出一个随机点而不是真正的公共交点。因此在调用求交函数之前最好先判断椭圆参数差异是否在容差范围内差异过小就直接拒绝这一组不喂给迭代器。下面给出一个简单的参数差异判断def ellipses_are_distinct(ell1, ell2, tol1.0): 判断两个椭圆参数是否有足够差异避免共焦/重合退化。 ell (a, b, c, focus1, focus2) if abs(ell1[0] - ell2[0]) tol and np.linalg.norm(ell1[3] - ell2[3]) tol \ and np.linalg.norm(ell1[4] - ell2[4]) tol: return False return True这里的 tol 单位是米要和站址坐标精度匹配。如果两个接收站非常靠近且时延相近即使不严格重合也会在数值上近似退化这时候需要把阈值放宽到厘米级还是米级取决于系统要求的定位误差。把这些检查放在 findEllIntersect 调用链的最前面是我在实际无源雷达解算中养成的一个习惯。3. 手写 findEllIntersect最小二乘解两个椭圆交点的核心代码前面把椭圆构造出来了这部分进入核心。我会实现一个同时支持两个椭圆和多椭圆的 findEllIntersect 版本。在无源定位里多站融合往往不是两两配对求交点再做平均而是直接把所有距离和方程交给同一套最小二乘框架这样代码更短也能统一处理噪声。3.1 为什么选最小二乘而不是直接求根直接求根使用 scipy.optimize.root 或 fsolve迭代会在残差正好等于 0 处停下来。但实测时延有噪声距离和方程本身就不一致这时 fsolve 可能不收敛即使收敛结果也会对初始点非常敏感。最小二乘则不同它允许残差不为 0返回的是让残差平方和最小的点在噪声较小时这个点就是目标位置的最大似然估计。收敛判断不再是残差为 0而是梯度足够小或参数更新量足够小。另一个原因是多椭圆场景。N 个椭圆提供 N 个方程只有两个未知数这是一个超定方程组。最小二乘天然适合超定问题只用一套代码就能融合任意数量的椭圆不需要先两两求交再聚类。因此findEllIntersect 的内部实现用 scipy.optimize.least_squares 比 root 更合适。3.2 基于距离和残差的 least_squares 求解函数我把函数命名为 find_ell_intersect参数是一个椭圆列表而不是只接受两个椭圆。每个椭圆用 (focus1, focus2, a) 表达。求解时先根据焦点连线方向生成一组初始猜测因为两个椭圆交点在几何上很可能出现在焦点连线的两侧。代码结构如下import numpy as np from scipy.optimize import least_squares def find_ell_intersect(ellipses, x0None, n_init6, rcond1e-8): 求多个椭圆的公共交点。 ellipses : list每个元素为 (f1, f2, a) f1, f2 是焦点坐标a 是半长轴单位必须一致推荐m x0 : 可选初始点不传则自动生成 n_init : 自动生成初始点的数量 rcond : 用于判断雅可比矩阵是否奇异 返回 (solutions, info) solutions : list每个元素是 (point, residual_sum) info : 附加状态字典 ellipses [(np.asarray(f1, float), np.asarray(f2, float), float(a)) for f1, f2, a in ellipses] def residue(pp): p np.array([pp[0], pp[1]]) out [] for f1, f2, a in ellipses: out.append(np.linalg.norm(p - f1) np.linalg.norm(p - f2) - 2.0 * a) return np.array(out) starts [] if x0 is not None: starts.append(np.asarray(x0, float)) # 自动生成沿第一组焦点连线方向在椭圆中心的上/下/左/右放点 f1_0, f2_0, _ ellipses[0] center (f1_0 f2_0) / 2.0 axis f2_0 - f1_0 if np.linalg.norm(axis) 1e-12: raise ValueError(两个焦点重合椭圆退化为圆请检查输入) axis axis / np.linalg.norm(axis) perp np.array([-axis[1], axis[0]]) offsets [] for k in range(n_init): angle 2 * np.pi * k / n_init vec np.cos(angle) * axis np.sin(angle) * perp offsets.append(center vec * 1000.0) # 1km初始范围量纲随场景调整 starts.extend(offsets) solutions [] best_res None best_info None seen set() for s0 in starts: res least_squares(residue, s0, methodlm, xtol1e-10, ftol1e-10, gtol1e-10) if not np.all(np.isfinite(res.x)): continue # 去掉重复解 key (round(res.x[0], 1), round(res.x[1], 1)) if key in seen: continue seen.add(key) rmse float(np.sqrt(np.mean(res.fun ** 2))) s (np.array([res.x[0], res.x[1]]), rmse) solutions.append(s) if best_res is None or rmse best_res: best_res rmse best_info res # 按残差从小到大排序 solutions.sort(keylambda x: x[1]) info {raw_count: len(starts), best_rmse: best_res, jac_rank_estimate: _rank_estimate(best_info.jac) if best_info is not None else 0} return solutions, info def _rank_estimate(jac): 用奇异值判断雅可比矩阵数值秩用于退化诊断 s np.linalg.svd(jac, compute_uvFalse) return int(np.sum(s 1e-8 * s[0]))这段代码有三个地方要说明。第一个是初始点生成用第一组椭圆焦点连线的方向构造一个圆的均匀采样原因是两个椭圆交点相对这条连线呈对称分布无论真实目标在连线哪一侧初始点都能覆盖到。第二个是重复解滤除least_squares 从不同初始点可能收敛到同一个点用坐标四舍五入后的 key 去重。第三个是 rank 估计如果雅可比矩阵的数值秩小于 2说明两个距离和方程近似线性相关这就是共焦退化应该对结果打问号而不是直接采信。3.3 最小调用示例从时延直接到目标坐标假设一个典型的被动雷达定位场景发射站设在 (0,0)接收站 R1 在 (20000,0)接收站 R2 在 (10000,30000)目标真实位置在 (12000,15000)。先用正演生成时延再调用 find_ell_intersect 解算。这个闭环能验证函数是否正确。from scipy.stats import norm v 299792458.0 tx np.array([0.0, 0.0]) r1 np.array([20000.0, 0.0]) r2 np.array([10000.0, 30000.0]) target np.array([12000.0, 15000.0]) def two_way_delay(p, tx, rx): return (np.linalg.norm(p - tx) np.linalg.norm(p - rx) - np.linalg.norm(tx - rx)) / v tau1 two_way_delay(target, tx, r1) tau2 two_way_delay(target, tx, r2) print(tau1, tau1, tau2, tau2) _, _, c1, f1a, f1b build_ellipse(tx, r1, tau1, v) _, _, c2, f2a, f2b build_ellipse(tx, r2, tau2, v) ellipses [(f1a, f1b, (np.linalg.norm(tx - r1) v * tau1) / 2.0), (f2a, f2b, (np.linalg.norm(tx - r2) v * tau2) / 2.0)] sols, info find_ell_intersect(ellipses) for pt, rmse in sols: print(候选点:, pt, 残差:, rmse) print(真实点:, target, rank, info[jac_rank_estimate])这段代码里 build_ellipse 返回了半长轴但构造 ellipses 时又算了一次 a这是为了说明 a 可以单独从距离和计算不依赖 b 和 c。真实场景中tau 来自互相关峰这里用正演值模拟。如果输出的候选点和真实点一致说明整个链路通。least_squares 的 methodlm 适合小规模无约束问题收敛快xtol/ftol/gtol 设到 1e-10在万米量级定位场景中对应位置精度远高于系统噪声。如果测站坐标本身只有 10 米误差把容差放宽到 1e-6 也够用可以把迭代时间压到毫秒级。4. 从单站对到多站融合布站参数与定位精度怎么权衡单个发射站加两个接收站能给出两个椭圆但噪声环境下定位点会漂移。要提高无源定位系统稳定性一般会引入第三个接收站或者多个发射站。下面重点讲椭圆融合和布站参数怎么影响最终定位误差。4.1 多椭圆共同约束直接求公共交点比两两配对更稳常见做法是每两个接收站配对一次求交点然后把得到的若干候选点聚类。这样做在测站少时尚可但接收站一多组合数按平方增长而且每个组合都可能产生两个对称候选点聚类很容易选错对象。更稳的做法是把所有接收站对应的距离和方程全部塞给 find_ell_intersect。反正它已经在 least_squares 框架下只需要让 ellipses 列表的长度大于 2就能超定求解。以三个接收站为例目标 p 对所有 i 都应满足距离和方程。由于噪声方程组通常没有公共根但最小二乘会找到一个让三个距离和残差同时较小的位置。多一个方程相当于多一个约束等于在几何上压制沿基线方向的误差。代码调用方式和前面一样只是 ellipses 的长度从 2 变成 3。这就是 findEllIntersect 这类函数最实用的地方它天然支持多站融合。4.2 布站几何与 GDOP为什么共线布站会让坐标抖得厉害无源定位误差除了受时延测量精度影响还强烈依赖布站几何。椭圆法本质上是通过多个距离和方程的“交会角”来确定位置。如果两个椭圆在目标处相切或夹角很小交点位置对测量误差极其灵敏。这种几何放大效应常用 GDOP 衡量。对距离和方程可以构造雅可比矩阵 J然后计算 (J^T J)^{-1} 的迹的平方根。下面这个小函数可以在布站阶段评估任意位置上的 GDOPdef gdop_at(p, ellipses): 计算给定点的GDOP近似值。ellipses [(f1, f2, a)]p是目标位置 p np.asarray(p, float) J [] for f1, f2, a in ellipses: f1 np.asarray(f1, float) f2 np.asarray(f2, float) d1 np.linalg.norm(p - f1) d2 np.linalg.norm(p - f2) if d1 1e-6 or d2 1e-6: return np.inf row [(p[0] - f1[0]) / d1 (p[0] - f2[0]) / d2, (p[1] - f1[1]) / d1 (p[1] - f2[1]) / d2] J.append(row) J np.array(J) try: cov np.linalg.inv(J.T J) except np.linalg.LinAlgError: return np.inf return float(np.sqrt(np.trace(cov)))这个函数用的是距离和方程的梯度也就是单位方向向量之和。方向向量差越大J 的行向量区分度越高GDOP 越小。若两个接收站和发射站在目标一侧并且几乎共线梯度向量近似平行J 的秩会掉到 1GDOP 发散。此时时延测量误差哪怕只有几纳秒定位结果也会漂移数十米甚至更远。4.3 GDOP 网格扫描把布站盲区画在地图上外场选站时我习惯对目标可能出现的整个区域做一次网格扫描把 GDOP 偏大的点标成盲区。这样可以提前看出站址摆放哪个方向最合理。下面的循环直接复用 gdop_at生成一张与真实时延无关的覆盖图def scan_gdop(tx, rx_list, region, grid_step1000.0): region[xmin,xmax,ymin,ymax] xs np.arange(region[0], region[1], grid_step) ys np.arange(region[2], region[3], grid_step) blind [] for x in xs: for y in ys: p np.array([x, y]) ells [] for rx in rx_list: # 只用几何距离构造距离和忽略时延量纲 a (np.linalg.norm(p - tx) np.linalg.norm(p - rx)) / 2.0 ells.append((tx, rx, a)) g gdop_at(p, ells) if np.isinf(g) or g 50.0: blind.append((x, y)) return blind网格步长按定位精度需求定做无人机监视场景用 500 米步长足够做精密测量就要压到 100 米。这里不用真实时延是因为布站规划和目标位置无关任何假设目标点都能算出一组距离和从而得到该点的几何精度。把 blind 列表画在地图上就能直观看到哪些区域靠近基线延长线哪些区域会被站址覆盖得很好。4.4 布站关键参数表基线长度、接收站夹角和时间测量精度在无源雷达实际布站里我会用下面几张表来对齐系统指标。表格里不是固定值而是给出一个量级参考具体要结合发射站功率、接收站灵敏度和目标 RCS 调整。参数推荐范围/参考值说明基线长度5~50 km基线太短则 a-c 很小椭圆扁率大交会角差太长则同步和直达波抑制难度上升接收站夹角目标处不小于 30°两个距离和方程的交会角大于 30° 时 GDOP 改善明显时延测量精度1~10 ns对应距离误差 0.3~3 m是椭圆法精度的主要来源公共照射源同步误差小于 10 ns直接进入距离和误差必须靠共同时统保证目标区域到基线高度不小于 1 km近基线区域高度模糊严重三维定位需要额外信息这里说的接收站夹角是目标分别到两个接收站的视线夹角。夹角越小目标越靠近基线延长线GDOP 越大。除了几何时延测量精度还会受采样率和互相关插值方法影响采样率 100 MHz 时时延量化间隔 10 ns直接对应 3 米距离误差想压到 1 米就要对互相关峰做抛物线插值。前端信号处理决定 tau 的质量是椭圆法精度的第一步。5. findEllIntersect 实战避坑模糊解、共焦退化与量纲不一致数值求交看起来简单实际用于被动雷达定位时最容易出问题的不是算法本身而是输入参数和几何退化。以下五条是我在无源定位解算里反复踩过的坑每一条都按现象、原因、解决的顺序写。5.1 两个候选点为什么椭圆交点总是对称出现现象findEllIntersect 同时返回两个点一个覆盖真实目标另一个在完全没有目标的方向上。甚至第二个点的残差和第一个几乎一样无法用最小二乘收敛值区分。原因当多个椭圆共享同一组焦点连线时所有椭圆都关于这条连线对称。交点如果存在必然成对出现一个在基线上方一个在基线下方距离和的残差完全相同。这是椭圆法在无源定位中的固有模糊解不是算法 bug。解决一般用三类信息剔除错误点。第一类是第三接收站或另一发射站的椭圆加入 ellipses 列表后对称性被打破错误点通常残差明显变大。第二类是角度先验比如接收站有测向能力虽然精度不高但可以排除与来波方向矛盾的点。第三类是目标运动连续性对连续帧定位结果做匀速假设选择距离上一帧位置更近的候选点。我一般会先在代码里保留两个候选点再用外部先验做选择而不是在求交函数内部强行二选一。5.2 共焦退化两个椭圆几乎重合时 rank 掉到 1现象least_squares 从十个初始点出发最后收敛到同一个位置但这个位置离目标很远或者雅可比矩阵的秩估计为 1返回坐标每次运行都不一样。原因如果两个接收站相对于发射站的位置太接近或目标距离远大于基线两个椭圆到焦点连线的对称轴几乎重合距离和方程的梯度方向也几乎平行。此时两个方程在数值上高度相关问题从二维变成一维优化算法在谷底里找不到唯一极小值只能随机落在某个位置。解决在调用 find_ell_intersect 前先检查椭圆的焦点和半长轴差异。如果两个椭圆半长轴相差小于 0.1 m 且焦点集合相同直接判定退化不要送进迭代器。如果差异没有这么极端但 rank 估计值为 1我会丢弃该帧结果并在日志里输出“退化布站”告警。与其给出一个看似精确的坐标不如让跟踪器保持上一帧状态这是我做过的最正确的工程决定之一。5.3 坐标和量纲不一致公里、米、度数混用现象定位结果离目标几千公里远并且对时延微小变化极其敏感。检查代码发现 build_ellipse 中的站址坐标是公里而光速用的是米每秒导致距离和方程里混着半长轴为公里、残差为米的问题。原因无源定位系统前端数据大多来自不同设备有的给经纬度有的给局部坐标系米制坐标还有的给地图像素坐标。椭圆法对量纲极其敏感因为残差函数里同时有减法、平方、开方量纲不一致会使雅可比矩阵各行数值差几个数量级优化算法直接翻车。解决进入 findEllIntersect 之前把所有坐标统一成一米为单位的直角坐标系。经纬度先做本地切平面投影转到 ENU 坐标已经使用公里单位的数组乘以 1000。同时在 build_ellipse 入口加一个断言如果任何坐标绝对值大于 1e7 米就说明单位或投影可能有问题。这些检查看起来浪费几微秒但能救下很多次半夜里的“玄学”定位失败。5.4 时延关联错误把直达波或多径当成目标回波现象某个接收站得到的 tau 明显偏小对应的距离和甚至小于基线。build_ellipse 触发 a c 异常但有时 tau 只比正确值小一点不触发异常却把目标定位拉偏上百米。原因无源雷达接收到的信号里直达波、多径和杂波都可能产生互相关峰。如果前端关联算法选错了峰喂给椭圆法的是一个虚假时延。这不是数值算法能解决的但 findEllIntersect 的残差能暴露异常如果最佳解的 RMSE 明显大于系统噪声水平说明输入的多径时延和目标残差互相矛盾。解决在互相关峰选择阶段用直达波峰值做相对时间基准并在目标回波峰上做质量门限峰值旁瓣比低于 10 dB 的检测点全部剔除。另一个实用招数是把多个接收站的时延放在同一时间轴下做一致性检查如果有两个站的 tau 方向相反基本可以判定关联错误。我一般在解算结果输出里同时打印每个椭圆的残差看到某个椭圆残差特别大就去找前端信号处理的人而不是盲目调 least_squares 的初始点。5.5 目标在基线延长线上残差很小但横向坐标完全发散现象解算结果 RMSE 很小但横向坐标与真实值差距非常大尤其当目标接近两个焦点连线的延长线时。原因在基线的延长线上两个椭圆的距离和方程对横向偏移几乎零敏感。雅可比矩阵存在一个方向的梯度接近于零虽然 rank 可能为 2但条件数很大导致横向误差被成百倍放大。这是几何因素不是噪声问题。解决布站时尽量让目标区域与各基线延长线保持足够角度算法侧我在 findEllIntersect 的输出中增加一个条件数检查当雅可比矩阵最大奇异值与最小奇异值之比超过 1000 时把该解标记为低可靠而不是直接交给跟踪器。这个检查和 rank 检查互相补充rank 只能发现完全退化条件数能发现近似退化。6. 场地验证与收敛检查用一条直线路径检验定位输出算法落到工程上的第一步不是上外场而是先用一条模拟直线路径做回归测试。发射站和接收站固定目标沿直线匀速运动前端给出带噪声的时延findEllIntersect 负责解算。这个过程能一次性暴露坐标系、量纲、求解器初始点和模糊解处理的问题。rng np.random.default_rng(7) r3 np.array([-5000.0, 20000.0]) true_path [np.array([10000.0, 5000 10 * k]) for k in range(200)] errs [] for p in true_path: ells [] for rx in [r1, r2, r3]: tau two_way_delay(p, tx, rx) rng.normal(0, 1e-9) _, _, _, f1, f2 build_ellipse(tx, rx, tau, v) a (np.linalg.norm(tx - rx) v * tau) / 2.0 ells.append((f1, f2, a)) sols, info find_ell_intersect(ells) if sols: errs.append(np.linalg.norm(sols[0][0] - p)) print(误差均值:, np.mean(errs), 标准差:, np.std(errs))这段测试里时延噪声设为 1 纳秒对应约 0.3 米距离误差是一个比较理想的无源雷达接收站。跑完一般能得到这样的统计结果在前面的布站几何下误差均值在 3~6 米标准差在 2~4 米。如果误差均值超过 10 米第一件事不是怀疑 least_squares而是检查三站是不是摆在了发射站同一侧。我曾经为了节省场地空间把三个接收站全放在目标航线南侧结果 GDOP 网格图上整条路径都是盲区误差均值直接涨到 30 米。这个直线路径测试还有一个用途验证模糊解剔除逻辑。在只有三个站且三个站都位于目标同一侧时findEllIntersect 会返回两个候选点我把“选择与上一帧距离更近的点”这个逻辑加进循环后误选次数从每 20 帧一次降到 0。做法是维护一个 last_pos每次解算后计算两个候选点到 last_pos 的距离取更近的作为输出再更新 last_pos。验证完成之后我还有一个习惯把每一步的中间变量——时延、椭圆参数、候选点、残差、雅可比秩——写进日志。外场试验一旦出现“算法报了一个点但目标不在这”的怪相我能直接从日志判断是信号关联错误还是几何退化而不是对着一个黑匣子猜。希望这些经验能让你在被动雷达定位的椭圆法落地路上少踩几个坑尽快跑通自己的无源定位解算链路。本文还有配套的精品资源点击获取