在 Javascript 中有效地迭代图像
Efficiently Iterating through image in Javascript
我正在 JavaScript 中做一些图像处理项目,我需要遍历每个图像像素并进行一些其他处理以实现我的目标。
我正在使用 canvas 获取图像像素数据数组。
对于尺寸为 500x300 像素的小图像,它工作正常并且花费的时间可以接受。但是对于大图像,比如尺寸为 3000x3000 像素,迭代过程正在成为瓶颈,并且需要花费大量时间,比如 10-12 秒。
那么有什么方法或技巧可以用来减少迭代步骤中使用的时间吗?
这就是我的想法:我正在尝试使用并行网络工作者(假设为 4 个)来遍历图像数据的相等部分:(例如 0-[len/4]
、[len/4]+1-[len/2]
, [len/2]+1 - [len*3/4]
, [len*3/4]+1 - len
) 其中 len
是图像数据数组的大小。
我怀疑这种方法是否更省时,因为 Javascript 是单线程的。
function rgb2grey(pix,offset){
return (0.2989*pix[offset] + 0.5870*pix[offset+1] +
0.1140*pix[offset+2]);
}
function imgcompare(fileData1,fileData2,targetpix){
var len = pix.length;
for (var j = 0; j <len; j+=4) {
var grey1 = rgb2grey(fileData1,j);
var grey2 = rgb2grey(fileData2,j);
if(grey1!=grey2){
targetpix[j] = 255;
targetpix[j+1] = targetpix[j+2] = 0;
}
else{
targetpix[j] = fileData1[j];
targetpix[j+1] = fileData1[j+1];
targetpix[j+2] = fileData1[j+2];
}
targetpix[j+3] = fileData1[j+3];
}
}
2D canvas API 和 GPU 辅助图像处理。
canvas 2D API 提供了一组强大的 GPU 辅助合成操作。很多时候,他们可以通过 Javascript 逐个像素地替换缓慢的像素操作,并通过 getImageData.
读取像素。
很多时候,这可以使处理成为视频或动画的实时解决方案,而且它还有一个优势,即可以处理受污染的 canvases,否则使用任何其他方法都无法处理。
OP 通过 GPU 辅助合成进行处理
在问题示例中,使用 canvas 二维复合操作还有一些优化空间。这将利用 GPU 为您进行逐像素数学运算,但您必须创建两个额外的 canvases.
用红色标记两个图像之间不同的像素。
- 创建两个副本
- 使用补偿“差异”获取像素差异
- 使用补偿“饱和度”改变 BW
- 通过使用补偿“打火机”在自身上渲染差异来最大化差异
- 使用补偿差异反转差异并在其上渲染白色矩形
- 使用 comp“multiply”将 imageA 的副本与反向差异相乘
- 再次反转蒙版
- 使用补偿“乘法”将绿色和蓝色通道的差异 canvas 设置为零。
- 使用 comp“lighter”将蒙版添加到原始蒙版图像。
演示
该演示加载了两张图片,然后使用上述方法用红色 ("#F00"
) 标记了两张图片之间的差异。
// creates a copy of an image as a canvas
function copyImage(image) {
const copy = document.createElement("canvas");
copy.width = image.width;
copy.height = image.height;
copy.ctx = copy.getContext("2d"); // add context to the copy for easy reference
copy.ctx.drawImage(image, 0, 0);
return copy;
}
// returns a new canvas containing the difference between imageA and imageB
function getDifference(imageA, imageB) {
const dif = copyImage(imageA);
dif.ctx.globalCompositeOperation = "difference";
dif.ctx.drawImage(imageB, 0, 0);
return dif;
}
// Desaturates the image to black and white
function makeBW(image) { // color is a valid CSS color
image.ctx.globalCompositeOperation = "saturation";
image.ctx.fillStyle = "#FFF";
image.ctx.fillRect(0, 0, image.width, image.height);
return image;
}
// Will set all channels to max (255) if over value 0
function maxChannels(image) {
var i = 8; // 8 times as the channel values are doubled each draw Thus 1 * 2^8 to get 255
image.ctx.globalCompositeOperation = "lighter";
while (i--) {
image.ctx.drawImage(image, 0, 0)
}
return image;
}
// Inverts the color channels resultRGB = 255 - imageRGB
function invert(image) {
image.ctx.globalCompositeOperation = "difference";
image.ctx.fillStyle = "#FFF";
image.ctx.fillRect(0, 0, image.width, image.height);
return image;
}
// Keeps pixels that are white in mask and sets pixels to black if black in mask.
function maskOut(image, mask) {
image.ctx.globalCompositeOperation = "multiply";
image.ctx.drawImage(mask, 0, 0);
return image;
}
// Adds the channels from imageB to imageA. resultRGB = imageA_RGB + imageB_RGB
function addChannels(imageA, imageB) { // adds imageB channels to imageA channels
imageA.ctx.globalCompositeOperation = "lighter";
imageA.ctx.drawImage(imageB, 0, 0);
return imageA;
}
// zeros channels is its flag (red, green, blue) is true
function zeroChannels(image, red, green, blue) { // set channels to zero to true
image.ctx.fillStyle = `#${red ? "0" : "F"}${green ? "0" : "F"}${blue ? "0" : "F"}`;
image.ctx.globalCompositeOperation = "multiply";
image.ctx.fillRect(0, 0, image.width, image.height);
return image;
}
// returns a new canvas that is a copy of imageA with pixels that are different from imageB marked in red.
function markDifference(imageA, imageB) {
const result = copyImage(imageA);
const mask = invert( maxChannels( makeBW( getDifference(imageA, imageB))));
maskOut(result, mask);
return addChannels(result,zeroChannels(invert(mask), false, true, true));
}
const images = [
"https://i.stack.imgur.com/ImeHB.jpg",
"https://i.stack.imgur.com/UrrnL.jpg"
];
var imageCount = 0;
function onImageLoad(){
imageCount += 1;
if(imageCount === 2){
addImageToPage(markDifference(images[0],images[1]));
addImageToPage(images[0]);
addImageToPage(images[1]);
}
}
function addImageToPage(image){
image.className = "images";
document.body.appendChild(image);
}
images.forEach((url, i) => {
images[i] = new Image;
images[i].src = url;
images[i].onload = onImageLoad;
});
.images {
width : 100%;
}
您可以使用 Javascript 图像处理框架,例如 MarvinJ。下面的代码片段演示了如何遍历像素以实现颜色阈值算法。
var canvas1 = document.getElementById("canvas1");
var canvas2 = document.getElementById("canvas2");
image = new MarvinImage();
image.load("https://i.imgur.com/gaW8OeL.jpg", imageLoaded);
function imageLoaded(){
image.draw(canvas1);
var threshold=200;
for(var y=0; y<image.getHeight(); y++){
for(var x=0; x<image.getWidth(); x++){
var r = image.getIntComponent0(x,y);
var g = image.getIntComponent1(x,y);
var b = image.getIntComponent2(x,y);
if(r <= threshold && g <= threshold && b <= threshold){
image.setIntColor(x, y, 0xFF000000);
} else{
image.setIntColor(x, y, 0xFFFFFFFF);
}
}
}
image.draw(canvas2);
}
<script src="https://www.marvinj.org/releases/marvinj-0.7.js"></script>
<canvas id="canvas1" width="200" height="200"></canvas>
<canvas id="canvas2" width="200" height="200"></canvas>
我正在 JavaScript 中做一些图像处理项目,我需要遍历每个图像像素并进行一些其他处理以实现我的目标。
我正在使用 canvas 获取图像像素数据数组。
对于尺寸为 500x300 像素的小图像,它工作正常并且花费的时间可以接受。但是对于大图像,比如尺寸为 3000x3000 像素,迭代过程正在成为瓶颈,并且需要花费大量时间,比如 10-12 秒。
那么有什么方法或技巧可以用来减少迭代步骤中使用的时间吗?
这就是我的想法:我正在尝试使用并行网络工作者(假设为 4 个)来遍历图像数据的相等部分:(例如 0-[len/4]
、[len/4]+1-[len/2]
, [len/2]+1 - [len*3/4]
, [len*3/4]+1 - len
) 其中 len
是图像数据数组的大小。
我怀疑这种方法是否更省时,因为 Javascript 是单线程的。
function rgb2grey(pix,offset){
return (0.2989*pix[offset] + 0.5870*pix[offset+1] +
0.1140*pix[offset+2]);
}
function imgcompare(fileData1,fileData2,targetpix){
var len = pix.length;
for (var j = 0; j <len; j+=4) {
var grey1 = rgb2grey(fileData1,j);
var grey2 = rgb2grey(fileData2,j);
if(grey1!=grey2){
targetpix[j] = 255;
targetpix[j+1] = targetpix[j+2] = 0;
}
else{
targetpix[j] = fileData1[j];
targetpix[j+1] = fileData1[j+1];
targetpix[j+2] = fileData1[j+2];
}
targetpix[j+3] = fileData1[j+3];
}
}
2D canvas API 和 GPU 辅助图像处理。
canvas 2D API 提供了一组强大的 GPU 辅助合成操作。很多时候,他们可以通过 Javascript 逐个像素地替换缓慢的像素操作,并通过 getImageData.
读取像素。很多时候,这可以使处理成为视频或动画的实时解决方案,而且它还有一个优势,即可以处理受污染的 canvases,否则使用任何其他方法都无法处理。
OP 通过 GPU 辅助合成进行处理
在问题示例中,使用 canvas 二维复合操作还有一些优化空间。这将利用 GPU 为您进行逐像素数学运算,但您必须创建两个额外的 canvases.
用红色标记两个图像之间不同的像素。
- 创建两个副本
- 使用补偿“差异”获取像素差异
- 使用补偿“饱和度”改变 BW
- 通过使用补偿“打火机”在自身上渲染差异来最大化差异
- 使用补偿差异反转差异并在其上渲染白色矩形
- 使用 comp“multiply”将 imageA 的副本与反向差异相乘
- 再次反转蒙版
- 使用补偿“乘法”将绿色和蓝色通道的差异 canvas 设置为零。
- 使用 comp“lighter”将蒙版添加到原始蒙版图像。
演示
该演示加载了两张图片,然后使用上述方法用红色 ("#F00"
) 标记了两张图片之间的差异。
// creates a copy of an image as a canvas
function copyImage(image) {
const copy = document.createElement("canvas");
copy.width = image.width;
copy.height = image.height;
copy.ctx = copy.getContext("2d"); // add context to the copy for easy reference
copy.ctx.drawImage(image, 0, 0);
return copy;
}
// returns a new canvas containing the difference between imageA and imageB
function getDifference(imageA, imageB) {
const dif = copyImage(imageA);
dif.ctx.globalCompositeOperation = "difference";
dif.ctx.drawImage(imageB, 0, 0);
return dif;
}
// Desaturates the image to black and white
function makeBW(image) { // color is a valid CSS color
image.ctx.globalCompositeOperation = "saturation";
image.ctx.fillStyle = "#FFF";
image.ctx.fillRect(0, 0, image.width, image.height);
return image;
}
// Will set all channels to max (255) if over value 0
function maxChannels(image) {
var i = 8; // 8 times as the channel values are doubled each draw Thus 1 * 2^8 to get 255
image.ctx.globalCompositeOperation = "lighter";
while (i--) {
image.ctx.drawImage(image, 0, 0)
}
return image;
}
// Inverts the color channels resultRGB = 255 - imageRGB
function invert(image) {
image.ctx.globalCompositeOperation = "difference";
image.ctx.fillStyle = "#FFF";
image.ctx.fillRect(0, 0, image.width, image.height);
return image;
}
// Keeps pixels that are white in mask and sets pixels to black if black in mask.
function maskOut(image, mask) {
image.ctx.globalCompositeOperation = "multiply";
image.ctx.drawImage(mask, 0, 0);
return image;
}
// Adds the channels from imageB to imageA. resultRGB = imageA_RGB + imageB_RGB
function addChannels(imageA, imageB) { // adds imageB channels to imageA channels
imageA.ctx.globalCompositeOperation = "lighter";
imageA.ctx.drawImage(imageB, 0, 0);
return imageA;
}
// zeros channels is its flag (red, green, blue) is true
function zeroChannels(image, red, green, blue) { // set channels to zero to true
image.ctx.fillStyle = `#${red ? "0" : "F"}${green ? "0" : "F"}${blue ? "0" : "F"}`;
image.ctx.globalCompositeOperation = "multiply";
image.ctx.fillRect(0, 0, image.width, image.height);
return image;
}
// returns a new canvas that is a copy of imageA with pixels that are different from imageB marked in red.
function markDifference(imageA, imageB) {
const result = copyImage(imageA);
const mask = invert( maxChannels( makeBW( getDifference(imageA, imageB))));
maskOut(result, mask);
return addChannels(result,zeroChannels(invert(mask), false, true, true));
}
const images = [
"https://i.stack.imgur.com/ImeHB.jpg",
"https://i.stack.imgur.com/UrrnL.jpg"
];
var imageCount = 0;
function onImageLoad(){
imageCount += 1;
if(imageCount === 2){
addImageToPage(markDifference(images[0],images[1]));
addImageToPage(images[0]);
addImageToPage(images[1]);
}
}
function addImageToPage(image){
image.className = "images";
document.body.appendChild(image);
}
images.forEach((url, i) => {
images[i] = new Image;
images[i].src = url;
images[i].onload = onImageLoad;
});
.images {
width : 100%;
}
您可以使用 Javascript 图像处理框架,例如 MarvinJ。下面的代码片段演示了如何遍历像素以实现颜色阈值算法。
var canvas1 = document.getElementById("canvas1");
var canvas2 = document.getElementById("canvas2");
image = new MarvinImage();
image.load("https://i.imgur.com/gaW8OeL.jpg", imageLoaded);
function imageLoaded(){
image.draw(canvas1);
var threshold=200;
for(var y=0; y<image.getHeight(); y++){
for(var x=0; x<image.getWidth(); x++){
var r = image.getIntComponent0(x,y);
var g = image.getIntComponent1(x,y);
var b = image.getIntComponent2(x,y);
if(r <= threshold && g <= threshold && b <= threshold){
image.setIntColor(x, y, 0xFF000000);
} else{
image.setIntColor(x, y, 0xFFFFFFFF);
}
}
}
image.draw(canvas2);
}
<script src="https://www.marvinj.org/releases/marvinj-0.7.js"></script>
<canvas id="canvas1" width="200" height="200"></canvas>
<canvas id="canvas2" width="200" height="200"></canvas>