파이썬에서 두 개의 n 차원 벡터 사이의 각도


82

파이썬에서 두 개의 n 차원 벡터 사이의 각도를 결정해야합니다. 예를 들어 입력은 다음과 같은 두 개의 목록이 될 수 있습니다. [1,2,3,4][6,7,8,9].


1
이것은 정확하게 수학적 표현 인 theta = atan2 (u ^ v, uv)이기 때문에 @ MK83의 가장 좋은 대답입니다. u = [0 0] 또는 v = [0 0] 인 경우에도 atan2가 NaN을 생성 할 시간이기 때문에 다른 답변에서 NaN은 / norm (u) 또는 / norm (v)에 의해 생성됩니다.
PilouPili

답변:


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 of angle 만 필요한 경우 math.acos를 건너 뛰어 코사인을 얻고 외적을 사용하여 사인을 얻을 수 있습니다.
mbeckish

9
주어진 math.sqrt(x)에 해당 x**0.5하고 math.pow(x,y)와 같습니다 x**y, 나는 이러한 중복 도끼 파이썬 2.x-> 3.0 전환하는 동안 녀석 살아 놀라게하고있다. 실제로 저는 일반적으로 더 큰 계산 집약적 프로세스의 일부로 이러한 종류의 숫자 작업을 수행하고 있으며, '**'에 대한 인터프리터의 지원은 바이트 코드 BINARY_POWER로 직접 이동하는 반면 '수학'조회, 액세스 속성 'sqrt'와 고통스럽게 느린 바이트 코드 CALL_FUNCTION을 사용하면 코딩이나 가독성 비용없이 측정 가능한 속도 향상을 얻을 수 있습니다.
PaulMcG

5
numpy의 대답에서와 같이 반올림 오류가 발생하면 실패 할 수 있습니다! 이것은 병렬 및 반 평행 벡터에서 발생할 수 있습니다!
BandGap

2
참고 : 벡터가 동일하면 (예 :) 실패 합니다 angle((1., 1., 1.), (1., 1., 1.)). 약간 더 정확한 버전은 내 대답을 참조하십시오.
David Wolever

2
위의 구현에 대해 이야기하고 있다면 벡터가 평행하기 때문이 아니라 반올림 오류로 인해 실패합니다.
Pace

150

참고 : 두 벡터가 동일한 방향 (예 : (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))

np.isnan수학 라이브러리에있는 것 대신 사용하는 것이 더 낫지 않을까요 ? 이론적으로는 동일해야하지만 실제로는 확실하지 않습니다. 어느 쪽이든 더 안전하다고 생각합니다.
Hooked

2
내 numpy (version == 1.12.1)는 arccos직접 안전하게 사용할 수 있습니다 . : 입력 [140] : np.arccos (np.dot (np.array ([1,0,0]), np.array ([-1,0,0]))) 출력 [140] : 3.1415926535897931 입력 [ 141] : np.arccos (np.dot (np.array ([1,0,0]), np.array ([1,0,0]))) Out [141] : 0.0
ene

2
하나 이상의 입력 벡터가 0 벡터 인 특수한 경우는 생략되어 unit_vector. 한 가지 가능성은이 경우이 함수에서 입력 벡터를 반환하는 것입니다.
kafman

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
마지막 줄은 반올림 오류로 인해 오류가 발생할 수 있습니다. 따라서 dot (u, u) / norm (u) ** 2를 사용하면 1.0000000002가되고 arccos는 실패합니다 (역 평행 벡터에도 '작동')
BandGap

u = [1,1,1]로 테스트했습니다. u = [1,1,1,1]은 잘 작동하지만 추가 된 모든 차원은 1보다 약간 크거나 작은 값을 반환합니다 ...
BandGap

3
참고 : 두 벡터의 방향이 동일하거나 반대이면 실패합니다 (yield nan). 더 정확한 버전은 내 대답을 참조하십시오.
David Wolever 2012

2
이것에 neo의 코멘트를 추가하면 마지막 줄은 angle = arccos(clip(c, -1, 1))반올림 문제를 피하는 것입니다. 이것은 @DavidWolever의 문제를 해결합니다.
Tim Tisdall

4
위의 코드 스 니펫을 사용하는 사람들을 위해 : clipnumpy 가져 오기 목록에 추가해야합니다.
Liam Deacon

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

1
이것은 2D 용입니다. 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의 솔루션 은 좋지만

부호있는 각도 를 원한다면 주어진 쌍이 오른 손잡이인지 왼손잡이인지 결정해야합니다 ( 추가 정보는 위키 참조 ).

이에 대한 내 해결책은 다음과 같습니다.

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으로 나누기).


0

(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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.