Эффективно перебирая изображение в Javascript

1

Я делаю некоторый проект обработки изображений в JavaScript, где мне нужно перебирать пиксели каждого изображения и выполнять некоторую другую обработку для достижения моей цели.
Я использую canvas для получения массива данных пикселей изображения.
Для небольших изображений, например, 500x300 пикселей в размерах, он отлично работает и занимает приемлемое время. Но для больших изображений, например, 3000x3000 px в размерах, процесс итерации становится узким местом и занимает огромное время, например, 1 0- 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];
        }
}
  • 0
    Цикл с 90000 итерациями действительно не так впечатляет. Я думаю, что возможный выигрыш в производительности заключается в том, что вы действительно делаете в цикле. Разместите свой код ( минимальный воспроизводимый пример ), возможно, там что-то не так.
  • 0
    Вы должны показать свой код, чтобы мы могли хотя бы увидеть, с чем вы работаете, и даже помочь вам.
Показать ещё 23 комментария
Теги:
image-processing
multidimensional-array
canvas
web-worker

2 ответа

2
Лучший ответ

2D-холст API и обработка изображений с помощью графического процессора.

Canvas 2D API предоставляет мощный набор составных операций, поддерживаемых графическим процессором. Много раз они могут заменить медленные операции пикселя на пиксель, выполненные с помощью Javascript, и чтение пикселя через getImageData.

Много раз это может сделать обработку в реальном времени решением для видео или анимации, а также имеет то преимущество, что она может обрабатывать испорченные полотна, которые иначе были бы невозможны с использованием любого другого метода.

Обработка OP через GPU помогла компоновке

В случае примера вопроса есть место для оптимизации с использованием двухмерных композитных операций холста. Это будет использовать графический процессор для выполнения математических вычислений для каждого пикселя, хотя вам придется создать два дополнительных холста.

Чтобы отметить пиксели с красным цветом, которые отличаются между двумя изображениями.

  • Создайте две копии
  • Получите разницу в пикселях, используя comp "difference"
  • Сделайте разницу BW, используя comp "saturation"
  • Максимальное различие между показаниями разницы по сравнению с самим собой, используя comp "lightighter"
  • Инвертируйте разницу, используя разницу в comp и рендеринг белого прямоугольника над ней
  • Умножить копию изображения с перевернутой разницей, используя comp "multiply"
  • Инвертировать маску снова
  • Установите каналы "Зеленый" и "Синий" на ноль в холсте разницы, используя comp "multiply".
  • Добавьте маску к исходному замаскированному изображению, используя comp "lightighter".

демонстрация

Демонстрация загружает два изображения, а затем отмечает различия между двумя изображениями в красном ("#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%;
}
  • 2
    Куда делись эти люди? Или море замерзло? В любом случае, да, хорошая идея показать совершенно альтернативный способ сделать почти то же самое.
  • 0
    Спасибо чувак. Ваш ответ дал мне другой подход к решению моей проблемы, а также вдохновил меня на изучение холста.
Показать ещё 3 комментария
0

Вы можете использовать Javascript Image Processing Framework, например 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>

Ещё вопросы

Сообщество Overcoder
Наверх
Меню