Мне нужно определить угол (s) между двумя n-мерными векторами в Python. Например, вход может состоять из двух списков: [1,2,3,4]
и [6,7,8,9]
.
Углы между двумя n-мерными векторами в Python
Ответ 1
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)))
Примечание: это не удастся, если векторы имеют одинаковое или противоположное направление. Правильная реализация находится здесь: fooobar.com/questions/141806/...
Ответ 2
Примечание: все остальные ответы здесь потерпят неудачу, если два вектора имеют одно и то же направление (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))
Ответ 3
Используя 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
Ответ 4
Другая возможность использует только 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
Ответ 5
Если вы работаете с трехмерными векторами, вы можете сделать это кратко, используя 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.
Ответ 6
Для тех немногих, кто мог (из-за сложностей с 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
Ответ 7
Использование 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).
Ответ 8
Решение Дэвида Вулевер хорошо, но
Если вы хотите иметь углы со знаком, вы должны определить, является ли данная пара правой или левой рукой (см. Вики для получения дополнительной информации).
Мое решение для этого:
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
но для моего случая это работает хорошо. Это поведение может быть исправлено (потому что для любой данной пары определяется handness), но требуется больше кода, который я хочу и должен написать.