【问题标题】:Efficiently Iterating through image in Javascript在 Javascript 中有效地遍历图像
【发布时间】:2018-02-14 21:37:00
【问题描述】:

我正在用 JavaScript 做一些图像处理项目,我需要遍历每个图像像素并进行一些其他处理以实现我的目标。
我正在使用画布获取图像像素数据数组。
对于尺寸为 500x300 像素的小图像,它可以正常工作并花费可接受的时间。但是对于大型图像,例如 3000x3000 像素的尺寸,迭代过程正在成为瓶颈并且需要很长时间,例如 10-12 秒。

那么有什么方法或技巧可以用来减少迭代步骤中使用的时间吗?

这是我的想法:我正在尝试使用并行网络工作者(假设为 4)来遍历图像数据的相等部分:(例如 0-[len/4][len/4]+1-[len/2][len/2]+1 - [len*3/4][len*3/4]+1 - len) 其中len是图像数据数组的大小。

我怀疑这种方法会更省时,因为 Javascript 是单线程的。

 function rgb2grey(pix,offset){
        return (0.2989*pix[offset] + 0.5870*pix[offset+1] + 
    0.1140*pix[offset+2]);

}

function imgcompare(fileData1,fileData2,targetpix){
        var len = pix.length;
        for (var j = 0; j <len; j+=4) {
                var grey1 = rgb2grey(fileData1,j);
            var grey2 = rgb2grey(fileData2,j);
            if(grey1!=grey2){
                targetpix[j] = 255;
                targetpix[j+1] = targetpix[j+2] = 0;
            }
            else{
                targetpix[j] = fileData1[j];
                targetpix[j+1] = fileData1[j+1];
                targetpix[j+2] = fileData1[j+2];
            }       
            targetpix[j+3] = fileData1[j+3];
        }
}

【问题讨论】:

  • 在所有情况下,尝试尾调用优化循环。网络工作者将允许您将工作委托给其他线程,或使用类似 gpujs 的东西;如果这是在服务器端完成的,请考虑将这项工作委托给具有更好并行编程支持的其他语言。
  • @flamelite:如果您不发布minimal reproducible example,我们将无法帮助您。问题很可能不在于您迭代每个像素这一事实,而在于您每次迭代所做的事情。
  • @flamelite 然后我会避免对网络工作者和 gpujs 发狂,因为它会显着降低用户的计算机速度;仅使用一个或最多两个 web worker 可以帮助减轻主 ui 线程上的负载,从而改善 UX。但是,我会尝试制作某种排队机制,以免一次处理太多图像。您需要避免阻塞用户的 GUI(例如这里的 promises)。
  • 我也会避免在每次迭代时计算数组长度,而是这样做:for (var j=0, jmax=pix.length ; j&lt;jmax ; j+=4) { ... }
  • 确保 rgb2grey 使用 Math.floor() 返回一个整数值或在末尾添加一个 shift-0 OP(浏览器优化器和比较器都会为此感到高兴)。使用 !== 比较严格。对于网络工作者,您可以使用 SharedTypedArrays(仅限较新的浏览器)。迭代时转换为 Uint32Array(shift + 和 mask 通常比查找更大的数组更快)。根据您需要亮度(灰色)值的准确程度,您还可以将其转换为完整整数或平均值(无论如何,您都希望使用 rec.709 来获得准确度)。

标签: javascript image-processing canvas multidimensional-array web-worker


【解决方案1】:

2D 画布 API 和 GPU 辅助图像处理。

canvas 2D API 提供了一组强大的 GPU 辅助合成操作。很多时候,他们可以通过 Javascript 逐个像素地替换慢速操作,并通过 getImageData 读取像素。

很多时候,这可以使处理成为视频或动画的实时解决方案,并且它还具有可以处理受污染的画布的优势,否则使用任何其他方法是不可能的。

OP 通过 GPU 辅助合成处理

在问题示例的情况下,通过使用画布 2D 复合操作有一些优化空间。尽管您必须创建两个额外的画布,但这将利用 GPU 为您执行每个像素的数学运算。

用红色标记两个图像之间不同的像素。

  • 创建两个副本
  • 使用comp“差异”获取像素差异
  • 使用comp“饱和度”来改变BW
  • 通过使用comp“lighter”渲染差异来最大化差异
  • 使用 comp 差异反转差异并在其上渲染白色矩形
  • 使用 comp "multiply" 将 imageA 的副本与反向差相乘
  • 再次反转蒙版
  • 使用 comp“乘法”在差异画布中将绿色和蓝色通道设置为零。
  • 使用comp“lighter”将蒙版添加到原始蒙版图像中。

演示

演示加载两张图片,然后使用上述方法将两张图片之间的差异标记为红色 ("#F00")。

