Pythonの2つのn次元ベクトル間の角度


82

Pythonで2つのn次元ベクトル間の角度を決定する必要があります。たとえば、入力は次のような2つのリストにすることができます:[1,2,3,4][6,7,8,9]


1
これは、まさに数式theta = atan2(u ^ v、uv)であるため、@ MK83のベストアンサーです。u = [00]またはv = [0 0]の場合でも、atan2が他の回答でNaNを生成するのはこのときだけなので、NaNは/ norm(u)または/ norm(v)によって生成されます。
PilouPili 2018

回答:


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 2010年

10
それmath.sqrt(x)がと同等でx**0.5あり、math.pow(x,y)と同等であることを考えるとx**y、Python 2.x-> 3.0への移行中に使用された冗長性の斧をこれらが生き残ったことに驚いています。実際には、私は通常、より大きな計算集約型プロセスの一部としてこれらの種類の数値処理を行っており、「**」のインタープリターによるサポートは、バイトコードBINARY_POWERに直接送信されますが、「math」のルックアップ、アクセスその属性「sqrt」に加えて、痛々しいほど遅いバイトコードCALL_FUNCTIONを使用すると、コーディングや可読性を犠牲にすることなく、速度を大幅に向上させることができます。
PaulMcG 2010年

5
numpyの答えのように:丸め誤差が発生した場合、これは失敗する可能性があります!これは、平行ベクトルと反平行ベクトルで発生する可能性があります。
バンドギャップ2012年

2
注:ベクトルが同一の場合(例、)、これは失敗しangle((1., 1., 1.), (1., 1., 1.))ます。もう少し正しいバージョンについては、私の答えを参照してください。
David Wolever 2012

2
上記の実装について話している場合、ベクトルが並列であるためではなく、丸め誤差が原因で失敗します。
ペース

153

:二つのベクトルは(例、同じ方向のいずれかがある場合は、ここで他の回答のすべてが失敗する(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(バージョン== 1.12.1)はarccos直接安全に使用できます。:In [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
少なくとも1つの入力ベクトルがゼロベクトルである特殊なケースは省略されunit_vectorます。これは、の除算で問題になります。この場合、1つの可能性は、この関数で入力ベクトルを返すことです。
kafman 2017年

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 2012年

u = [1,1,1]でテストしました。uが1よりも値がわずかに大きいかsmaler戻り...罰金を動作しますが、すべてのディメンションが追加され、[1,1,1,1]を=
バンドギャップ

3
注:2つのベクトルの方向が同じまたは反対の場合、これは失敗します(yield nan)。より正しいバージョンについては、私の答えを参照してください。
David Wolever 2012

2
これにneoのコメントを追加すると、最後の行はangle = arccos(clip(c, -1, 1))丸めの問題を回避する必要があります。これにより、@ DavidWoleverの問題が解決されます。
Tim Tisdall 2014

4
上記のコードスニペットを使用している人のために:clipnumpyインポートのリストに追加する必要があります。
リアムディーコン2015年

27

他の可能性はちょうど使用することで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 2018

1
これは2D用です。OPはnDを求めていた
normanius

3

あなたは、3Dベクトルで作業している場合は、簡潔ツールベルト使ってこれを行うことができ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が、私の場合はうまく機能します。この動作は修正できますが(ハンドネスは特定のペアに対して決定されるため)、必要なコードが必要になり、作成する必要があります。


2

2つのベクトル間の角度を見つける簡単な方法(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

1

サージェントペッパーの素晴らしい答えに基づいて構築し、整列されたベクトルのサポートを追加し、さらに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ループ)

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)

ベクトルの1つがゼロの大きさ(0で除算)の場合、この関数は例外をスローすることに注意してください。


0

(SEOの複雑さのために)ここで幾何学的な線のようにPythonで2つの線の間の角度を計算しようとして終了した可能性のある少数の人のために(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.