Найти области с похожим цветом в изображении

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

Например: http://unsplash.com/photos/SoC1ex6sI4w/

На этом изображении темные облака наверху, вероятно, попадут в одну группу. Некоторые из серой скалы на горе в другой, а некоторые из оранжевой травы в другой. Снег будет другим - красный рюкзак - и т.д.

Я пытаюсь разработать алгоритм, который будет как точным, так и эффективным (его нужно запускать в случае ms на аппаратном уровне среднего класса ноутбука)


Ниже я попытался:

Используя алгоритм, основанный на подключенном компоненте, пройти через каждый пиксель сверху слева, просматривая каждую строку пикселей слева направо (и сравнивая текущий пиксель с верхним пикселем и левым пикселем). Используя формулу разности цветов CIEDE2000, если пиксель в верхней или левой части находился в определенном диапазоне, он будет считаться "похожим" и частью группы.

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

Чтобы попытаться исправить это, я решил установить каждый посещенный пиксельный цвет в цвет большинства "похожих" соседних пикселей (верхний или левый). Если нет одинаковых пикселей, они сохраняют оригинальный цвет. Это несколько устраняет проблему более размытых границ или мягких краев, потому что первый цвет новой группы будет "переноситься" по мере продвижения алгоритма, и в итоге разница между этим цветом и текущим сравниваемым цветом будет превышать "сходство" threashold и больше не будут частью этой группы.

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

Я не ищу код специально, но больше идей относительно того, как алгоритм может быть структурирован для успешной борьбы с этой проблемой. У кого-нибудь есть идеи об этом?

Спасибо!

Ответ 1

Вы можете конвертировать из RGB в HSL, чтобы упростить вычисление расстояния между цветами. Я устанавливаю толерантность цветовой разницы в строке:

if (color_distance(original_pixels[i], group_headers[j]) < 0.3) {...}

Если вы измените 0.3, вы можете получить разные результаты.

Посмотрите, как работает.

Пожалуйста, дайте мне знать, если это поможет.

function hsl_to_rgb(h, s, l) {
    // from http://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
    var r, g, b;

    if (s == 0) {
      r = g = b = l; // achromatic
    } else {
      var hue2rgb = function hue2rgb(p, q, t) {
        if (t < 0) t += 1;
        if (t > 1) t -= 1;
        if (t < 1 / 6) return p + (q - p) * 6 * t;
        if (t < 1 / 2) return q;
        if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
        return p;
      }

      var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
      var p = 2 * l - q;
      r = hue2rgb(p, q, h + 1 / 3);
      g = hue2rgb(p, q, h);
      b = hue2rgb(p, q, h - 1 / 3);
    }

    return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
  }

function rgb_to_hsl(r, g, b) {
    // from http://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
    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; // achromatic
    } 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];
  }

function color_distance(v1, v2) {
  // from http://stackoverflow.com/a/13587077/1204332
  var i,
    d = 0;

  for (i = 0; i < v1.length; i++) {
    d += (v1[i] - v2[i]) * (v1[i] - v2[i]);
  }
  return Math.sqrt(d);
};

function round_to_groups(group_nr, x) {
  var divisor = 255 / group_nr;
  return Math.ceil(x / divisor) * divisor;
};

function pixel_data_to_key(pixel_data) {
  return pixel_data[0].toString() + '-' + pixel_data[1].toString() + '-' + pixel_data[2].toString();

}

function posterize(context, image_data, palette) {
  for (var i = 0; i < image_data.data.length; i += 4) {
    rgb = image_data.data.slice(i, i + 3);
    hsl = rgb_to_hsl(rgb[0], rgb[1], rgb[2]);
    key = pixel_data_to_key(hsl);
    if (key in palette) {
      new_hsl = palette[key];

      new_rgb = hsl_to_rgb(new_hsl[0], new_hsl[1], new_hsl[2]);
      rgb = hsl_to_rgb(hsl);
      image_data.data[i] = new_rgb[0];
      image_data.data[i + 1] = new_rgb[1];
      image_data.data[i + 2] = new_rgb[2];
    }
  }
  context.putImageData(image_data, 0, 0);
}


