检查图像B中是否存在图像A.

时间:2017-06-27 21:16:03

标签: html html5 canvas html5-canvas

我需要使用JavaScript检查另一个图像中是否存在图像,我需要知道执行此操作的最佳方法(算法)和解决方案(例如:librarie)是什么

我在此图片中解释了我需要做的事情:enter image description here

1 个答案:

答案 0 :(得分:3)

使用GPU帮助进行图像处理。

使用2D API和一些简单的技巧,您可以利用GPU加速Javascript。

差分

要查找图像,您需要将要查找的像素(A)与图像中的像素(B)进行比较。如果Math.abs(A-B)=== 0之间的差异,那么像素是相同的。

执行此操作的功能可能如下所示

function findDif(imageDataSource, imageDataDest, xx,yy)
    const ds = imageDataSource.data;
    const dd = imageDataDest.data;
    const w =  imageDataSource.width;
    const h =  imageDataSource.height;
    var x,y;
    var dif = 0;
    for(y = 0; y < h; y += 1){
        for(x = 0; x < w; x += 1){
            var indexS = (x + y * w) * 4;
            var indexD = (x + xx + (y + yy) * imageDataDest.width) * 4;
            dif += Math.abs(ds[indexS]-dd[indexD]);
            dif += Math.abs(ds[indexS + 1]-dd[indexD + 1]);
            dif += Math.abs(ds[indexS + 2]-dd[indexD + 2]);
         }
     }
     return dif;
  }

var source = sourceCanvas.getContext("2d").getImageData(0,0,sourceCanvas.width,sourceCanvas.height);
var dest = destinationCanvas.getContext("2d").getImageData(0,0,destinationCanvas.width,destinationCanvas.height);

if(findDif(source,dest,100,100)){ // is the image at 100,100?
     // Yes image is very similar
}

如果源是我们正在寻找的图像,而dest是我们想要找到它的图像。我们为图像可能的每个位置运行函数,如果结果在一个级别之下那么它是一个好的我们找到它的机会。

但这在JS中非常慢。这是GPU可以提供帮助的地方。 使用ctx.globalCompositeOperation = "difference";操作,我们可以加快流程,因为它将为我们进行差异计算

使用comp操作"difference"进行渲染时,生成的像素是您正在绘制的像素与画布上已有的像素之间的差异。因此,如果你绘制相同的东西,结果是所有像素都是黑色(没有差异)

要在图像中查找类似图像,请在画布上要测试的每个位置渲染要测试的图像。然后,您获得刚刚渲染的所有像素的总和,如果结果低于您设置的阈值,则该区域下的图像与您正在测试的图像非常相似。

但我们仍然需要逐个计算所有像素。

GPU均值函数

comp op“差异”已经为你做了像素差异计算,但为了获得总和,你可以使用内置的图像平滑。

渲染后找到差异后,您可以使用该区域以较小的比例渲染它,并使用ctx.imageSmoothingEnabled = true默认设置。 GPU将做类似于平均值的事情,并且可以将JS必须完成的工作量减少几个数量级。

现在,您可以将其缩小到4或16,而不是100或1000像素,具体取决于您需要的精确度。

一个例子。

使用这些方法,您只需进行基本的数值分析,即可在图像搜索中获得近实时图像。

点击开始测试。显示结果加上花费的时间。正在搜索的图像位于右上角。

