numpy 测试两个线段是否大致共线(在同一条线上)

9bfwbjaz  于 2023-08-05  发布在  其他
关注(0)|答案(4)|浏览(108)

我想使用numpy.cross测试两个线段是否大致共线(在同一条线上)。我有每段的米坐标。

import numpy as np

segment_A_x1 = -8020537.5158307655
segment_A_y1 = 5674541.918222183
segment_A_x2 = -8020547.42095263
segment_A_y2 = 5674500.781350276

segment_B_x1 = -8020556.569040865
segment_B_y1 = 5674462.788207927
segment_B_x2 = -8020594.740831952
segment_B_y2 = 5674328.095911447

a = np.array([[segment_A_x1, segment_A_y1], [segment_A_x2, segment_A_y2]])
b = np.array([[segment_B_x1, segment_B_y1], [segment_B_x2, segment_B_y2]])
crossproduct = np.cross(a, b)

>>>array([7.42783487e+08, 1.65354844e+09])

字符串
crossproduct值相当高,即使我会说这两个部分大致共线。为什么?为什么?
如何确定线段是否与crossproduct结果共线?
是否有可能使用以米为单位的公差来判断线段是否大致共线?

ffdz8vbo

ffdz8vbo1#

两个向量abcross-product可以定义为:其中θ是两个向量之间的Angular 。问题是,即使θ很小,两个范数的乘积也可能很大,并且可能导致很大的叉积。这是你的案子:

na = np.linalg.norm(a, axis=1)  # array([9824940.10284589, 9824924.42969893])
nb = np.linalg.norm(b, axis=1)  # array([9824909.95439353, 9824863.32407282])
nprod = na * nb                 # array([9.65291518e+13, 9.65285397e+13])
sinθ = crossproduct / nprod     # array([7.69491364e-06, 1.71301508e-05])

字符串
实际上,sinθ非常小。这意味着θ也非常小(实际上,θ大致等于sinθ的值,因为sin(θ)θ = 1的导数为1)。
如何确定线段是否与叉积结果共线?
您可以根据上面的代码计算值np.abs(sinθ),设置任意阈值并检查该值是否小于所选阈值。阈值的最佳值取决于您的实际应用程序/输入(例如:统计噪声、输入精度、计算精度等)。如果你不知道该使用什么,你可以从1e-4开始。
是否有可能使用以米为单位的公差来判断线段是否大致共线?
请注意,假设输入向量以米为单位,则基于上述公式,叉积的值以平方米为单位。因此,以米为单位的公差没有多大意义。更一般地说,设置一个绝对的公差肯定是一个坏主意.上述解决方案使用相对于输入向量的容差来工作。

64jmpszr

64jmpszr2#

共线性有两个方面:段之间的Angular 和接近度。您可以分别测试这些方面,并根据其中任何一个来简化检查。我提出了一个接近度检查,完全消除了Angular 检查的需要。以下显示的Angular 检查是出于传统原因,因为它可能对其他原因有用。

**Angular **

叉积只有在3D中才能真正定义。点积到处都有定义。两个向量之间的Angular 定义为它们的单位向量的点积的反余弦。这适用于2D和10 D。
您可以将两个从原点开始的向量定义为

a = np.array([segment_A_x2 - segment_A_x1,
              segment_A_y2 - segment_A_y1])
b = np.array([segment_B_x2 - segment_B_x1,
              segment_B_y2 - segment_B_y1])

a /= np.linalg.norm(a)
b /= np.linalg.norm(b)

angle = np.arccos(a.dot(b))

字符串
angle将在-np.pinp.pi之间运行.接近pi的Angular 表示反平行线,而接近零的Angular 表示平行线。您可以实现如下测试

if abs(angle) < angular_threshold or abs(angle) > np.pi - angular_threshold:
   ...


如果您发现自己经常进行这种计算,则可以完全跳过arccos,从而保存一些周期。当Angular 变为0或pi时,点积分别变为+1或-1。这意味着您只需要预先计算dot_threshold = np.cos(angular_threshold)一次:

