问题描述
我有3个接收器(A,B和C),以及一些位置未知的信号产生源(比如声音或光)。给定A,B和C的位置,以及每个接收器“听到”信号的时间,我想确定信号源的方向。
我知道可以使用TDoA多边测量/三边测量来进行此操作,但是我在实现计算时遇到了麻烦。对于那些刚接触该主题的人,这里没有很多清晰,详细的信息。对于我来说,那里含糊不清,更加理论化或太深奥了。
关于SO的一些类似帖子(但不完全是我所追求的): TDOA multilateration to locate a sound source Trilateration of a signal using Time Difference(TDOA)
这也很有趣,但是假设我们有一些界限: Multiliteration implementation with inaccurate distance data
@Dave也评论了一种出色且相当容易获得的资源https://sites.tufts.edu/eeseniordesignhandbook/files/2017/05/FireBrick_OKeefe_F1.pdf,但它的深度不足以至于人们可能可以在代码中实际实现这一点(至少对于不具备回归知识的人而言) ,找到所得双曲线的交集,等等。
[EDIT]:我要补充一点,我可以假设3个传感器和位于地球表面,并且地球曲率的影响可以忽略不计(即我们可以二维地工作。
解决方法
有趣的问题。我懒得导出代数解的方程。相反,为什么不拟合结果呢?
因此,可以使用任何能够找到局部解的拟合方法(使用某些误差值的优化/最小化)简单地拟合2D(或更高)位置。当我使用我的简单approximation search来适应位置时,结果看起来还不错。
算法是:
-
迭代您范围内的“所有”职位
不是所有的试探法都能大大减少问题。
-
在每个测试位置上计算要测量的增量时间
从测试位置到接收站的简单旅行时间。
-
归一化所有增量时间,以便从零开始
因此从所有收款时间中减去最短的到达时间。实际测量时间也是如此。这样可以确保时间不涉及相对偏移。
-
计算实际测量时间与计算时间之间的差值
简单的腹肌差异就足够了。将此值用作拟合参数(优化)。
这是一个小C ++示例,使用上面链接中的我的近似类来做到这一点:
//---------------------------------------------------------------------------
// TDoA Time Difference of Arrival
//---------------------------------------------------------------------------
const int n=3;
double recv[n][3]; // (x,y) [m] receiver position,[s] time of arrival of signal
double pos0[2]; // (x,y) [m] object's real position
double pos [2]; // (x,y) [m] object's estimated position
double v=340.0; // [m/s] speed of signal
double err=0.0; // [m] error between real and estimated position
//---------------------------------------------------------------------------
void compute()
{
int i;
double x,y,a,da,t0;
//---------------------------------------------------------
// init positions
da=2.0*M_PI/(n);
for (a=0.0,i=0;i<n;i++,a+=da)
{
recv[i][0]=256.0+(220.0*cos(a));
recv[i][1]=256.0+(220.0*sin(a));
}
pos0[0]=300.0;
pos0[1]=220.0;
// simulate measurement
t0=123.5; // some start time
for (i=0;i<n;i++)
{
x=recv[i][0]-pos0[0];
y=recv[i][1]-pos0[1];
a=sqrt((x*x)+(y*y)); // distance to receiver
recv[i][2]=t0+(a/v); // start time + time of travel
}
//---------------------------------------------------------
// normalize times into deltas from zero
a=recv[0][2]; for (i=1;i<n;i++) if (a>recv[i][2]) a=recv[i][2];
for (i=0;i<n;i++) recv[i][2]-=a;
// fit position
int N=6;
approx ax,ay;
double e,dt[n];
// min,max,step,recursions,&error
for (ax.init( 0.0,512.0,32.0,N,&e);!ax.done;ax.step())
for (ay.init( 0.0,&e);!ay.done;ay.step())
{
// simulate measurement -> dt[]
for (i=0;i<n;i++)
{
x=recv[i][0]-ax.a;
y=recv[i][1]-ay.a;
a=sqrt((x*x)+(y*y)); // distance to receiver
dt[i]=a/v; // time of travel
}
// normalize times dt[] into deltas from zero
a=dt[0]; for (i=1;i<n;i++) if (a>dt[i]) a=dt[i];
for (i=0;i<n;i++) dt[i]-=a;
// error
e=0.0; for (i=0;i<n;i++) e+=fabs(recv[i][2]-dt[i]);
}
pos[0]=ax.aa;
pos[1]=ay.aa;
//---------------------------------------------------------
// compute error
x=pos[0]-pos0[0];
y=pos[1]-pos0[1];
err=sqrt((x*x)+(y*y)); // [m]
}
//---------------------------------------------------------------------------
这里预览:
蓝点是接收器,红点是对象的真实位置,黄叉是其估计位置。该区域为512x512 m
,我适合初始步骤32 m
和6
递归,导致错误~0.005 m
我对结果感到非常满意...您可以更改接收方n
的数量,而无需真正更改源或算法。我开始将接收器位置均匀分布在圆上,但这些位置可能是其他任何位置(并非全部都在粗略的单行上)
最简单(但不是最快)的方法是使用gradient descent求解方程。
我假设我们知道
- 接收器A,B和C的位置不在同一行;
- 未知源X到A,B和C的伪距。
直观地,我们模拟一个具有三个ideal springs的物理系统,其中每个弹簧的平衡长度是相应的伪距。
A
|
X
/ \
B C
当距离太小时弹簧会推动,而当距离太大时弹簧会拉。 X的大约静止位置应该是一个合理的估计值(尽管您可能需要根据应用程序进行其他验证)。
以下是一些使用复数作为2D向量的示例Python代码,应该易于音译。
import random
def distance(p,q):
return abs(p - q)
# Force exerted by an ideal spring between variable y and fixed q of equilibrium
# length dxq.
def force(y,q,dxq):
dyq = distance(y,q)
return (dxq - dyq) * (y - q) / dyq
# Trilateration via gradient descent.
def trilaterate(
a,dxa,b,dxb,c,dxc,*,max_iterations=1000000,gamma=0.001,precision=1e-12
):
# Use the centroid of the receivers as the initial estimate.
y = (a + b + c) / 3
for i in range(max_iterations):
f = force(y,dxa) + force(y,dxb) + force(y,dxc)
y += gamma * f
if abs(f) <= precision:
return y
return None
def random_point():
return complex(random.random(),random.random())
def test_error():
a = random_point()
b = random_point()
c = random_point()
x = random_point()
y = trilaterate(a,distance(x,a),b),c))
return distance(x,y)
if __name__ == "__main__":
print(test_error())