【问题标题】:Subsampling an array of numbers对数字数组进行二次采样
【发布时间】:2015-08-05 15:23:28
【问题描述】:

我有一系列 100 个整数值,我需要将它们减少/二次采样到 77 个值,以便适应屏幕上的预定义空间。这给出了每像素 77/100 个值的一小部分 - 不是很整洁。

假设 77 是固定的并且不能更改,那么将 100 个数字向下采样到 77 的典型技术是什么。我感觉这将是一个锯齿状映射,我的意思是第一个新值是[0, 1] 然后下一个值是 [3],然后是平均 [4, 5] 等等。但是我如何获得这个映射的模式?

我正在使用 C++,尽管我对技术比实现更感兴趣。

提前致谢。

【问题讨论】:

  • 为什么不直接使用标准插值技术?
  • 我投票结束这个问题,因为它不是关于编程的。

标签: c++ downsampling


【解决方案1】:

无论是下采样还是过采样,您都试图在非采样时间点上重建信号......因此您必须做出一些假设。

采样定理告诉您,如果您对一个信号进行采样,并且知道它没有超过一半采样频率的频率分量,您就可以在整个时序周期内连续且完全地恢复该信号。有一种方法可以使用sinc() 函数重构信号(这是sin(x)/x

sinc()(实际上是sin(M_PI/Sampling_period*x)/M_PI/x)是一个具有以下属性的函数:

  1. x == 0.0 的值为 1,x == k*Sampling_periodk == 0, +-1, +-2, ... 的值为 0
  2. 它没有超过从Sampling_period 派生的sample_frequency 一半的频率分量。

因此,如果您将函数 F_x(x) = Y[k]*sinc(x/Sampling_period - k) 的总和视为等于位置 k 处的采样值和其他采样值处的 0 的 sinc 函数,并对样本中的所有 k 求和,您将得到最佳连续函数,其特性是在超过一半采样频率的频率上没有分量,并且具有与您的样本集相同的值。

也就是说,您可以在任何您喜欢的位置重新采样此函数,从而获得重新采样数据的最佳方式。

到目前为止,这是一种复杂的数据重采样方式,(它也存在非因果关系的问题,因此无法实时实现)并且您过去使用过几种方法来简化插值。您必须为每个采样点构造所有 sinc 函数并将它们加在一起。然后,您必须将结果函数重新采样到新的采样点并给出结果。

接下来是刚刚描述的插值方法的一个例子。它接受一些输入数据(in_sz 样本)并使用前面描述的方法输出插值数据(我认为极值一致,这使得N+1 样本等于N+1 样本,这使得(in_sz - 1)/(out_sz - 1) 的计算有些复杂在代码中(如果要进行简单的N samples -> M samples 转换,请更改为in_sz/out_sz

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

/* normalized sinc function */
double sinc(double x)
{
    x *= M_PI;
    if (x == 0.0) return 1.0;
    return sin(x)/x;
} /* sinc */

/* interpolate a function made of in samples at point x */
double sinc_approx(double in[], size_t in_sz, double x)
{
    int i;
    double res = 0.0;
    for (i = 0; i < in_sz; i++)
            res += in[i] * sinc(x - i);
    return res;
} /* sinc_approx */

/* do the actual resampling.  Change (in_sz - 1)/(out_sz - 1) if you
 * don't want the initial and final samples coincide, as is done here.
 */
void resample_sinc(
    double in[],
    size_t in_sz,
    double out[],
    size_t out_sz)
{
    int i;
    double dx = (double) (in_sz-1) / (out_sz-1);
    for (i = 0; i < out_sz; i++)
            out[i] = sinc_approx(in, in_sz, i*dx);
}

/* test case */
int main()
{
    double in[] = {
            0.0, 1.0, 0.5, 0.2, 0.1, 0.0,
    };

    const size_t in_sz = sizeof in / sizeof in[0];
    const size_t out_sz = 5;
    double out[out_sz];
    int i;

    for (i = 0; i < in_sz; i++)
            printf("in[%d] = %.6f\n", i, in[i]);
    resample_sinc(in, in_sz, out, out_sz);
    for (i = 0; i < out_sz; i++)
            printf("out[%.6f] = %.6f\n", (double) i * (in_sz-1)/(out_sz-1), out[i]);

    return EXIT_SUCCESS;
} /* main */

【讨论】:

  • 真的谢谢你的详细解释,很有用
【解决方案2】:

有不同的插值方式(见wikipedia

线性的应该是这样的:

std::array<int, 77> sampling(const std::array<int, 100>& a)
{
     std::array<int, 77> res;

     for (int i = 0; i != 76; ++i) {
         int index = i * 99 / 76;
         int p = i * 99 % 76;

         res[i] = ((p * a[index + 1]) + ((76 - p) * a[index])) / 76;
    }
    res[76] = a[99]; // done outside of loop to avoid out of bound access (0 * a[100])
    return res;
}

Live example

【讨论】:

    【解决方案3】:

    根据其位置的加权平均值创建 77 个新像素。

    作为一个玩具示例,考虑一下您想要对 2 进行二次采样的 3 像素情况。

    原始(表示为多维数组original,RGB 为 [0, 1, 2]):

    |----|----|----|
    

    子样本(表示为多维数组subsample,RGB 为 [0, 1, 2]):

    |------|------|
    

    在这里,可以直观地看到第一个子样本似乎是第一个原始像素的 2/3 和下一个原始像素的 1/3。

    对于第一个子样本像素subsample[0],您将其设为重叠的m 原始像素的RGB 平均值,在本例中为original[0] and original[1]。但我们以加权方式这样做。

    subsample[0][0] = original[0][0] * 2/3 + original[1][0] * 1/3  # for red
    subsample[0][1] = original[0][1] * 2/3 + original[1][1] * 1/3  # for green
    subsample[0][2] = original[0][2] * 2/3 + original[1][2] * 1/3  # for blue
    

    在此示例中,original[1][2] 是第二个原始像素的绿色分量。

    请记住,对于不同的子采样,您必须确定对子样本有贡献的一组原始单元格,然后进行归一化以找到每个单元格的相对权重。

    还有更复杂的图形技术,但这个很简单而且很有效。

    【讨论】:

    • 感谢 lollercoaster,非常清楚的解释。幸运的是,我的问题只是一维值,所以我可以跳过 RGB 元素。 3->2 看起来很直观,但 100 -> 77 则不然。我该如何努力实现这种精神上的飞跃?
    • 用笔和纸?尝试更多案例并发展对数字如何工作的直觉?这不是一个编程问题,只是一个坐下来思考的问题。
    • 您将需要一个函数,通过为每个 mth 样本分配每个 n 原始数字的标准化权重,将 n 原始数字转换为 m 新插槽。在实践中,不会有很多原始 bin 有贡献,但是您需要一个函数来逐步执行并逐个分配子样本数组。当您采用 1.0/100 的步长时,当您跨越 1.0/77 的每个倍数时,您需要以加权方式将最后一个子样本单元分配给最后一组原始单元。如果这种方法回答了您的问题,请将其标记为这样!
    【解决方案4】:

    一切都取决于您希望如何处理数据 - 您希望如何将其可视化。

    一种非常简单的方法是将图像渲染为 100 宽的图像,然后将图像平滑地缩小到更窄的尺寸。无论您使用什么图形/开发框架,都一定会支持这样的操作。

    不过,假设您的目标可能是保留数据的某些品质,例如最小值和最大值。在这种情况下,对于每个 bin,您将绘制一条较暗颜色的线,直到最小值,然后继续使用较浅的颜色,直到最大值。或者,您可以不只是将像素放在平均值处,而是从最小值到最大值画一条线。

    最后,您可能希望像只有 77 个值一样进行渲染 - 然后目标是以某种方式将 100 个值转换为 77。这意味着某种插值。线性或二次插值很容易,但会增加信号失真。理想情况下,您可能想在问题上抛出一个 sinc 插值器。可以在here 找到一个很好的列表。理论背景请看here

    【讨论】:

      猜你喜欢
      • 2012-06-06
      • 1970-01-01
      • 2019-10-22
      • 2014-11-10
      • 1970-01-01
      • 2011-07-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多