4

如何在 C++ 中测量这个区域?

(更新:我发布了解决方案和代码作为答案,而不是再次编辑问题)

如何量化理想曲线和实测曲线之间的蓝色区域
理想线(红色虚线)是从起点绘制的图,每个测量角度加上平均上升;这是我通过平均获得的。我用黑色测量了测试数据。如何量化蓝色倾角的面积?X 轴是统一的,因此简化了斜率和数学。

我可以确定像这样的区域大小的截止值,然后标记这部分以进行重新测试或失败。很少会出现更靠近右侧的另一个下降,但为标准偏差设置截止值通常会使这些部分失效。

更新

迭戈的回答帮助我形象化了这一点。现在我可以看到我正在尝试做什么,我将研究实现“自制倾角检测器”的算法。:)
更好地可视化问题


为什么?

我创建了一个测试台来测试我正在销售的节气门位置传感器。我试图通过分析收集的数据以编程方式量化绘图的直接程度。这个特殊的模型让我很烦恼。

我不想出售的零件的示例图: 测试数据中有一条曲线

X 轴是等距的节气门开度角。步进电机转动输入轴,每 0.75° 停止以测量 10 位 ADC 上的输出,该输出被转换到 Y 轴。该图是映射到位图坐标的data[idx]转换。然后我使用 Bresenham 算法在位图中的点之间画线。idx,value(x,y)

我的其他 TPS 产品产生惊人的线性输出

地块的下(左)部分对于任何机动车辆的正常使用都至关重要;这是当你在城里开车、进入停车场等时。这个特定的部分倾向于在 15° 开口附近形成一个倾角,我希望使用该程序来量化曲线中的这种“倾角”并减少依赖测试者的直觉。在上面的例子中,情节下降但没有回到理想的线。

即使这是一个嵌入式应用程序,打印报告也需要 10 秒,因此我不认为多次单步执行 120 个数据点的数组会浪费周期。另外,由于我使用的是uC32 PIC32 微控制器,内存很大,所以我可以在控制器中思考这个问题。


我已经在尝试什么

测试点之间的上升阵列:我完全忽略了 X 轴,考虑到它是一体的,然后从一个读数到下一个读数进行一系列更改。该数组有助于报告的“点之间的最小上升:0 最大:14”。我称这个数组deltas

我尝试在 上使用标准偏差deltas,但是,在测试期间,我发现低标准偏差不是这部分的可靠衡量标准。如果下降很快回到早期数据点所暗示的原始线,则标准偏差可能会很低(观察到低至 2.3),但该部分仍然是我不想使用的部分。我尝试将截止值设置为 2.6,但它失败了太多部分,情节很好。与上述相关的另一个更线性的部分可以可靠地依赖 Std Dev 的质量。

峰度似乎根本不适用于这种情况。我今天了解了峰度,并找到了一个统计库,其中包括峰度和偏度。在继续测试的过程中,我发现在这两个测量中,没有对应于通过或失败的正、负或幅度趋势。同一位先生共享了一个线性回归库,但我相信 Lin Reg 与我的情况无关,因为我对 AVGdeltas作为我的理想线的假设感到满意。线性回归和 R^2 更适合从不太理想的数据或更大的集合中找到一条线。

将每个 delta 与 AVG 和 Std Dev 进行比较,我设置了一个监视器来检查每个 delta 与deltas的数据的最终平均值。在这里,我也找不到可靠的指标。太多好的零件无法通过将任何 delta 限制在距离平均值 2 倍 Std Dev 之内的测试。最终,我可以确定的与 AVG 的唯一差异是AVG+Std Dev与 AVG 本身的差异。任何更具限制性的东西都会失败,否则好的部分。大约 15° 开口的难以捉摸的倾角可以通过这个测试。

自制dip检测器给电脑的串口监视器供电deltas时,我观察到deltasdip期间连续出现负值,所以我在dip检测器中编程,但对我来说感觉很粗糙。如果连续有 5 个或更多负数deltas,我将它们相加。我已经看到,如果我将这个总和与 AVG 的下降差异除以负增量的数量,超过 2.9 或 3 的值可能意味着失败。我观察到持续 6 到 15 个三角洲的下降。容易观察到的下降与 AVG 总和的差异将达到 -35。

与 AVG 相比的趋势累积变化上述内容让我认为,deltas当它偏离 AVG 时,观察其总和可能是答案。意思是,我逐步遍历数组并将每个增量与 AVG 的差异相加。我以为我正在做某事,直到一个很好的部分打破了这个理论。我看到了一个趋势,即运行总和变化AVG小于的次数越少2x AVG,直线出现的越直。许多理想零件只会显示 8 个或更少的增量点,这些点sumOfDiffs会偏离 AVG 很远。

float sumOfDiffs=0.0;
for( int idx=0; idx<stop; idx++ ){
    float spread = deltas[idx] - line->AdcAvgRise;
    sumOfDiffs = sumOfDiffs + spread;
    ...
    testVal = 2*line->AdcAvgRise;
    if( sumOfDiffs > testVal || sumOfDiffs < -testVal ){
        flag = 'S';
    }
    ...
}

