如何将 RGB 或 HEX 颜色代码分组为更大的颜色组?

Posted

技术标签:

【中文标题】如何将 RGB 或 HEX 颜色代码分组为更大的颜色组?【英文标题】:How to group RGB or HEX color codes to bigger sets of color groups? 【发布时间】:2021-01-07 15:59:40 【问题描述】:

我正在分析大量图像并提取主要颜色代码。

我想将它们分组为通用颜色名称的范围,例如绿色、深绿色、浅绿色、蓝色、深蓝色、浅蓝色等。

我正在寻找一种与语言无关的方式来自己实现某些东西,如果有我可以研究的例子来实现这一点,我将不胜感激。

【问题讨论】:

我建议您检查图像在 RGB 颜色空间中的直方图,查看每个通道后,您可以得出图像颜色的结论。 Histogram of an image 【参考方案1】:

在机器学习领域,您要做的称为classification,其目标是将其中一个类(颜色)的标签分配给每个观察值(图像)。 为此,必须预先定义类。假设这些是我们要分配给图像的颜色:

要确定图像的主色,必须计算其每个像素与表中所有颜色之间的距离。请注意,此距离是在 RGB 颜色空间中计算的。要计算图像的第 ij 个像素点与表格的第 k 个颜色之间的距离,可以使用以下等式:

d_ijk = sqrt((r_ij-r_k)^2+(g_ij-g_k)^2+(b_ij-b_k)^2)

在下一步中,对于每个像素,选择表中最接近的颜色。这是用于使用indexed colors 压缩图像的概念(除了这里的调色板对于所有图像都是相同的,并且不会为每个图像计算以最小化原始图像和索引图像之间的差异)。现在,正如@jairoar 指出的那样,我们可以获得图像的直方图(不要与 RGB 直方图或强度直方图混淆),并确定重复次数最多的颜色。 为了展示这些步骤的结果,我使用了这件艺术品的随机裁剪!我的:

这是索引前后图像的外观(左:原始,右:索引): 这些是最重复的颜色(左:索引,右:主色):

但既然你说图像数量很大,你应该知道这些计算是比较耗时的。但好消息是有一些方法可以提高性能。例如,您可以使用City Block 或Chebyshev 距离,而不是使用Euclidean distance(上面的公式)。您也可以只计算一小部分像素的距离,而不是计算图像中所有像素的距离。为此,您可以先将图像缩放到更小的尺寸(例如,32 x 32),然后对该缩小图像的像素执行计算。如果您决定调整图像大小,不要费心使用双线性或双三次插值,这不值得额外计算。取而代之的是nearest neighbor,它实际上对原始图像执行rectangular lattice 采样。

虽然上面提到的变化会大大提高计算速度,但没有什么好东西是免费的。这是性能与准确性的权衡。例如,在前面两张图片中,我们看到最初被识别为橙色(代码 20)的图像在调整大小后已被识别为粉红色(代码 26)。 要确定算法的参数(距离测量、缩小图像大小和缩放算法),您必须首先以尽可能高的精度对多个图像进行分类操作,并将结果作为基本事实。然后,通过多次实验,得到不使分类误差超过最大容许值的参数组合。

【讨论】:

我明白了,所以您可以使用颜色量化 (en.wikipedia.org/wiki/Color_quantization) 来减少图像信息,然后使用欧几里德距离将颜色与预定义列表进行匹配 @GeorgeKaf 如果“减少图像信息”只是指调整图像大小,那么可以。如您链接的页面中所述,“大多数标准技术将颜色量化视为三维空间中聚类点的问题”。我建议将其视为分类问题。我们不需要知道“RGB 空间”中的哪些颜色最能描述图像,相反,我们有兴趣找到“预定义调色板”中的哪些颜色最能描述图像。重点是,当调色板已知时,我们可以省略聚类阶段。 @GeorgeKaf 如果您没有调色板,您可以对样本数据集执行两个聚类步骤来获取它。首先对集合中的每个图像进行颜色量化并存储其所有调色板,然后将所有调色板中的所有颜色再次聚类以获得单个调色板。【参考方案2】:

