Canvas getImageData 对于 2 个视觉上不同的图像总是相同的

Canvas getImageData is always the same for 2 visually different images

我有 3 张图片,其中 2 张看起来完全相同,但文件名不同,还有一张完全不同。我加载图像,将它们放在 canvas 上,获取图像数据并比较图像。

视觉上相同的2个returns正确哪个正确 当比较 2 个在视觉上不相同时,它也是 returns true,这是不正确的。

从下面的@obscure 回答更新

window.onload = function () {
setTimeout(process, 5000);
};

async function process() {
  const img1 = document.getElementById("img1");
  const img2 = document.getElementById("img2");
  const img3 = document.getElementById("img3");

  img1.crossOrigin = "Anonymous";
  img2.crossOrigin = "Anonymous";
  img3.crossOrigin = "Anonymous";

  const canvas1 = document.createElement("canvas");
  const ctx1 = canvas1.getContext("2d");
  canvas1.width = img1.width;
  canvas1.height = img1.height;
  ctx1.drawImage(img1, 0, 0);
  const pixData1 = ctx1.getImageData(0, 0, img1.width, img1.height).data;

  const canvas2 = document.createElement("canvas");
  const ctx2 = canvas2.getContext("2d");
  canvas2.width = img2.width;
  canvas2.height = img2.height;
  ctx2.drawImage(img2, 0, 0);
  const pixData2 = ctx2.getImageData(0, 0, img2.width, img2.height).data;

  const canvas3 = document.createElement("canvas");
  const ctx3 = canvas3.getContext("2d");
  canvas3.width = img3.width;
  canvas3.height = img3.height;
  ctx3.drawImage(img3, 0, 0);
  const pixData3 = ctx3.getImageData(0, 0, img3.width, img3.height).data;

  const utf8A = new TextEncoder().encode(pixData1.toString());
  let img1Hash = await crypto.subtle
.digest("SHA-256", utf8A)
.then((hashBuffer) => {
  return Array.from(new Uint8Array(hashBuffer)).toString();
});

  const utf8B = new TextEncoder().encode(pixData2.toString());
  let img2Hash = await crypto.subtle
.digest("SHA-256", utf8B)
.then((hashBuffer) => {
  return Array.from(new Uint8Array(hashBuffer)).toString();
});

  const utf8C = new TextEncoder().encode(pixData3.toString());
  let img3Hash = await crypto.subtle
.digest("SHA-256", utf8C)
.then((hashBuffer) => {
  return Array.from(new Uint8Array(hashBuffer)).toString();
});

  console.log(img1Hash);
  console.log(img2Hash);
  console.log(img3Hash);

  console.log(img1Hash === img2Hash);
  console.log(img1Hash === img3Hash); // Should be false
  console.log(img2Hash === img3Hash); // Should be false
}
<!DOCTYPE html>
<html>
  <head>
<script src="index.js"></script>
  </head>
  <body>
<img src="https://i.imgur.com/M0K21iS.jpg" id="img1" />
<img src="https://i.imgur.com/uNbsNAd.jpg" id="img2" />
<img src="https://i.imgur.com/QdqhGb9.jpg" id="img3" />
  </body>
</html>

要比较两个数组是否相等,您确实可以使用散列算法。使用 crypto.subtle 是一个简单的解决方案,但恐怕您不知道 .digest() 方法是什么 does/returns.

从你的代码看来你认为这是一个同步操作:

  let img1Hash = "";
  const utf8A = new TextEncoder().encode(pixData1.toString());
  crypto.subtle.digest("SHA-256", utf8A).then((hashBuffer) => {
    img1Hash = Array.from(new Uint8Array(hashBuffer));
  });
  console.log(img1Hash); // nothing logged

这是一个异步操作,digest()return是一个承诺。因此,如果您在调用 digest() 之后简单地记录 img1Hash 将是一个空字符串,因为 promise 尚未实现。同样,像 img1Hash === img2Hash 这样的比较将产生 true,因为两个变量在那个时间点都包含空字符串。

因此您需要等到两个承诺都已解决。这可以通过将整个 onload 代码块包装在异步函数 process()await 调用 digest() 的结果中来完成。不幸的是,如果您进行比较,这仍然不会 return 正确,因为您再次将结果设为数组:

Array.from(new Uint8Array(hashBuffer))

如果将其转换为字符串,则可以比较它是否相等。

完整代码如下:

window.onload = function() {
  process();


};

async function process() {
  const img1 = document.getElementById("img1");
  const img2 = document.getElementById("img2");

  img1.crossOrigin = "Anonymous";
  img2.crossOrigin = "Anonymous";

  const canvas1 = document.createElement("canvas");
  const ctx1 = canvas1.getContext("2d");
  canvas1.width = img1.width;
  canvas1.height = img1.height;
  ctx1.drawImage(img1, 0, 0);
  const pixData1 = ctx1.getImageData(0, 0, img1.width, img1.height).data;

  const canvas2 = document.createElement("canvas");
  const ctx2 = canvas2.getContext("2d");
  canvas2.width = img2.width;
  canvas2.height = img2.height;
  ctx2.drawImage(img2, 0, 0);
  const pixData2 = ctx2.getImageData(0, 0, img2.width, img2.height).data;


  const utf8A = new TextEncoder().encode(pixData1.toString());
  let img1Hash = await crypto.subtle.digest("SHA-256", utf8A).then((hashBuffer) => {
    return Array.from(new Uint8Array(hashBuffer)).toString();
  });


  const utf8B = new TextEncoder().encode(pixData2.toString());
  let img2Hash = await crypto.subtle.digest("SHA-256", utf8B).then((hashBuffer) => {
    return Array.from(new Uint8Array(hashBuffer)).toString();
  });



  console.log(img1Hash); // nothing logged
  console.log(img2Hash); // nothing logged
  console.log(img1Hash === img2Hash); // true

}
<img src="https://i.imgur.com/M0K21iS.jpg" id="img1" />
<img src="https://i.imgur.com/uNbsNAd.jpg" id="img2" />

