如何计算一组循环数据的平均值?

167

我想计算一组循环数据的平均值。例如,我可能有从指南针读数中获取的多个样本。问题在于如何处理循环。同样的算法也可以用于时钟表盘。

实际问题更加复杂 - 统计学在球面或“环绕”的代数空间中的含义是什么,例如模n的加法群。答案可能不唯一,例如359度和1度的平均值可以是0度或180度,但统计上看起来0度更好。

这对我来说是一个真正的编程问题,我正在尝试使它不仅仅像一个数学问题。


2
通过平均角度,我认为你实际上想要的是平均方位。一个角存在于两条线之间,而方位是单条线的方向。在这种情况下,starblue是正确的。 - SmacL
1
我实际上想要的是稍微复杂一些的东西(但类似于轴承),并试图简化问题,使问题更容易回答,结果反而让它变得更加复杂。我在http://catless.ncl.ac.uk/Risks/7.44.html#subj4找到了想要的答案。我会重新编辑问题。 - Nick Fortescue
@ShaneMacLaughlin,你深入研究了似乎仅在你非常狭窄的领域内“极其重要”的行话和区分。问题提问者所询问的数据类型通用术语是“循环数据”,而不是轴承。循环数据包括时钟上的时间、方向(按照通常的理解,而不是你所说的任何内容)、颜色、方向等。 - abcd
@dbliss,你刚刚编辑了问题,并用圆形数据替换了角度术语,以使其更符合你自己的评论!真的吗?为了澄清,原始问题已经被OP编辑过了,变成了“为了解决所有混淆,当我提到角度时,您可以假设我指的是方位角”。如果你真的认为角度和方位角的测量是一个非常狭窄的领域,那么我可以假设你从未在学校学过三角学,手机上没有GPS,也从未使用过谷歌地球。 - SmacL
为了进一步澄清时钟表盘上轴承和角度的区别,如果我在中午看时钟,两只指针的轴承都是0度。如果我在1天后看时钟,它们仍然具有0度的轴承,但大指针顺时针方向旋转了720度。因此,平均轴承将等同于平均日时间,而平均角度将等同于平均时间花费。在原始问题的背景下,这是至关重要的区别。 - SmacL
显示剩余6条评论
31个回答

112

从角度计算单位向量,然后取它们的平均角度。