//------------------------------------------------------------------------
// Some helper functions 
var imageTools = (function () {
    var tools = {
        canvas(width, height) {  // create a blank image (canvas)
            var c = document.createElement("canvas");
            c.width = width;
            c.height = height;
            return c;
        },
        createImage : function (width, height) {
            var i = this.canvas(width, height);
            i.ctx = i.getContext("2d");
            return i;
        },
        image2Canvas(img) {
            var i = this.canvas(img.width, img.height);
            i.ctx = i.getContext("2d");
            i.ctx.drawImage(img, 0, 0);
            return i;
        },
        copyImage(img){ // just a named stub
            return this.image2Canvas(img);
        },
    };
    return tools;
})();
const U = undefined; 
const doFor = (count, callback) => {var i = 0; while (i < count && callback(i ++) !== true ); };
const setOf = (count, callback) => {var a = [],i = 0; while (i < count) { a.push(callback(i ++)) } return a };
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const rand  = (min, max = min + (min = 0)) => Math.random() * (max - min) + min;
const randA = (array) => array[(Math.random() * array.length) | 0];
const randG  = (min, max = min + (min = 0)) => Math.random() * Math.random() * Math.random() * Math.random() * (max - min) + min;

// end of helper functions
//------------------------------------------------------------------------


function doit(){
  document.body.innerHTML = ""; // clear the page;
  var canvas = document.createElement("canvas");
  document.body.appendChild(canvas);
  var ctx = canvas.getContext("2d");
  // a grid of 36 images
  canvas.width = 6 * 64;
  canvas.height = 6 * 64;
  console.log("test");

  // get a random character to look for
  const digit = String.fromCharCode("A".charCodeAt(0) + randI(26));
  // get some characters we dont want
  const randomDigits = setOf(6,i=>{
      return String.fromCharCode("A".charCodeAt(0) + randI(26));
  })
  randomDigits.push(digit); // add the image we are looking for
  
  var w = canvas.width;
  var h = canvas.height;
  
  // create a canvas for the image we are looking for
  const imageToFind = imageTools.createImage(64,64);
  
  // and a larger one to cover pixels on the sides
  const imageToFindExtend = imageTools.createImage(128,128);
  
  // Draw the character onto the image with a white background and scaled to fit
  imageToFindExtend.ctx.fillStyle = imageToFind.ctx.fillStyle = "White";
  imageToFind.ctx.fillRect(0,0,64,64);
  imageToFindExtend.ctx.fillRect(0,0,128,128);
  ctx.font = imageToFind.ctx.font = "64px arial black";
  ctx.textAlign = imageToFind.ctx.textAlign = "center";
  ctx.textBaseline = imageToFind.ctx.textBaseline = "middle";
  const digWidth = imageToFind.ctx.measureText(digit).width+8;
  const scale = Math.min(1,64/digWidth);
  imageToFind.ctx.fillStyle = "black";
  imageToFind.ctx.setTransform(scale,0,0,scale,32,32);
  imageToFind.ctx.fillText(digit,0,0);
  imageToFind.ctx.setTransform(1,0,0,1,0,0);
  imageToFindExtend.ctx.drawImage(imageToFind,32,32);
  imageToFind.extendedImage = imageToFindExtend;
  
  // Now fill the canvas with images of other characters 
  ctx.fillStyle = "white";
  ctx.setTransform(1,0,0,1,0,0);
  ctx.fillRect(0,0,w,h);
  ctx.fillStyle = "black";
  ctx.strokeStyle = "white";
  ctx.lineJoin = "round";
  ctx.lineWidth = 12;
  
  // some characters will be rotated 90,180,-90 deg
  const dirs = [
      [1,0,0,1,0,0],
      [0,1,-1,0,1,0],
      [-1,0,0,-1,1,1],
      [0,-1,1,0,0,1],

  ]
  // draw random characters at random directions
  doFor(h / 64, y => {
      doFor(w / 64, x => {
          const dir = randA(dirs)
          ctx.setTransform(dir[0] * scale,dir[1] * scale,dir[2] * scale,dir[3] * scale,x * 64 + 32, y * 64 + 32);
          const d = randA(randomDigits);
          ctx.strokeText(d,0,0);
          ctx.fillText(d,0,0);
      });
  });  
  ctx.setTransform(1,0,0,1,0,0);
  
  // get a copy of the canvas
  const saveCan = imageTools.copyImage(ctx.canvas);
  
  // function that finds the images
  // image is the image to find
  // dir is the matrix direction to find 
  // smapleSize is the mean sampling size samller numbers are quicker
  function checkFor(image,dir,sampleSize){
      const can = imageTools.copyImage(saveCan);
      const c = can.ctx;
      const stepx = 64;
      const stepy = 64;
      // the image that will contain the reduced means of the differences
      const results = imageTools.createImage(Math.ceil(w / stepx) * sampleSize,Math.ceil(h / stepy) * sampleSize);
      const e = image.extendedImage;
      // for each potencial image location 
      // set a clip area and draw the source image on it with
      // comp mode  "difference";
      for(var y = 0 ; y < h; y += stepy ){
          for(var x = 0 ; x < w; x += stepx ){
              c.save();
              c.beginPath();
              c.rect(x,y,stepx,stepy);
              c.clip();
              c.globalCompositeOperation = "difference";
              c.setTransform(dir[0],dir[1],dir[2],dir[3],x +32 ,y +32 );
              c.drawImage(e,-64,-64);
              c.restore();
          }

      }
      // Apply the mean (reducing nnumber of pixels to check
      results.ctx.drawImage(can,0,0,results.width,results.height);
      // get the pixel data
      var dat = new Uint32Array(results.ctx.getImageData(0,0,results.width,results.height).data.buffer);
      // for each area get the sum of the difference
      for(var y = 0; y < results.height; y += sampleSize){
          for(var x = 0; x < results.width; x += sampleSize){
              var val = 0;
              for(var yy = 0; yy < sampleSize && y+yy < results.height; yy += 1){
                  var i = x + (y+yy)*results.width;
                  for(var xx = 0; xx < sampleSize && x + xx < results.width ; xx += 1){
                      val += dat[i++] & 0xFF;
                  }
              }
              // if the sum is under the threshold we have found an image
              // and we mark it
              if(val < sampleSize * sampleSize * 5){
                  ctx.strokeStyle = "red";
                  ctx.fillStyle = "rgba(255,0,0,0.5)";
                  ctx.lineWidth = 2;
                  ctx.strokeRect(x * (64/sampleSize),y * (64/sampleSize),64,64);
                  ctx.fillRect(x * (64/sampleSize),y * (64/sampleSize),64,64);
                  foundCount += 1;
              }
          }
      }
  }
  var foundCount = 0;
  // find the images at different orientations 
  var now = performance.now();
  checkFor(imageToFind,dirs[0],4);
  checkFor(imageToFind,dirs[1],6); // rotated images need larger sample size
  checkFor(imageToFind,dirs[2],6);
  checkFor(imageToFind,dirs[3],6);
  var time = performance.now() - now;
  var result = document.createElement("div");
  result.textContent = "Found "+foundCount +" matching images in "+time.toFixed(3)+"ms. Click to try again.";
  document.body.appendChild(result);
  
  // show the image we are looking for
  imageToFind.style.left = (64*6 + 16) + "px";
  imageToFind.id = "lookingFor";
  document.body.appendChild(imageToFind);
}

document.addEventListener("click",doit);
canvas { 
    border : 2px solid black;
    position : absolute;
    top : 28px;
    left : 2px;
}
#lookingFor { 
    border : 4px solid red;
}
div { 
    border : 2px solid black;
    position : absolute;
    top : 2px;
    left : 2px;
}
Click to start test.

不完美

这个例子并不完美,有时会犯错误。提高准确性和速度有很大的空间。这只是我作为一个例子展示如何通过2D API使用GPU的例子。需要进一步的数学来找到统计上的好结果。

此方法也适用于不同的比例和旋转,您甚至可以使用其他一些补偿模式来移除颜色和标准化对比度。我使用了一个非常相似的approch来稳定网络摄像头,通过跟踪从一帧到下一帧的点,以及其他图像跟踪用途。