最接近 3D 空间中多条线的 3d 点

Posted

技术标签:

【中文标题】最接近 3D 空间中多条线的 3d 点【英文标题】:3d point closest to multiple lines in 3D space 【发布时间】:2018-06-17 15:54:39 【问题描述】:

我搜索非迭代、封闭形式的算法,以找到最接近 3d 线集的点的最小二乘解。它类似于 3d 点三角测量(以最小化重新投影)但似乎更简单更快?

线可以用任何形式描述,2点,点和单位方向或类似的。

【问题讨论】:

您最好在 math.stackexchange.com 上询问这个问题 - 如果您需要帮助编码,请回到这里。 您可以用全套线测试每个点。如果您对点和线进行排序,可以实现一些改进,因此可以快速丢弃其中的许多。对于点线 3D 距离,您可以使用例如 this 我的回答对你有用吗?我认为效果很好,你从来没有评论过。 抱歉,这个任务耽误了很久。生病回来时,a 会写出 cmets 和结果。抱歉暂停。 【参考方案1】:

我需要这个作为 Processing 中的草图,所以我移植了 Gene 的答案。效果很好,并认为它可以为其他人节省一点时间。不幸的是,PVector/PMatrix 没有向量或矩阵的数组访问器,所以我不得不将它们添加为本地函数。

float getv(PVector v, int i) 
  if(i == 0) return v.x;
  if(i == 1) return v.y;
  return v.z;


void setv(PVector v, int i, float value) 
  if      (i == 0) v.x = value;
  else if (i == 1) v.y = value;
  else             v.z = value;


void incv(PVector v, int i, float value) 
  setv(v,i,getv(v,i) + value);


float getm(float[] mm, int r, int c)               return mm[c + r*4]; 
void  setm(float[] mm, int r, int c, float value)  mm[c + r*4] = value; 
void  incm(float[] mm, int r, int c, float value)  mm[c + r*4] += value; 

PVector findNearestPoint(PVector a[], PVector d[]) 
  var mm = new float[16];
  var b  = new PVector();
  var n  = a.length;

  for (int i = 0; i < n; ++i) 
    var d2 = d[i].dot(d[i]);
    var da = d[i].dot(a[i]);
    
    for (int ii = 0; ii < 3; ++ii) 
      for (int jj = 0; jj < 3; ++jj) 
        incm(mm,ii,jj, getv(d[i],ii) * getv(d[i],jj));
      
      
      incm(mm, ii,ii, -d2);
      incv(b, ii, getv(d[i], ii) * da - getv(a[i], ii) * d2);
    
  
  
  var p = solve(mm, new float[] b.x, b.y, b.z);
  return new PVector(p[0],p[1],p[2]);



// Verifier
float dist2(PVector p, PVector a, PVector d) 
  PVector pa  = new PVector( a.x-p.x, a.y-p.y, a.z-p.z );
  float   dpa = d.dot(pa);
  return  d.dot(d) * pa.dot(pa) - dpa * dpa;


//double sum_dist2(VEC p, VEC a[], VEC d[], int n) 
float sum_dist2(PVector p, PVector a[], PVector d[]) 
  int n = a.length;
  float sum = 0;
  for (int i = 0; i < n; ++i)  
    sum += dist2(p, a[i], d[i]);
  
  return sum;


// Check 26 nearby points and verify the provided one is nearest.
boolean isNearest(PVector p, PVector a[], PVector d[]) 
  float min_d2 = 3.4028235E38;
  int   ii = 2, jj = 2, kk = 2;
  final float D  = 0.1f;
  
  for (int i = -1; i <= 1; ++i) 
    for (int j = -1; j <= 1; ++j)
      for (int k = -1; k <= 1; ++k) 
        PVector pp = new PVector( p.x + D * i, p.y + D * j, p.z + D * k );
        float d2 = sum_dist2(pp, a, d);
        // Prefer provided point among equals.
        if (d2 < min_d2 || i == 0 && j == 0 && k == 0 && d2 == min_d2) 
          min_d2 = d2;
          ii = i; jj = j; kk = k;
        
      
      
  return ii == 0 && jj == 0 && kk == 0;




