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
,则可以确定图像相等。
我有 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
,则可以确定图像相等。