使用 atan2查找两个向量之间的角度

我明白:

atan2(vector.y, vector.x) = 向量和 X 轴之间的夹角。

但是我想知道如何使用 atan2得到 两个向量之间的角度,所以我找到了这个解决方案:

atan2(vector1.y - vector2.y, vector1.x - vector2.x)

我的问题很简单:

下面两个公式会产生相同的数字吗?

  • atan2(vector1.y - vector2.y, vector1.x - vector2.x)

  • atan2(vector2.y - vector1.y, vector2.x - vector1.x)

否则: 我怎么知道哪个向量在减法中排第一?

145912 次浏览
 atan2(vector1.y - vector2.y, vector1.x - vector2.x)

差分向量(连接矢量2和矢量1)和 x 轴之间的角度, 你可能不是这个意思。

矢量1到矢量2的(定向)角度可以计算为

angle = atan2(vector2.y, vector2.x) - atan2(vector1.y, vector1.x);

你可能想把它标准化为范围[0,2 π) :

if (angle < 0) { angle += 2 * M_PI; }

- π,π ] :

if (angle > M_PI)        { angle -= 2 * M_PI; }
else if (angle <= -M_PI) { angle += 2 * M_PI; }

一个可靠的方法是使用叉积求角度的正弦,使用点积求角度的余弦,并将两者与 Atan2()函数相结合。

C#中,这是:

public struct Vector2
{
public double X, Y;


/// <summary>
/// Returns the angle between two vectos
/// </summary>
public static double GetAngle(Vector2 A, Vector2 B)
{
// |A·B| = |A| |B| COS(θ)
// |A×B| = |A| |B| SIN(θ)


return Math.Atan2(Cross(A,B), Dot(A,B));
}


public double Magnitude { get { return Math.Sqrt(Dot(this,this)); } }


public static double Dot(Vector2 A, Vector2 B)
{
return A.X*B.X+A.Y*B.Y;
}
public static double Cross(Vector2 A, Vector2 B)
{
return A.X*B.Y-A.Y*B.X;
}
}


class Program
{
static void Main(string[] args)
{
Vector2 A=new Vector2() { X=5.45, Y=1.12};
Vector2 B=new Vector2() { X=-3.86, Y=4.32 };


double angle=Vector2.GetAngle(A, B) * 180/Math.PI;
// angle = 120.16850967865749
}
}

参见上面 GeoGebra 的测试案例。

GeoGebra

没有人指出,如果你有一个矢量,并且想要找到这个矢量从 X 轴的角度,你可以利用这个事实,atan2()的参数实际上是直线的斜率,或者(delta Y/delta X)。如果你知道斜率,你可以这样做:

给定:

A = 要确定的矢量/直线的角度(从 X 轴)。

M = 向量/直线的有符号斜率。

然后:

A = atan2(m,1)

非常有用!

我认为这里有一个更好的公式: Http://www.mathworks.com/matlabcentral/answers/16243-angle-between-two-vectors-in-3d

angle = atan2(norm(cross(a,b)), dot(a,b))

这个公式适用于二维或三维空间。 对于2维,这个公式简化为上述公式。

如果你关心小角度的准确性,你可以这样做:

Angle = 2 * atan2(| | | | b | | a-| a | | b | | | | b | a + | a | | b | |)

其中“ | |”表示绝对值,即“向量的长度”。参见 https://math.stackexchange.com/questions/1143354/numerically-stable-method-for-angle-between-3d-vectors/1782769

然而,这样做的缺点是,在二维空间中,它失去了角度的符号。

不能使用 atan2来计算两个向量之间的夹角。如果您只想要最快的方式,您可以使用 dot(v1, v2)=|v1|*|v2|*cos A 得到

A = Math.acos( dot(v1, v2)/(v1.length()*v2.length()) );

作为@martin-r 答案的补充,我们应该注意到,可以使用求和/差分公式来求正弦。

angle = atan2(vec2.y, vec2.x) - atan2(vec1.y, vec1.x);
angle = -atan2(vec1.x * vec2.y - vec1.y * vec2.x, dot(vec1, vec2))
where dot = vec1.x * vec2.x  + vec1.y * vec2.y
  • 警告1 : 确保角度保持在-pi... + pi 之内
  • 警告2 : 当矢量变得非常相似时,你可能会在第一个参数中灭绝,导致数值不准确
angle(vector.b,vector.a)=pi/2*((1+sgn(xa))*(1-sgn(ya^2))-(1+sgn(xb))*(1-sgn(yb^2)))


+pi/4*((2+sgn(xa))*sgn(ya)-(2+sgn(xb))*sgn(yb))


+sgn(xa*ya)*atan((abs(xa)-abs(ya))/(abs(xa)+abs(ya)))


-sgn(xb*yb)*atan((abs(xb)-abs(yb))/(abs(xb)+abs(yb)))

Xb,yb 和 xa,ya 是两个向量的坐标

我发送的 angle(vector.b,vector.a)公式给出了结果

以及任何坐标 xa,yaxb,yb

对于坐标 xa=ya=0或者xb=yb=0未定义

角度可以大于或小于 pi,可以是正的

或者阴性。

下面是 Python 中的一个小程序,它使用向量之间的角度来确定一个点是在某个多边形内部还是外部

import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from shapely.geometry import Point, Polygon
from pprint import pprint


