如何让我的 JavaScript 程序 运行 更快?

How can I make my JavaScript programs run faster?

好的,所以我正在研究一种检测系统,我会将摄像头对准屏幕,它必须找到红色物体。我可以用图片成功地做到这一点,但问题是加载需要几秒钟。我希望能够对实时视频执行此操作,因此我需要它来立即找到对象。这是我的代码:

video.addEventListener('pause', function () {

let reds = [];

for(x=0; x<= canvas.width; x++){
for(y=0; y<= canvas.height; y++){

let data = ctx.getImageData(x, y, 1, 1).data;
let rgb = [ data[0], data[1], data[2] ];

if (rgb[0] >= rgb[1] && rgb[0] >=rgb[2] && !(rgb[0]>100 && rgb[1]>100 && rgb[2]>100) && rgb[1]<100 && rgb[2]<100 && rgb[0]>150){
reds[reds.length] = [x, y]
}

let addedx = 0
let addedy = 0

for(i=0; i<reds.length; i++){
    addedx = addedx + reds[i][0]
    addedy = addedy + reds[i][1]
}

let center = [addedx/reds.length, addedy/reds.length]

ctx.rect(center[0]-5, center[1]-5, 10, 10)
ctx.stroke() 

}, 0);

是的,我知道它很乱。 for 循环有什么慢的地方吗?我知道我正在遍历数千个像素,但这是我能想到的唯一方法。

我会 运行 webassembly 模块中的检测算法。因为它只是像素数据,所以就在它的胡同里。

然后您可以将单个帧传递给 wasm 模块的不同实例。

就直接回答您的问题而言,我会抓取整个帧,而不是一次抓取 1 个像素,否则您可能会从不同的帧中采样像素。然后你可以将该框架提交给一个工作人员,你甚至可以将框架分开并将它们发送给不同的工作人员(或如前所述的 wasm 模块)

此外,由于您有一个数组,因此您可以使用 Arrray.map 和 Array.reduce 通过测试相邻像素而不是所有比较来获取红色值以及它们的大小.不确定它是否会更快但值得一试。

对于速度,你应该考虑你所有的过程:

  • 您的语言越接近机器语言,您的结果就会越好。这么说吧,C++更适合算法。
  • CPU 速度是你的朋友。在 Atom 处理器或 i7 处理器上启动代码就像白天和黑夜一样。此外,某些类型的处理器专用于视觉,例如 VPU

对于您的代码:

希望对你有帮助:)

如前所述,Javascript 不是这项任务的最佳表现。但是,我注意到了一些可能会减慢您速度的事情。

  1. 您一次抓取一个像素的图像数据。由于此方法可以return整帧,所以一次即可。

  2. 优化您的 isRed 条件:

rgb[0] >= rgb[1] &&                                // \
rgb[0] >= rgb[2] &&                                //  >-- This is useless
!(rgb[0] > 100 && rgb[1] > 100 && rgb[2] > 100) && // /
rgb[1] < 100 && // \
rgb[2] < 100 && //  >-- These 3 conditions imply the others
rgb[0] > 150    // /
  1. 您在每个像素后计算 for 循环中的 center,但只有在处理整个帧后才有意义。

  2. 由于视频源来自相机,也许您不需要查看每个像素。也许每 5 个像素就足够了?这就是下面的例子所做的。调整一下。

包含这些优化的演示

节点:此演示包括对 this answer 中代码的改编,以将视频复制到 canvas

const video  = document.getElementById("video"),
      canvas = document.getElementById("canvas"),
      ctx    = canvas.getContext("2d");

let width,
    height;

// To make this demo work
video.crossOrigin = "Anonymous";

// Set canvas to video size when known
video.addEventListener("loadedmetadata", function() {
  width = canvas.width = video.videoWidth;
  height = canvas.height = video.videoHeight;
});

video.addEventListener("play", function() {
  const $this = this; // Cache

  (function loop() {
    if (!$this.paused && !$this.ended) {
      ctx.drawImage($this, 0, 0);
      
      const reds = [],
            data = ctx.getImageData(0, 0, width, height).data,
            len  = data.length;

      for (let i = 0; i < len; i += 5 * 4) { // 4 because data is made of RGBA values
        const rgb = data.slice(i, i + 3);

        if (rgb[0] > 150 && rgb[1] < 100 && rgb[2] < 100) {
          reds.push([i / 4 % width, Math.floor(i / 4 / width)]); // Get [x,y] from i
        }
      }

      if (reds.length) { // Can't divide by 0
        const sums = reds.reduce(function (res, point) {
          return [res[0] + point[0], res[1] + point[1]];
        }, [0,0]);

        const center = [
          Math.round(sums[0] / reds.length),
          Math.round(sums[1] / reds.length)
        ];

        ctx.strokeStyle = "blue";
        ctx.lineWidth = 10;
        ctx.beginPath();
        ctx.rect(center[0] - 5, center[1] - 5, 10, 10);
        ctx.stroke();
      }

      setTimeout(loop, 1000 / 30); // Drawing at 30fps
    }
  })();
}, 0);
video, canvas { width: 250px; height: 180px; background: #eee; }
<video id="video" src="https://shrt-statics.s3.eu-west-3.amazonaws.com/redball.mp4" controls></video>
<canvas id="canvas"></canvas>