编辑

当您努力为每张图片获取正确的哈希值时,让我们做一些不同的事情。与其引用真正的 html <img> 元素,不如动态创建这些元素,并在准备就绪时将它们添加到 DOM。

所以下面的片段:

let sources = ['https://i.imgur.com/M0K21iS.jpg', 'https://i.imgur.com/uNbsNAd.jpg', 'https://i.imgur.com/QdqhGb9.jpg'];
let images = [];
let imageData = [];
let hashes = [];
let counter = 0;

function loaded(e) {
  counter++;
  if (counter == 3) {
    process();
  }
}

async function process() {
  let utf8;
  let canvas = document.createElement("canvas");
  let ctx = canvas.getContext("2d");
  canvas.width = images[0].width;
  canvas.height = images[0].height;
  for (let a = 0; a < images.length; a++) {
    ctx.drawImage(images[a], 0, 0);
    imageData.push(ctx.getImageData(0, 0, canvas.width, canvas.height).data);

    utf8 = new TextEncoder().encode(imageData[a].toString());
    hashes.push(await crypto.subtle
      .digest("SHA-256", utf8)
      .then((hashBuffer) => {
        return Array.from(new Uint8Array(hashBuffer)).toString();
      }));
  }

  console.log(hashes[0]);
  console.log(hashes[1]);
  console.log(hashes[2]);
}



let img;
for (let a = 0; a < sources.length; a++) {
  img = new Image();
  images.push(img);
  img.crossOrigin = 'anonymous';
  document.body.appendChild(img);
  img.onload = loaded;
  img.src = sources[a];
}

return三个独特的、完全不同的哈希值。

100,172,184,128,122,59,32,239,211,133,243,51,25,159,237,239,175,140,198,232,133,184,77,224,174,85,38,1,164,52,30,68
88,209,142,171,42,213,152,27,60,14,200,193,162,134,50,183,110,70,166,231,237,163,215,129,184,249,106,41,16,147,151,97
72,2,137,13,168,131,212,29,170,19,57,24,39,91,164,32,38,2,170,231,124,72,78,64,168,135,84,1,108,11,161,216

正如您肯定已经猜到的那样,现在使用散列在视觉上比较两个图像并不是可行的方法。您可以做的是将图像 A 在 x、y 处的颜色与图像 B 在同一位置的颜色进行比较,然后对差异求和。如果总差异在某个阈值内,则图像应被视为相等。

为此,我们需要将 RGB 颜色转换为 HSV 颜色模型,因为它更适合 'human' 颜色比较。

let sources = ['https://i.imgur.com/M0K21iS.jpg', 'https://i.imgur.com/uNbsNAd.jpg', 'https://i.imgur.com/QdqhGb9.jpg'];
let images = [];
let imageData = [];
let hashes = [];
let counter = 0;

function loaded(e) {
  counter++;
  if (counter == 3) {
    process();
  }
}

async function process() {
  let canvas = document.createElement("canvas");
  let ctx = canvas.getContext("2d");
  canvas.width = images[0].width;
  canvas.height = images[0].height;
  for (let a = 0; a < images.length; a++) {
    ctx.drawImage(images[a], 0, 0);
    imageData.push(ctx.getImageData(0, 0, canvas.width, canvas.height).data);
  }

  compare(imageData[0], imageData[1]);
  compare(imageData[0], imageData[2]);
}

function compare(imgDataA, imgDataB) {
  let hslA, hslB, avgH, avgS, avgL, difference;
  let differences = 0;
  let counter = 0;
  for (let a = 0; a < imgDataA.length; a += 4) {
    hslA = rgbToHsl(imgDataA[a], imgDataA[a + 1], imgDataA[a + 2]);
    hslB = rgbToHsl(imgDataB[a], imgDataB[a + 1], imgDataB[a + 2]);
    avgH = (hslA[0] + hslB[0]) / 2;
    avgS = (hslA[1] + hslB[1]) / 2;
    avgL = (hslA[2] + hslB[2]) / 2;
    differences += (Math.abs(hslA[0] - avgH) + Math.abs(hslA[1] - avgS) + Math.abs(hslA[2] - avgL)) / 3;

    counter++;
  }
  console.log(differences / (imgDataA.length / 4));
}

let img;
for (let a = 0; a < sources.length; a++) {
  img = new Image();
  images.push(img);
  img.crossOrigin = 'anonymous';
  document.body.appendChild(img);
  img.onload = loaded;
  img.src = sources[a];
}

// taken from: https://gist.github.com/mjackson/5311256#file-color-conversion-algorithms-js
function rgbToHsl(r, g, b) {
  r /= 255, g /= 255, b /= 255;

  var max = Math.max(r, g, b),
    min = Math.min(r, g, b);
  var h, s, l = (max + min) / 2;

  if (max == min) {
    h = s = 0;
  } else {
    var d = max - min;
    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);

    switch (max) {
      case r:
        h = (g - b) / d + (g < b ? 6 : 0);
        break;
      case g:
        h = (b - r) / d + 2;
        break;
      case b:
        h = (r - g) / d + 4;
        break;
    }

    h /= 6;
  }

  return [h, s, l];
}

附带说明:上面的 rgbToHsl() 函数取自 here。如果你 运行 这个例子,你会得到第一张和第二张图片之间的 0.012553120747668494 差异,以及第一张和第三张图片之间的 0.02681219030137108 差异。因此,如果图像的差异小于或等于 0.018,则可以确定图像相等。