如何将位图减少到已知的RGB颜色集合?

8
作为一个兴趣项目,我打算编写一个程序,当给定一张位图图像时,将其创建为PDF的十字绣图案。我将在Mac上使用Cocoa/Objective C。
源位图通常是24bpp图像,但是在数百万可用颜色中,只有少数作为十字绣线。线材有各种类型。DMC是最广泛可用的,几乎他们的全部范围都可以从各个网站的RGB值获得。例如这里
DMC#  Name               R   G   B
----- ------------------ --- --- ---
blanc White              255 255 255
208   Lavender - vy dk   148  91 128
209   Lavender - dk      206 148 186
210   Lavender - md      236 207 225
211   Lavender - lt      243 218 228
      ...etc...

我认为我的第一个问题是从图像中的像素的RGB值作为起点,选择最接近DMC颜色组中可用的颜色。在数学上找到最接近的DMC颜色的最佳方法是什么,并确保它在颜色上也是比较接近的匹配?

虽然我将使用Cocoa,但请随意在任何发布的代码中使用伪代码(甚至Java!)。


重复?https://dev59.com/pnRB5IYBdhLWcg3w1Kv0 - bzlm
你可以使用ImageMagick来完成这个任务,网址为http://www.imagemagick.org/Usage/quantize/。 - Daniel Wedlund
如果你的十字绣不需要对齐,那么抖动是你的朋友(因为你有固定的调色板)。量化通常限制了颜色数量,使其少于输入图像的颜色数,但你的调色板可以包含图像中不存在的颜色,从而导致奇怪的颜色失真。为了正确地进行抖动,你至少需要:红色、绿色、蓝色、青色、洋红色、黄色、黑色、白色。如果你有多个强度,那就更好了,但单个强度也可以使用。 - Spektre
6个回答

10

使用LAB颜色空间,找到最接近欧几里得距离的颜色。在RGB颜色空间中进行此操作将产生令人费解的结果。(或者使用HSL颜色空间。)

因此,只需迭代每个像素,并在所选择的颜色空间中找到具有最接近距离的颜色。请注意,某些颜色空间(例如,那些采用色调的颜色空间)必须进行循环计算距离。

(大多数颜色量化都围绕着实际选择调色板,但在您的情况下已经处理好了,因此您不能使用更流行的量化技术。)

此外,请查看这个问题

要在Cocoa中查找HSB色相,似乎可以使用NSColor.h中声明的getHue方法

然而,如果您仅使用此技术将图像转换为十字绣设计,实际上很难将其缝制在一起。 它将充满单像素颜色字段,这种情况有点违背了十字绣的初衷。


你假定源图像是一张照片,没错。还有一堆其他十字绣的东西我考虑过,但是不想在问题上负担太重。 - banjollity
不,我实际上并没有假设那样。我只是在谈论“每像素”方法如何不能将图像作为一个整体考虑。但如果您的源图像已经被大量量化,那当然就没有问题了。(我也曾经历过拇指疼痛的抱怨缝合。) - bzlm
我认为你的意图细节不会使问题过载。 - bzlm
请注意,如果您像 HSL 这样使用空格,则需要在色相通道上执行圆形欧几里得距离。 - Mr Fooz
我已经编辑了答案,使其更加清晰。欧几里得会感到自豪的。 - bzlm
这个问题只是整个问题的一部分。大量量化的图像,白色背景,尽可能少的颜色都可以使十字绣更好。此外还有其他技巧,如基本抗锯齿的半针,用线条绣出细节等等。这些内容太多了,不适合一个问题来回答。 - banjollity

3

这被称为颜色量化,有许多可用的算法。

其中一个基本方法是将RGB颜色视为空间中的点,并使用颜色之间的普通欧几里得距离来确定它们的“接近程度”。这种方法有缺点,因为人眼在空间中的不同位置具有不同的敏感度,因此这种距离与人们感知颜色的方式不符。您可以使用各种加权方案来改善这种情况。


我建议使用另一种颜色空间,而不是自制的加权方案。 - bzlm

2

有趣... :)

不仅需要识别最近的颜色,还需要减少使用的颜色数量。您不希望最终得到一个使用数百种不同颜色的针织图案...

