我如何知道两个线段是否接近共线

时间:2012-04-10 21:48:06

标签: vector parallel-processing dot-product cross-product

由于浮点精度,我在确定两个线段是否共线时遇到了一些麻烦。如何确定线段是否与某个公差共线?

4 个答案:

答案 0 :(得分:3)

编辑:

如果线段包含两个相同的点,则线段是共线的。如果它们共享一个点并且接近平行,则它们接近共线。

如果它们之间的角度小于您指出的阈值,则矢量有效地平行。也许小于.000027度,这是十分之一度十分之一的十进制当量(在纬度距离,相当于赤道的纵向距离,大约十英尺的差异;这是关于民用GPS的精度)。

您没有告诉我们您使用的是哪种语言或图书馆;在.NET的System.Windows.Media.3D库中有一个Vector3D结构,它有一个AngleBetween()方法,使得这个检查成为一个单行。

“基本”数学(实际上是矢量三角形,而不是大多数定义的“基本”概念)是θ= cos -1 (A * B / | A || B |) ;也就是说,两个向量的标量积的弧余弦除以它们的大小的乘积。

载体A和载体B的点积,均含有X,Y和Z成分,是X A X B + Y A Y B + Z A Z B 。向量A的大小为sqrt(X A 2 + Y A 2 + Z A 2 )。

所以,在伪C-ish:

//Vector is a simple immutable class or struct containing integer X, Y and Z components
public bool CloseEnough(Vector a, Vector b, decimal threshold = 0.000027m)
{
   int dotProduct = a.X*b.X + a.Y*b.Y + a.Z*b.Z;
   decimal magA = sqrt(a.X*a.X + a.Y*a.Y + a.Z*a.Z); //sub your own sqrt
   decimal magB = sqrt(b.X*b.X + b.Y*b.Y + b.Z*b.Z); //sub your own sqrt

   decimal angle = acos(dotProduct/(magA*magB)); //sub your own arc-cosine

   if(angle <= threshold
}

答案 1 :(得分:3)

如果两个片段接近共线,我需要知道我的应用。这是关于从激光扫描中提取线条。我将解释我正在使用的解决方案。它工作得很好。 (对不起我的英文!)

我认为 KeithS 为近共线性提出的条件是错误的。

  

如果它们共享一个点并且接近平行,则它们接近共线。

如果两个线段(或线)是平行的但它们在附近,我们可以认为它们接近共线。

我的解决方案在于使用线条的极坐标表示。

  

y * sin(theta)= rho - x * cos(theta)

通过这种表示,可以使用theta和rho将线表示为点。诀窍在于,如果这些“点”接近,则线条接近共线。您只需计算欧氏距离并使用阈值来确定它们是否接近共线。

答案 2 :(得分:3)

根据您对“几乎共线”的定义,有几种解决方案似乎是合理的。

通过4点拟合一条线

两个线段由4个点定义,您可以通过2个线段的起点和终点拟合一条线。

你可以使用SVD通过4个点获得一条线的最小方形拟合,类似于这个答案:https://stackoverflow.com/a/2333251/5069869

使用这种方法,您可以考虑线段的方向以及第一个线段的末端和第二个线段的开始之间的偏移量。

但是,如果两个线段平行但不共线,并且如果它们相对于它们之间的距离较短,则拟合线将垂直于两个线段。对于单元测试来说这不是问题,你喜欢“几乎共线到零后几位数”,但对于其他应用程序,这可能是个问题。

独立使用平均方向和平均位置

或者,您可以使用两个线段的平均方向构建目标线作为目标线的方向。如果向量(v1/|v1|+v2/|v2|)/2v1指向同一方向,则可以通过v2计算Thuis。然后使用4个点的平均值作为目标线的锚点。

最后,您可以计算4个点到目标线的个别distances,并将它们用作共线性的度量。

您的细分和长度的影响段之间的空间

当你找到一个合适的“几乎共线”的定义时,你需要考虑不同长度的线段如何影响结果,正如Vahid在评论中指出的那样。此外,您应该考虑两个线段之间空间的影响。

通过计算4个点到目标线的距离,我尝试减少不同段长度的影响(每个段只贡献2个点),但我不能完全消除它

答案 3 :(得分:0)

在我看来,如果两条线近似共线,则应满足以下条件:

<块引用>
  1. 两条线的夹角小于阈值
  2. 点到两条线段的距离小于阈值

我的解决方案:

import math
import numpy as np

def line_to_angle(line, use_abs=True):
    x1, y1, x2, y2 = line[:4]
    if abs(x2 - x1) <= 0.0000001:
        angle_val = 90 if y2 > y1 else -90
    else:
        angle_val = np.degrees(math.atan((y2 - y1) / (x2 - x1)))
    if use_abs:
        angle_val = abs(angle_val)
    return angle_val


def distance_line_point(p, line):
    p1 = np.array(p)
    p2, p3 = np.array(line[:2]), np.array(line[2:4])
    return np.abs(np.cross(p2-p1, p1-p3) / np.linalg.norm(p2-p1))


def near_collinear(linea, lineb, angle_thres=1.5, distance_thres=5):
    pointa = ((linea[0] + lineb[0]) / 2, (linea[1] + lineb[1]) / 2)
    pointb = ((linea[2] + lineb[2]) / 2, (linea[3] + lineb[3]) / 2)
    mid_angle = line_to_angle([*pointa, *pointb], use_abs=False)
    angle_a, angle_b = line_to_angle(linea, use_abs=False), line_to_angle(lineb, use_abs=False)
    angle_diff_a, angle_diff_b = abs(angle_a - mid_angle), abs(angle_b - mid_angle)
    angle_diff_a = min(angle_diff_a, 180 - angle_diff_a)
    angle_diff_b = min(angle_diff_b, 180 - angle_diff_b)
    if angle_diff_a > angle_thres or angle_diff_b > angle_thres:
        return False
    mid_point = ((pointa[0] + pointb[0]) / 2, (pointa[1] + pointb[1]) / 2)
    distance_a = distance_line_point(mid_point, linea)
    distance_b = distance_line_point(mid_point, lineb)
    if distance_a > distance_thres or distance_b > distance_thres:
        return False
    return True