if abs(a.dot(b)) >= dot_threshold:

接近度

若要测试接近度,您需要定义如何测量距离。对于完全平行的线,这是明确的:这两条线必须在彼此的distance_threshold内。
对于不完全平行的线,可以执行类似的操作:在一个线段上没有点可以比另一个线段的线的distance_threshold更远。使用此定义,您可以将单独Angular 计算的需求直接纳入计算中。
请参考下图:
x1c 0d1x的数据
您必须对照其他缐检查所有四个端点。如果您选择这样做,您可以根据距离的差异计算Angular ,并可能根据线段的长度将缩放系数套用至临界值。
您也可以使用点积来计算距离:

def dist(p, p1, p2):
    s = p2 - p1
    q = p1 + (p - p1).dot(s) / s.dot(s) * s
    return np.linalg.norm(p - q)

a1 = np.array([segment_A_x1, segment_A_y1])
a2 = np.array([segment_A_x2, segment_A_y2])
b1 = np.array([segment_B_x1, segment_B_y1])
b2 = np.array([segment_B_x2, segment_B_y2])

dists = np.array([[dist(a1, b1, b2), dist(b1, a1, a2)],
                  [dist(a2, b1, b2), dist(b2, a1, a2)]])


在我创建的实用程序库haggis中有一个更健壮的dist版本。可以按如下方式使用haggis.math.segment_distance

dists = segment_distance([[a1, b1], [a2, b2]], # From point
                         [b1, a1],             # Segment start
                         [b2, a2],             # Segment end
                         axis=-1,              # Axis containing vectors
                         segment=False)        # Distance to entire line


输入一起广播,axis应用于广播的形状,因此您不需要重复两次端点。
最简单的测试版本是直接约束距离:

if (dists < distance_threshold).all():
    ...


可以通过按线段长度缩放来说明Angular 。100倍长的线段可以偏离另一线段的直线100倍,但仍被视为共线。在这种情况下,将distance_threshold定义为一个 unit 向量与另一个向量的直线之间的最远距离。该数字必须小于1才有意义:

scale = np.linalg.norm([a2 - a1, b2 - b1], axis=-1)
if (dists < distance_threshold * scale).all():
    ...


这个版本预先假定了我们在两个版本的计算中对dists所施加的形状,因为scale的元素数与dists的列数一样多。
也考虑线段之间的距离的更复杂的方案也是可能的。这种对邻近性的定义留给读者作为练习。

2nc8po8w

2nc8po8w3#

您的方法的问题在于叉积值取决于测量尺度。
也许共线性的最直观的度量是线段之间的Angular 。让我们计算一下:

import math

def slope(line): 
    """Line slope given two points"""
    p1, p2 = line
    return (p2[1] - p1[1]) / (p2[0] - p1[0])

def angle(s1, s2): 
    """Angle between two lines given their slopes"""
    return math.degrees(math.atan((s2 - s1) / (1 + (s2 * s1))))

ang = angle(slope(b), slope(a))
print('Angle in degrees = ', ang)

个字符
我使用了安德森Oliveira的answer

nbysray5

nbysray54#

如果您想以有效的方式查看两个向量是否几乎处于同一方向,您还可以尝试以下使用numpy is_close进行接近检查的方法,这允许您进一步配置要使用的公差类型以及它的紧密程度。

import numpy as np

# define your segement start and end coordinates here
# segment1_start = ...
# segemnt2_start = ...
# segemnt1_end = ...
# segment1_end = ...

# we use the convention of end-start
# modify this to suit your conventions
a = segment1_end - segment1_start
b = segment2_end - segment2_start

def is_parallel_to_axes(a, b):
    vector1 = np.asarray(a)
    vector2 = np.asarray(b)
    vector1_norm = np.linalg.norm(vector1)
    vector2_norm = np.linalg.norm(vector2)
    dot_prod = np.dot(vector1, vector2)

    # Check if the vector is parallel to any of the axes
    is_parallel = np.isclose(dot_prod, vector1_norm * vector2_norm)
    
    return is_parallel

字符串

相关问题