# Plot variables
x_min, x_max = -6, 12
y_min, y_max = -3, 8
tick_interval = 1
FIG_SIZE = (10, 10)
DELTA_ERROR = 0.00001
IN_BOX_COLOR = 'yellow'
OUT_BOX_COLOR = 'black'




def angle_between(v1, v2):
""" Returns the angle in radians between vectors 'v1' and 'v2'
The sign of the angle is dependent on the order of v1 and v2
so acos(norm(dot(v1, v2))) does not work and atan2 has to be used, see:
https://stackoverflow.com/questions/21483999/using-atan2-to-find-angle-between-two-vectors
"""
arg1 = np.cross(v1, v2)
arg2 = np.dot(v1, v2)
angle = np.arctan2(arg1, arg2)
return angle




def point_inside(point, border):
""" Returns True if point is inside border polygon and False if not
Arguments:
:point: x, y in shapely.geometry.Point type
:border: [x1 y1, x2 y2, ... , xn yn] in shapely.geomettry.Polygon type
"""
assert len(border.exterior.coords) > 2,\
'number of points in the polygon must be > 2'


point = np.array(point)
side1 = np.array(border.exterior.coords[0]) - point
sum_angles = 0
for border_point in border.exterior.coords[1:]:
side2 = np.array(border_point) - point
angle = angle_between(side1, side2)
sum_angles += angle
side1 = side2


# if wn is 1 then the point is inside
wn = sum_angles / 2 / np.pi
if abs(wn - 1) < DELTA_ERROR:
return True
else:
return False




class MainMap():


@classmethod
def settings(cls, fig_size):
# set the plot outline, including axes going through the origin
cls.fig, cls.ax = plt.subplots(figsize=fig_size)
cls.ax.set_xlim(-x_min, x_max)
cls.ax.set_ylim(-y_min, y_max)
cls.ax.set_aspect(1)
tick_range_x = np.arange(round(x_min + (10*(x_max - x_min) % tick_interval)/10, 1),
x_max + 0.1, step=tick_interval)
tick_range_y = np.arange(round(y_min + (10*(y_max - y_min) % tick_interval)/10, 1),
y_max + 0.1, step=tick_interval)
cls.ax.set_xticks(tick_range_x)
cls.ax.set_yticks(tick_range_y)
cls.ax.tick_params(axis='both', which='major', labelsize=6)
cls.ax.spines['left'].set_position('zero')
cls.ax.spines['right'].set_color('none')
cls.ax.spines['bottom'].set_position('zero')
cls.ax.spines['top'].set_color('none')


@classmethod
def get_ax(cls):
return cls.ax


@staticmethod
def plot():
plt.tight_layout()
plt.show()




class PlotPointandRectangle(MainMap):


def __init__(self, start_point, rectangle_polygon, tolerance=0):


self.current_object = None
self.currently_dragging = False
self.fig.canvas.mpl_connect('key_press_event', self.on_key)
self.plot_types = ['o', 'o-']
self.plot_type = 1
self.rectangle = rectangle_polygon


# define a point that can be moved around
self.point = patches.Circle((start_point.x, start_point.y), 0.10,
alpha=1)
if point_inside(start_point, self.rectangle):
_color = IN_BOX_COLOR
else:
_color = OUT_BOX_COLOR
self.point.set_color(_color)
self.ax.add_patch(self.point)
self.point.set_picker(tolerance)
cv_point = self.point.figure.canvas
cv_point.mpl_connect('button_release_event', self.on_release)
cv_point.mpl_connect('pick_event', self.on_pick)
cv_point.mpl_connect('motion_notify_event', self.on_motion)


self.plot_rectangle()


def plot_rectangle(self):
x = [point[0] for point in self.rectangle.exterior.coords]
y = [point[1] for point in self.rectangle.exterior.coords]
# y = self.rectangle.y
self.rectangle_plot, = self.ax.plot(x, y,
self.plot_types[self.plot_type], color='r', lw=0.4, markersize=2)


def on_release(self, event):
self.current_object = None
self.currently_dragging = False


def on_pick(self, event):
self.currently_dragging = True
self.current_object = event.artist


def on_motion(self, event):
if not self.currently_dragging:
return
if self.current_object == None:
return


point = Point(event.xdata, event.ydata)
self.current_object.center = point.x, point.y
if point_inside(point, self.rectangle):
_color = IN_BOX_COLOR
else:
_color = OUT_BOX_COLOR
self.current_object.set_color(_color)


self.point.figure.canvas.draw()


def remove_rectangle_from_plot(self):
try:
self.rectangle_plot.remove()
except ValueError:
pass


def on_key(self, event):
# with 'space' toggle between just points or points connected with
# lines
if event.key == ' ':
self.plot_type = (self.plot_type + 1) % 2
self.remove_rectangle_from_plot()
self.plot_rectangle()
self.point.figure.canvas.draw()




def main(start_point, rectangle):


MainMap.settings(FIG_SIZE)
plt_me = PlotPointandRectangle(start_point, rectangle)  #pylint: disable=unused-variable
MainMap.plot()


if __name__ == "__main__":
try:
start_point = Point([float(val) for val in sys.argv[1].split()])
except IndexError:
start_point= Point(0, 0)


border_points = [(-2, -2),
(1, 1),
(3, -1),
(3, 3.5),
(4, 1),
(5, 1),
(4, 3.5),
(5, 6),
(3, 4),
(3, 5),
(-0.5, 1),
(-3, 1),
(-1, -0.5),
]


border_points_polygon = Polygon(border_points)
main(start_point, border_points_polygon)