void setup() 
  PVector a[] = 
    new PVector(-14.2, 17, -1), 
    new PVector(1, 1, 1),
    new PVector(2.3, 4.1, 9.8),
    new PVector(1,2,3)
  ;
  
  PVector d[] = 
    new PVector(1.3, 1.3, -10),
    new PVector(12.1, -17.2, 1.1),
    new PVector(19.2, 31.8, 3.5),
    new PVector(4,5,6)
  ;
  
  int n = 4;
  for (int i = 0; i < n; ++i)
    d[i].normalize();
    
  PVector p = findNearestPoint(a, d);
  println(p);
  
  if (!isNearest(p, a, d))
    println("Woops. Not nearest.\n");




// From rosettacode (with bug fix: added a missing fabs())
int mat_elem(int y, int x)  return y*4+x; 

void swap_row(float[] a, float[] b, int r1, int r2, int n)

  float tmp;
  int p1, p2;
  int i;

  if (r1 == r2) return;
  
  for (i = 0; i < n; i++) 
    p1 = mat_elem(r1, i);
    p2 = mat_elem(r2, i);
    
    tmp = a[p1];
    a[p1] = a[p2];
    a[p2] = tmp;
  
  
  tmp = b[r1];
  b[r1] = b[r2];
  b[r2] = tmp;


float[] solve(float[] a, float[] b)

  float[] x = new float[] 0,0,0;
  int n = x.length;
  int i, j, col, row, max_row, dia;
  float max, tmp;

  for (dia = 0; dia < n; dia++) 
    max_row = dia;
    max = abs(getm(a, dia, dia));
    for (row = dia + 1; row < n; row++) 
      if ((tmp = abs(getm(a, row, dia))) > max)  
        max_row = row;
        max = tmp;
      
    
    swap_row(a, b, dia, max_row, n);
    for (row = dia + 1; row < n; row++) 
      tmp = getm(a, row, dia) / getm(a, dia, dia);
      for (col = dia+1; col < n; col++) 
        incm(a, row, col, -tmp * getm(a, dia, col));
      
      setm(a,row,dia, 0);
      b[row] -= tmp * b[dia];
    
  
  for (row = n - 1; row >= 0; row--) 
    tmp = b[row];
    for (j = n - 1; j > row; j--) 
      tmp -= x[j] * getm(a, row, j);
    
    x[row] = tmp / getm(a, row, row);
  
  
  return x;

【讨论】:

【参考方案2】:

让第i行由点ai和单位方向向量di给出>。我们需要找到最小化点到线距离平方和的单点。这是梯度为零向量的地方:

扩大渐变,

代数产生一个规范的 3x3 线性系统,

矩阵 M 的第 k 行(一个 3 元素行向量)在哪里

用向量ek各自的单位基向量,而

将其转换为代码并不难。我从 Rosettacode 借用(并修复了一个小错误)一个高斯消除函数来解决这个系统。感谢作者!

#include <stdio.h>
#include <math.h>

typedef double VEC[3];
typedef VEC MAT[3];

void solve(double *a, double *b, double *x, int n);  // linear solver

double dot(VEC a, VEC b)  return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; 

void find_nearest_point(VEC p, VEC a[], VEC d[], int n) 
  MAT m = 0, 0, 0, 0, 0, 0, 0, 0, 0;
  VEC b = 0, 0, 0;
  for (int i = 0; i < n; ++i) 
    double d2 = dot(d[i], d[i]), da = dot(d[i], a[i]);
    for (int ii = 0; ii < 3; ++ii) 
      for (int jj = 0; jj < 3; ++jj) m[ii][jj] += d[i][ii] * d[i][jj];
      m[ii][ii] -= d2;
      b[ii] += d[i][ii] * da - a[i][ii] * d2;
    
  
  solve(&m[0][0], b, p, 3);


void pp(VEC v, char *l, char *r) 
  printf("%s%.3lf, %.3lf, %.3lf%s", l, v[0], v[1], v[2], r);
 

void pv(VEC v)  pp(v, "(", ")");  

void pm(MAT m)  for (int i = 0; i < 3; ++i) pp(m[i], "\n[", "]");  

// Verifier
double dist2(VEC p, VEC a, VEC d) 
  VEC pa =  a[0]-p[0], a[1]-p[1], a[2]-p[2] ;
  double dpa = dot(d, pa);
  return dot(d, d) * dot(pa, pa) - dpa * dpa;


