【问题标题】:Is there a faster way than a for loop for thresholding an image in javascript?有没有比 for 循环更快的方法来对 javascript 中的图像进行阈值处理?
【发布时间】:2020-01-25 06:34:20
【问题描述】:

我想使用滑块对大图像进行客户端实时交互式阈值处理。是否可以在 javascript 中对图像进行阈值处理以生成二进制图像,而无需在所有像素上使用 for 循环?如果是这样,它会更快吗?

【问题讨论】:

    标签: javascript image-thresholding


    【解决方案1】:

    这可以只使用 globalCompositeOperations 来完成,分两个阶段。

    1. 将低于阈值的所有像素设置为 0(黑色)。
    2. 使用定义 0/0 = 0 的算法自行“分割”此图像

    定义三个画布,一个在屏幕上,一个在屏幕外保存灰度图像,另一个在屏幕外“工作”画布。

        //--- on-screen canvas
        var onScreenCanvas=document.getElementById("canvasTest");
        var ctxOnScreen=onScreenCanvas.getContext("2d");
    
        //--- off-screen working canvas
        var drawingCanvas = document.createElement('canvas');
        var ctx=drawingCanvas.getContext("2d");
    
        //--- off-screen canvas to store the greyscale image
        var greyscaleImageCanvas = document.createElement('canvas');
        var ctxGreyscaleImage=greyscaleImageCanvas.getContext("2d");
    

    将灰度图像加载到grayscaleImageCanvas上,然后以下两个操作实现步骤1,其中thresh_str是一个十六进制字符串,用于每个RGB的0-FF之间的阈值

            //(1a) Threshold the image on the offscreen working canvas, 
            // reducing values above threshold to have threshold value
            ctx.drawImage(greyscaleImageCanvas, 0, 0);
            ctx.globalCompositeOperation='darken';
            ctx.fillStyle=thresh_str;
            ctx.fillRect(0,0, drawingCanvas.width, drawingCanvas.height);
    
            //(1b) Set everything *below* threshold to 0 (black) since that part is unchanged
            // from the original image. Pixels above threshold are all non-zero.
            ctx.globalCompositeOperation='difference';
            ctx.drawImage(greyscaleImageCanvas, 0, 0);
    

    没有为 HTML globalCompositeOperations 定义直接的“除法”操作,但是有一个“颜色减淡”,它将底层与倒置的顶层分开。因此,通过首先制作步骤 1 输出的反转副本,然后使用颜色减淡操作(确实定义 0/0=0)在除法之前“取消反转”它来实现所需的结果。结果是非零(高于阈值)像素变为 1,零(低于阈值)像素保持为零。

            //(2a) Copy the result of (1b) to the onscreen canvas
            ctxOnScreen.globalCompositeOperation='copy';
            ctxOnScreen.drawImage(drawingCanvas, 0, 0);
    
            //(2b) Invert the result of step (1b) so that it can be 'un-inverted' by color dodge
            ctx.globalCompositeOperation='difference';
            ctx.fillStyle='white';
            ctx.fillRect(0,0,onScreenCanvas.width,onScreenCanvas.height);
    
            //(2c) 'color-dodge' the results of (1b) with it's own inverse (2b) 
            ctxOnScreen.globalCompositeOperation='color-dodge';
            ctxOnScreen.drawImage(drawingCanvas, 0, 0);
    

    这种方法似乎比 for 循环快 3-5 倍,至少在 Mac 和 android (Huawei P10) 上的 Chrome 79 上JSPerf

       function img2grey(canvasContext) {
            canvasContext.globalCompositeOperation='color';
            canvasContext.fillStyle='white';
            canvasContext.fillRect(0,0,onScreenCanvas.width,onScreenCanvas.height);
        }
        
        //--- on-screen canvas
        var onScreenCanvas=document.getElementById("canvasTest");
        var ctxOnScreen=onScreenCanvas.getContext("2d");
        
        //--- off-screen working canvas
        var drawingCanvas = document.createElement('canvas');
        var ctx=drawingCanvas.getContext("2d");
    
        //--- off-screen canvas to store the greyscale image
        var greyscaleImageCanvas = document.createElement('canvas');
        var ctxGreyscaleImage=greyscaleImageCanvas.getContext("2d");
    
        var image = new Image();
    
    
        function thresholdImage(thresh_val) {
            
            if(thresh_val.length == 1){
                thresh_val = '0' + thresh_val;
            }
            thresh_str = '#'+thresh_val+thresh_val+thresh_val;
    
            ctxOnScreen.clearRect(0, 0, onScreenCanvas.width, onScreenCanvas.height);
            ctx.clearRect(0, 0, drawingCanvas.width, drawingCanvas.height);
    
            //----- (1) Threshold the image on the offscreen working canvas, 
            // reducing values above threshold to have threshold value
            ctx.drawImage(greyscaleImageCanvas, 0, 0);
            ctx.globalCompositeOperation='darken';
            ctx.fillStyle=thresh_str;
            ctx.fillRect(0,0,onScreenCanvas.width,onScreenCanvas.height);
            
            //----- (2) Set everything *below* threshold to 0 (black) since that part is unchanged
            // from the original image
            ctx.globalCompositeOperation='difference';
            ctx.drawImage(greyscaleImageCanvas, 0, 0);
    
            //----- (3) Copy the result to the onscreen canvas
            ctxOnScreen.globalCompositeOperation='copy';
            ctxOnScreen.drawImage(drawingCanvas, 0, 0);
    
            //----- (4) Invert the result of step (2) so that it can be 'un-inverted' by color dodge
            ctx.globalCompositeOperation='difference';
            ctx.fillStyle='white';
            ctx.fillRect(0,0,onScreenCanvas.width,onScreenCanvas.height);
            
            //----- (5) 'color-dodge' the results of (2) with it's own inverse (4) 
            //----- This makes use of 0/0 defined as 0 in this globalCompositeOperation,
            //----- so that non-zero (suprathreshold) pixels become 1, zero (sub-threshold) pixels stay zero
            //~ ctxOnScreen.globalCompositeOperation='color-dodge';
            ctxOnScreen.globalCompositeOperation='color-dodge';
            ctxOnScreen.drawImage(drawingCanvas, 0, 0);
    
        }
    
        image.onload = function() {
            onScreenCanvas.width = image.width;
            onScreenCanvas.height = image.height;
            drawingCanvas.width = image.width;
            drawingCanvas.height = image.height;
            greyscaleImageCanvas.width = image.width;
            greyscaleImageCanvas.height = image.height;
            //!!NB Doesn't work on chrome for local files, use firefox
            // https://stackoverflow.com/questions/45444097/the-canvas-has-been-tainted-by-cross-origin-data-local-image
            ctxGreyscaleImage.drawImage(image, 0, 0);
            img2grey(ctxGreyscaleImage);
    
            thresholdImage((Math.round(rng.value)).toString(16));
        };
    
        var rng = document.querySelector("input");
    
        var listener = function() {
          window.requestAnimationFrame(function() {
            thresholdImage( (Math.round(rng.value)).toString(16) );
          });
        };
    
        rng.addEventListener("mousedown", function() {
            listener();
            rng.addEventListener("mousemove", listener);
        });
        
        rng.addEventListener("mouseup", function() {
            rng.removeEventListener("mousemove", listener);
        });
    
        image.src = "https://i.imgur.com/vN0NbVu.jpg";
    .slider-width100 {
      width: 255px;
    }
    <html>
    
    <head>
    </head>
    
    <body>
    <canvas id="canvasTest"></canvas>
    <input class="slider-width100" type="range" min="0" max="254" value="122" />
    </body>
    
    </html>

    【讨论】:

    • 您是如何理解 OP 的上下文的?因为我迷失了上下文。
    • @GetSet 为清楚起见编辑了问题(我问过)。
    • 我错过了。您发布了对原始问题的答案。您是否正在寻找比您所展示的最佳实践?我不知道这个主题。尽管如此,如果它有效,为什么还要麻烦?
    • 是的,何必打扰。它在客户端正确运行。似乎没有正确循环?何必?一个进度条就足够了。好的,如果它用循环捆绑浏览器,然后优化。但同样的道理,如果它确实占用了 UI 堆栈,你难道不能简单地将其设为 step tasking 操作吗?有些事情只是需要很长时间才能执行。
    猜你喜欢
    • 2018-07-25
    • 2018-04-08
    • 2021-02-10
    • 1970-01-01
    • 2019-07-29
    • 2015-10-19
    • 1970-01-01
    • 2019-03-21
    • 1970-01-01
    相关资源
    最近更新 更多