数组值平滑处理

20
如果我有一个数字数组,例如[3, 5, 0, 8, 4, 2, 6],是否有一种方法可以“平滑”这些值,使它们更接近彼此并显示较小的差异?
我研究了使用高斯函数对数据进行窗口化的方法,针对的是一维情况,即我的数组,但我在实现时遇到了麻烦。这个帖子似乎完全解决了我需要的问题,但我不明白用户naschilling(第二篇帖子)是如何得出高斯矩阵值的。
背景:我正在开发一个音乐波形生成器(借鉴自SoundCloud的设计),将歌曲在时间t的振幅映射到相应的条形高度。不幸的是,存在很多噪声,当程序映射一个微小的振幅时,会导致高度突然下降,看起来特别丑陋。我基本上想平滑条形高度,使它们不那么变化。
我使用的语言是Javascript。

编辑:抱歉,让我更具体地说明“平滑”值的含义。根据上面链接的帖子,一个用户取了一个数组。

[10.00, 13.00, 7.00, 11.00, 12.00, 9.00, 6.00, 5.00]

并使用高斯函数将其映射到

[ 8.35,  9.35, 8.59,  8.98,  9.63, 7.94, 5.78, 7.32]

注意数字之间的距离更接近了。 编辑2:它起作用了!感谢用户Awal Garg的算法,以下是结果: 无平滑处理 一些平滑处理 最大平滑处理 编辑3:这是我的JS最终代码。我对其进行了微调,使数组的第一个和最后一个元素能够通过“环绕”数组找到其邻居,而不是调用自身。
var array = [10, 13, 7, 11, 12, 9, 6, 5];

function smooth(values, alpha) {
    var weighted = average(values) * alpha;
    var smoothed = [];
    for (var i in values) {
        var curr = values[i];
        var prev = smoothed[i - 1] || values[values.length - 1];
        var next = curr || values[0];
        var improved = Number(this.average([weighted, prev, curr, next]).toFixed(2));
        smoothed.push(improved);
    }
    return smoothed;
}

function average(data) {
    var sum = data.reduce(function(sum, value) {
        return sum + value;
    }, 0);
    var avg = sum / data.length;
    return avg;
}

smooth(array, 0.85);

1
我不确定我理解你的意思,平滑化数值。 - Sterling Archer
3个回答

11

有趣的问题!

平滑数值的算法显然会有很大的变化,但这是我的看法:

"use strict";
var array = [10, 13, 7, 11, 12, 9, 6, 5];

function avg (v) {
  return v.reduce((a,b) => a+b, 0)/v.length;
}

function smoothOut (vector, variance) {
  var t_avg = avg(vector)*variance;
  var ret = Array(vector.length);
  for (var i = 0; i < vector.length; i++) {
    (function () {
      var prev = i>0 ? ret[i-1] : vector[i];
      var next = i<vector.length ? vector[i] : vector[i-1];
      ret[i] = avg([t_avg, avg([prev, vector[i], next])]);
    })();
  }
  return ret;
}

function display (x, y) {
  console.clear();
  console.assert(x.length === y.length);
  x.forEach((el, i) => console.log(`${el}\t\t${y[i]}`));
}

display(array, smoothOut(array, 0.85));

注意:它使用一些 ES6 功能,如箭头函数和模板字符串。Firefox 35+ 和 Chrome 45+ 应该可以正常工作。否则请使用 babel repl。

我的方法基本上是预先计算数组中所有元素的平均值,并将其用作计算新值的主要因素,同时将当前元素值、其前一个元素值和其后一个元素值纳入考虑。我还使用先前的值作为“新”计算出的值,而不是来自原始数组的值。根据您的需求随意实验和修改。您还可以传递一个“方差”参数来控制元素之间的差异。降低它会使元素更加接近,因为它减小了平均值的值。

稍微变化一下以放松平滑处理:

"use strict";
var array = [10, 13, 7, 11, 12, 9, 6, 5];

function avg (v) {
  return v.reduce((a,b) => a+b, 0)/v.length;
}

function smoothOut (vector, variance) {
  var t_avg = avg(vector)*variance;
  var ret = Array(vector.length);
  for (var i = 0; i < vector.length; i++) {
    (function () {
      var prev = i>0 ? ret[i-1] : vector[i];
      var next = i<vector.length ? vector[i] : vector[i-1];
      ret[i] = avg([t_avg, prev, vector[i], next]);
    })();
  }
  return ret;
}

function display (x, y) {
  console.clear();
  console.assert(x.length === y.length);
  x.forEach((el, i) => console.log(`${el}\t\t${y[i]}`));
}

display(array, smoothOut(array, 0.85));

不以平均值为主要因素。

随意尝试,希望能帮到你!


8
您所描述的技术听起来像是一维版本的高斯模糊。将一维高斯数组的值乘以给定的窗口内的数组并求和,例如:
  1. 假设一个高斯数组 {.242, .399, .242}
  2. 要计算输入数组中位置n的新值-将输入数组中n-1、n和n+1处的值与(1)中的值相乘并求和。例如对于[3, 5, 0, 8, 4, 2, 6],n = 1:

    n1 = 0.242 * 3 + 0.399 * 5 + 0.242 * 0 = 2.721

