Góc giữa hai vectơ n chiều trong Python


82

Tôi cần xác định (các) góc giữa hai vectơ n-chiều trong Python. Ví dụ: đầu vào có thể là hai danh sách như sau: [1,2,3,4][6,7,8,9].


1
Đây là câu trả lời tốt nhất cho @ MK83 vì nó chính xác là biểu thức toán học theta = atan2 (u ^ v, uv). ngay cả trường hợp u = [0 0] hoặc v = [0 0] bị che bởi vì đây là thời điểm duy nhất atan2 tạo ra NaN trong các câu trả lời khác NaN sẽ được tạo ra bởi / tiêu chuẩn (u) hoặc / quy chuẩn (v)
PilouPili

Câu trả lời:


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

Lưu ý : điều này sẽ không thành công khi các vectơ có cùng hướng hoặc ngược hướng. Cách triển khai chính xác ở đây: https://stackoverflow.com/a/13849249/71522


2
Ngoài ra, nếu bạn chỉ cần cos, sin, tan của góc, chứ không cần chính góc đó, thì bạn có thể bỏ qua math.acos để nhận cosine và sử dụng tích chéo để nhận sin.
mbeckish

9
Cho rằng điều đó math.sqrt(x)tương đương x**0.5math.pow(x,y)tương đương với x**y, tôi ngạc nhiên vì chúng sống sót sau chiếc rìu dư thừa được sử dụng trong quá trình chuyển đổi Python 2.x-> 3.0. Trong thực tế, tôi thường làm những việc kiểu số này như một phần của quy trình máy tính chuyên sâu hơn và sự hỗ trợ của trình thông dịch cho '**' sẽ chuyển trực tiếp đến mã bytecode BINARY_POWER, so với tra cứu 'toán học', quyền truy cập đối với thuộc tính 'sqrt' của nó, và sau đó là mã bytecode CALL_FUNCTION chậm một cách đáng kinh ngạc, có thể tạo ra một sự cải thiện có thể đo lường được về tốc độ mà không cần mã hóa hoặc khả năng đọc.
PaulMcG

5
Như trong câu trả lời với numpy: Điều này có thể không thành công nếu lỗi làm tròn xuất hiện! Điều này có thể xảy ra đối với vectơ song song và chống song song!
BandGap

2
Lưu ý: điều này sẽ không thành công nếu các vectơ giống hệt nhau (ví dụ angle((1., 1., 1.), (1., 1., 1.)):). Xem câu trả lời của tôi cho một phiên bản đúng hơn một chút.
David Wolever 12/12/12

2
Nếu bạn đang nói về việc triển khai ở trên thì nó không thành công do lỗi làm tròn, không phải vì các vectơ song song.
Nhịp độ

150

Lưu ý : tất cả các câu trả lời khác ở đây sẽ không thành công nếu hai vectơ có cùng hướng (ví dụ (1, 0, 0), (1, 0, 0)) hoặc ngược hướng (ví dụ (-1, 0, 0), (1, 0, 0)).

Đây là một chức năng sẽ xử lý chính xác các trường hợp này:

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

Sẽ tốt hơn nếu sử dụng np.isnanthay vì một từ thư viện toán học? Về lý thuyết, chúng phải giống hệt nhau, nhưng tôi không chắc trong thực tế. Dù thế nào thì tôi cũng tưởng tượng sẽ an toàn hơn.
Hooked

2
Numpy của tôi (phiên bản == 1.12.1) có thể sử dụng arccostrực tiếp và an toàn. : Trong [140]: np.arccos (np.dot (np.array ([1,0,0]), np.array ([- 1,0,0]))) Hết [140]: 3,1415926535897931 Trong [ 141]: np.arccos (np.dot (np.array ([1,0,0]), np.array ([1,0,0]))) Hết [141]: 0,0
E

2
Trường hợp đặc biệt trong đó ít nhất một vectơ đầu vào là vectơ 0 bị bỏ qua, đây là vấn đề đối với phép chia trong unit_vector. Một khả năng là chỉ trả về vectơ đầu vào trong hàm này khi trường hợp này xảy ra.
kafman

1
angle_between ((0, 0, 0), (0, 1, 0)) sẽ cho kết quả là nan, chứ không phải 90
FabioSpaghetti

2
@kafman 0-vectors 'góc là không xác định (trong toán học). Vì vậy, thực tế là nó phát sinh lỗi là tốt.
người dùng

