在图像中找到相似颜色的区域

aba*_*haw 13 javascript image node.js

我一直在研究这个问题已经有一段时间没什么可喜的结果了.我试图将图像分割成相似颜色的连接区域.(基本上将所有像素的列表分成多个组(每个组包含属于它的像素的坐标并共享相似的颜色).

例如:http: //unsplash.com/photos/SoC1ex6sI4w/

在这张图片中,顶部的乌云可能会落入一组.山上的一些灰色岩石在另一个,另一些是橙色的草.雪会是另一个 - 背包的红色 - 等等.

我正在尝试设计一种既准确又高效的算法(它需要在中端笔记本电脑级硬件上运行几毫秒)


以下是我的尝试:

使用基于连通分量的算法从左上角扫描每个像素,从左到右扫描每行像素(并将当前像素与顶部像素和左像素进行比较).使用CIEDE2000色差公式,如果顶部或左侧的像素在一定范围内,那么它将被视为"相似"并且是该组的一部分.

这种方法有效 - 但问题在于它依赖于具有锐边的颜色区域 - 如果任何颜色组通过软渐变连接,它将沿着该渐变向下移动并继续"连接"像素,因为各个像素之间的差异是比较小到足以被认为是"相似的".

为了解决这个问题,我选择将每个访问过的像素的颜色设置为大多数"相似"相邻像素(顶部或左侧)的颜色.如果没有相似的像素,则保留其原始颜色.这在某种程度上解决了更加模糊的边界或柔化边缘的问题,因为随着算法的进展,新组的第一种颜色将被"携带",并且最终颜色与当前比较颜色之间的差异将超过"相似性"威胁和不再是该群体的一部分.

希望这是有道理的.问题是这些选项都没有真正起作用.在上面的图像上返回的不是干净的组,而是嘈杂的碎片组,这不是我想要的.

我不是专门寻找代码 - 而是关于如何构建算法以成功解决这个问题的更多想法.有没有人有这个想法?

谢谢!

Iva*_*aer 7

您可以转换RGBHSL更容易计算颜色之间的距离.我正在设置行中的色差容差:

if (color_distance(original_pixels[i], group_headers[j]) < 0.3) {...}
Run Code Online (Sandbox Code Playgroud)

如果更改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";
Run Code Online (Sandbox Code Playgroud)
canvas {
  width: 300px;
  height: 200px;
}
Run Code Online (Sandbox Code Playgroud)
<canvas id="canvas"></canvas>
Run Code Online (Sandbox Code Playgroud)