计算 JavaScript 中 canvas 数组中位数的有效方法

Efficient way to compute the median of an array of canvas in JavaScript

我有一个来自视频的 N 帧的 N HTMLCanvasElements 数组,我想计算“中位数 canvas”,因为每个分量 (r, g, b, opacity) 每个像素的是所有 canvases.

中相应分量的中值

视频帧为 1280x720,因此每个 canvas 的像素数据(通过 canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height).data 获得)是长度为 3.686.400 的 Uint8ClampedArray。

计算中位数的简单方法是:

但是它非常慢,即使是 4 canvases。

是否有有效的方法(或现有代码)来做到这一点?我的问题与 非常相似,但我需要在 JavaScript 中解决这个问题,而不是 Python。

注意:对于 b),我使用 d3.median(),据我所知,它不适用于类型化数组,因此它意味着转换为数字,然后转换回 Uint8Clamped。

注2:我不太了解GLSL着色器,但也许using the GPU would be a way to get faster results. It would require to pass data from the CPU to the GPU不过,如果重复这样做需要时间。

注3:天真的解决方案是:https://observablehq.com/@severo/compute-the-approximate-median-image-of-a-video

你写了

I use d3.median() which doesn't work on typed arrays…

虽然这不完全正确,但它引导了正确的方向。内部 d3.median() uses the d3.quantile() 方法是这样开始的:

export default function quantile(values, p, valueof) {
  values = Float64Array.from(numbers(values, valueof));

如您所见,这实际上确实使用了类型化数组,只是不是您的 Uint8ClampedArray but a Float64Array。因为浮点运算比它的整数对应物(包括转换本身)计算密集得多,这对代码的性能有显着影响。在紧密循环中执行大约 300 万次会降低解决方案的效率。

由于您是从 Uint8ClampedArray 中检索所有像素值,因此您可以确定您处理的始终是整数。也就是说,从 d3.median()d3.quantile():

派生的自定义 function median(values) 相当容易
function median(values) {
  // No conversion to floating point values needed.
  if (!(n = values.length)) return;
  if (n < 2) return d3.min(values);
  var n,
      i = (n - 1) * 0.5,
      i0 = Math.floor(i),
      value0 = d3.max(d3.quickselect(values, i0).subarray(0, i0 + 1)),
      value1 = d3.min(values.subarray(i0 + 1));
  return value0 + (value1 - value0) * (i - i0);
}

除了在第一行消除有问题的转换之外,此实现还应用了一些更多的微优化,因为在您的情况下,您总是在寻找 2 分位数(即中位数)。乍一看这似乎并不多,但在一个循环中执行数百万次确实会有所不同。

只需对您自己的代码进行最少的更改,您就可以这样调用它:

// medianImageData.data[i] = d3.median(arr);   Instead of this use line below.
medianImageData.data[i] = median(arr);

看看我的工作 fork 你的 Observable notebook。