10
如果向量互相抵消,那么这种方法就不起作用了。平均值在这种情况下可能仍然有意义,取决于其确切的定义。 - David Hanak
25
@David,两个方位角度差180度的平均方位角度是不确定的。这并不意味着starblue的回答是错误的,这只是一个异常情况,在许多几何问题中都会出现。 - SmacL
6
我同意@smacl的观点,如果角度表示方向的话。但是如果你考虑复数,例如定义平均值为“c的参数是什么,使得cc == ab”,其中a和b的模为1,则0和180的平均值是90。 - David Hanak
3
请参见http://math.stackexchange.com/questions/14530/how-to-average-cyclic-quantities - starblue
5
如果我向0度方向迈两步,再向90度方向迈一步,那么相对于起点,我会向26.56度方向移动。在这种意义下,26.56更能代表{0,0,90}度的平均方向比30度更合理。代数平均只是许多可能的平均方法之一(参见http://en.wikipedia.org/wiki/Mean),在平均方向方面似乎并不相关(就像其他许多情况一样)。 - Janus
显示剩余8条评论

65
这个问题在书籍《球面统计学》(Geoffrey S. Watson, University of Arkansas Lecture Notes in the Mathematical Sciences, 1983 John Wiley & Sons, Inc.)中有详细讨论,Bruce Karsh 提到的链接地址是:http://catless.ncl.ac.uk/Risks/7.44.html#subj4
从一组角度测量值 a[i](0<=i
                   sum_i_from_1_to_N sin(a[i])
a = arctangent ---------------------------
                   sum_i_from_1_to_N cos(a[i])

starblue提供的方法在计算上与原方法等效,但他的解释更加清晰,可能在编程上更有效,而且在零情况下也能很好地工作,所以要对他表示赞扬。

该主题现在在维基百科上有更详细的探讨,还包括其他用途,如小数部分。


8
这段话的意思是:“这个算法跟我和你同时发布的算法很相似。不过,你需要使用atan2而不是普通的atan函数,因为否则你无法确定答案在哪个象限。” - Alnitak
你仍然可能会得到一些不确定的答案,比如在0和180的示例中。所以你仍然需要检查边界情况。此外,在通常情况下有一个可用的atan2函数,它可能会更快。 - Loki

57

我明白问题所在 - 例如,如果您有一个45度角和一个315度角,"自然"平均值将是180度,但您实际想要的值是0度。

我认为Starblue的想法很有用。只需计算每个角的(x,y)笛卡尔坐标,并将这些结果向量相加即可。最终向量的角偏移应该是您需要的结果。

x = y = 0
foreach angle {
    x += cos(angle)
    y += sin(angle)
}
average_angle = atan2(y, x)

我现在暂时忽略一个指南针方向从北开始,沿顺时针方向转动,而“普通”的笛卡尔坐标系则从X轴上的零度开始,然后逆时针旋转的事实。无论如何,数学计算应该得出相同的结果。


18
你的数学库很可能使用弧度来表示角度。记得进行转换。 - Martin Beckett
2
也许已经太晚了,但是使用这个逻辑,我得到的平均角度是341.8947...而不是[320, 330, 340, 350, 10]的342。有人看到我的笔误了吗? - Alex Robinson
1
@AlexRobinson 这不是打字错误,而是因为最终角度只是通过单独取每个角度的一组步骤得到的最终角度。 - Alnitak
2
@AlexRobinson,更具体地说:cos()sin()atan2()提供了近似值(虽然很好,但仍然偏离1或2 ulps),因此您平均的次数越多,包含的错误就越多。 - Matthieu

35

对于两个角度的特殊情况:

答案 ((a + b) mod 360) / 2错误的。对于角度为350和2,最接近的点是356,而不是176。

使用单位向量和三角函数的方法可能太过复杂。

从一些小改动中我得到了以下结果:

diff = ( ( a - b + 180 + 360 ) mod 360 ) - 180
angle = (360 + b + ( diff / 2 ) ) mod 360
  • 0, 180 -> 90(对于此问题有两个答案:此等式使用从a开始的顺时针答案)
  • 180, 0 -> 270(见上文)
  • 180, 1 -> 90.5
  • 1, 180 -> 90.5
  • 20, 350 -> 5
  • 350, 20 -> 5(所有以下示例也能正确翻转)
  • 10, 20 -> 15
  • 350, 2 -> 356
  • 359, 0 -> 359.5
  • 180, 180 -> 180

这可以通过使用BAMS进一步优化:https://dev59.com/SXNA5IYBdhLWcg3wNrAy#1049285 - darron
不错。第一行计算了a相对于b在[-180, 179]范围内的相对角度,第二行从中计算出了中间角度。为了清晰起见,我会使用b + diff/2而不是a - diff/2。 - starblue
1
我有什么遗漏吗?我确实得到了295。 - darron
1
啊..我明白了。Matlab的模运算符将-10包装为350。我会改变代码的。只需要加上360即可。 - darron
4
这种方法的另一个好处是,很容易实现两个角度的加权平均值。在第二行中,将差乘以第一个角度的权重,并用权重之和替换分母中的2。角度 = (360 + b + (WEIGHT[a] * diff / (WEIGHT[a] + WEIGHT[b]))) mod 360 - oosterwal

16

ackb是正确的,这些基于向量的解决方案不能被视为角度的真正平均值,它们只是单位向量对应项的平均值。然而,ackb提出的解决方案似乎在数学上不太可靠。

以下是一个数学上从最小化(angle [i] - avgAngle)^2的目标推导出的解决方案(如果需要,可以校正差异),这使它成为角度的真实算术平均值。

首先,我们需要看看哪些情况下角度之间的差异与它们的常规数字对应项之间的差异不同。考虑角度x和y,如果y>= x - 180且y<= x + 180,则我们可以直接使用差(x-y)。否则,如果不满足第一个条件,则必须在计算中使用(y+360)。相应地,如果第二个条件未满足,则必须使用(y-360)代替y。由于我们要最小化的曲线方程仅在这些不等式从真变为假或从假变为真的点处改变,因此我们可以将整个[0,360)范围分为一组段,由这些点分隔开。然后,我们只需要找到每个段的最小值,然后是每个段的最小值的最小值,即平均值。

这里有一个演示计算角度差异问题发生位置的图像。如果x位于灰色区域中,则会出现问题。

Angle comparisons

为了最小化变量,根据曲线的不同,我们可以对我们想要最小化的内容取导数,然后找到拐点(即导数=0的点)。

在这里,我们将应用最小化平方差的思想来推导出常见的算术平均公式:sum(a [i])/n。可以通过以下方式最小化曲线y = sum((a [i]-x)^2):

y = sum((a[i]-x)^2)
= sum(a[i]^2 - 2*a[i]*x + x^2)
= sum(a[i]^2) - 2*x*sum(a[i]) + n*x^2

dy\dx = -2*sum(a[i]) + 2*n*x

for dy/dx = 0:
-2*sum(a[i]) + 2*n*x = 0
-> n*x = sum(a[i])
-> x = sum(a[i])/n

现在将其应用于使用我们调整后的差异的曲线:

b = a 的子集,其中包含正确的(角度)差异 a[i]-x c = a 的子集,其中包含正确的(角度)差异 (a[i]-360)-x cn = c 的大小 d = a 的子集,其中包含正确的(角度)差异 (a[i]+360)-x dn = d 的大小

y = sum((b[i]-x)^2) + sum(((c[i]-360)-b)^2) + sum(((d[i]+360)-c)^2)
= sum(b[i]^2 - 2*b[i]*x + x^2)
  + sum((c[i]-360)^2 - 2*(c[i]-360)*x + x^2)
  + sum((d[i]+360)^2 - 2*(d[i]+360)*x + x^2)
= sum(b[i]^2) - 2*x*sum(b[i])
  + sum((c[i]-360)^2) - 2*x*(sum(c[i]) - 360*cn)
  + sum((d[i]+360)^2) - 2*x*(sum(d[i]) + 360*dn)
  + n*x^2
= sum(b[i]^2) + sum((c[i]-360)^2) + sum((d[i]+360)^2)
  - 2*x*(sum(b[i]) + sum(c[i]) + sum(d[i]))
  - 2*x*(360*dn - 360*cn)
  + n*x^2
= sum(b[i]^2) + sum((c[i]-360)^2) + sum((d[i]+360)^2)
  - 2*x*sum(x[i])
  - 2*x*360*(dn - cn)
  + n*x^2

dy/dx = 2*n*x - 2*sum(x[i]) - 2*360*(dn - cn)

for dy/dx = 0:
2*n*x - 2*sum(x[i]) - 2*360*(dn - cn) = 0
n*x = sum(x[i]) + 360*(dn - cn)
x = (sum(x[i]) + 360*(dn - cn))/n

仅有这些还不足以得到最小值,虽然对于普通值可以使用,但由于它具有无界集合,因此结果肯定在集合的范围内,因此是有效的。我们需要在一个范围内找到最小值(由该段定义)。如果最小值小于我们段的下限,则该段的最小值必须在下限处(因为二次曲线只有一个转折点),如果最小值大于我们段的上限,则该段的最小值在上限处。找到每个段的最小值后,我们只需找到使我们正在最小化的内容的值最低的那个。

下面是一张曲线图,显示了当x =(a [i] +180)%360时它如何变化。所讨论的数据集为{65,92,230,320,250}。

Curve

这是Java中算法的实现,包括一些优化,其复杂度为O(nlogn)。如果您将基于比较的排序替换为非基于比较的排序(例如基数排序),则可以将其降低到O(n)。

static double varnc(double _mean, int _n, double _sumX, double _sumSqrX)
{
    return _mean*(_n*_mean - 2*_sumX) + _sumSqrX;
}
//with lower correction
static double varlc(double _mean, int _n, double _sumX, double _sumSqrX, int _nc, double _sumC)
{
    return _mean*(_n*_mean - 2*_sumX) + _sumSqrX
            + 2*360*_sumC + _nc*(-2*360*_mean + 360*360);
}
//with upper correction
static double varuc(double _mean, int _n, double _sumX, double _sumSqrX, int _nc, double _sumC)
{
    return _mean*(_n*_mean - 2*_sumX) + _sumSqrX
            - 2*360*_sumC + _nc*(2*360*_mean + 360*360);
}

static double[] averageAngles(double[] _angles)
{
    double sumAngles;
    double sumSqrAngles;

    double[] lowerAngles;
    double[] upperAngles;

    {
        List<Double> lowerAngles_ = new LinkedList<Double>();
        List<Double> upperAngles_ = new LinkedList<Double>();

        sumAngles = 0;
        sumSqrAngles = 0;
        for(double angle : _angles)
        {
            sumAngles += angle;
            sumSqrAngles += angle*angle;
            if(angle < 180)
                lowerAngles_.add(angle);
            else if(angle > 180)
                upperAngles_.add(angle);
        }


        Collections.sort(lowerAngles_);
        Collections.sort(upperAngles_,Collections.reverseOrder());


        lowerAngles = new double[lowerAngles_.size()];
        Iterator<Double> lowerAnglesIter = lowerAngles_.iterator();
        for(int i = 0; i < lowerAngles_.size(); i++)
            lowerAngles[i] = lowerAnglesIter.next();

        upperAngles = new double[upperAngles_.size()];
        Iterator<Double> upperAnglesIter = upperAngles_.iterator();
        for(int i = 0; i < upperAngles_.size(); i++)
            upperAngles[i] = upperAnglesIter.next();
    }

    List<Double> averageAngles = new LinkedList<Double>();
    averageAngles.add(180d);
    double variance = varnc(180,_angles.length,sumAngles,sumSqrAngles);

    double lowerBound = 180;
    double sumLC = 0;
    for(int i = 0; i < lowerAngles.length; i++)
    {
        //get average for a segment based on minimum
        double testAverageAngle = (sumAngles + 360*i)/_angles.length;
        //minimum is outside segment range (therefore not directly relevant)
        //since it is greater than lowerAngles[i], the minimum for the segment
        //must lie on the boundary lowerAngles[i]
        if(testAverageAngle > lowerAngles[i]+180)
            testAverageAngle = lowerAngles[i];

        if(testAverageAngle > lowerBound)
        {
            double testVariance = varlc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,i,sumLC);

            if(testVariance < variance)
            {
                averageAngles.clear();
                averageAngles.add(testAverageAngle);
                variance = testVariance;
            }
            else if(testVariance == variance)
                averageAngles.add(testAverageAngle);
        }

        lowerBound = lowerAngles[i];
        sumLC += lowerAngles[i];
    }
    //Test last segment
    {
        //get average for a segment based on minimum
        double testAverageAngle = (sumAngles + 360*lowerAngles.length)/_angles.length;
        //minimum is inside segment range
        //we will test average 0 (360) later
        if(testAverageAngle < 360 && testAverageAngle > lowerBound)
        {
            double testVariance = varlc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,lowerAngles.length,sumLC);

            if(testVariance < variance)
            {
                averageAngles.clear();
                averageAngles.add(testAverageAngle);
                variance = testVariance;
            }
            else if(testVariance == variance)
                averageAngles.add(testAverageAngle);
        }
    }


    double upperBound = 180;
    double sumUC = 0;
    for(int i = 0; i < upperAngles.length; i++)
    {
        //get average for a segment based on minimum
        double testAverageAngle = (sumAngles - 360*i)/_angles.length;
        //minimum is outside segment range (therefore not directly relevant)
        //since it is greater than lowerAngles[i], the minimum for the segment
        //must lie on the boundary lowerAngles[i]
        if(testAverageAngle < upperAngles[i]-180)
            testAverageAngle = upperAngles[i];

        if(testAverageAngle < upperBound)
        {
            double testVariance = varuc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,i,sumUC);

            if(testVariance < variance)
            {
                averageAngles.clear();
                averageAngles.add(testAverageAngle);
                variance = testVariance;
            }
            else if(testVariance == variance)
                averageAngles.add(testAverageAngle);
        }

        upperBound = upperAngles[i];
        sumUC += upperBound;
    }
    //Test last segment
    {
        //get average for a segment based on minimum
        double testAverageAngle = (sumAngles - 360*upperAngles.length)/_angles.length;
        //minimum is inside segment range
        //we test average 0 (360) now           
        if(testAverageAngle < 0)
            testAverageAngle = 0;

        if(testAverageAngle < upperBound)
        {
            double testVariance = varuc(testAverageAngle,_angles.length,sumAngles,sumSqrAngles,upperAngles.length,sumUC);

            if(testVariance < variance)
            {
                averageAngles.clear();
                averageAngles.add(testAverageAngle);
                variance = testVariance;
            }
            else if(testVariance == variance)
                averageAngles.add(testAverageAngle);
        }
    }


    double[] averageAngles_ = new double[averageAngles.size()];
    Iterator<Double> averageAnglesIter = averageAngles.iterator();
    for(int i = 0; i < averageAngles_.length; i++)
        averageAngles_[i] = averageAnglesIter.next();


    return averageAngles_;
}
一组角度的算术均值可能与您对平均数的直觉想法不同。例如,集合{179, 179, 0, 181, 181}的算术平均值为216(和144)。您立即想到的答案可能是180,但众所周知,算术平均值受边缘值影响很大。还应记住,角度不是向量,尽管在处理角度时这种理解非常吸引人。
当然,此算法也适用于所有遵循模算术的量(经过最少的调整),例如一天中的时间。
我还想强调的是,尽管这是角度的真正平均值,与向量解决方案不同,但这并不一定意味着您应该使用它来求解,相应单位向量的平均值可能是您实际需要使用的值。