// creates a copy of an image as a canvas
function copyImage(image) {
    const copy = document.createElement("canvas");
    copy.width = image.width;
    copy.height = image.height;
    copy.ctx = copy.getContext("2d"); // add context to the copy for easy reference
    copy.ctx.drawImage(image, 0, 0);
    return copy;
}
// returns a new canvas containing the difference between imageA and imageB
function getDifference(imageA, imageB) {
    const dif = copyImage(imageA);
    dif.ctx.globalCompositeOperation = "difference";
    dif.ctx.drawImage(imageB, 0, 0);
    return dif;
}
// Desaturates the image to black and white
function makeBW(image) { // color is a valid CSS color
    image.ctx.globalCompositeOperation = "saturation";
    image.ctx.fillStyle = "#FFF";
    image.ctx.fillRect(0, 0, image.width, image.height);
    return image;
}
// Will set all channels to max (255) if over value 0
function maxChannels(image) { 
    var i = 8; // 8 times as the channel values are doubled each draw Thus 1 * 2^8 to get 255
    image.ctx.globalCompositeOperation = "lighter";
    while (i--) {
        image.ctx.drawImage(image, 0, 0)
    }
    return image;
}
// Inverts the color channels resultRGB = 255 - imageRGB
function invert(image) {
    image.ctx.globalCompositeOperation = "difference";
    image.ctx.fillStyle = "#FFF";
    image.ctx.fillRect(0, 0, image.width, image.height);
    return image;
}
// Keeps pixels that are white in mask and sets pixels to black if black in mask.
function maskOut(image, mask) {
    image.ctx.globalCompositeOperation = "multiply";
    image.ctx.drawImage(mask, 0, 0);
    return image;
}
// Adds the channels from imageB to imageA. resultRGB = imageA_RGB + imageB_RGB
function addChannels(imageA, imageB) { // adds imageB channels to imageA channels
    imageA.ctx.globalCompositeOperation = "lighter";
    imageA.ctx.drawImage(imageB, 0, 0);
    return imageA;
}
// zeros channels is its flag (red, green, blue) is true
function zeroChannels(image, red, green, blue) { // set channels to zero to true
    image.ctx.fillStyle = `#${red ? "0" : "F"}${green ? "0" : "F"}${blue ? "0" : "F"}`;
    image.ctx.globalCompositeOperation = "multiply";
    image.ctx.fillRect(0, 0, image.width, image.height);
    return image;
}
// returns a new canvas that is a copy of imageA with pixels that are different from imageB marked in red.
function markDifference(imageA, imageB) {
    const result = copyImage(imageA);
    const mask = invert( maxChannels( makeBW(  getDifference(imageA, imageB))));
    maskOut(result, mask);
    return addChannels(result,zeroChannels(invert(mask), false, true, true));
}
const images = [
    "https://i.stack.imgur.com/ImeHB.jpg",
    "https://i.stack.imgur.com/UrrnL.jpg"
  ];
  var imageCount = 0;
  function onImageLoad(){
    imageCount += 1;
    if(imageCount === 2){
      addImageToPage(markDifference(images[0],images[1]));
      addImageToPage(images[0]);
      addImageToPage(images[1]);
       
    }
  }
  function addImageToPage(image){
    image.className = "images";
    document.body.appendChild(image);
  }
  images.forEach((url, i) => {
    images[i] = new Image;
    images[i].src = url;
    images[i].onload = onImageLoad;
  });
.images {
  width : 100%;
}

【讨论】:

  • 这些人去哪儿了?还是海水结冰了?无论如何,展示一个完全替代的几乎相同的方法是个好主意。
  • 谢谢伙计。您的回答为我提供了解决问题的不同方法,也激发了我对画布的更多探索。
  • @Blindman67 通过运行您的示例示例,我可以看到在差异图像中,一些没有差异的部分也被标记为红色,例如。孩子周围的额外区域标记为红色。
  • @flamelite 我已经以编程方式检查了结果,它是正确的。如果您将其与您的问题进行比较,您将拥有未标记为不同的像素,因为您正在比较灰度,例如(没有权重)#F00 = #0F0 = #00F(红色与绿色和蓝色相同)与权重同样的事情也会发生。如果你想比较灰度使用函数makeBW并设置image.ctx.fillStyle = "rgb("+(255 * 0.2989 | 0) + ","+(255 * 0.5870 | 0) + ","+(255 * 0.1140 | 0) + ")"这将减少动态范围并使更多像素相同。
  • @flamelite 忘了说,第一步转换为加权BW。
【解决方案2】:

您可以使用像 MarvinJ 这样的 Javascript 图像处理框架。下面的 sn-p 演示了如何通过像素迭代来实现颜色阈值算法。

var canvas1 = document.getElementById("canvas1");
var canvas2 = document.getElementById("canvas2");

image = new MarvinImage();
image.load("https://i.imgur.com/gaW8OeL.jpg", imageLoaded);


function imageLoaded(){
  image.draw(canvas1);
  var threshold=200;
  for(var y=0; y<image.getHeight(); y++){
    for(var x=0; x<image.getWidth(); x++){
       var r = image.getIntComponent0(x,y);
       var g = image.getIntComponent1(x,y);
       var b = image.getIntComponent2(x,y);
       
       if(r <= threshold && g <= threshold && b <= threshold){
         image.setIntColor(x, y, 0xFF000000);
       } else{
         image.setIntColor(x, y, 0xFFFFFFFF);
       }
     }
   }
   
   image.draw(canvas2);
}
<script src="https://www.marvinj.org/releases/marvinj-0.7.js"></script>
<canvas id="canvas1" width="200" height="200"></canvas>
<canvas id="canvas2" width="200" height="200"></canvas>

【讨论】:

    猜你喜欢
    • 2022-01-23
    • 2013-05-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-23
    • 2015-01-15
    • 2019-08-24
    相关资源
    最近更新 更多