计算 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。
计算中位数的简单方法是:
- 准备一个长度为 3.686.400 的结果 Uint8ClampedArray
- 准备一个长度为 N
的临时 Uint8ClampedArray
- 从 0 循环到 3.686.399
- a) 遍历 N canvases 以填充数组
- b) 计算数组的中位数
- c) 将中位数存储到结果数组
但是它非常慢,即使是 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。
我有一个来自视频的 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。
计算中位数的简单方法是:
- 准备一个长度为 3.686.400 的结果 Uint8ClampedArray
- 准备一个长度为 N 的临时 Uint8ClampedArray
- 从 0 循环到 3.686.399
- a) 遍历 N canvases 以填充数组
- b) 计算数组的中位数
- c) 将中位数存储到结果数组
但是它非常慢,即使是 4 canvases。
是否有有效的方法(或现有代码)来做到这一点?我的问题与
注意:对于 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。