三田方法实际上给出了起始角度和从起始角度旋转的平均值。因此,要得到类似的方法,考虑测量误差,您需要观察旋转并估计其误差。我认为您需要对旋转进行分布才能估计它们的误差。 - Nimble

8
我想分享一种我用过的微控制器方法,该微控制器没有浮点或三角函数功能。 我仍然需要“平均”10个原始轴承读数以平滑变化。
  1. 检查第一个轴承是否在270-360度或0-90度范围内(北半球两个象限)
  2. 如果是,则将此及所有后续读数旋转180度,保持所有值在0 <= bearing < 360范围内。否则按照读数进行。
  3. 一旦取得了10个读数,请计算数值平均值,假设没有环绕。
  4. 如果180度旋转已生效,则将计算出的平均值旋转180度以返回“真实”轴承。
这不是理想的; 它可能会崩溃。 在这种情况下,我成功地使用了它,因为该设备只会非常缓慢地旋转。 我将其发布出来,以防其他人发现自己也在类似的限制下工作。

6

在Python中,角度范围为[-180, 180)

def add_angles(a, b):
  return (a + b + 180) % 360 - 180

def average_angles(a, b):
  return add_angles(a, add_angles(-a, b)/2)

细节:

对于两个角度的平均值,有两个相差180°的平均值,但我们可能希望更接近的平均值。

