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

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

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

【问题讨论】:

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

标签: algorithm computer-vision geometry linear-algebra computational-geometry


【解决方案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) = Sum{over 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 个方程(我只写了一个)。您是在谈论从点集中选择最佳点吗?
      猜你喜欢
      • 2021-08-06
      • 1970-01-01
      • 2020-05-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多