然后一个具有奇妙线性图的部分出现了 58 个数据点,sumOfDiffs是 AVG 的两倍多!我觉得这很神奇,因为在 ~120 个数据点的末尾,sumOfDiffs值为 -0.000057。

在测试期间,最终sumOfDiffs结果通常会记录为 0.000000,并且只有在异常糟糕的零件上才会大于 0.000100。实际上,我发现这非常令人惊讶:“坏部分”如何积累很高的准确性。

监控 sumOfDiffs 的示例输出下面的输出显示发生了下降。在整个测试中,运行sumOfDiffs距离 AVG 的 2 倍以上的 AVG 是测试的结果。这种下降从deltas idx23 持续到 49;从 17.25° 开始,持续 19.5°。

Avg rise: 6.75    Std dev: 2.577
idx: delta  diff from avg   sumOfDiffs  Flag
 23:   5    -1.75           -14.05      S
 24:   6    -0.75           -14.80      S
 25:   7     0.25           -14.55      S
 26:   5    -1.75           -16.30      S
 27:   3    -3.75           -20.06      S
 28:   3    -3.75           -23.81      S
 29:   7     0.25           -23.56      S
 30:   4    -2.75           -26.31      S
 31:   2    -4.75           -31.06      S
 32:   8     1.25           -29.82      S
 33:   6    -0.75           -30.57      S
 34:   9     2.25           -28.32      S
 35:   8     1.25           -27.07      S
 36:   5    -1.75           -28.82      S
 37:  15     8.25           -20.58      S
 38:   7     0.25           -20.33      S
 39:   5    -1.75           -22.08      S
 40:   9     2.25           -19.83      S
 41:  10     3.25           -16.58      S
 42:   9     2.25           -14.34      S
 43:   3    -3.75           -18.09      S
 44:   6    -0.75           -18.84      S
 45:  11     4.25           -14.59      S
 47:   3    -3.75           -16.10      S
 48:   8     1.25           -14.85      S
 49:   8     1.25           -13.60      S
Final Sum of diffs: 0.000030
RunningStats analysis:
NumDataValues= 125
Mean= 6.752
StandardDeviation= 2.577
Skewness= 0.251
Kurtosis= -0.277

关于质量的发人深省的说明:让我开始这一旅程的是了解主要汽车 OEM 供应商如何将 4 点测试视为这些零件的标准衡量标准。我的第一个测试台使用 8k RAM 的 Arduino,没有 TFT 显示器也没有打印机,机械分辨率只有 3°!那时我只是deltas在任意总范围内进行测试,并选择任何单个增量可能有多大的限制。与之前的 30 点测试相比,我的 120+ 点测试感觉很高级,但该测试不知道这些下降。

4

2 回答 2

1

前提

  • 一组数据的平均值具有数学特性,即与平均值的偏差之和为 0。
    • 这解释了为什么坏数据集和好的数据集总是几乎为 0。
    • 基本上,与零不同时的结果本质上是差异中舍入误差的累积,这就是为什么不幸无法保存有用信息的原因
  • 最清楚地定义您要寻找的东西是您的形象:您正在寻找一个区域,这就是为什么您没有以这种方式找到解决方案的原因:
    • 在单点中查看度量过于局部,无法提取该信息
    • 寻找全局累积或参数(全局标准偏差)过于全局,您会在太多信息和变化源中丢失数据
    • 峰度(你已经告诉我知道但为了完整性)不在其应用领域,因为这不是概率分布
    • 最后,您已经尝试过的更合适的方法是“自制倾角检测器”,因为它以一种本地化但又不过分的方式思考。
  • 最后但并非最不重要的:
    • 您要选择的任何算法都有其默认点。
      • 因此,也许有人正在寻找一种超级聪明的算法,该算法无需参数化和调整即可自动适应问题并自行定义方法等。
      • 另一方面,有一种算法将基于典型数据行为(好的和坏的)的作者的知识,它本身就是愚蠢的,如果有另一种不同的和意想不到的行为,结果是不可预测的
      • 好的,正确的方法是这两种方法之一,或者介于两者之间,具体取决于应用程序。因此,如果它也有效,“自制倾角探测器”也可以成为一个解决方案。没有理由将其定义为粗略,但根据应用需求,这可能是不够的,那是另一回事。