从视觉上看,蓝色(b)和绿色(a)的平均值得到青色点:

Original

角度“环绕”(例如,355 + 10 = 5),但标准算术将忽略此分支点。 然而,如果角度b与分支点相对,则(b + g)/2会给出最接近的平均值:青色点。

对于任何两个角度,我们可以旋转问题,使其中一个角度与分支点相对,执行标准平均,然后旋转回来。

rotatedreturned


6
你需要更准确地定义平均值。对于两个角度的特定情况,我可以想到两种不同的情景:
  1. "真正的"平均值,即(a + b) / 2 % 360。
  2. 在保持在同一半圆内的两个角之间指向的角度,例如对于355和5,这将是0,而不是180。为此,你需要检查两个角之间的差异是否大于180。如果是,使用上述公式之前将较小的角度增加360。

但我不知道如何将第二种选择推广到多于两个角度的情况。


虽然问题涉及角度,但最好将其视为平均方向,并且这是一个常见的导航问题。 - SmacL
好观点,大卫。例如,一个180度角和一个540度角的平均值是多少?是360度还是180度? - Baltimark
3
@Baltimark,我猜这取决于你正在做什么。如果是导航,可能会选择后者。如果是花式滑雪跳跃,也许会选择前者 ;) - SmacL
那么1和359的“真正”平均值是(360/2)%360=180吗?我认为不是。 - Die in Sente
1
@Die in Sente:从数字上来说,当然是这样的。比如说,如果角度代表的是转弯而不是方向,那么359度和1度的平均值肯定是180度。这完全取决于解释的方式。 - David Hanak
“True?”不,你想说的是“算术”。 - Константин Ван