double sum_dist2(VEC p, VEC a[], VEC d[], int n) 
  double sum = 0;
  for (int i = 0; i < n; ++i) sum += dist2(p, a[i], d[i]);
  return sum;


// Check 26 nearby points and verify the provided one is nearest.
int is_nearest(VEC p, VEC a[], VEC d[], int n) 
  double min_d2 = 1e100;
  int ii = 2, jj = 2, kk = 2;
#define D 0.01
  for (int i = -1; i <= 1; ++i) 
    for (int j = -1; j <= 1; ++j)
      for (int k = -1; k <= 1; ++k) 
        VEC pp =  p[0] + D * i, p[1] + D * j, p[2] + D * k ;
        double d2 = sum_dist2(pp, a, d, n);
        // Prefer provided point among equals.
        if (d2 < min_d2 || i == 0 && j == 0 && k == 0 && d2 == min_d2) 
          min_d2 = d2;
          ii = i; jj = j; kk = k;
        
      
  return ii == 0 && jj == 0 && kk == 0;


void normalize(VEC v) 
  double len = sqrt(dot(v, v));
  v[0] /= len;
  v[1] /= len;
  v[2] /= len;


int main(void) 
  VEC a[] = -14.2, 17, -1, 1, 1, 1, 2.3, 4.1, 9.8, 1,2,3;
  VEC d[] = 1.3, 1.3, -10, 12.1, -17.2, 1.1, 19.2, 31.8, 3.5, 4,5,6;
  int n = 4;
  for (int i = 0; i < n; ++i) normalize(d[i]);
  VEC p;
  find_nearest_point(p, a, d, n);
  pv(p);
  printf("\n");
  if (!is_nearest(p, a, d, n)) printf("Woops. Not nearest.\n");
  return 0;


// From rosettacode (with bug fix: added a missing fabs())
#define mat_elem(a, y, x, n) (a + ((y) * (n) + (x)))

void swap_row(double *a, double *b, int r1, int r2, int n)

  double tmp, *p1, *p2;
  int i;

  if (r1 == r2) return;
  for (i = 0; i < n; i++) 
    p1 = mat_elem(a, r1, i, n);
    p2 = mat_elem(a, r2, i, n);
    tmp = *p1, *p1 = *p2, *p2 = tmp;
  
  tmp = b[r1], b[r1] = b[r2], b[r2] = tmp;


void solve(double *a, double *b, double *x, int n)

#define A(y, x) (*mat_elem(a, y, x, n))
  int i, j, col, row, max_row, dia;
  double max, tmp;

  for (dia = 0; dia < n; dia++) 
    max_row = dia, max = fabs(A(dia, dia));
    for (row = dia + 1; row < n; row++)
      if ((tmp = fabs(A(row, dia))) > max) max_row = row, max = tmp; 
    swap_row(a, b, dia, max_row, n);
    for (row = dia + 1; row < n; row++) 
      tmp = A(row, dia) / A(dia, dia);
      for (col = dia+1; col < n; col++)
        A(row, col) -= tmp * A(dia, col);
      A(row, dia) = 0;
      b[row] -= tmp * b[dia];
    
  
  for (row = n - 1; row >= 0; row--) 
    tmp = b[row];
    for (j = n - 1; j > row; j--) tmp -= x[j] * A(row, j);
    x[row] = tmp / A(row, row);
  
#undef A

这没有经过广泛的测试,但似乎工作正常。

【讨论】:

【参考方案3】:

设直线的基点为p,单位方向向量为d。 然后可以计算点v到这条线的距离using cross product

SquaredDist = ((v -  p) x d)^2

使用Maple包符号计算,可以得到

d := <dx, dy, dz>;
v := <vx, vy, vz>;
p := <px, py, pz>;
w := v - p;
cp := CrossProduct(d, w);
nrm := BilinearForm(cp, cp, conjugate=false);  //squared dist
nr := expand(nrm);       

//now partial derivatives
nrx := diff(nr, vx);     
//results:
nrx := -2*dz^2*px-2*dy^2*px+2*dz^2*vx+2*dy^2*vx
       +2*dx*py*dy-2*dx*vy*dy+2*dz*dx*pz-2*dz*dx*vz
nry := -2*dx^2*py-2*dz^2*py-2*dy*vz*dz+2*dx^2*vy
       +2*dz^2*vy+2*dy*pz*dz+2*dx*dy*px-2*dx*dy*vx