如何找到该区域

  • 获得数据后,首先要明确定义“理论直线”。我给出了一些选择:
    • 使用 RANSAC 算法(正式的最佳选择恕我直言)
      • 这使您最适合对齐的点,而不管未对齐的点
      • 这项工作非常困难,而且可能过大(恕我直言)
    • 考虑由第一个点和最后一个点定义的线
      • 你告诉过倾角几乎总是在不靠近边界的同一个位置,所以第一个和最后一个点可以被认为是负担得起的
      • 很容易实现
      • 这是一个使用我之前所说的关于预期行为的知识的例子,所以你需要考虑你是否以及对这个假设有多大的信心
    • 考虑对前 10 个点和后 10 个点进行线性拟合
      • 只是以前的一个更实惠的版本,因为使用更多的点你可以不用担心可能只是第一点或最后一点受到任何测量问题的影响,因此所有都失败了
      • 也很容易实现
      • 如果我是你,我会用这个或受此启发的东西
  • 计算每个 X 的直线给出的 Y 值
  • 使用以下过程计算两条曲线之间的面积(或在Y_dev = Y_data - Y_straight数学上相同的函数下的面积):
    • PositiveMax = 0; NegativeMax = 0;
    • 从第一个点开始(值可以是正数或负数)并放入临时区域累加器tmp_Area
    • 对于每个下一个点
      • 如果符号相同,则累积值
      • 如果不同
        • 停止积累
        • 检查累积值是否大于 PositiveMax 或小于 NegativeMax,如果大于存储为新 PositiveMax 或 NegativeMax
        • 在任何情况下,将累加器重置为tmp_Area = Y_dev;当前值,以这种方式开始一个新的累加
    • 最后,您将获得最大被高估的连续区域和最大被低估的连续区域的值,我认为这是您正在寻找的分数。
    • 如果您愿意,您只能根据观察到的和预期的数据行为来管理 NegativeMax
    • 您可能会发现设置一个阈值很有用,这样如果一个值Y_dev低于阈值,您就不会累积它。
    • 这是为了不从靠近直线的许多点获得大的积累,这可能类似于远离直线的几个点的积累
      • 需要对一些样本数据进行评估,以及适当的阈值
    • 你需要为这个连续区域找到一个合适的阈值,你只能通过观察样本数据来获得它。
      • 再说一遍:你可以观察和决定阈值,或者你可以建立一个好样本和坏样本的存储库,然后编写一个程序来自动学习使用哪个阈值。但他的不是算法,这是如何找到它的操作参数,人脑并没有错……这仅取决于我们是否正在寻找一种区分坏事和好事的方法,或者我们是否'重新寻找执行此操作的自适应算法.. ..您决定目标。
于 2013-10-09T22:42:23.553 回答
1

事实证明,我的直觉和 Diego 的方法是积分的平均值。我仍然不喜欢这个名字,所以我已经描述了这个算法,并在 Math.SE 上询问了如何称呼它,它被迁移到“交叉验证”,Stats.SE

在对我的 Math.SE 问题进行大量编辑后,我更新了图表。事实证明,我正在取数据导数的闭积分的平均值。:P 首先,我们收集数据:

收集的数据图,突出显示下降

接下来是“导数”:逐步遍历原始数据数组,形成deltasADC值从一个0.75°步长到下一个增量的数组。“上升”或“斜率”是导数:dy/dx。

随着“斜率”或平均值趋于平稳,我可以deltas 连续找到多个负数,将它们相加,然后除以下降结束时的计数。总和是平均值和之间面积的积分,deltas当跌幅回到正值时,我可以将总和除以跌幅数。

导数的数值积分的平均值

在测试期间,我为这个积分的平均值提出了一个截止值,即 2.6。这很好地衡量了我的“直觉”,看情节认为一个部分是好是坏。

如果其他人发现自己试图量化这一点,这是我实现的代码。请注意,它只是在寻找负面的下跌。此外,dipCountLimit 在其他地方被定义为 5。除了 dip 检测器/累加器(即数值积分器)之外,我还有一个尖峰检测器,如果任何数据点偏离平均值的平均值 + 标准量,它会任意将测试标记为坏偏差。AVG+STD DEV 作为峰值限制是根据观察到的可能失败的零件图任意选择的。

int dipdx=0;
//  inDipFlag also counts the length of this dip
int inDipFlag=0;
float dips[140] = { 0.0 };
for( int idx=0; idx<stop; idx++ ){
    const float diffFromAvg = deltas[idx] - line->AdcAvgRise;
    //  state machine to monitor dips
    const int _stop = stop-1;
    if( diffFromAvg < 0 && idx < _stop ) {
        //  check NEXT data point for negative diff & set dipFlag to put state in dip
        const float nextDiff = deltas[idx+1] - line->AdcAvgRise;
        if( nextDiff < 0 && inDipFlag == 0 )
            inDipFlag = 1;
        //  already IN a dip, and next diff is negative
        if( nextDiff < 0 && inDipFlag > 0 ) {
            inDipFlag++;
        }

        //  accumulate this dip
        dips[dipdx]+= diffFromAvg;

        //  next data point ends this dip and we advance dipdx to next dip
        if( inDipFlag > 0 && nextDiff > 0 ) {
            if( inDipFlag < dipCountLimit ){
                //  reset the accumulator, do not advance dipdx to next entry
                dips[dipdx]=0.0;
            } else {
                //  change this entry's value from dip sum to its ratio
                dips[dipdx] = -dips[dipdx]/inDipFlag;
                //  advance dipdx to next entry
                dipdx++;
            }
            //  Next diff isn't negative, so the dip is done
            inDipFlag = 0;
        }
    }
}
于 2013-10-29T00:22:19.893 回答