背景
在做 项目 的时候,需要判断一个颜色值所在色系(如:红橙黄绿青蓝紫黑白灰),用眼睛观察太慢,算不上好办法,那么怎么判断呢?通过阅读 该讨论 知道了一种方案:将 RGB 色值转化为 HSV,之后通过 Hue 去判断彩色的种类,用明度去判断黑白灰。具体实现如下。
基础知识
RGB
RGB 是从颜色发光的原理来设计定的,通俗点说它的颜色混合方式就好像有红、绿、蓝三盏灯,当它们的光相互叠合的时候,色彩相混,而亮度却等于两者亮度之总和,越混合亮度越高,即加法混合。
红、绿、蓝三个颜色通道每种色各分为 256 阶亮度,在 0 时“灯”最弱——是关掉的,而在 255 时“灯”最亮。当三色灰度数值相同时,产生不同灰度值的灰色调,即三色灰度都为 0 时,是最暗的黑色调;三色灰度都为 255 时,是最亮的白色调。
在电脑中,RGB 的所谓“多少”就是指亮度,并使用整数来表示。通常情况下,RGB 各有 256 级亮度,用数字表示为从 0、1、2…直到 255。注意虽然数字最高是 255,但 0 也是数值之一,因此共 256 级。
HSV
HSV 是一种比较直观的颜色模型,所以在许多图像编辑工具中应用比较广泛,这个模型中颜色的参数分别是:色调(H, Hue),饱和度(S,Saturation),明度(V, Value)。
色调(Hue)
用角度度量,取值范围为 0°~360°,从红色开始按逆时针方向计算,红色为 0°,绿色为 120°,蓝色为 240°。它们的补色是:黄色为 60°,青色为 180°,品红为 300°;
饱和度(Saturation)
饱和度 S 表示颜色接近光谱色的程度。一种颜色,可以看成是某种光谱色与白色混合的结果。其中光谱色所占的比例愈大,颜色接近光谱色的程度就愈高,颜色的饱和度也就愈高。饱和度高,颜色则深而艳。光谱色的白光成分为 0,饱和度达到最高。通常取值范围为 0%~100%,值越大,颜色越饱和。
明度(Value)
明度表示颜色明亮的程度,对于光源色,明度值与发光体的光亮度有关;对于物体色,此值和物体的透射比或反射比有关。通常取值范围为 0%(黑)到 100%(白)。
取值范围说明
我们需要注意的在不同应用场景中,HSV 取值范围是不尽相同的。
1.PS 软件时,H 取值范围是 0-360,S 取值范围是(0%-100%),V 取值范围是(0%-100%)。
2.利用 openCV 中 cvSplit 函数的在选择图像 IPL_DEPTH_32F 类型时,H 取值范围是 0-360,S 取值范围是 0-1(0%-100%),V 取值范围是 0-1(0%-100%)。
3.利用 openCV 中 cvSplit 函数的在选择图像 IPL_DEPTH_8UC 类型时,H 取值范围是 0-180,S 取值范围是 0-255,V 取值范围是 0-255。
那么,开始写代码吧!
RGB 转 HSV
公式
- 常量
$$
\begin{array}{l}
R^{\prime}=R / 255\\
G^{\prime}=G / 255\\
B^{\prime}=B / 255\\
\operatorname{Cmax}=\max \left(R^{\prime}, G^{\prime}, B^{\prime}\right)\\
C \min =\min \left(R^{\prime}, G^{\prime}, B^{\prime}\right)\\
\Delta=\mathrm{Cmax}-\mathrm{Cmin}
\end{array}
$$ - H 计算
$$
H=\left\{\begin{array}{cc}0^{\circ} & \Delta=0 \\ 60^{\circ} \times\left(\frac{G^{\prime}-B^{\prime}}{A} \bmod 6\right) & , C_{\max }=R^{\prime} \\ 60^{\circ} \times\left(\frac{B^{\prime}-R^{\prime}}{\Delta}+2\right) & , C_{\max }=G^{\prime} \\ 60^{\circ} \times\left(\frac{R^{\prime}-G^{\prime}}{\Delta}+4\right) & , C_{\max }=B^{\prime}\end{array}\right.
$$ - S 计算
$$
S=\left\{\begin{array}{cl}
0 & C \max =0 \\
\frac{\Delta}{C \max } & C \max \neq 0
\end{array}\right.
$$ - V 计算
$V=C \max$
代码(RGB 转 HSV)
另一种计算方式来自此处:👉 Python Math: Convert RGB color to HSV color - w3resourcedef hue_calculate_org(round1, round2, delta, add_num):
return ((round1 - round2) / delta + add_num) * 60
def rgb_to_hsv_org(rgb_seq):
r, g, b = rgb_seq
r_round = float(r) / 255
g_round = float(g) / 255
b_round = float(b) / 255
max_c = max(r_round, g_round, b_round)
min_c = min(r_round, g_round, b_round)
delta = max_c - min_c
h = None
if delta == 0:
h = 0
elif max_c == r_round:
h = ((g_round - b_round) / delta % 6) * 60
elif max_c == g_round:
h = hue_calculate_org(b_round, r_round, delta, 2)
elif max_c == b_round:
h = hue_calculate_org(r_round, g_round, delta, 4)
if max_c == 0:
s = 0
else:
s = delta / max_c
return h, s, max_cdef hue_calculate(round1, round2, delta, add_num):
return (((round1 - round2) / delta) * 60 + add_num) % 360
def rgb_to_hsv(rgb_seq):
r, g, b = rgb_seq
r_round = float(r) / 255
g_round = float(g) / 255
b_round = float(b) / 255
max_c = max(r_round, g_round, b_round)
min_c = min(r_round, g_round, b_round)
delta = max_c - min_c
h = None
if delta == 0:
h = 0
elif max_c == r_round:
h = hue_calculate(g_round, b_round, delta, 360)
elif max_c == g_round:
h = hue_calculate(b_round, r_round, delta, 120)
elif max_c == b_round:
h = hue_calculate(r_round, g_round, delta, 240)
if max_c == 0:
s = 0
else:
s = (delta / max_c) * 100
v = max_c * 100
return h, s, v颜色划分
分析
Color | Color name | (H,S,V) | Hex | (R,G,B) |
---|---|---|---|---|
Black | (0°,0%,0%) | #000000 | (0,0,0) | |
Gray | (0°,0%,50%) | #808080 | (128,128,128) | |
White | (0°,0%,100%) | #FFFFFF | (255,255,255) | |
Red | (0°,100%,100%) | #FF0000 | (255,0,0) | |
Yellow | (60°,100%,100%) | #FFFF00 | (255,255,0) | |
Lime | (120°,100%,100%) | #00FF00 | (0,255,0) | |
Cyan | (180°,100%,100%) | #00FFFF | (0,255,255) | |
Blue | (240°,100%,100%) | #0000FF | (0,0,255) | |
Magenta | (300°,100%,100%) | #FF00FF | (255,0,255) |
数据来源:HSV to RGB conversion | color conversion
看色图,哦不,色环图。😳
先不管黑灰白三种色,找其他颜色与色环的对应关系,我们很容易发现 H 与色环的对应关系。
- 变换色相 H,保持 S,V 不变,颜色发生变化;修改 H 会引起颜色变化;
- 变换亮度 V,保持 H,S 不变,我们发现颜色在黑灰白之间变化;
- 变换饱和度 S,保持 H,V 不变,颜色不会变化,变的只是颜色的深浅程度;因为人眼对明亮的颜色更加敏感,所以我们适当调高亮度,之后逐渐修改 S,观察颜色变化。
有内味儿了,颜色变得越来越浓郁了!
代码
颜色归类(按色系)
|
代码验证
|
返回结果:blue
cyan
red
cyan
yellow
至此,我们完成了数据颜色粗略的归类。
到这里就结束了吗?显然没有。实际上,这种数据返回结果是很粗糙的,我们需要对 Hue 进行反复修正才能得出一个较为满意的结果。正如物理实验一样:理论和实践可能存在较大偏差甚至可能得出完全相反的结论,此处我们不再展开。😂我们进一步分析数据,发现:
甚三紅、黄朽葉、千歳緑、錆青磁、江戸紫、白練……
你细品~是的,部分颜色名称里面已经包含了颜色的色系关键字。我们假定名称的命名是靠谱的。那么,我们可以利用正则把色系关键字提取出来,然后只对没有指明的颜色进行计算。
可是上面的代码已经写完了,我不想再大动干戈修改此函数,怎么办?此处,我们使用装饰器。
颜色归类(按名称)
import re |
关于此处装饰器的写法,参见《Python Cookbook 中文版》V3 P342 第 9.5 章节:定义一个属性可由用户修改的装饰器。
最后,我们给之前写的函数戴上这顶叫做装饰器的“帽子”。
装饰器使用
|
返回结果red
red
red
yellow
green
总结展望
经过上面的步骤我们完成了颜色的分类,但是这种分类是粗粒度的,结果也可能是不够准确的。如果需要更精准的计算,可能需要建模及算法甚至训练模型去实现,这里不再展开(主要是我不会)。
如果颜色本身命名错误,如上例中的“万红丛中一点绿”,我们直接走装饰器判断逻辑,就会使计算过滤的逻辑失效。所以我们可以数据分别走两套逻辑,对于两者返回一致的,我们认为这个数据是可信的;对于不一致的部分,我们则需要使用人工智能的“人工”部分对数据进行二次编辑,之后将结果更新上去。
西风吟
- 我们知道 Python 是一门自带电池的语言,关于颜色 rgb 转 hsv,其实 官方模块
colorsys
中已经有自己的实现,我们需要的只是对数据做相应比例的放大。当然我们也可以在后续逻辑中直接用官方返回的数据划分区域。>>> import colorsys
>>> colorsys.rgb_to_hsv(0.2, 0.4, 0.4)
(0.5, 0.5, 0.4)
>>> colorsys.hsv_to_rgb(0.5, 0.5, 0.4)
(0.2, 0.4, 0.4) - 关于颜色的判断,我们知道色的三原色为 RGB(红绿蓝),而实际颜色是按照不同比例混合的,所以颜色是没有办法真的做明显界线区分的。
谁能在彩虹里画下紫色结束、橙色开始的分界线呢?我们能清晰地看到颜色的不同,但是究竟在什么地方一种颜色逐渐地混入了另一种颜色呢?理智和疯狂的界限,亦是如此。
- 日常黑男性环节:
链接
源码下载
🍭 my_practices/codes/tell_color_by_rgb at master · imoyao/my_practices
参考链接
- 从 RGB 到 HSV 的转换详细介绍_人工智能_hanshanbuleng 的博客-CSDN 博客
- 知道一个颜色的 RGB 如何归类到特定颜色(如赤橙黄绿青蓝紫黑白灰)? - V2EX
- How can I tell basic color a hex code is closest to? - Graphic Design Stack Exchange
- algorithm - “Rounding” colour values to the nearest of a small set of colours - Stack Overflow