我编写了一些基础代码来实现这一点。(很抱歉它是用C#编写的,但我希望它可以在某种程度上有所帮助。)

当然,在方法有效之前还需要进行一些进一步的调整。GetDistance方法会权衡色相、饱和度和亮度之间的重要性,找到最佳平衡当然是很重要的,以便找到最接近的颜色。

在减少调色板的方法方面,还有很多可以做的。在这个例子中,我只选择了最常用的颜色,但您可能想考虑调色板中颜色的相似度。这可以通过选择最常用的颜色,根据到选定颜色的距离降低其余颜色的计数,然后重新排序列表来完成。

包含DMC颜色的Hsl类可以计算与另一种颜色的距离,并在颜色列表中找到最近的颜色:

public class Hsl {

    public string DmcNumber { get; private set; }
    public Color Color { get; private set; }
    public float Hue { get; private set; }
    public float Saturation { get; private set; }
    public float Brightness { get; private set; }
    public int Count { get; set; }

    public Hsl(Color c) {
        DmcNumber = "unknown";
        Color = c;
        Hue = c.GetHue();
        Saturation = c.GetSaturation();
        Brightness = c.GetBrightness();
        Count = 0;
    }

    public Hsl(string dmc, int r, int g, int b)
        : this(Color.FromArgb(r, g, b))
    {
        DmcNumber = dmc;
    }

    private static float AngleDifference(float a1, float a2) {
        float a = Math.Abs(a1 - a2);
        if (a > 180f) {
            a = 360f - a;
        }
        return a / 180f;
    }

    public float GetDistance(Hsl other) {
        return
            AngleDifference(Hue, other.Hue) * 3.0f +
            Math.Abs(Saturation - other.Saturation) +
            Math.Abs(Brightness - other.Brightness) * 4.0f;
    }

    public Hsl GetNearest(IEnumerable<Hsl> dmcColors) {
        Hsl nearest = null;
        float nearestDistance = float.MaxValue;
        foreach (Hsl dmc in dmcColors) {
            float distance = GetDistance(dmc);
            if (distance < nearestDistance) {
                nearestDistance = distance;
                nearest = dmc;
            }
        }
        return nearest;
    }

}

这段代码设置了一个(大大减少的)DMC颜色列表,加载了一张图片,计算了颜色数量,缩小了调色板并转换了图像。当然,您还需要将缩小的调色板信息保存在某个地方。

Hsl[] dmcColors = {
    new Hsl("blanc", 255, 255, 255),
    new Hsl("310", 0, 0, 0),
    new Hsl("317", 167, 139, 136),
    new Hsl("318", 197, 198, 190),
    new Hsl("322", 81, 109, 135),
    new Hsl("336", 36, 73, 103),
    new Hsl("413", 109, 95, 95),
    new Hsl("414", 167, 139, 136),
    new Hsl("415", 221, 221, 218),
    new Hsl("451", 179, 151, 143),
    new Hsl("452", 210, 185, 175),
    new Hsl("453", 235, 207, 185),
    new Hsl("503", 195, 206, 183),
    new Hsl("504", 206, 221, 193),
    new Hsl("535", 85, 85, 89)
};

Bitmap image = (Bitmap)Image.FromFile(@"d:\temp\pattern.jpg");

// count colors used
List<Hsl> usage = new List<Hsl>();
for (int y = 0; y < image.Height; y++) {
    for (int x = 0; x < image.Width; x++) {
        Hsl color = new Hsl(image.GetPixel(x, y));
        Hsl nearest = color.GetNearest(dmcColors);
        int index = usage.FindIndex(h => h.Color.Equals(nearest.Color));
        if (index != -1) {
            usage[index].Count++;
        } else {
            nearest.Count = 1;
            usage.Add(nearest);
        }
    }
}

// reduce number of colors by picking the most used
Hsl[] reduced = usage.OrderBy(c => -c.Count).Take(5).ToArray();

// convert image
for (int y = 0; y < image.Height; y++) {
    for (int x = 0; x < image.Width; x++) {
        Hsl color = new Hsl(image.GetPixel(x, y));
        Hsl nearest = color.GetNearest(reduced);
        image.SetPixel(x, y, nearest.Color);
    }
}

image.Save(@"d:\temp\pattern.png", System.Drawing.Imaging.ImageFormat.Png);

1

netpbm实用程序集中获取ppmquant应用程序的源代码


1

其他人已经提出了各种颜色量化技术。可以使用马尔可夫随机场等技术,尝试对系统在相邻像素位置切换线程颜色进行惩罚。有一些通用的多标签MRF库,包括Boykov's

要使用其中之一,数据元素将是输入颜色,标签将是线程颜色集合,数据项可以是bzlm建议的LAB空间中的欧几里得距离,而邻域项将会惩罚切换线程颜色。


1
谁能想到缝纫需要计算机科学学位? - bzlm
也许计算机科学学位应该要求一些缝纫技能! - banjollity

-1

根据您的颜色操作的相关性,请记得考虑颜色空间。虽然我因为我的摄影爱好而研究了一些,但我仍然对一切感到有些困惑。

但是,如前所述,尽可能使用LAB,因为(据我所知)它是颜色空间不可知的,而所有其他方法(RGB / HSL / CMYK)在没有定义的颜色空间的情况下理论上毫无意义。

RGB,例如,只是三个百分比值(0-255 => 0-100%,具有8位色深度)。因此,如果您有一个RGB三元组(0,255,0),它会被翻译为“仅绿色,并尽可能多地使用它”。所以,问题是“红色有多红?”这就是颜色空间回答的问题 - sRGB 100%绿色不如AdobeRGB 100%绿色那么绿。甚至不是相同的hue

如果这个话题偏离了主题,我很抱歉。


我同意,在这里考虑色调(使用LAB或HSx)是很重要的。 - bzlm
维基百科:“此外,许多 Lab 空间中的“颜色”超出了人类视觉的色域范围,因此纯属虚构;这些“颜色”无法在物理世界中再现。” - LAB 真是让我头疼! - banjollity
好吧,为什么要局限于现实世界呢?毕竟你们都只是存在于我的脑海中。 - bzlm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接