5

与所有平均数一样,答案取决于度量标准的选择。对于给定的度量标准M,在[-pi,pi]中对于k在[1,N]的某些角度a_k,其平均值是那个角度a_M,它最小化平方距离d^2_M(a_M,a_k)的总和。对于加权平均值,只需在总和中包括权重w_k(使sum_k w_k = 1)。即,

a_M = arg min_x sum_k w_k d^2_M(x,a_k)

两个常见的度量标准是Frobenius度量和Riemann度量。对于Frobenius度量,存在一个直接的公式,对应于圆形统计学中通常的平均方位概念。有关详细信息,请参见Maher Moakher的文章“Means and Averaging in the Group of Rotations”,SIAM Journal on Matrix Analysis and Applications,Volume 24,Issue 1,2002。
http://link.aip.org/link/?SJMAEL/24/1/1

这是用于GNU Octave 3.2.4的计算函数:

function ma=meanangleoct(a,w,hp,ntype)
%   ma=meanangleoct(a,w,hp,ntype) returns the average of angles a
%   given weights w and half-period hp using norm type ntype
%   Ref: "Means and Averaging in the Group of Rotations",
%   Maher Moakher, SIAM Journal on Matrix Analysis and Applications,
%   Volume 24, Issue 1, 2002.

if (nargin<1) | (nargin>4), help meanangleoct, return, end 
if isempty(a), error('no measurement angles'), end
la=length(a); sa=size(a); 
if prod(sa)~=la, error('a must be a vector'); end
if (nargin<4) || isempty(ntype), ntype='F'; end
if ~sum(ntype==['F' 'R']), error('ntype must be F or R'), end
if (nargin<3) || isempty(hp), hp=pi; end
if (nargin<2) || isempty(w), w=1/la+0*a; end
lw=length(w); sw=size(w); 
if prod(sw)~=lw, error('w must be a vector'); end
if lw~=la, error('length of w must equal length of a'), end
if sum(w)~=1, warning('resumming weights to unity'), w=w/sum(w); end

