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



我有三个图像,其中两个在视觉上完全相同,但它们有不同的文件名,其中一个完全不同。我加载图像,将它们放在画布上,获取图像数据并比较图像。

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

更新自下方的模糊答案

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()方法的作用/返回结果。

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

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()返回一个promise。因此,如果您在调用digest((后简单地记录img1Hash,那么它将是一个空字符串,因为promise尚未实现。类似地,像img1Hash === img2Hash这样的比较将产生true,因为两个变量在该时间点都包含空字符串。

因此,你需要等到这两个承诺都得到解决。这可以通过将整个onload代码块封装在异步函数process()await(调用digest()的结果(中来实现。不幸的是,如果你进行比较,这仍然不会返回true,因为你再次使结果成为数组:

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];
}

返回三个完全不同的唯一散列。

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颜色模型,因为它更适合"人类"颜色比较。

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()函数就是从这里获得的。如果运行该示例,则第一个图像和第二个图像之间的差为0.012553120747668494,第一个和第三个图像的差为0.02681219030137108。因此,例如,如果图像的差小于或等于0.018,则可以确定图像是相等的。

最新更新