javascript 对数组的值进行平滑处理

62o28rlo  于 2022-12-21  发布在  Java
关注(0)|答案(3)|浏览(385)

如果我有一个数字数组,如[3,5,0,8,4,2,6],有没有办法“平滑”这些值,使它们彼此更接近,显示更小的差异?
我已经考虑过使用一种叫做高斯函数的东西来为一维情况(这是我的数组)设置数据窗口,但在实现它时遇到了麻烦。This thread似乎正好解决了我需要的问题,但我不明白用户nashilling(第二篇文章)是如何得出高斯矩阵值的。

**背景:**我正在开发一个音乐波形发生器(借用SoundCloud的设计),它可以将歌曲在时间 t 的振幅Map到相应的小节高度。不幸的是,这会产生很多噪音,而且当程序Map到一个微小的振幅时,会导致高度突然下降,这看起来特别难看。我基本上是想平滑小节高度,这样它们就不会变化太大。

我使用的语言是Javascript。

EDIT:对不起,让我更具体地“平滑”这些值。根据上面链接的线程,用户使用了一个数组

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

并使用高斯函数将其Map到

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

注意这些数字是如何相互接近的。

EDIT 2:成功了!感谢用户Awal Garg的算法,以下是结果:

  • 无平滑 * x1c 0d1x * 部分平滑 *
  • 最大平滑 *

EDIT 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);
vyswwuz2

vyswwuz21#

有趣的问题!
平滑这些值的算法显然可能会有很大的不同,但我的看法是:

"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。
我的方法基本上是预先计算数组中所有元素的平均值,并将其用作一个 * major * 因子,以计算新值以及当前元素值(即之前的元素值),和它后面的那个。我也使用了先前的值作为新计算的值,而不是原始数组中的值。请根据您的需要随意进行实验和修改。你也可以传递一个"方差"参数来控制元素之间的差异,降低这个参数会使元素之间的距离更近,因为它会降低平均值。
放松平滑的一个细微变化如下:

"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));

其不将平均值作为主要因素。
请随意尝试,希望对您有所帮助!

hsvhsicv

hsvhsicv2#

您描述的技术听起来像Gaussian blur的1D版本。将1D高斯数组的值乘以数组中的给定窗口,然后求和。
1.假设高斯阵列{.242,.399,.242}
1.要计算输入数组中位置n处的新值,请将输入数组中位置n-1、n和n+1处的值乘以(1)中的值,然后求和。例如,对于[3,5,0,8,4,2,6],n = 1:
标准差= 0.242 * 3 + 0.399 * 5 + 0.242 * 0 = 2.721
您可以更改高斯的方差以增加或减少模糊的影响。

8mmmxcuj

8mmmxcuj3#

我偶然发现这篇文章有同样的问题,试图从FFT平均值中获得平滑的圆波。我试过归一化,平滑和最疯狂的数学来扩展0和1之间的平均值数组的动态。这当然是可能的,但平均值的急剧增加仍然是一个麻烦,基本上使这些值无法直接显示。
相反,我使用FFT平均值来增加单独构造的干净正弦的振幅、频率和波长。设想以给定速度(频率)乘以当前平均值从右向左移动的穿过屏幕的正弦曲线,并且具有当前平均值乘以随后将被Map到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值驱动正弦函数,如下所示-假设该值被Map到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; 
  }
  
}

所以这个圆圈让我注意到,FFT样本随时间的变化就像一个波,特别是当我将这些圆圈的alpha设置为与它们的值相反时。-所以我尝试将圆圈(freq数组)中的值分配给cellSize/grid size数组,以便进行距离查找,瞧-出现了一个完美的有节奏正弦波,它可以具有潜在光谱的颜色或只是一些旋转值。

相关问题