a=a(:);     % make column vector
w=w(:);     % make column vector
a=mod(a+hp,2*hp)-hp;    % reduce to central period
a=a/hp*pi;              % scale to half period pi
z=exp(i*a); % U(1) elements

% % NOTA BENE:
% % fminbnd can get hung up near the boundaries.
% % If that happens, shift the input angles a
% % forward by one half period, then shift the
% % resulting mean ma back by one half period.
% X=fminbnd(@meritfcn,-pi,pi,[],z,w,ntype);

% % seems to work better
x0=imag(log(sum(w.*z)));
X=fminbnd(@meritfcn,x0-pi,x0+pi,[],z,w,ntype);

% X=real(X);              % truncate some roundoff
X=mod(X+pi,2*pi)-pi;    % reduce to central period
ma=X*hp/pi;             % scale to half period hp

return
%%%%%%

function d2=meritfcn(x,z,w,ntype)
x=exp(i*x);
if ntype=='F'
    y=x-z;
else % ntype=='R'
    y=log(x'*z);
end
d2=y'*diag(w)*y;
return
%%%%%%

% %   test script
% % 
% % NOTA BENE: meanangleoct(a,[],[],'R') will equal mean(a) 
% % when all abs(a-b) < pi/2 for some value b
% % 
% na=3, a=sort(mod(randn(1,na)+1,2)-1)*pi;
% da=diff([a a(1)+2*pi]); [mda,ndx]=min(da);
% a=circshift(a,[0 2-ndx])    % so that diff(a(2:3)) is smallest
% A=exp(i*a), B1=expm(a(1)*[0 -1; 1 0]), 
% B2=expm(a(2)*[0 -1; 1 0]), B3=expm(a(3)*[0 -1; 1 0]),
% masimpl=[angle(mean(exp(i*a))) mean(a)]
% Bsum=B1+B2+B3; BmeanF=Bsum/sqrt(det(Bsum)); 
% % this expression for BmeanR should be correct for ordering of a above
% BmeanR=B1*(B1'*B2*(B2'*B3)^(1/2))^(2/3);
% mamtrx=real([[0 1]*logm(BmeanF)*[1 0]' [0 1]*logm(BmeanR)*[1 0]'])
% manorm=[meanangleoct(a,[],[],'F') meanangleoct(a,[],[],'R')]
% polar(a,1+0*a,'b*'), axis square, hold on
% polar(manorm(1),1,'rs'), polar(manorm(2),1,'gd'), hold off

