上采样:在向量的每个相邻元素之间插入额外的值。

5

假设我们有一个由20个浮点数组成的向量V。是否可以在每对这些浮点数之间插入值,使得向量V变成恰好50个数字的向量。

插入的值应该是我决定插入两个值之间的中点的随机数。

我尝试了以下方法:

 vector<double> upsample(vector<double>& in)
 {

    vector<double> temp;
    for (int i = 1; i <= in.size() - 1 ; i++)
    {
        double sample = (in[i] + in[i - 1]) / 2;
        temp.push_back(in[i - 1]);
        temp.push_back(sample);
    }
    temp.push_back(in.back());
    return temp;
}

使用此函数,输入向量元素会增加2(n)- 1(20个元素变为39个)。 输入向量可能具有不同的大小小于50。
我认为可以通过在两个元素之间随机插入多个值来获得大小为50的向量(例如,在V [0]和V [1]之间插入3个值,在V [3]和V [4]之间插入1个值等)。这可行吗?
请问如何执行此操作? 谢谢。

应该尽可能统一地将数组进行上采样吗?也就是说,如果在元素n和n + 1之间添加了5个项目,但在n + 1和n + 2之间没有添加任何项目,这是否会成为问题? - SilverTear
例如,如果V={0.1,0.2,0.3},则期望的输出可能类似于V'={0.1,0.12,0.14,0.17,0.2,0.27,0.29,0.3}。 - student_11
2
你也可以检查一下Bresenham的线算法(将几何线条别名到像素光栅基本上是相同的别名问题...你有20个输入值=假设为“y轴”,你想要50个输出值=“x轴”,所以你正在绘制每条起点为新行的线(+50,+20),其余一个或两个点是生成的新值。如果你想尽可能均匀地将新值分布在整个范围内,重要的是要理解,由于“别名”它只对非常特定的值完美,因此首先决定如何别名。 - Ped7g
你可以使用Bresenham算法的数学原理来决定何时填充(使用新生成的值)和何时复制(原始输入值)。如何生成填充与此无关。如果您使用简单的线性插值(即真正绘制线条:)),则大部分放大的数据将显示此行为(如果数据很重要并且有人需要知道哪些是原始数据,则可能很好),添加一些随机噪声(例如perlin噪声2D高度图)可以帮助如果数据用于某些视觉效果,例如凸起映射等。 - Ped7g
2
在数字信号处理中,一个标准的方法是,如果你想将一个信号从X Hz缩放到Y Hz,那么做法就是上采样到一个公共倍数,然后再降采样。在这种情况下,就需要上采样到100 Hz,然后丢弃每隔一个样本。这里的好处是你只需要使用整数,而且无论是上采样还是下采样都是相同的过程。问题在于,如果要转换的频率之间没有共同的质因数(例如,从49 Hz到50 Hz需要上采样到2450 Hz作为中间值),这个过程会变得非常低效。 - Frodyne
显示剩余6条评论
1个回答

3
因为我很好奇如何获得权重比例,所以我自己进行了一些数学计算(例如线性上采样到公共倍数,然后从大数组中提取目标值的权重,但不创建大数组,只使用权重知道左+右元素对特定值的贡献量)。示例代码总是通过简单的加权平均值创建新值(即40%的123.4与60%的567.8将给出“上采样”值390.04),没有随机用于修改上采样值(此部分留给OP)。
权重比例如下:
如果大小为M的向量被上采样到大小为N(M <= N)(此“上采样”将始终保留输入向量的第一个和最后一个元素,这些在此提案中是“固定”的)
那么每个上采样元素都可以视为在某些原始元素[i,i + 1]之间的某个位置。
如果我们将源元素[i,i + 1]之间的“距离”声明为d = N-1,则上采样元素之间的距离始终可以表示为某个j / d,其中j:[0,d](当j为实际d时,它正好在“ i + 1”元素处,可以认为与[j]=0相同,但具有[i + 1,i + 2]源元素)
然后,两个上采样元素之间的距离为M-1。
因此,当源向量大小为4,上采样向量大小应为5时,上采样元素的比例为[[4/4,0/4],[1/4,3/4],[2/4 ,2/4],[3/4,1/4],[0/4,4/4]],其中indices into vector是[[0,1],[0,1],[1,2],[2,3],[2,3]]。 (源元素之间的“距离”为5-1 = 4,这是将权重规范化为“/ 4”的原因,“上采样”元素之间的“距离”为4-1 = 3,这就是为什么比例在每个步骤中移位[-3,+3]的原因)。
很抱歉我的描述远非“显而易见”(在我头脑中找出来后的感觉),但如果您将其放入电子表格中并进行玩耍,希望它会有些意义。或者也可以调试代码以更好地理解上述咕哝声如何转换为实际代码。
代码示例1,如果权重恰好全部在源元素上,则仅“复制”源元素(即,在示例数据中,仅第一个和最后一个元素被“复制”,其余的上采样元素是原始值的加权平均值)。
#include <iostream>
#include <vector>
#include <cassert>

static double get_upscale_value(const size_t total_weight, const size_t right_weight, const double left, const double right) {
    // do the simple weighted average for demonstration purposes
    const size_t left_weight = total_weight - right_weight;
    return (left * left_weight + right * right_weight) / total_weight;
}

