对数字数组进行子采样
Subsampling an array of numbers
我有一系列100个整数值,我需要将其减少/subsample到77个值,以便适合屏幕上预定义的空间。这给出了每像素77/100个值的一小部分——不是很整洁。
假设77是固定的,不能改变,将100个数字子采样到77的一些典型技术是什么?我感觉这将是一个锯齿映射,我的意思是第一个新值是[0,1]的平均值,然后下一个值是[3],然后是[4,5]的平均值,等等。但是我如何获得这个映射的模式呢?
我正在用c++工作,尽管我对技术比对实现更感兴趣。
提前感谢。
无论是下采样还是过采样,您都试图在时间上重构非采样点上的信号…所以你必须做一些假设。
采样定理告诉你,如果你对一个信号进行采样,知道它没有超过采样频率一半的频率成分,你可以在整个定时周期内连续地完全恢复信号。有一种方法来重建信号使用sinc()
函数(这是sin(x)/x
)
sinc()
(实际上是sin(M_PI/Sampling_period*x)/M_PI/x
)是一个具有以下性质的函数:
-
x == 0.0
的值为1,x == k*Sampling_period
的值为0,k == 0, +-1, +-2, ...
- 没有超过
Sampling_period
采样频率一半的频率分量。
因此,如果您认为函数F_x(x) = Y[k]*sinc(x/Sampling_period - k)
的和是sinc函数,等于位置k
的采样值和其他采样值的0,并对样本中的所有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] = %.6fn", i, in[i]);
resample_sinc(in, in_sz, out, out_sz);
for (i = 0; i < out_sz; i++)
printf("out[%.6f] = %.6fn", (double) i * (in_sz-1)/(out_sz-1), out[i]);
return EXIT_SUCCESS;
} /* main */
有不同的插值方法(参见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;
}
实例
根据其位置的加权平均值创建77个新像素。
作为一个简单的例子,考虑一个3像素的情况,你想要将其子采样到2。
Original(表示为多维数组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]
是第二个原始像素的绿色分量。
请记住,对于不同的子抽样,您必须确定对子样本有贡献的原始单元的集合,然后规范化以找到每个单元的相对权重。
有更复杂的图形技术,但这是一个简单和工作。
一切都取决于您希望如何处理数据-您希望如何可视化它。
一个非常简单的方法是渲染到100宽的图像,然后平滑缩放图像到更窄的尺寸。无论您使用的是哪种图形/开发框架,都肯定支持这样的操作。
但是,假设您的目标可能是保留数据的某些质量,例如最小值和最大值。在这种情况下,对于每个箱子,您绘制一条较深的颜色线直到最小值,然后继续使用较浅的颜色直到最大值。或者,你可以不只是在平均值处画一个像素,而是在最小值和最大值之间画一条线。
最后,您可能希望只呈现77个值—然后目标是以某种方式将100个值转换为77个。这意味着某种插值。线性或二次插值很容易,但会增加信号的失真。理想情况下,您可能希望在这个问题上使用一个正弦插值器。你可以在这里找到一个很好的清单。
- 通过指向指针数组的指针访问子类的属性
- 遍历二维数组的所有子数组
- 这种用于查找连续子数组中最大和的递归算法有什么优势吗?
- 在子数组中查找多个查询的不同(唯一)值的数量
- 如何在子类中重新定义数组大小?
- 查找连续子数组的第二小和
- 你能将数组的子部分传递给函数吗?C++
- 在大整数数组中查找子数组
- 如何对0,1,..中的k个随机数进行采样..,n-1,而不缓存到数组
- 玩数字编程挑战(子数组的平均值)
- 对于多个查询,查找在 l 到 r 范围内具有相同元素的最长公共子数组
- 关于骰子概率和 2D 数组的问题
- 计算在 O(n) 中具有负积的子数组总数
- 使用分而治之的最大总和子数组,为什么要以不同的方式使用 for 循环,答案是错误的?
- 给定一个数组,找到具有 m 个奇数的子数组的数量?
- 非重叠连续子数组的最大长度总和
- 用于查找连续子数组的最大总和的代码使测试用例失败
- 将类型化数组写入子进程 stdin 无法正常工作
- 在C++中初始化抽象基类的子类数组
- 在子字符串数组中高效地查找字符串