@saastn 的绝妙答案假设您有一组预定义的颜色,您希望将图像分类到这些颜色。如果您只是想将图像分类为一组 X 等距颜色中的一种颜色(即直方图),则实施会更容易。

总而言之,将图像中每个像素的颜色四舍五入到一组等距颜色箱中最接近的颜色。这会将您的颜色精度降低到您想要的任何颜色数量。然后计算图像中的所有颜色,并选择最常见的颜色作为该图像的分类。

这是我在 Python 中的实现:

import cv2
import numpy as np

#Set this to the number of colors that you want to classify the images to
number_of_colors = 8

#Verify that the number of colors chosen is between the minimum possible and maximum possible for an RGB image.
assert 8 <= number_of_colors <= 16777216

#Get the cube root of the number of colors to determine how many bins to split each channel into.
number_of_values_per_channel = number_of_colors ** ( 1 / 3 )

#We will divide each pixel by its maximum value divided by the number of bins we want to divide the values into (minus one for the zero bin).
divisor = 255 / (number_of_values_per_channel - 1)

#load the image and convert it to float32 for greater precision. cv2 loads the image in BGR (as opposed to RGB) format.
image = cv2.imread("image.png", cv2.IMREAD_COLOR).astype(np.float32)

#Divide each pixel by the divisor defined above, round to the nearest bin, then convert float32 back to uint8.
image = np.round(image / divisor).astype(np.uint8)

#Flatten the columns and rows into just one column per channel so that it will be easier to compare the columns across the channels.
image = image.reshape(-1, image.shape[2])

#Find and count matching rows (pixels), where each row consists of three values spread across three channels (Blue column, Red column, Green column).
uniques = np.unique(image, axis=0, return_counts=True)

#The first of the two arrays returned by np.unique is an array compromising all of the unique colors.
colors = uniques[0]

#The second of the two arrays returend by np.unique is an array compromising the counts of all of the unique colors.
color_counts = uniques[1]

#Get the index of the color with the greatest frequency
most_common_color_index = np.argmax(color_counts)

#Get the color that was the most common
most_common_color = colors[most_common_color_index]

#Multiply the channel values by the divisor to return the values to a range between 0 and 255
most_common_color = most_common_color * divisor

#If you want to name each color, you could also provide a list sorted from lowest to highest BGR values comprising of
#the name of each possible color, and then use most_common_color_index to retrieve the name.
print(most_common_color)

【讨论】:

我想你使用Image.open('image.png').convert('RGB').getcolors() 来获取实际的number_of_colors ? number_of_colors 将是您要将图像分类到的颜色类别的数量。这是一个你会决定的数字。例如,如果您想将图像分类为以下 8 个类别:[“黑色”、“红色”、“绿色”、“蓝色”、“青色”、“紫色”、“黄色”、“白色”],您将输入 8。 PIL 用于计算直方图的 getcolors() 函数仅适用于黑白图像,并且不允许您设置 bin 的数量。 CV2 的直方图功能可让您设置 bin 的数量,但不允许您使用彩色图像。使用 CV2,您必须拆分通道,然后组合生成的直方图,这似乎比我做的更难实现。在某个库中的某个地方应该有一个函数可以完成我所做的事情,但我不知道。我还担心过度依赖库会导致难以用另一种语言实现。

以上是关于如何将 RGB 或 HEX 颜色代码分组为更大的颜色组?的主要内容,如果未能解决你的问题,请参考以下文章

在 C 中从 HEX 颜色转换为 RGB 结构

如何将由小正方形组成的区域划分为更大的矩形?

Sass/Compass - 将 Hex、RGB 或命名颜色转换为 RGBA

将RGB颜色转换为HEX颜色

如何使用快捷键(只有键盘没有鼠标)更改 css 文件中的颜色类型(rgb、hsl、hex)?代码

在 Delphi 中是不是可以将枚举合并为更大的枚举?