nrz := -2*dy^2*pz+2*dy^2*vz-2*dy*dz*vy+2*dx^2*vz
       -2*dx^2*pz-2*dz*vx*dx+2*dy*dz*py+2*dz*px*dx

为了最小化距离平方和,我们必须为零偏导数建立线性方程组,如下所示:

  vx*2*(Sum(dz^2)+Sum(dy^2)) + vy * (-2*Sum(dx*dy)) + vz *(-2*Sum(dz*dx)) = 
     2*Sum(dz^2*px)-2*Sum(dy^2*px) -2*Sum(dx*py*dy)-2*Sum(dz*dx*pz)

where 
  Sum(dz^2) = Sumover all i in line indexes dz[i] * dz[i]

求解未知数 vx, vy, vz

编辑:飞机而不是线的旧错误答案,留作参考

如果我们使用一般的直线方程

 A * x + B * y + C * z + D = 0

那么点 (x, y, z) 到这条线的距离是

Dist = Abs(A * x + B * y + C * z + D) / Sqrt(A^2 + B^2 + C^2)

为了简化 - 只需标准化除以Norm's 的所有线方程

Norm = Sqrt(A^2 + B^2 + C^2)
a = A / Norm
b = B / Norm
c = C / Norm
d = D / Norm

现在方程是

 a * x + b * y + c * z  + d = 0

和距离

Dist = Abs(a * x + b * y + c * z + d)

我们可以使用像 LS 方法这样的平方距离(ai, bi, ci, di 是第 i 行的系数)

F = Sum(ai*x + bi*y + ci * z + d)^2 = 
Sum(ai^2*x^2 + bi^2*y^2 + ci^2*z^2 + d^2 +
2 * (ai*bi*x*y + ai*ci*x*z + bi*y*ci*z + ai*x*di + bi*y*di + ci*z*di))

  partial derivatives
dF/dx = 2*Sum(ai^2*x + ai*bi*y + ai*ci*z + ai*di) = 0
dF/dy = 2*Sum(bi^2*y + ai*bi*x + bi*ci*z + bi*di) = 0
dF/dz = 2*Sum(ci^2*z + ai*ci*x + bi*ci*y + ci*di) = 0
  so we have system of linear equation 
x * Sum(ai^2) + y * Sum(ai*bi) + z * Sum(ai*ci)= - Sum(ai*di)
y * Sum(bi^2) + x * Sum(ai*bi) + z * Sum(bi*ci)= - Sum(bi*di)
z * Sum(ci^2) + x * Sum(ai*ci) + y * Sum(bi*ci)= - Sum(ci*di)

x * Saa + y * Sab + z * Sac = - Sad
x * Sab + y * Sbb + z * Sbc = - Sbd
x * Sac + y * Sbc + z * Scc = - Scd

where S** are corresponding sums

并且可以解决未知数x, y, z

【讨论】:

A * x + B * y + C * z + D = 0 不是 3D 中的线,而是平面。因此,以下计算可能也不符合 OP 的问题。 稍微考虑一下:到一条线的平方距离可以通过将平方距离添加到两个正交平面(在线相交)来计算。因此,如果您为每条线创建两个平面,您的方法会很有用。 @Ralf Kleberhoff OMG,是的 :( 我回答了 2D 案例,然后愚蠢地将其扩展到 3D 案例 我没有看到您的最终解决方案是如何工作的。可能有任意数量的点,所以你写的是任意数量的方程。但只有3个未知数。正如我在 cmets 中所说的(根据您的变量名进行调整),您需要求解 gradient( sum_i (d_i x (p_i - v))^2 ) = 0,即 3 个未知数中的 3 个方程。 @Gene 我考虑计算点的坐标,以最小化给定线集的距离平方和。它准确地解决了 3 个未知数中的 3 个方程(我只写了一个)。您是在谈论从点集中选择最佳点吗?

以上是关于最接近 3D 空间中多条线的 3d 点的主要内容,如果未能解决你的问题,请参考以下文章

根据相互距离对 2D/3D 点数组进行排序的启发式方法

[搬运]自动驾驶中的单目 3D 车道线检测——综述

AS3 围绕空间点旋转 3D 对象

确定一个点是不是在 3D 空间中的三角形内

在 3d 空间中移动一个点

为 3D 空间中的点生成随机运动