std::vector<double> upsample_weighted(std::vector<double>& in, size_t n)
{
    assert( 2 <= in.size() && in.size() <= n ); // this is really only upscaling (can't downscale)

    // resulting vector variable
    std::vector<double> upscaled;
    upscaled.reserve(n);

    // upscaling factors variables and constants
    size_t index_left = 0;                      // first "left" item is the in[0] element
    size_t weight_right = 0;                    // and "right" has zero weight (i.e. in[0] is copied)
    const size_t in_weight = n - 1;             // total weight of single "in" element
    const size_t weight_add = in.size() - 1;    // shift of weight between "upscaled" elements

    while (upscaled.size() < n) {               // add N upscaled items
        if (0 == weight_right) {
            // full weight of left -> just copy it (never tainted by "upscaling")
            upscaled.push_back(in[index_left]);
        } else {
            // the weight is somewhere between "left" and "right" items of "in" vector
            // i.e. weight = 1..(in_weight-1) ("in_weight" is full "right" value, never happens)
            double upscaled_val = get_upscale_value(in_weight, weight_right, in[index_left], in[index_left+1]);
            upscaled.push_back(upscaled_val);
        }
        weight_right += weight_add;
        if (in_weight <= weight_right) {
            // the weight shifted so much that "right" is new "left"
            ++index_left;
            weight_right -= in_weight;
        }
    }

    return upscaled;
}

int main(int argc, const char *argv[])
{
    std::vector<double> in { 10, 20, 30 };
//    std::vector<double> in { 20, 10, 40 };

    std::vector<double> upscaled = upsample_weighted(in, 14);
    std::cout << "upsample_weighted from " << in.size() << " to " << upscaled.size() << ": ";
    for (const auto i : upscaled) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
    return 0;
}

输出:

从3到14进行加权上采样: 10 11.5385 13.0769 14.6154 16.1538 17.6923 19.2308 20.7692 22.3077 23.8462 25.3846 26.9231 28.4615 30

代码示例2,这个示例将“复制”每个源元素,并仅在填充间隙时使用加权平均值,以尽可能保留原始数据(代价是结果不是线性地放大原始数据集,而是“别名”为目标大小定义的“网格”):

(代码与第一个示例几乎相同,除了上采样器中的一行if语句)

#include <iostream>
#include <vector>
#include <cassert>

static double get_upscale_value(const size_t total_weight, const size_t right_weight, const double left, const double right) {
    // do the simple weighted average for demonstration purposes
    const size_t left_weight = total_weight - right_weight;
    return (left * left_weight + right * right_weight) / total_weight;
}

// identical to "upsample_weighted", except all source values from "in" are copied into result
// and only extra added values (to make the target size) are generated by "get_upscale_value"
std::vector<double> upsample_copy_preferred(std::vector<double>& in, size_t n)
{
    assert( 2 <= in.size() && in.size() <= n ); // this is really only upscaling (can't downscale)

    // resulting vector variable
    std::vector<double> upscaled;
    upscaled.reserve(n);

    // upscaling factors variables and constants
    size_t index_left = 0;                      // first "left" item is the in[0] element
    size_t weight_right = 0;                    // and "right" has zero weight (i.e. in[0] is copied)
    const size_t in_weight = n - 1;             // total weight of single "in" element
    const size_t weight_add = in.size() - 1;    // shift of weight between "upscaled" elements

    while (upscaled.size() < n) {               // add N upscaled items
/* ! */ if (weight_right < weight_add) { /* ! this line is modified */
            // most of the weight on left -> copy it (don't taint it by upscaling)
            upscaled.push_back(in[index_left]);
        } else {
            // the weight is somewhere between "left" and "right" items of "in" vector
            // i.e. weight = 1..(in_weight-1) ("in_weight" is full "right" value, never happens)
            double upscaled_val = get_upscale_value(in_weight, weight_right, in[index_left], in[index_left+1]);
            upscaled.push_back(upscaled_val);
        }
        weight_right += weight_add;
        if (in_weight <= weight_right) {
            // the weight shifted so much that "right" is new "left"
            ++index_left;
            weight_right -= in_weight;
        }
    }

    return upscaled;
}

int main(int argc, const char *argv[])
{
    std::vector<double> in { 10, 20, 30 };
//    std::vector<double> in { 20, 10, 40 };

    std::vector<double> upscaled = upsample_copy_preferred(in, 14);
    std::cout << "upsample_copy_preferred from " << in.size() << " to " << upscaled.size() << ": ";
    for (const auto i : upscaled) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
    return 0;
}

输出:

将3进行上采样复制到14:10、11.5385、13.0769、14.6154、16.1538、17.6923、19.2308、20、22.3077、23.8462、25.3846、26.9231、28.4615、30

(请注意,示例1中的“20.7692”在这里只是原始样本的副本“20”,即使在那个点上如果考虑线性插值,“30”也有一些小权重。)


非常感谢您的帮助。似乎加权上采样对我的程序给出了更准确的输出。但我发现理解您代码的细节有点困难。 - student_11
@student_11 在这里的评论中询问特定部分即可...我尽量保持简单易懂,如果这是我的应用程序的“生产”代码,我会更改一些部分以获得更好的性能,但阅读起来可能会稍微困难一些,所以你的反馈对我非常有价值,这对我来说总是非常有趣的,听听其他人如何思考代码,以及他们觉得哪些部分比较困难。 :) - Ped7g
但是主要原则与原始评论中提到的相同,Frodyne所写的只是我提供的代码的不太有效的变体,在考虑了他的评论之后,我终于(经过那么多年)意识到Bresenham如何得出他的线算法的数学原理,它实际上是相同的原则。当您有两个值M,N时,两个原则都以M * N(或优化为公共倍数)作为“总步数”开始,因为这样的值可以被分为M和N步而没有剩余。因此,这就像一般的“滴答声”值[0..M * N],其他所有内容都与其相关。 - Ped7g
感谢您宝贵的评论。我在我的项目中使用了您的代码(经过一些修改),它运行得非常好。所以我还没有对其进行调试。在调试并深入研究Bresenham算法之后,我会就代码提出我的实际问题:)再次感谢您。 - student_11

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