我需要确定 Python 中两个 n 维向量之间的角度。例如,输入可以是两个列表,如下所示:[1,2,3,4]
和[6,7,8,9]
。
12 回答
注意:如果两个向量具有相同的方向(例如,,)或相反的方向(例如,,),则此处的所有其他答案(1, 0, 0)
都(1, 0, 0)
将(-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))
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
使用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
另一种可能性是使用just 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
如果您正在使用 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 中冗长或不透明的简单想法。
查找两个向量之间角度的简单方法(适用于 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
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
但就我而言,它运作良好。这种行为可以修复(因为任何给定对的手性都是确定的),但它需要更多我想要并且必须编写的代码。
基于 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 次循环)
使用 numpy 中的一些函数。
import numpy as np
def dot_product_angle(v1,v2):
if np.linalg.norm(v1) == 0 or np.linalg.norm(v2) == 0:
print("Zero magnitude vector!")
else:
vector_dot_product = np.dot(v1,v2)
arccos = np.arccos(vector_dot_product / (np.linalg.norm(v1) * np.linalg.norm(v2)))
angle = np.degrees(arccos)
return angle
return 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
使用 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),此函数将引发异常。
获得两个向量之间的角度的传统方法(即arccos(dot(u, v) / (norm(u) * norm(v)))
,如其他答案中所述)在几个极端情况下存在数值不稳定性。以下代码适用于n维和所有极端情况(它不检查零长度向量,但很容易添加,如其他一些答案所示)。请参阅下面的注释。
from numpy import arctan, pi, signbit
from numpy.linalg import norm
def angle_btw(v1, v2):
u1 = v1 / norm(v1)
u2 = v2 / norm(v2)
y = u1 - u2
x = u1 + u2
a0 = 2 * arctan(norm(y) / norm(x))
if (not signbit(a0)) or signbit(pi - a0):
return a0
elif signbit(a0):
return 0.0
else:
return pi
此代码改编自 Jeffrey Sarnoff(MIT 许可)的Julia 实现,又基于W. Kahan 教授的这些注释(第 15 页)。