web-dev-qa-db-fra.com

Angles entre deux vecteurs de dimension n dans Python

Je dois déterminer l'angle ou les angles entre deux vecteurs à n dimensions en Python. Par exemple, l'entrée peut être deux listes comme celle-ci: [1,2,3,4] et [6,7,8,9].

62
Peter
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)))

Remarque : cela échouera lorsque les vecteurs auront la même direction ou la direction opposée. La mise en œuvre correcte est la suivante: https://stackoverflow.com/a/13849249/71522

55
Alex Martelli

Note: toutes les autres réponses ici échoueront si les deux vecteurs ont la même direction (ex, (1, 0, 0), (1, 0, 0)) ou des directions opposées (ex, (-1, 0, 0), (1, 0, 0)).

Voici une fonction qui gérera correctement ces cas:

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))
114
David Wolever

En utilisant numpy (hautement recommandé), vous feriez:

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
37
Olivier Verdier

L’autre possibilité est d’utiliser numpy et cela vous donne l’angle intérieur

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)

et voici la sortie:

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
19
MK83

Si vous travaillez avec des vecteurs 3D, vous pouvez le faire de manière concise en utilisant la palette d'outils vg . C'est une couche légère au-dessus de Numpy.

import numpy as np
import vg

vec1 = np.array([1, 2, 3])
vec2 = np.array([7, 8, 9])

vg.angle(vec1, vec2)

Vous pouvez également spécifier un angle de vue pour le calculer via projection:

vg.angle(vec1, vec2, look=vg.basis.z)

Ou calculez l'angle signé par projection:

vg.signed_angle(vec1, vec2, look=vg.basis.z)

J'ai créé la bibliothèque lors de mon dernier démarrage, où elle était motivée par des utilisations telles que celle-ci: des idées simples qui sont verbeuses ou opaques dans NumPy.

1
paulmelnikow

Pour les rares qui peuvent avoir (en raison de complications SEO) terminé ici en essayant de calculer l'angle entre deux lignes en python, comme dans (x0, y0), (x1, y1) lignes géométriques, voici la solution ci-dessous minimale (utilise le module shapely, mais peut être facilement modifiée):

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)

Et l'utilisation serait

>>> 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
Julio Cezar Silva

la solution de David Wolever c'est bien, mais

Si vous voulez avoir des angles signés , vous devez déterminer si une paire donnée est droitier ou gaucher (voir wiki pour plus d'informations. Info).

Ma solution pour cela est:

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))

Ce n'est pas parfait pour cette raison NotImplementedError mais, dans mon cas, cela fonctionne bien. Ce comportement peut être corrigé (la cause est déterminée pour une paire donnée) mais nécessite plus de code que je souhaite et que je dois écrire.

0
sgt pepper

En utilisant numpy et en prenant soin des erreurs d'arrondi de 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)

Notez que cette fonction lève une exception si l'un des vecteurs a une magnitude nulle (division par 0).

0
Pace