用于测量向量之间角度的廉价算法
找到两个向量之间的角度并不难 使用余弦法则。但是,由于我正在为资源非常有限的平台进行编程,因此我希望避免诸如 sqrt
和 arccos
之类的计算。即使是简单的划分也应该尽可能地受到限制。
幸运的是,我不需要角度本身,而只需要一些与所述角度成比例的值。
因此,我正在寻找一些计算成本低的算法来计算与两个向量之间的角度相关的量。到目前为止,我还没有找到符合要求的东西,我自己也无法想出一些东西。
Finding the angle between two vectors is not hard using the cosine rule. However, because I am programming for a platform with very limited resources, I would like to avoid calculations such as sqrt
and arccos
. Even simple divisions should be limited as much as possible.
Fortunately, I do not need the angle per se, but only need some value that is proportional to said angle.
So I am looking for some computationally cheap algorithm to calculate a quantity that is related to the angle between two vectors. So far, I haven't found something that fits the bill, nor have I been able to come up with something myself.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
如果您不需要实际的欧几里德角度,而是可以用作角度比较基础的东西,那么更改为出租车几何形状可能是一个选择,因为您可以放弃三角学,并且它的速度很慢,同时保持准确性(或至少准确性确实有很小的损失,见下文)。
在主要的现代浏览器引擎中,加速系数在 1.44 - 15.2 之间,并且精度几乎与 atan2 中的相同。计算菱形角度平均比 atan2 快 5.01 倍,并且在 Firefox 18 中使用内联代码,加速达到系数 15.2。速度比较:http://jsperf.com/diamond-angle-vs-atan2/2< /a>.
代码非常简单:
上面的代码给出了 0 到 4 之间的角度,而 atan2 给出了 -PI 和 PI 之间的角度,如下表所示:
请注意,菱形角度始终为正且在 0-4 范围内,而 atan2 也给出负弧度。所以钻石角度更加标准化。另一个注意事项是atan2给出了更精确的结果,因为范围长度是2*pi(即6.283185307179586),而在菱形角度中它是4。在实践中这不是很重要,例如。 rad 2.3000000000000001 和 2.3000000000000002 都采用菱形角度 1.4718731421442295,但如果我们通过删除一个零来降低精度,则 rad 2.300000000000001 和 2.300000000000002 会给出两个不同的菱形角度。菱形角度的这种“精度损失”非常小,只有当距离很大时才会产生重大影响。您可以在 http://jsbin.com/bewodonase/1/edit?output(旧版本:http://jsbin.com/idoyon/1):
上面的代码足以进行快速角度比较,但在许多情况下需要将菱形角度转换为弧度,反之亦然。如果你例如。有一些弧度角度的公差,然后你有一个 100,000 次循环,将此公差与其他角度进行比较,使用 atan2 进行比较是不明智的。相反,在循环之前,您将弧度公差更改为出租车(菱形角度)公差,并使用菱形公差进行循环内比较,这样您就不必在代码的速度关键部分使用慢速三角函数(= in循环)。
进行此转换的代码如下:
正如您所注意到的,有
cos
和sin
。如您所知,它们很慢,但您不必在循环内进行转换,但在循环之前进行转换,并且加速是巨大的。如果由于某种原因,您必须将菱形角度转换为弧度,例如。循环并进行角度比较后返回例如。比较的最小角度或其他弧度,代码如下:
这里使用
atan2
,它很慢,但想法是在任何循环之外使用它。您不能通过简单地乘以某个因子来将菱形角转换为弧度,而是在出租车几何形状中找到一个点,该点与正 X 轴之间的菱形角就是所讨论的菱形角,然后使用 atan2 将此点转换为弧度。这对于快速角度比较来说应该足够了。
当然,还有其他 atan2 加速技术(例如 CORDIC 和查找表),但据我所知,它们都失去了准确性,并且仍然可能比 atan2 慢。
背景:我已经测试了几种技术:点积、内积、余弦定律、单位圆、查找表等,但在速度和准确性都很重要的情况下,没有什么是足够的。最后我在 http:// www.freesteel.co.uk/wpblog/2009/06/encoding-2d-angles-without-trigonometry/ 具有所需的功能和原理。
我首先假设出租车距离也可以用于准确和快速的距离比较,因为欧几里得中的距离越大,出租车中的距离也越大。我意识到与欧几里德距离相反,起点和终点之间的角度会影响出租车距离。只有垂直和水平向量的长度可以在欧几里德和出租车之间轻松快速地转换,但在所有其他情况下,您都必须考虑角度,然后过程太慢(?)。
因此,作为结论,我认为在速度关键的应用程序中,角度和/或距离的多次比较的循环或递归,在出租车空间中比较角度和在欧几里得(平方,不使用 sqrt)空间中的距离比较速度更快。
If you don't need the actual euclidean angle, but something that you can use as a base for angle comparisons, then changing to taxicab geometry may be a choice, because you can drop trigonometry and it's slowness while MAINTAINING THE ACCURACY (or at least with really minor loosing of accuracy, see below).
In main modern browser engines the speedup factor is between 1.44 - 15.2 and the accuracy is nearly the same as in atan2. Calculating diamond angle is average 5.01 times faster than atan2 and using inline code in Firefox 18 the speedup reaches factor 15.2. Speed comparison: http://jsperf.com/diamond-angle-vs-atan2/2.
The code is very simple:
The above code gives you angle between 0 and 4, while atan2 gives you angle between -PI and PI as the following table shows:
Note that diamond angle is always positive and in the range of 0-4, while atan2 gives also negative radians. So diamond angle is sort of more normalized. And another note is that atan2 gives a little more precise result, because the range length is 2*pi (ie 6.283185307179586), while in diamond angles it is 4. In practice this is not very significant, eg. rad 2.3000000000000001 and 2.3000000000000002 are both in diamond angles 1.4718731421442295, but if we lower the precision by dropping one zero, rad 2.300000000000001 and 2.300000000000002 gives both different diamond angle. This "precision loosing" in diamond angles is so small, that it has some significant influence only if the distances are huge. You can play with conversions in http://jsbin.com/bewodonase/1/edit?output (Old version: http://jsbin.com/idoyon/1):
The above code is enough for fast angle comparisons, but in many cases there is need to convert diamond angle to radians and vice verca. If you eg. have some tolerance as radian angles, and then you have a 100,000 times loop where this tolerance is compared to other angles, it's not wise to make comparisons using atan2. Instead, before looping, you change the radian tolerance to taxicab (diamond angles) tolerance and make in-loop comparisons using diamond tolerance and this way you don't have to use slow trigonometric functions in speed-critical parts of the code ( = in loops).
The code that makes this conversion is this:
As you notice there is
cos
andsin
. As you know, they are slow, but you don't have to make the conversion in-loop, but before looping and the speedup is huge.And if for some reason, you have to convert diamond angle to radians, eg. after looping and making angle comparisons to return eg. the minimum angle of comparisons or whatever as radians, the code is as follows:
Here you use
atan2
, which is slow, but idea is to use this outside any loops. You cannot convert diamond angle to radians by simply multiplying by some factor, but instead finding a point in taxicab geometry of which diamond angle between that point and the positive X axis is the diamond angle in question and converting this point to radians using atan2.This should be enough for fast angle comparisons.
Of course there is other atan2 speedup techniques (eg. CORDIC and lookup tables), but AFAIK they all loose accuracy and still may be slower than atan2.
BACKGROUND: I have tested several techniques: dot products, inner products, law of cosine, unit circles, lookup tables etc. but nothing was sufficient in case where both speed and accuracy are important. Finally I found a page in http://www.freesteel.co.uk/wpblog/2009/06/encoding-2d-angles-without-trigonometry/ which had the desired functions and principles.
I assumed first that also taxicab distances could be used for accurate and fast distance comparisons, because the bigger distance in euclidean is bigger also in taxicab. I realized that contrary to euclidean distances, the angle between start and end point has affect to the taxicab distance. Only lengths of vertical and horizontal vectors can be converted easily and fast between euclidean and taxicab, but in every other case you have to take the angle into account and then the process is too slow (?).
So as a conclusion I am thinking that in speed critical applications where is a loop or recursion of several comparisons of angles and/or distances, angles are faster to compare in taxicab space and distances in euclidean (squared, without using sqrt) space.
您是否尝试过 CORDIC 算法?它是一个仅用加/减/位移+表格来解决极坐标 ↔ 矩形问题的通用框架,本质上是通过 tan-1 (2-n) 形式的角度进行旋转。您可以通过更改迭代次数来权衡准确性和执行时间。
在您的情况下,将一个向量作为固定参考,并将另一个向量复制到临时向量,使用弦线角度将其旋转到第一个向量(大致二等分),直到达到所需的角度精度。
(编辑:使用点积的符号来确定每一步是否向前或向后旋转。尽管如果乘法足够便宜以允许使用点积,那么就不必担心 CORDIC,也许可以使用角度 π/2n 的旋转矩阵的正弦/余弦对表,以解决二分问题。)
(编辑:我喜欢 Eric Bainville 在评论中的建议:旋转两个向量都趋于零并跟踪角度差。)
Have you tried a CORDIC algorithm? It's a general framework for solving polar ↔ rectangular problems with only add/subtract/bitshift + table, essentially doing rotation by angles of the form tan-1 (2-n). You can trade off accuracy with execution time by altering the number of iterations.
In your case, take one vector as a fixed reference, and copy the other to a temporary vector, which you rotate using the cordic angles towards the first vector (roughly bisection) until you reach a desired angular accuracy.
(edit: use sign of dot product to determine at each step whether to rotate forward or backward. Although if multiplies are cheap enough to allow using dot product, then don't bother with CORDIC, perhaps use a table of sin/cos pairs for rotation matrices of angles π/2n to solve the problem with bisection.)
(edit: I like Eric Bainville's suggestion in the comments: rotate both vectors towards zero and keep track of the angle difference.)
回到几 K RAM 和数学能力有限的机器的时代,我使用查找表和线性插值。基本思想很简单:创建一个具有所需分辨率的数组(更多元素可以减少插值产生的误差)。然后在查找值之间进行插值。
这是一个处理中的示例 (原始死链接)。
您也可以使用其他三角函数来执行此操作。在 6502 处理器上,这使得计算完整 3D 线框图形的速度提高了一个数量级。
Back in the day of a few K of RAM and machines with limited mathematical capabilities I used lookup tables and linear interpolation. The basic idea is simple: create an array with as much resolution as you need (more elements reduce the error created by interpolation). Then interpolate between lookup values.
Here is an example in processing (original dead link).
You can do this with your other trig functions as well. On the 6502 processor this allowed for full 3D wire frame graphics to be computed with an order of magnitude speed increase.
在此,我仍然没有发表评论的特权(尽管我在 math.se 上有评论),所以这实际上是对 Timo 关于钻石角度的帖子的回复。
基于 L1 范数的菱形角的整个概念是最有趣的,如果它只是比较哪个向量相对于正 X 轴具有更大/更小,那就足够了。然而,OP确实提到了两个通用向量之间的角度,我猜想OP想要将它与一些公差进行比较,以找到平滑度/角状态或类似的东西,但不幸的是,似乎只有jsperf.com上提供的公式或者 freesteel.co.uk (上面的链接)似乎不可能使用菱形角来做到这一点。
观察我的公式 Asymptote 实现的以下输出:
因此,重点是您不能执行 Diamond(alpha)-diamond(beta) 并将其与某个容差进行比较,这与您可以对 atan2 的输出进行的操作不同。如果您想做的只是钻石(alpha)>钻石(beta)那么我认为钻石就可以了。
Here on SO I still don't have the privilege to comment (though I have at math.se) so this is actually a reply to Timo's post on diamond angles.
The whole concept of diamond angles based on the L1 norm is most interesting and if it were merely a comparison of which vector has a greater/lesser w.r.t. the positive X axis it would be sufficient. However, the OP did mention angle between two generic vectors, and I presume the OP wants to compare it to some tolerance for finding smoothness/corner status or sth like that, but unfortunately, it seems that with only the formulae provided on jsperf.com or freesteel.co.uk (links above) it seems it is not possible to do this using diamond angles.
Observe the following output from my Asymptote implementation of the formulae:
So the point is you can't do diamond(alpha)-diamond(beta) and compare it to some tolerance unlike you can do with the output of atan2. If all you want to do is diamond(alpha)>diamond(beta) then I suppose diamond is fine.
叉积与两个向量之间的角度成正比,当向量归一化且角度较小时,由于角度近似值较小,叉积非常接近以弧度表示的实际角度。
具体来说:
I1Q2-I2Q1 与 I1Q1 和 I2Q2 之间的角度成正比。
The cross product is proportional to the angle between two vectors, and when the vectors are normalized and the angle is small the cross product is very close to the actual angle in radians due to the small angle approximation.
specifically:
I1Q2-I2Q1 is proportional to the angle between I1Q1 and I2Q2.
如果使用极坐标而不是笛卡尔坐标(或者“以及”使用笛卡尔坐标)来定义/存储向量,那么解决方案将是微不足道的。
The solution would be trivial if the vectors were defined/stored using polar coordinates instead of cartesian coordinates (or, 'as well as' using cartesian coordinates).
两个向量 (x1, y1) 和 (x2, y2) 的点积等于
且 等于两个向量的长度乘以它们之间角度的余弦的乘积。
因此,如果您首先对两个向量进行归一化(将坐标除以长度),
那么归一化向量是
归一化向量的点积(与向量之间角度的余弦相同)当然
这可能就像计算上一样与计算余弦一样困难
dot product of two vectors (x1, y1) and (x2, y2) is
and is equivilent to the product of the lengths of the two vectors times the cosine of the angle between them.
So if you normalize the two vectors first (divide the coordinates by the length)
Then normalized vectors are
And dot product of normalized vectors (which is the same as the cosine of angle between the vectors) would be
of course this may be just as computationally difficult as calculating the cosine
如果您需要计算平方根,请考虑使用 invsqrt hack。
if you need to compute the square root, then consider using the invsqrt hack.
点积可能适合您的情况。它与角度不成正比,而是“相关”。
The dot product might work in your case. It's not proportional to the angle, but "related".
为了更便宜地计算向量差角的度量,我制作了“全弧度”方程的简化版本,它仅使用 1 次除法和多次乘法(可以轻松地放入 SIMD 中以并行处理多对向量):
也仅一个条件(仅符号检查) - 也可以将其放入带有符号位提取的 SIMD 中并用作掩码(或与零比较)。
没有使用 sqrt() 和 arccos()。
它将向量差值返回为固定范围 0..2(浮点)内的唯一正度量。其中 0 是完全相干矢量(零角度差),2 是完全反向(最大可能角度差)。
当前的 C 函数是:
我的用例是要比较的仅整数 x,y 向量,以便您可以将其重写为全浮点输入和输出。
To make more cheap to compute metric of vectors difference angle I make simplified version of 'full radian' equation and it uses only 1 division and many multiplications (can be put to SIMD easily enough to process many pairs of verctors in parallel):
Also only one condition (sign check only) - it can be also put to SIMD with sign bit extraction and use as mask (or compare with zero).
No sqrt() and no arccos() used.
It returns vectors difference as positive only metric in fixed range of 0..2 (float). Where 0 is full coherent vectors (zero angle difference) and 2 is full counter-directed (max possible angle difference).
Current C-function is:
My use case is integer only x,y vectors to compare so you can rewrite it to all-float input and output.