45

Sử dụng numpy (rất khuyến khích), bạn sẽ làm được:

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
Dòng cuối cùng có thể dẫn đến lỗi như tôi đã phát hiện ra do lỗi làm tròn. Vì vậy, nếu bạn chấm (u, u) / định mức (u) ** 2 nó kết quả trong 1,0000000002 và ArccOS sau đó thất bại (cũng 'công trình' để phản song song vectơ)
bandgap

Tôi đã thử nghiệm với u = [1,1,1]. u = [1,1,1,1] hoạt động tốt nhưng giá trị mỗi chiều thêm lợi nhuận hơi lớn hoặc smaler hơn 1 ...
bandgap

3
Lưu ý: điều này sẽ không thành công (kết quả nan) khi hướng của hai vectơ giống hệt nhau hoặc ngược chiều. Xem câu trả lời của tôi để có phiên bản chính xác hơn.
David Wolever 12/12/12

2
thêm nhận xét của neo vào điều này, dòng cuối cùng nên angle = arccos(clip(c, -1, 1))để tránh các vấn đề làm tròn. Điều này giải quyết vấn đề của @DavidWolever.
Tim Tisdall

4
Đối với những người sử dụng đoạn mã trên: clipnên được thêm vào danh sách các nhập khẩu không cần thiết.
Liam Deacon

26

Khả năng khác là sử dụng chỉ numpyvà nó cung cấp cho bạn góc nội thất

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)

và đây là đầu ra:

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
Đây là câu trả lời tốt nhất vì nó chính xác là biểu thức toán học theta = atan2 (u ^ v, uv). Và điều này không bao giờ thất bại!
PilouPili

1
Điều này dành cho 2-D. OP đã yêu cầu nD
normanius

3

Nếu bạn đang làm việc với vectơ 3D, bạn có thể thực hiện điều này một cách chính xác bằng cách sử dụng vg đai công cụ . Đó là một lớp nhẹ trên đầu trang của numpy.

import numpy as np
import vg

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

vg.angle(vec1, vec2)

Bạn cũng có thể chỉ định góc xem để tính toán góc thông qua phép chiếu:

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

Hoặc tính toán góc đã ký thông qua phép chiếu:

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

Tôi đã tạo thư viện ở lần khởi động cuối cùng của mình, nơi nó được thúc đẩy bởi những cách sử dụng như thế này: những ý tưởng đơn giản nhưng dài dòng hoặc không rõ ràng trong NumPy.


3

Giải pháp của David Wolever là tốt, nhưng

Nếu bạn muốn có các gócchữ ký, bạn phải xác định xem một cặp đã cho là thuận tay phải hay trái (xem wiki để biết thêm thông tin).

Giải pháp của tôi cho điều này là:

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

Nó không hoàn hảo vì điều này NotImplementedErrornhưng đối với trường hợp của tôi, nó hoạt động tốt. Hành vi này có thể được khắc phục (vì độ thuận tay được xác định cho bất kỳ cặp nào nhất định) nhưng tôi cần nhiều mã hơn mà tôi muốn và phải viết.


1

Xây dựng dựa trên câu trả lời tuyệt vời của sgt Pepper và thêm hỗ trợ cho các vectơ được căn chỉnh cộng với việc tăng tốc hơn 2 lần bằng Numba

@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 kết quả không có Numba

  • 359 µs ± 2,86 µs mỗi vòng (trung bình ± std. Dev. Của 7 lần chạy, mỗi lần 1000 vòng)

Và với

  • 151 µs ± 820 ns mỗi vòng (trung bình ± std. Dev. Của 7 lần chạy, 10000 vòng mỗi vòng)

1

Cách dễ dàng để tìm góc giữa hai vectơ (hoạt động đối với vectơ n chiều),

Mã 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

Sử dụng numpy và xử lý các lỗi làm tròn của 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)

Lưu ý, hàm này sẽ ném ra một ngoại lệ nếu một trong các vectơ có độ lớn bằng 0 (chia cho 0).


0

Đối với một số người có thể có (do các biến chứng SEO) đã kết thúc ở đây khi cố gắng tính toán góc giữa hai đường trong python, như trong (x0, y0), (x1, y1)các đường hình học, có giải pháp tối thiểu dưới đây (sử dụng shapelymô-đun, nhưng có thể dễ dàng sửa đổi không):

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)

Và việc sử dụng sẽ là

>>> 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.