function draw(img) {


  var canvas = document.getElementById('canvas');
  var context = canvas.getContext('2d');
  context.drawImage(img, 0, 0, canvas.width, canvas.height);
  img.style.display = 'none';
  var image_data = context.getImageData(0, 0, canvas.width, canvas.height);
  var data = image_data.data;


  context.drawImage(target_image, 0, 0, canvas.width, canvas.height);
  data = context.getImageData(0, 0, canvas.width, canvas.height).data;

  original_pixels = [];
  for (i = 0; i < data.length; i += 4) {
    rgb = data.slice(i, i + 3);
    hsl = rgb_to_hsl(rgb[0], rgb[1], rgb[2]);
    original_pixels.push(hsl);
  }

  group_headers = [];
  groups = {};
  for (i = 0; i < original_pixels.length; i += 1) {
    if (group_headers.length == 0) {
      group_headers.push(original_pixels[i]);
    }
    group_found = false;
    for (j = 0; j < group_headers.length; j += 1) {
      // if a similar color was already observed
      if (color_distance(original_pixels[i], group_headers[j]) < 0.3) {
        group_found = true;
        if (!(pixel_data_to_key(original_pixels[i]) in groups)) {
          groups[pixel_data_to_key(original_pixels[i])] = group_headers[j];
        }
      }
      if (group_found) {
        break;
      }
    }
    if (!group_found) {
      if (group_headers.indexOf(original_pixels[i]) == -1) {
        group_headers.push(original_pixels[i]);
      }
      if (!(pixel_data_to_key(original_pixels[i]) in groups)) {
        groups[pixel_data_to_key(original_pixels[i])] = original_pixels[i];
      }
    }
  }
  posterize(context, image_data, groups)
}


var target_image = new Image();
target_image.crossOrigin = "";
target_image.onload = function() {
  draw(target_image)
};
target_image.src = "http://i.imgur.com/zRzdADA.jpg";
canvas {
  width: 300px;
  height: 200px;
}
<canvas id="canvas"></canvas>

Ответ 2

Вы можете использовать алгоритм "Среднее смещение", чтобы сделать то же самое.

Вот пример. введите описание изображения здесь

Вам нужно будет определить параметры функции эвристически.

И вот обертка для того же самого в node.js

npm Wrapper для алгоритма смены средств

Надеюсь, это поможет!

Ответ 3

Процесс, который вы пытаетесь выполнить, называется Сегментация изображений, и это изучался область в компьютерном зрении, с сотнями различных алгоритмов и реализаций.

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

У меня мало опыта в Node.js, однако из Googling я нашел библиотеку GraphicsMagic, которая как функция сегмента которые могли бы выполнить эту работу (не подтверждены).

В любом случае я бы попытался найти библиотеки "сегментации изображений" и, если возможно, не ограничивался только реализациями Node.js, так как этот язык не является обычной практикой для написания приложений для видения, в отличие от С++/Java/Python.

Ответ 5

Я попробую другой подход. Отметьте это описание о том, как может работать алгоритм заполнения заливки:

  • Создайте массив для хранения информации о уже окрашенных координатах.
  • Создайте массив рабочих списков для хранения координат, на которые нужно посмотреть. Поместите в него начальную позицию.
  • Когда рабочий список пуст, мы закончили.
  • Удалите одну пару координат из списка работ.
  • Если эти координаты уже находятся в нашем массиве цветных пикселей, вернитесь к шагу 3.
  • Цвет пикселя в текущих координатах и ​​добавьте координаты в массив цветных пикселей.
  • Добавить координаты каждого смежного пикселя, цвет которого совпадает с исходным цветом исходного пикселя в рабочем списке.
  • Вернитесь к шагу 3.

"Поисковый подход" превосходит, потому что он выполняет поиск не только слева направо, но и во всех направлениях.