%     Meanangleoct Version 1.0
%     Copyright (C) 2011 Alphawave Research, robjohnson@alphawaveresearch.com
%     Released under GNU GPLv3 -- see file COPYING for more info.
%
%     Meanangle is free software: you can redistribute it and/or modify
%     it under the terms of the GNU General Public License as published by
%     the Free Software Foundation, either version 3 of the License, or (at
%     your option) any later version.
%
%     Meanangle is distributed in the hope that it will be useful, but
%     WITHOUT ANY WARRANTY; without even the implied warranty of
%     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
%     General Public License for more details.
%
%     You should have received a copy of the GNU General Public License
%     along with this program.  If not, see `http://www.gnu.org/licenses/'.

4

这是完整的解决方案: (输入为度数制下的轴承数组(0-360)

public static int getAvarageBearing(int[] arr)
{
    double sunSin = 0;
    double sunCos = 0;
    int counter = 0;

    for (double bearing : arr)
    {
        bearing *= Math.PI/180;

        sunSin += Math.sin(bearing);
        sunCos += Math.cos(bearing);
        counter++; 
    }

    int avBearing = INVALID_ANGLE_VALUE;
    if (counter > 0)
    {
        double bearingInRad = Math.atan2(sunSin/counter, sunCos/counter);
        avBearing = (int) (bearingInRad*180f/Math.PI);
        if (avBearing<0)
            avBearing += 360;
    }

    return avBearing;
}

这个问题困扰了我一段时间,你的解决方案很有效(使用Arduino,所以对你的代码进行了一些更改但不多),我正在显示罗盘读数并每50毫秒进行一次读数,并将其存储到16 x读数数组中,然后在上面使用你的函数,0-360包裹问题得到解决!谢谢 :) - Andology

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接