对数字数组进行子采样
Subsampling an array of numbers
我有一系列 100 个整数值,我需要 reduce/subsample 到 77 个值以适应屏幕上预定义的 space。这给出了每像素 77/100 值的一小部分 - 不是很整洁。
假设 77 是固定的且无法更改,将 100 个数字二次采样到 77 的一些典型技术是什么。我觉得这将是一个锯齿状映射,我的意思是第一个新值是[0, 1] 的平均值然后下一个值是 [3],然后是 [4, 5] 的平均值等等。但是我如何获取此映射的模式?
我正在使用 C++,尽管我对技术比实现更感兴趣。
提前致谢。
根据位置的加权平均值创建 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]
是第二个原始像素的绿色分量。
请记住,对于不同的子抽样,您必须确定对子样本有贡献的原始单元格集,然后进行归一化以找到每个单元格的相对权重。
还有更复杂的图形技术,但这个很简单并且有效。
有不同的插值方式(参见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;
}
一切都取决于您希望如何处理数据 - 您希望如何将其可视化。
一个非常简单的方法是渲染一个 100 宽的图像,然后平滑地将图像缩小到更窄的尺寸。无论您使用什么 graphics/development 框架,都一定会支持这样的操作。
但是,假设您的目标可能是保留数据的某些质量,例如最小值和最大值。在这种情况下,对于每个 bin,您将绘制一条颜色较深的线直到最小值,然后继续使用较浅的颜色直到最大值。或者,您可以不只是在平均值处放置一个像素,而是从最小值到最大值画一条线。
最后,您可能希望像只有 77 个值一样进行渲染 - 那么目标是以某种方式将 100 个值转换为 77。这将意味着某种插值。线性或二次插值很容易,但会增加信号失真。理想情况下,您可能想在问题上使用 sinc 插值器。 here. For theoretical background, look here.
中有一个很好的列表
无论是下采样还是过采样,您都在尝试在非采样时间点上重建信号...因此您必须做出一些假设。
采样定理告诉你,如果你采样一个信号,知道它没有超过采样频率一半的频率分量,你可以在整个时间周期内连续和完全地恢复信号。有一种方法可以使用 sinc()
函数重建信号(这是 sin(x)/x
)
sinc()
(实际上是 sin(M_PI/Sampling_period*x)/M_PI/x
)是一个具有以下属性的函数:
- 它的值对于
x == 0.0
是 1,对于 x == k*Sampling_period
和 k == 0, +-1, +-2, ...
是 0
- 从
Sampling_period
导出的sampling_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 */
我有一系列 100 个整数值,我需要 reduce/subsample 到 77 个值以适应屏幕上预定义的 space。这给出了每像素 77/100 值的一小部分 - 不是很整洁。
假设 77 是固定的且无法更改,将 100 个数字二次采样到 77 的一些典型技术是什么。我觉得这将是一个锯齿状映射,我的意思是第一个新值是[0, 1] 的平均值然后下一个值是 [3],然后是 [4, 5] 的平均值等等。但是我如何获取此映射的模式?
我正在使用 C++,尽管我对技术比实现更感兴趣。
提前致谢。
根据位置的加权平均值创建 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]
是第二个原始像素的绿色分量。
请记住,对于不同的子抽样,您必须确定对子样本有贡献的原始单元格集,然后进行归一化以找到每个单元格的相对权重。
还有更复杂的图形技术,但这个很简单并且有效。
有不同的插值方式(参见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;
}
一切都取决于您希望如何处理数据 - 您希望如何将其可视化。
一个非常简单的方法是渲染一个 100 宽的图像,然后平滑地将图像缩小到更窄的尺寸。无论您使用什么 graphics/development 框架,都一定会支持这样的操作。
但是,假设您的目标可能是保留数据的某些质量,例如最小值和最大值。在这种情况下,对于每个 bin,您将绘制一条颜色较深的线直到最小值,然后继续使用较浅的颜色直到最大值。或者,您可以不只是在平均值处放置一个像素,而是从最小值到最大值画一条线。
最后,您可能希望像只有 77 个值一样进行渲染 - 那么目标是以某种方式将 100 个值转换为 77。这将意味着某种插值。线性或二次插值很容易,但会增加信号失真。理想情况下,您可能想在问题上使用 sinc 插值器。 here. For theoretical background, look here.
中有一个很好的列表无论是下采样还是过采样,您都在尝试在非采样时间点上重建信号...因此您必须做出一些假设。
采样定理告诉你,如果你采样一个信号,知道它没有超过采样频率一半的频率分量,你可以在整个时间周期内连续和完全地恢复信号。有一种方法可以使用 sinc()
函数重建信号(这是 sin(x)/x
)
sinc()
(实际上是 sin(M_PI/Sampling_period*x)/M_PI/x
)是一个具有以下属性的函数:
- 它的值对于
x == 0.0
是 1,对于x == k*Sampling_period
和k == 0, +-1, +-2, ...
是 0
- 从
Sampling_period
导出的sampling_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 */