我一直在研究这个问题已经有一段时间没有什么好处了。我试图将图像分割成相似颜色的连接区域。 (基本上将所有像素的列表分成多个组(每个组包含属于它的像素的坐标并共享相似的颜色)。
例如: http://unsplash.com/photos/SoC1ex6sI4w/
在此图像中,顶部的乌云可能会分成一组。山上的一些灰色岩石在另一个,另一些是橙色的草。雪将是另一个 - 背包的红色 - 等等。
我正在尝试设计一种既准确又高效的算法(它需要在中档笔记本电脑级硬件上以毫秒运行)
以下是我的尝试:
使用基于连通分量的算法从左上角扫描每个像素,从左到右扫描每行像素(并将当前像素与顶部像素和左像素进行比较)。使用CIEDE2000色差公式,如果顶部或左侧的像素在一定范围内,那么它将被视为“相似”并且是该组的一部分。
这种方法有效 - 但问题是它依赖于具有锐边的颜色区域 - 如果任何颜色组通过软渐变连接,它将向下移动该渐变并继续“连接”像素作为被比较的各个像素小到足以被认为是“相似的”。
为了尝试解决这个问题,我选择将每个访问过的像素的颜色设置为大多数“相似”相邻像素(顶部或左侧)的颜色。如果没有相似的像素,则保留其原始颜色。这在某种程度上解决了更加模糊的边界或柔化边缘的问题,因为随着算法的进展,新组的第一种颜色将被“携带”,并且最终该颜色与当前比较颜色之间的差异将超过“相似性”威胁和不再是该组的成员。
希望这是有道理的。问题是这些选项都没有真正起作用。在上面的图像上返回的不是干净的组,而是嘈杂的碎片组,这不是我想要的。
我不是专门寻找代码 - 而是关于如何构建算法以成功解决此问题的更多想法。有没有人有这方面的想法?
谢谢!
答案 0 :(得分:7)
您可以从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";
&#13;
canvas {
width: 300px;
height: 200px;
}
&#13;
<canvas id="canvas"></canvas>
&#13;
答案 1 :(得分:3)
您可以使用“Mean Shift Filtering”算法来执行相同的操作。
您必须启发式地确定功能参数。
这是node.js中相同的包装器
npm Wrapper for meanshift algorithm
希望这有帮助!
答案 2 :(得分:1)
您尝试完成的过程称为Image Segmentation,它是计算机视觉中的well studied area,具有数百种不同的算法和实现。
你提到的算法应该适用于简单的图像,但是对于真实世界的图像,例如你链接到的图像,你可能需要一个更复杂的算法,甚至可能需要特定于域的算法(你的所有图像都包含一个查看?)。
我对Node.js的经验不多,但是从谷歌搜索中我找到了GraphicsMagic库,它作为可以完成工作的段函数(尚未验证)。
无论如何,我会尝试寻找“图像分割”库,如果可能的话,不要仅限于Node.js实现,因为这种语言不是编写视觉应用程序的常用做法,而不是C ++ / Java / Python。
答案 3 :(得分:-1)
我会尝试不同的方法。查看this description flood填充算法的工作原理:
- 创建一个数组来保存有关已着色坐标的信息。
- 创建工作列表数组以保存必须查看的坐标。将起始位置放入其中。
- 当工作清单为空时,我们就完成了。
- 从工作清单中删除一对坐标。
- 如果这些坐标已经在我们的彩色像素数组中,请返回步骤3.
- 为当前坐标处的像素着色,并将坐标添加到彩色像素数组中。
- 将颜色与起始像素的原始颜色相同的每个相邻像素的坐标添加到工作清单中。
- 返回第3步。
“搜索方法”更优越,因为它不仅从左到右搜索,而且在所有方向搜索。
答案 4 :(得分:-1)
你可能会看一下k-means聚类。 http://docs.opencv.org/3.0-beta/modules/core/doc/clustering.html