Python中两个n维向量之间的角度


82

我需要确定Python中两个n维向量之间的角度。例如,输入可以是两个列表,如下所示:[1,2,3,4][6,7,8,9]


1
最好的答案是@ MK83,因为它正是数学表达式theta = atan2(u ^ v,uv)。即使涉及u = [0 0]或v = [0 0]的情况,因为这只是atan2会在其他答案中生成NaN的时间,而NaN将由/ norm(u)或/ norm(v)生成
PilouPili '18

Answers:


66
import math

def dotproduct(v1, v2):
  return sum((a*b) for a, b in zip(v1, v2))

def length(v):
  return math.sqrt(dotproduct(v, v))

def angle(v1, v2):
  return math.acos(dotproduct(v1, v2) / (length(v1) * length(v2)))

注意:当向量的方向相同或相反时,这将失败。正确的实现在这里:https : //stackoverflow.com/a/13849249/71522


2
另外,如果只需要cos,sin,tan角而不是角度本身,则可以跳过math.acos来获取余弦,而使用叉积来获取正弦。
mbeckish

9
给定math.sqrt(x)等于x**0.5math.pow(x,y)等于x**y,我很惊讶这些在Python 2.x-> 3.0过渡期间使用的冗余斧头幸免于难。实际上,我通常是在较大的计算密集型过程中执行这些数字操作,而解释器对“ **”的支持直接进入了字节码BINARY_POWER,而不是对“ math”的查找,访问赋予其属性“ sqrt”,然后痛苦的缓慢字节码CALL_FUNCTION可以在不增加编码或可读性的情况下显着提高速度。
PaulMcG 2010年

5
就像在numpy的答案中一样:如果舍入错误起作用,这可能会失败!对于并行和反并行向量可能会发生这种情况!
BandGap 2012年

2
注意:如果向量相同(例如,),则此操作将失败angle((1., 1., 1.), (1., 1., 1.))。请参阅我的答案以获取更正确的版本。
David Wolever

2
如果您在谈论上面的实现,那么它会由于舍入错误而失败,而不是因为向量是并行的。
佩斯

150

注意:如果两个向量的方向相同(ex (1, 0, 0),,(1, 0, 0))或方向相反(ex (-1, 0, 0),,(1, 0, 0)),则此处的所有其他答案都将失败。

这是一个可以正确处理这些情况的函数:

import numpy as np

def unit_vector(vector):
    """ Returns the unit vector of the vector.  """
    return vector / np.linalg.norm(vector)

def angle_between(v1, v2):
    """ Returns the angle in radians between vectors 'v1' and 'v2'::

            >>> angle_between((1, 0, 0), (0, 1, 0))
            1.5707963267948966
            >>> angle_between((1, 0, 0), (1, 0, 0))
            0.0
            >>> angle_between((1, 0, 0), (-1, 0, 0))
            3.141592653589793
    """
    v1_u = unit_vector(v1)
    v2_u = unit_vector(v2)
    return np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0))

np.isnan而不是数学库中的那个不是更好吗?从理论上讲,它们应该是相同的,但实际上我不确定。无论哪种方式,我都认为它会更安全。
钩上2013年

2
我的numpy(version == 1.12.1)可以arccos直接安全使用。:在[140]中:np.arccos(np.dot(np.array([1,0,0]),np.array([-1,0,0])))Out [140]:3.1415926535897931 In [ 141]:np.arccos(np.dot(np.array([1,0,0]),np.array([1,0,0])))Out [141]:0.0
ene

2
省略了至少一个输入向量为零向量的特殊情况,这对于in的除法是有问题的unit_vector。在这种情况下,一种可能性是仅在此函数中返回输入向量。
卡夫曼