您可以改变高斯函数的方差来增加或减少模糊的影响。

这非常接近我所寻找的东西。你是如何想出高斯数组 [.242, .399, .242] 的? - robinnnnn
2
它们是高斯函数在-1、0和1处(均值为0,标准差为1)的离散值。 - copeg
抱歉,您能具体说明如何计算吗?我假设您使用了这个函数。我理解均值和标准差的位置;对于x,您是否输入了-1、0和1? - robinnnnn
我知道这已经过时了,但我认为你可以在这里查找值:https://keisan.casio.com/exec/system/1180573188 - electronix384128

0
我偶然发现了这篇文章,与尝试从FFT平均值中实现平滑的圆形波浪的问题相同。 我尝试过归一化、平滑和最疯狂的数学方法来将一组平均值的动态范围在0到1之间分布。当然是可能的,但是平均值的急剧增加仍然是一个问题,基本上使得这些值不适合直接显示。
相反,我使用FFT平均值来增加幅度、频率和波长,以单独结构的干净正弦波为基础。 想象一条正弦曲线横跨屏幕,以给定速度(频率)向右移动,乘以当前平均值,并具有当前平均值的振幅乘以将要映射到0,1的任何东西,以最终确定“波”的z。
计算元素大小、颜色、移位或任何可视化“波”的函数将必须基于距离中心的距离和某个包含每个距离的值的数组,例如一定数量的平均值。
那个非常相同的数组可以改为用正弦波的值来填充 - 受FFT平均值的影响 - 它们本身因此无需平滑处理并且可以保持不变。 效果是令人愉悦的干净正弦波似乎受到声音“能量”的驱动。

就像这样 - 'rings'是一个数组,距离函数使用它来读取“波”的x、y位置的“z”值。

const wave = {
          y: height / 2,
          length: 0.02,
          amplitude: 30,
          frequency: 0.5
      }
      //var increment = wave.frequency;
      var increment = 0;
      function sinewave(length,amplitude,frequency) { 
        
        ctx.strokeStyle = 'red';        
        ctx.beginPath();
        ctx.moveTo(0, height / 2);
        for (let i = 0; i < width; i+=cellSize) {
          //ctx.lineTo(i, wave.y + Math.sin(i * wave.length + increment) * wave.amplitude)
          ctx.lineTo(i, wave.y + Math.sin(i * length + increment) * amplitude);
          rings.push( map( Math.sin(i * length + increment) * amplitude,0,20,0.1,1) );
          rings.shift();
        }  
        ctx.stroke();
        increment += frequency;
      }

该函数在每帧(从绘制中)被调用,使用当前平均fft值驱动正弦函数,如下所示 - 假设该值映射为0,1:

sinewave(0.006,averg*20,averg*0.3)

允许波长或频率的波动值可以产生一些视觉上令人愉悦的效果。然而,“波浪”的运动永远不会看起来自然。

在我的情况下,我已经实现了接近的结果。要使正弦波似乎受到每个“节拍”的驱动,您需要进行节拍检测,以确定“波浪”所要可视化的“声音”的确切速度。

在fft频谱较低范围内较大峰值之间的距离的连续平均可能适用于设置半固定频率 - 与edm一起使用......

我知道,问题是关于平滑数组值的。请原谅我改变话题。我只是认为目标“声波”是一个有趣的目标,可以通过不同的方式实现。并且,为了完整起见,这里有一个简单地为每个fft绘制圆圈并根据音量分配颜色的位。线宽相对于总半径和音量总和,这非常好:

//col generator
function getCol(n,m,f){
  var a             = (PIx5*n)/(3*m) + PIdiv2;
  var r             = map(sin(a),-1,1,0,255);
  var g             = map(sin(a - PIx2/3),-1,1,0,255);
  var b             = map(sin(a - PIx4/3),-1,1,0,255);
  return ("rgba(" + r + "," + g + "," + b + "," + f + ")");
}


//draw circles for each fft with linewidth and colour relative to value

function drawCircles(arr){
  var nC      = 20; //number of elem from array we want to use
  var cAv     = 0;
  var cAvsum  = 0;

  //get the sum of all values so we can map a single value with regard to this
  for(var i = 0; i< nC; i++){
    cAvsum += arr[i];
  }
  cAv         = cAvsum/nC;
  
  var lastwidth = 0;

  //draw a circle for each elem from array
  //compute linewith a fraction of width relative to value of elem vs. sum of elems
  for(var i = 0; i< nC; i++){
    ctx.beginPath();
    var radius = lastwidth;//map(arr[i]*2,0,255,0,i*300);
    //use a small col generator to assign col - map value to spectrum
    ctx.strokeStyle = getCol(map(arr[i],0,255,0,1280),1280,0.05);
    //map elem value as fraction of elem sum to linewidth/total width of outer circle
    ctx.lineWidth = map(arr[i],0,cAvsum,0,width);
    //draw
    ctx.arc(centerX, centerY, radius, 0, Math.PI*2, false); 
    ctx.stroke();
    //add current radius and linewidth to lastwidth
    var lastwidth = radius + ctx.lineWidth/2; 
  }
  
}

Codepen在这里:https://codepen.io/sumoclub/full/QWBwzaZ 欢迎各种建议。


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