是否有比for循环更快的方法来对javascript中的图像进行阈值处理?

问题描述 投票:0回答:1

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

javascript image-thresholding
1个回答
0
投票

这可以在两个阶段中仅使用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");

将灰度图像加载到greyscaleImageCanvas上,然后以下两个操作实现了步骤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)JSPerf上的Chrome 79上如此

   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>
© www.soinside.com 2019 - 2024. All rights reserved.