1
angle_between(((0,0,0),(0,1,0))将给出nan而不是90
FabioSpaghetti

2
@kafman 0向量的角度未定义(在数学中)。因此,它引发错误的事实很好。
用户

45

使用numpy(强烈建议),您可以执行以下操作:

from numpy import (array, dot, arccos, clip)
from numpy.linalg import norm

u = array([1.,2,3,4])
v = ...
c = dot(u,v)/norm(u)/norm(v) # -> cosine of the angle
angle = arccos(clip(c, -1, 1)) # if you really want the angle

3
我发现由于舍入错误,最后一行可能会导致错误。因此,如果您对(u,u)/ norm(u)** 2进行点运算,则结果为1.0000000002,然后arccos失败(对反平行向量也有效)
BandGap 2012年

我已经用u = [1,1,1]进行了测试。u = [1,1,1,1]可以正常工作,但是每个添加的维返回的值都比1稍大或更小...
BandGap

3
注意:当两个向量的方向相同或相反时,这将失败(屈服nan)。请参阅我的答案以获得更正确的版本。
David Wolever

2
在此添加neo的评论,最后一行应该是angle = arccos(clip(c, -1, 1))避免舍入问题。这解决了@DavidWolever的问题。
Tim Tisdall 2014年

4
对于使用上述代码段的人员:clip应该添加到numpy导入列表中。
Liam Deacon 2015年

26

另一种可能性是使用公正numpy,它给你内角

import numpy as np

p0 = [3.5, 6.7]
p1 = [7.9, 8.4]
p2 = [10.8, 4.8]

''' 
compute angle (in degrees) for p0p1p2 corner
Inputs:
    p0,p1,p2 - points in the form of [x,y]
'''

v0 = np.array(p0) - np.array(p1)
v1 = np.array(p2) - np.array(p1)

angle = np.math.atan2(np.linalg.det([v0,v1]),np.dot(v0,v1))
print np.degrees(angle)

这是输出:

In [2]: p0, p1, p2 = [3.5, 6.7], [7.9, 8.4], [10.8, 4.8]

In [3]: v0 = np.array(p0) - np.array(p1)

In [4]: v1 = np.array(p2) - np.array(p1)

In [5]: v0
Out[5]: array([-4.4, -1.7])

In [6]: v1
Out[6]: array([ 2.9, -3.6])

In [7]: angle = np.math.atan2(np.linalg.det([v0,v1]),np.dot(v0,v1))

In [8]: angle
Out[8]: 1.8802197318858924

In [9]: np.degrees(angle)
Out[9]: 107.72865519428085

6
这是最佳答案,因为它恰好是数学表达式theta = atan2(u ^ v,uv)。这永远不会失败!
PilouPili '18

1
这是二维的。OP要求获得nD
normanius

3

如果您正在使用3D向量,则可以使用toolbelt vg简洁地执行此操作。它是numpy之上的一个浅层。

import numpy as np
import vg

vec1 = np.array([1, 2, 3])
vec2 = np.array([7, 8, 9])

vg.angle(vec1, vec2)

您还可以指定视角以通过投影计算角度:

vg.angle(vec1, vec2, look=vg.basis.z)

或通过投影计算符号角:

vg.signed_angle(vec1, vec2, look=vg.basis.z)

我在上次启动时创建了该库,该库的灵感来自于以下用途:在NumPy中冗长或不透明的简单想法。


3

David Wolever的解决方案很好,但是

如果要具有正负号,则必须确定给定的一对是右手还是左手(有关更多信息,请参见Wiki)。

我对此的解决方案是:

def unit_vector(vector):
    """ Returns the unit vector of the vector"""
    return vector / np.linalg.norm(vector)

def angle(vector1, vector2):
    """ Returns the angle in radians between given vectors"""
    v1_u = unit_vector(vector1)
    v2_u = unit_vector(vector2)
    minor = np.linalg.det(
        np.stack((v1_u[-2:], v2_u[-2:]))
    )
    if minor == 0:
        raise NotImplementedError('Too odd vectors =(')
    return np.sign(minor) * np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0))

因此,它并不完美,NotImplementedError但就我而言,它效果很好。此行为可能是固定的(因为是否为任何给定的对确定了惯用性),但是它需要我要写的更多代码。


1

基于sgt pepper的出色答案,并增加了对对齐矢量的支持,并且使用Numba的速度提高了2倍以上

