如何从图像中生成突出颜色的调色板?

Posted

技术标签:

【中文标题】如何从图像中生成突出颜色的调色板?【英文标题】:How can I generate a palette of prominent colors from an image? 【发布时间】:2011-08-14 23:54:23 【问题描述】:

我试图弄清楚如何对图像中的所有像素进行采样并从中生成调色板,例如this 或this。我什至不知道从哪里开始。谁能指出我正确的方向?

__编辑:__

这是我到目前为止的结果:

我使用这个Pixelate 函数来获得像 joe_coolish 建议的大块部分。它工作得很好,给了我一个很好的颜色样本(这是来自 windows 样本果冻鱼图片):

现在,如果有人能帮我找到 5 种最独特的颜色(最深的蓝色、最浅的蓝色、橙色、灰色和桃色(?)),我会永远爱你。我真的不明白如何将颜色 平均add 在一起。我也无法弄清楚如何以编程方式判断颜色是否相似,您的解释中有很多数字和变量,我在试图弄清楚什么对谁做了什么时迷失了方向。

【问题讨论】:

像这样的问题和答案让我很自豪能成为这个社区的一员。 我看到您在问题中添加了更多内容。您还需要添加和平均颜色的帮助吗? 找到与真彩色图像最匹配的调色板是一个老问题,几种算法都试图解决这个问题。您可以从有关Color Quantization 的***页面开始,并查看此list of algorithms。 【参考方案1】:

涉及代码的答案向您展示了如何获得完整的调色板。如果您想获得您发布的网站中的平均颜色,我会这样做。

源图片:

首先,我会通过应用低通滤镜(类似于高斯模糊)来平均颜色

这样你就限制了整个调色板。从那里我会将屏幕分成 N 个块(N 是您想要在调色板中使用的像素总数)

从那里,定位每个块并迭代每个像素,并获取该块的平均像素并将其添加到您的调色板索引中。结果是这样的:

这样您的调色板就会受到限制,并且您可以从不同区域获得平均颜色。你可以在代码中完成所有这些,如果你需要一些帮助,请告诉我,我会发布一些。这只是高级别的“我会做什么”。

【讨论】:

有不同的技术。假设您在 24x24 块中工作,并且您的图像是 6x4 块(144x96 像素)。对于块 8(索引 1,1),我将确定平滑图像中的像素并将所有颜色值相加并取平均值。在此示例中,它将是 24-48 x 24-48 像素。我也会避免使用 RGB 并使用 HSI 或 HSL,然后平均这三个值。这会给你一个更好的结果 低通滤波器(模糊)用于平滑图像并去除“伪影”。您可以平均立方体中的所有颜色并且没问题,但有时您会在立方体中获得“伪影”。例如。在第一个立方体中飞行的飞机会导致随机的红色、绿色和蓝色与天空平均。模糊将平滑这些急剧的颜色变化,以获得更可预测的结果。无论哪种方式都可以。 这将不可避免地删除您想要保留的亮点。那里有很多青色,因为那里有很多海域,但你并不真的需要所有那么多的颜色。与此同时,你失去了所有黄色海滩的颜色。 这不能很好地找到图像中的突出颜色。它找到不同突出颜色的平均值,几乎可以保证测试人员发现的颜色实际存在于图像中。云不是白色,天空是蓝色,而是很多浅蓝色和灰色。 我什至不是在谈论机器学习。实际上,当您在几十年前发布此答案时,此问题已得到解决。 “自 1970 年代以来,人们一直在研究对位图进行颜色量化的计算机算法。” (quoting Wikipedia)。很抱歉对您的解决方案如此消极,我真的很惊讶这得到了如此多的支持...... :)【参考方案2】:

首先,取图片中的像素:(假设using System.Drawing.Imaging;using System.Runtime.InteropServices

Bitmap b = new Bitmap(myImage);
BitmapData bd = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, ImageFormat.Format32Bpp);
int[] arr = new int[bd.Width * bd.Height - 1];
Marshal.Copy(bd.Scan0, arr, 0, arr.Length);
b.UnlockBits(bd);

然后你就可以创建你的调色板了:

var distinctColors = arr.Distinct();

可选:消除相似的颜色,直到您拥有您喜欢的调色板尺寸。以下是您可以这样做的方法(尽管这绝对不是最有效或最准确的方法,只是最简单的方法):

var dc = distinctColors.toArray(); // int dc[] = distinctColors.toArray() is what it used to be
int cmIndex1 = -1;
int cmIndex2 = -1;
int cmDiff = -1;
for (int i = 0; i < dc.length; i++) 
    Color c1 = Color.FromArgb(dc[i]);
    for (int j = i + 1; j < dc.length; j++) 
        Color c2 = Color.FromArgb(dc[j]);
        // Note: you might want to include alpha below
        int diff = Math.Abs(c1.R - c2.R) + Math.Abs(c1.G - c2.G) + Math.Abs(c1.B - c2.B);
        if (cmDiff < 0 || diff < cmDiff) 
            cmIndex1 = i;
            cmIndex2 = j;
            cmDiff = diff;
        
    

// Remove the colors, replace with average, repeat until you have the desired number of colors

【讨论】:

有消除相似颜色的好方法吗?我知道我可以迭代像素以获得所有不同的颜色,但我想尝试消除相似的像素,直到只剩下 4 或 5 个 是的,有很多方法。我认为最简单的是:虽然有超过 5 种颜色,但循环遍历所有颜色,然后在该循​​环内循环遍历所有颜色,将颜色的 (A)RGB 总和与外部循环中的颜色进行比较。为最接近的匹配保留 3 个变量(差异、匹配 1、匹配 2)。删除最接近的两个并替换为颜色平均值。抱歉,如果这有点令人困惑:P【参考方案3】:

在任何丰富的图像中,您的大多数颜色都可能在某种程度上是独一无二的。因此,获取不同的颜色可能无法帮助您实现目标。

我建议检查图像中每个像素的 HSV 值。我将为您提供无数在线示例,这些示例将图像检索为 HSV 值数组。

使用您的 HSV 值,您可以通过创建一个包含 256 个色调计数的整数数组来计算突出色调的集群,从而计算图像数据中的色调直方图。您可以通过查找具有高计数和的 4-6 个连续色调的集群来确定突出的色调。

在挑选出几个突出的色调后,将这些色调的像素细分为另一个测量饱和度的直方图,并挑选出突出的簇,等等。

粗略示例

下面的代码尝试帮助识别突出的色调。很可能还有其他很棒的方法可以做到这一点;不过,这可能会提供一些想法。

首先,我将所有图像颜色作为Color 对象的数组,如下所示:

private static Color[] GetImageData(Image image)

    using (var b = new Bitmap(image))
    
        var bd = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
        byte[] arr = new byte[bd.Width * bd.Height * 3];
        Color[] colors = new Color[bd.Width * bd.Height];
        Marshal.Copy(bd.Scan0, arr, 0, arr.Length);
        b.UnlockBits(bd);

        for (int i = 0; i < colors.Length; i++)
        
            var start = i*3;
            colors[i] = Color.FromArgb(arr[start], arr[start + 1], arr[start + 2]);
        

        return colors;
    

您可以考虑验证我在Color.FromArgb 方法调用中以正确的顺序获得了RGB 的顺序。

接下来,我存储了一个用于转换为 HSV 的实用方法。在我的示例中,我将只使用色调,但这里有一个完整的转换示例:

private static void ColorToHSV(Color color, out int hue, out int saturation, out int value)

    int max = Math.Max(color.R, Math.Max(color.G, color.B));
    int min = Math.Min(color.R, Math.Min(color.G, color.B));

    hue = (int)(color.GetHue() * 256f / 360f);
    saturation = (max == 0) ? 0 : (int)(1d - (1d * min / max));
    value = (int)(max / 255d);

最后,我构建了色调直方图,定义了一个色调宽度(例如 9 种色调),将计数汇总在一起,然后将计数报告给控制台。

private static void ProcessImage(Color[] imagecolors)

    var hues = new int[256];
    var hueclusters = new int[256];
    int hue, saturation, value;

    // build hue histogram.
    foreach (var color in imagecolors) 
        ColorToHSV(color, out hue, out saturation, out value);
        hues[hue]++;
    

    // calculate counts for clusters of colors.
    for (int i = 0; i < 256; i++) 
        int huecluster = 0;
        for (int count = 0, j = i; count < 9; count++, j++) 
            huecluster += hues[j % 256];
        

        hueclusters[(i + 5) % 256] = huecluster;
    

    // Print clusters on the console
    for (int i = 0; i < 256; i++) 
        Console.WriteLine("Hue 0, Score 1.", i, hueclusters[i]);
    

我没有尝试过滤到 哪些 色调可供选择。您可能需要考虑一些启发式方法,而不是盲目地选择这么多的计数,因为您可能想要选择在色谱上有些分离的色调。我没有时间进一步探讨这个问题,但我希望这能让您对可以考虑的策略有所了解。

【讨论】:

那应该是new byte[bd.Height * bd.Stride] - lines always align to 4 bytes,所以每种颜色有 3 个字节,行长很少会完全匹配 width * 3。这也意味着您需要专门迭代 y 和 x 以将这些填充字节考虑在内。【参考方案4】:

我从这里开始:

System.Drawing.Image img = System.Drawing.Bitmap.FromFile("file");
System.Drawing.Imaging.ColorPalette palette = img.Palette;
foreach (Color color in palette.Entries)

  //...

【讨论】:

这仅适用于具有 256 色或更少颜色的位图,否则 img.Palette.Entries 属性将不包含任何条目(像素映射到 RGB 颜色值而不是调色板)跨度> 如果图像已经有调色板,这整个问题都无关紧要,所以我很确定它没有......【参考方案5】:

我将在非常高的层次上描述最佳方法。

首先,您构建图片中颜色及其频率的直方图。

您最终会得到图像中所有颜色的列表,您可以使用数据聚类来找到要合并的候选颜色。根据原始颜色的频率将颜色合并为加权平均值。

通过这种方式,您可以逐渐将调色板降低到所需的保真度,同时保留高对比度但精细的细节,并且只在渐变更微妙的地方失去保真度。

一旦你有了缩小的调色板,你就可以使用调色板中最近邻的颜色为图片重新着色。

【讨论】:

有趣的是,有人花了 7 年的时间才找到这个充满业余答案的问题,并最终发布正确、成熟的解决方案。 请参阅this answer to a different question,了解业界为此实际使用的一些算法的简短描述。 优秀。非常感谢,克里斯【参考方案6】:

K-Means 聚类算法可以很好地解决这个问题。它在提取图像颜色簇的质心方面做得很好,但请注意,它的非确定性行为使得确定每个簇的实际突出度变得困难。

【讨论】:

以上是关于如何从图像中生成突出颜色的调色板?的主要内容,如果未能解决你的问题,请参考以下文章

当调色板发散时,如何使用最小值和最大值设置热图颜色?

如何将颜色减少到指定的调色板

是否有可以从图像生成调色板的 js 库?

图像的准确颜色量化以最小化调色板

如何从调色板中进行颜色渐变

如何使用 OpenCV 减少图像中的颜色数量?