Углы между двумя n-мерными векторами в Python - PullRequest
59 голосов
/ 13 мая 2010

Мне нужно определить угол (ы) между двумя n-мерными векторами в Python. Например, входными данными могут быть два списка, подобные следующему: [1,2,3,4] и [6,7,8,9].

Ответы [ 8 ]

108 голосов
/ 13 декабря 2012

Примечание : все остальные ответы здесь потерпят неудачу, если два вектора имеют одинаковое направление (например, (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))
52 голосов
/ 13 мая 2010
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

36 голосов
/ 13 мая 2010

Используя 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
15 голосов
/ 01 февраля 2016

Другая возможность использует просто 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
1 голос
/ 21 апреля 2019

Для тех немногих, кто мог (из-за сложностей с SEO) закончить здесь, пытаясь вычислить угол между двумя линиями в питоне, как в (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
1 голос
/ 04 апреля 2019

Если вы работаете с трехмерными векторами, вы можете сделать это кратко, используя пояс для инструментов 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.

0 голосов
/ 09 июня 2019

Решение Дэвида Волвера хорошо, но

Если вы хотите иметь углов со знаком , вы должны определить, является ли данная пара правой или левой рукой (см. 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, но для моего случая это работает хорошо. Это поведение может быть исправлено (потому что для каждой данной пары определяется handness), но требуется больше кода, который я хочу и должен написать.

0 голосов
/ 13 марта 2013

Использование 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).

...