@njit(cache=True, nogil=True)
def angle(vector1, vector2):
    """ Returns the angle in radians between given vectors"""
    v1_u = unit_vector(vector1)
    v2_u = unit_vector(vector2)
    minor = np.linalg.det(
        np.stack((v1_u[-2:], v2_u[-2:]))
    )
    if minor == 0:
        sign = 1
    else:
        sign = -np.sign(minor)
    dot_p = np.dot(v1_u, v2_u)
    dot_p = min(max(dot_p, -1.0), 1.0)
    return sign * np.arccos(dot_p)

@njit(cache=True, nogil=True)
def unit_vector(vector):
    """ Returns the unit vector of the vector.  """
    return vector / np.linalg.norm(vector)

def test_angle():
    def npf(x):
        return np.array(x, dtype=float)
    assert np.isclose(angle(npf((1, 1)), npf((1,  0))),  pi / 4)
    assert np.isclose(angle(npf((1, 0)), npf((1,  1))), -pi / 4)
    assert np.isclose(angle(npf((0, 1)), npf((1,  0))),  pi / 2)
    assert np.isclose(angle(npf((1, 0)), npf((0,  1))), -pi / 2)
    assert np.isclose(angle(npf((1, 0)), npf((1,  0))),  0)
    assert np.isclose(angle(npf((1, 0)), npf((-1, 0))),  pi)

%%timeit 没有Numba的结果

  • 每个循环359 µs±2.86 µs(平均±标准偏差,共运行7次,每个循环1000个)

  • 每个回路151 µs±820 ns(平均±标准偏差,共运行7次,每个回路10000个)

1

查找两个向量之间角度的简单方法(适用于n维向量),

Python代码:

import numpy as np

vector1 = [1,0,0]
vector2 = [0,1,0]

unit_vector1 = vector1 / np.linalg.norm(vector1)
unit_vector2 = vector2 / np.linalg.norm(vector2)

dot_product = np.dot(unit_vector1, unit_vector2)

angle = np.arccos(dot_product) #angle in radian

0

使用numpy并注意BandGap的舍入错误:

from numpy.linalg import norm
from numpy import dot
import math

def angle_between(a,b):
  arccosInput = dot(a,b)/norm(a)/norm(b)
  arccosInput = 1.0 if arccosInput > 1.0 else arccosInput
  arccosInput = -1.0 if arccosInput < -1.0 else arccosInput
  return math.acos(arccosInput)

请注意,如果向量之一的幅度为零(除以0),则此函数将引发异常。


0

对于一些可能由于SEO复杂性而在这里结束尝试在python中计算两条线之间的角度(如(x0, y0), (x1, y1)几何线)的人,下面是最小的解决方案(使用该shapely模块,但可以很容易地修改为:)

from shapely.geometry import LineString
import numpy as np

ninety_degrees_rad = 90.0 * np.pi / 180.0

def angle_between(line1, line2):
    coords_1 = line1.coords
    coords_2 = line2.coords

    line1_vertical = (coords_1[1][0] - coords_1[0][0]) == 0.0
    line2_vertical = (coords_2[1][0] - coords_2[0][0]) == 0.0

    # Vertical lines have undefined slope, but we know their angle in rads is = 90° * π/180
    if line1_vertical and line2_vertical:
        # Perpendicular vertical lines
        return 0.0
    if line1_vertical or line2_vertical:
        # 90° - angle of non-vertical line
        non_vertical_line = line2 if line1_vertical else line1
        return abs((90.0 * np.pi / 180.0) - np.arctan(slope(non_vertical_line)))

    m1 = slope(line1)
    m2 = slope(line2)

    return np.arctan((m1 - m2)/(1 + m1*m2))

def slope(line):
    # Assignments made purely for readability. One could opt to just one-line return them
    x0 = line.coords[0][0]
    y0 = line.coords[0][1]
    x1 = line.coords[1][0]
    y1 = line.coords[1][1]
    return (y1 - y0) / (x1 - x0)

而使用将是

>>> line1 = LineString([(0, 0), (0, 1)]) # vertical
>>> line2 = LineString([(0, 0), (1, 0)]) # horizontal
>>> angle_between(line1, line2)
1.5707963267948966
>>> np.degrees(angle_between(line1, line2))
90.0
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.