拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

HSV与HSL颜色空间全解析:从原理到图像识别实战

HSV与HSL颜色空间全解析:从原理到图像识别实战

做图像处理这几年,我踩过最不值当的坑,就是拿RGB通道直接去识别颜色。有一回做一个交通信号灯的识别demo,代码逻辑简单得不能再简单——红灯就判断R通道大于150、G和B小于100。中午在实验室测得好好的,跑到傍晚的十字路口,同样的红灯,R通道数值被阳光压到100以下,G通道反而飙到150。那一刻我才意识到,RGB这种面向显示设备的颜色模型,根本不解决“人眼怎么感知颜色”这个问题。后来把判断逻辑全改成基于HSV(HSB)和HSL颜色空间,把“色相”单独拿出来用,问题才真正落地。

这篇文章不打算做教科书式的概念复述,而是用我实际做颜色识别项目时反复对比、踩坑、验证的过程,把HSV(HSB)和HSL颜色空间的原理、公式、工程差异和典型应用讲透。不管你是刚接触图像处理的新手,还是在机器视觉项目里被颜色阈值折磨过的工程师,这篇都值得你花十分钟看完。

1. 为什么颜色判断不能直接靠RGB

1.1 同一个物体,RGB数值在不同光照下差异极大

RGB是面向显示设备的加法混色模型,R、G、B三个通道本质上是在描述“三个发光原色各开多大亮度”。这种描述直接绑定硬件和设备,却和人眼对颜色的主观感知严重脱节。

我用一个非常简单的例子说明。交通信号灯的正红,在正午太阳直射时可能是(210, 60, 55),到傍晚逆光时可能是(120, 110, 100)。如果你拿RGB的欧氏距离去判断这两个像素是不是同一个颜色,结果会非常离谱:这两组数值在三维空间里距离巨大,程序会判定它们根本不是一种颜色。

但人眼一看就知道,这俩都是红色。问题出在哪?因为RGB的三个通道高度相关,光照强度一变,三通道的数值一起漂移,你很难用一组固定阈值框住同一个颜色在不同亮度下的所有表现。

在固定光照的工业检测线里,RGB还能勉强用一用,但换到户外场景、移动机器人、低照度监控这类环境,直接靠RGB阈值做颜色判断,基本等于给自己埋雷。

1.2 人眼感知颜色靠的是色相、饱和度和明度

人类描述颜色,更自然的方式是问:这是什么颜色?颜色纯不纯?亮不亮?这三个问题对应到颜色空间里,就是色相(Hue)、饱和度(Saturation)和明度(Value或Lightness)。

  • 色相H:决定颜色是红、绿、蓝还是黄,用角度表示,0度是红,120度是绿,240度是蓝。
  • 饱和度S:决定颜色的鲜艳程度,S越高颜色越纯,S越低颜色越“灰”。
  • 明度V/L:决定颜色的明暗程度,0是黑,最大值是白。

HSV(HSB)和HSL颜色空间就是把RGB立方体的信息重新投影成这三个维度的模型,让“颜色本身”和“光照明暗”解耦。这就是为什么做视觉识别时,把RGB转到HSV后再做分割,比直接在RGB上抠阈值稳得多。

1.3 HSB和HSV是一回事,HSL是另一回事

先说清楚一个最常见的概念混淆:HSB就是HSV。Photoshop颜色面板上写的HSB,里面的B是Brightness,和HSV里的V(Value)表达的是同一个物理量,只是命名习惯不同。网上很多教程把HSB和HSL当成两个不同东西,其实严格说应该是“HSV/HSB”和“HSL”两种模型。

HSL和HSV最大的区别不在H,而在亮度L的计算方式。HSV里V取RGB三个通道中的最大值,HSL里L取最大通道和最小通道的平均值。看起来只是一个小差别,却导致两个模型在工程上的表现完全不同。这个差异我放在第2章详细拆开讲。

2. 从几何模型看懂HSV和HSL的本质

2.1 色相H:从RGB立方体剪出360度色环

理解H的几何来源,比死记硬背公式有用得多。RGB空间是一个立方体,顶点分别是黑、白、红、绿、蓝、青、品红、黄。把立方体沿黑白对角线立起来,然后像切洋葱一样把它切出若干层,再把每一层的外轮廓拉成圆环,就能得到HSV和HSL共用的那个色相环。

色相环上六个关键角度,值得刻在脑子里:

角度颜色
0° / 360°红
60°黄
120°绿
180°青
240°蓝
300°品红

注意红色很特殊,它同时落在0度和360度的位置,也就是色相环的“接缝”处。这个特性在写红色阈值分割时特别坑,后面我在实战章节会专门讲。

2.2 V和L的算法差别,决定了两个模型的性格

用一个具体数值来感受。设像素RGB = (255, 0, 0),纯红:

  • HSV里:V = max(255, 0, 0) = 255,饱和度S = (255 - 0) / 255 = 1。
  • HSL里:L = (255 + 0) / 2 = 127.5,饱和度按最常用的计算方法,在L小于等于0.5时是 (255-0)/(255+0) = 1,在L大于0.5时则是 (255-0)/(255+127.5×2-255) = 1。

再看一个浅粉色 (255, 160, 160):

  • HSV里:V = 255,饱和度S = (255 - 160)/255 ≈ 0.37。
  • HSL里:L = (255 + 160)/2 = 207.5,换算成0-1约为0.81,L大于0.5,所以S = (255 - 160)/(255 + 160 - 2×207.5) = 95/(10)?不对,重新按归一化公式算:S = delta / (1 - |2L - 1|),delta = (255-160)/255 ≈ 0.372,L = 207.5/255 ≈ 0.814,|2L-1| = 0.628,S = 0.372 / (1 - 0.628) ≈ 1.0。

同样的浅粉色,在HSL里饱和度被计算成接近1,在HSV里只有0.37左右。这就是两种模型的性格差异:HSL会把人眼觉得“很淡”的颜色在模型上算得很“饱和”,而HSV在颜色发白时饱和度会快速下降,从像素分割角度讲反而更接近我们的直觉。很多东西用文字描述很抽象,但转换两次颜色空间,看看S通道的差异,就一目了然了。

2.3 一张表看清两者关键差异

对比项HSV(HSB)HSL
H通道含义色相,0-360度色相,0-360度,与HSV完全一致
V/L含义V取RGB最大通道值L取最大通道与最小通道的平均值
S计算(归一化)delta / maxdelta / (1 -
纯白位置V=1, S=0L=1, S=0
纯黑位置V=0, S=0L=0, S=0
模型形状倒六棱锥双六棱锥
感知差异V对暗部的变化不敏感,暗部信息少L更接近黑白灰阶的感知顺序
典型场景计算机视觉、机器视觉颜色分割UI设计、CSS调色、取色器

工程上有一条经验可以分享:做颜色检测和机器视觉,优先用HSV;做界面设计、前端调色、可读性分析,HSL更顺手。这不是谁比谁先进的问题,而是两者设计目标和数学特性决定的。

3. RGB转HSV的数学推导和工程实现

3.1 转换公式逐个拆解

先把最常用的RGB转HSV公式完整写出来。RGB取值归一化到[0,1]区间,令:

  • max = max(R, G, B)
  • min = min(R, G, B)
  • delta = max - min

明度V最简单:V = max。

饱和度S为:当max = 0时,S = 0;否则S = delta / max。

色相H需要按max的取值分三路计算,delta必须大于0:

  • 当max = R:H = 60° × ((G - B) / delta mod 6)
  • 当max = G:H = 60° × ((B - R) / delta + 2)
  • 当max = B:H = 60° × ((R - G) / delta + 4)

如果算出的H小于0,H += 360。上面的第一种写法用mod 6可以直接避免负值,工程上更省事。

这个公式不需要死记,但一定要理解分段的逻辑:色相由RGB三个通道中谁最大、以及最大通道与最小通道之间的差值比例决定。delta越小,说明颜色越不纯;delta等于0,颜色就是灰色,此时H没有意义,工程上习惯把它置0。

3.2 边界情况和工程上的精度陷阱

写转换代码时,下面几个边界问题十有八九会碰见。

第一,gray像素。max等于min时delta为0,H除零,必须提前判断。S在这个情况下也应该是0。很多初学代码在这里直接崩溃,或者H算出一个毫无意义的数。

第二,纯黑色。max为0时,S的分母为0。虽然黑色没有“颜色”,饱和度没有意义,但工程上必须置0,避免得到NaN。

第三,H的负角度问题。用最传统的分段公式时,当max=R且G小于B时,(G-B)/delta是负数,算出的H是负的,必须加360。如果你见过有人把H的取值范围写成[-180, 180],就是这个原因造成的。

第四,浮点和整数的取舍。很多嵌入式设备上只能用整数运算,Round方式不同会导致转换后的H、S、V相差1~2个灰度级。做阈值分割通常无所谓,但如果你在做颜色校准这类精度要求高的活,就需要统一量表。

3.3 OpenCV里的BGR2HSV和你以为的RGB2HSV不是一回事

OpenCV里的cvtColor可能是多数人落地HSV的第一站,但这里藏着一个非常经典的坑:OpenCV读图片默认是BGR通道顺序,不是RGB。如果你习惯性写成cv2.COLOR_RGB2HSV,用在一张以红色为主的图上,输出的H通道会严重偏离预期,因为红色(B=255,G=0,R=0)会被当成蓝色参与计算,结果当然一塌糊涂。

正常用法是这样:

import cv2 import numpy as np img = cv2.imread("traffic_light.jpg") # OpenCV默认读进来是BGR hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

另一个大坑是取值范围。教科书里HSV的H是0到360,S和V是0到1,但OpenCV里H的范围是0到179,S和V的范围是0到255。很多人直接拿网上查到的标准HSV阈值来用,结果整个图像都被滤掉了,就是因为没换算范围。OpenCV之所以把H范围压缩到180,是为了能用一个字节(0-255)存放同时尽量保留角度分辨率。

做分割时,需要把H、S、V阈值放在同一个范围内。下面以检测红色为例:

lower_red1 = np.array([0, 100, 100]) upper_red1 = np.array([10, 255, 255]) lower_red2 = np.array([156, 100, 100]) upper_red2 = np.array([180, 255, 255]) mask1 = cv2.inRange(hsv, lower_red1, upper_red1) mask2 = cv2.inRange(hsv, lower_red2, upper_red2) mask = cv2.bitwise_or(mask1, mask2)

红色必须同时用两个区间,原因前面说过:色相环在0度和360度接缝处都是红,对应到OpenCV的0-180范围,就是0-10和156-180两段。

3.4 一份不依赖OpenCV的手写转换代码

有些场景没法用OpenCV,比如工业相机SDK直接回调的裸数据,或者嵌入式环境。下面这份NumPy实现可以作为对照标准:

import numpy as np def rgb_to_hsv(rgb): rgb = rgb.astype(np.float32) / 255.0 r, g, b = rgb[..., 0], rgb[..., 1], rgb[..., 2] mx = np.maximum(np.maximum(r, g), b) mn = np.minimum(np.minimum(r, g), b) delta = mx - mn v = mx s = np.zeros_like(mx) h = np.zeros_like(mx) eps = 1e-10 idx_nonzero = mx > eps s[idx_nonzero] = delta[idx_nonzero] / np.maximum(mx[idx_nonzero], eps) idx_h = delta > eps idx_r = idx_h & (mx == r) idx_g = idx_h & (mx == g) idx_b = idx_h & (mx == b) h[idx_r] = 60.0 * (((g[idx_r] - b[idx_r]) / delta[idx_r]) % 6) h[idx_g] = 60.0 * ((b[idx_g] - r[idx_g]) / delta[idx_g] + 2.0) h[idx_b] = 60.0 * ((r[idx_b] - g[idx_b]) / delta[idx_b] + 4.0) return np.stack([h, s, v], axis=-1)

这份代码输出的H是0-360,S和V是0-1。如果你拿它和OpenCV的cvtColor做对比,会发现H值换算到0-180后偶尔有1个单位的偏差,主要原因是OpenCV内部用整型查表实现,精度做了取舍。实际做阈值分割时,这个偏差几乎无感。

4. 实战:用HSV做颜色检测的完整流程

4.1 全套处理链路:降噪、转换、阈值、形态学、连通域

只用inRange分割一次就完事的工程,基本只存在于教程demo里。现实中做颜色检测,我建议走完下面这条链路:

  1. 高斯模糊降噪。
  2. BGR转HSV。
  3. inRange生成掩膜。
  4. 形态学开运算去孤立噪点。
  5. 形态学闭运算填充目标内部空洞。
  6. 连通域分析,按面积和宽高比过滤候选目标。

第一步和第四步最容易被忽略,却直接影响精度。为什么先高斯模糊?因为HSV转换对微小颜色扰动极其敏感,原始图像里的传感器噪声会让同一个平坦区域的像素在H通道上剧烈波动,先模糊一下能压制这种波动,让阈值分割结果更干净。

形态学开运算是先腐蚀再膨胀,专门用来去掉掩膜上的离散小白点;闭运算是先膨胀再腐蚀,用来把目标内部的细小断裂缝起来。核大小从3×3到7×7,先试5×5通常不会出错。

连通域分析的代码结构很固定:

num, labels, stats, centroids = cv2.connectedComponentsWithStats(mask, connectivity=8) for i in range(1, num): area = stats[i, cv2.CC_STAT_AREA] if area < 500: continue x = stats[i, cv2.CC_STAT_LEFT] y = stats[i, cv2.CC_STAT_TOP] w = stats[i, cv2.CC_STAT_WIDTH] h = stats[i, cv2.CC_STAT_HEIGHT]

这里建议把面积下限当参数暴露出来,不同分辨率下同一目标的像素面积差别很大。实际项目中我一般会先等比缩放图像,再做检测,阈值参数不用改,速度能快好几倍。

4.2 Halcon等机器视觉软件里的HSV差异

如果你在某类机器视觉项目里用的是Halcon,那HSV的处理习惯又完全不一样。Halcon里的trans_from_rgb可以直接把RGB图转为HSV图,但转换出来的H、S、V三个通道都是0到255的byte图像,不是教科书里的0-360和0-100,也不是OpenCV里的H 0-180。Halcon把360度的色相环均匀塞进256个灰度级,0和255都在红色附近,这意味着你在Halcon里分割红色,同样要处理“色相环接缝”问题。

Halcon下做红色分割,思路和OpenCV完全一致,只是算子不同:

trans_from_rgb (R, G, B, H, S, V, 'hsv') threshold (H, RegionRedHue1, 0, 20) threshold (H, RegionRedHue2, 235, 255) concat_obj (RegionRedHue1, RegionRedHue2, RegionRedHue) threshold (S, RegionSat, 30, 255) threshold (V, RegionVal, 40, 255) intersection (RegionRedHue, RegionSat, RegionTmp) intersection (RegionTmp, RegionVal, RegionRed)

注意这里H的20和235对应的真实角度大约是28度和338度,和OpenCV里红色跨度的习惯是近似的,但具体范围一定要自己在Halcon的H通道上拉直方图看,不要盲目套用任意一种阈值。

4.3 用调试工具动态调参,快速锁定最佳阈值

颜色检测项目里,调阈值占了整个开发时间的三成以上。很多人配置文件里写死一组阈值,然后开始一遍遍跑程序打印结果,效率非常低。

我的经验是写一个带滑块的小工具,把H_min、H_max、S_min、S_max、V_min、V_max六个值全部暴露成滑块,实时显示当前阈值下的掩膜和原图叠加结果,这样五分钟能试完几十组组合,找到最合适的阈值区间后,再固化到配置文件。

有两点值得注意:第一,滑块范围要匹配你当前用的库的标度。OpenCV的H就到180,Halcon的H到255,别让滑块范围写错。第二,调参样本要覆盖不同光照时段。只拿中午一张图调参,下午夕阳光一照,之前调的阈值八成作废。

5. 进阶玩法:基于HSV空间融合与Retinex的低照度图像增强

5.1 为什么增强算法更愿意在HSV空间里做

低照度图像增强是监控、航拍、手机摄影里绕不开的问题。常见的Retinex系列算法如果直接在RGB三个通道上分别做增强,处理后大概率得到两类让人头疼的结果:一是整体发灰,像蒙了一层雾;二是颜色严重偏移,红色物体增强完变成橘红甚至粉色。

原因很简单:RGB三个通道的差值不仅承载颜色信息,也承载光照信息。Retinex在RGB上对三个通道独立估算照射分量,三个通道的校正系数天然不一致,很容易把原本的通道比例关系破坏掉,颜色自然就歪了。

而HSV空间把颜色(H和S)与亮度(V)完全拆开。低照度图像主要问题出在V通道太低,颜色通道相对稳定。所以只对V通道做Retinex增强,H和S保持不动,再合并回RGB,色彩还原和亮度提升能同时得到保障。这就是“基于HSV空间融合与Retinex算法”这类方法的核心动机。

5.2 Retinex算法原理与多尺度实现

Retinex的核心假设是:人眼观察到的图像S(x,y)是入射光照L(x,y)和物体反射率R(x,y)的乘积:

S = L × R

目标是估计出R,因为反射率才是物体的固有属性,不受光照影响。两边取对数,乘法变减法:

log R = log S - log L

问题变成怎么估计L。经典的SSR(单尺度Retinex)用高斯核和原图卷积来近似入射光照:

L ≈ S * G_σ

然后得到增强后的反射分量:

R_log = log S - log(S * G_σ)

单尺度的问题是高斯核尺度不好选:尺度小,细节保留多,但动态范围压缩不够;尺度大,整体亮度提升明显,但细节容易丢。MSR(多尺度Retinex)就是拿几个不同尺度的高斯核分别做SSR,然后加权平均,通常取三个尺度:σ = 15、80、250,权重各1/3。MSRCR则是在MSR基础上增加了颜色恢复因子,用来缓解灰雾现象,但参数更敏感,工程上调试成本高。在HSV空间里做时,把MSR/MSRCR全部作用在V通道上,效果和可控性比在RGB上直接做好得多。

5.3 HSV空间融合策略与参数细节

一个稳妥的流程是这样的:先将图像从BGR转到HSV,分离出V通道;对V做多尺度Retinex;把增强后的V和原始V按权重融合;再和原H、S通道合并,转回RGB。

融合这一步很多人会直接拿Retinex的输出当头文件,但Retinex处理后的V往往被拉得太狠,暗部细节是出来了,整体却像被漂白过。所以我在项目里习惯做一个线性加权融合:

V_enhanced = α · V_retinex + β · V_original

其中α取0.6到0.8,β取0.2到0.4,α和β之和保持为1。这样既恢复暗部细节,又保留原始影像的自然层次。强烈建议把α做成可调参数,因为不同视频监控场景对增强力度的需求差别很大。

代码大致长这样:

import cv2 import numpy as np def gaussian_kernel(size, sigma): ax = np.linspace(-(size // 2), size // 2, size) x, y = np.meshgrid(ax, ax) kernel = np.exp(-(x**2 + y**2) / (2 * sigma**2)) return kernel / kernel.sum() def msr_v_channel(v, scales=(15, 80, 250)): vf = v.astype(np.float32) log_v = np.log(vf + 1.0) msr = np.zeros_like(vf) for sigma in scales: size = max(31, int(sigma * 6) | 1) kernel = gaussian_kernel(size, sigma) blur = cv2.filter2D(vf, -1, kernel) blur = np.maximum(blur, 1.0) ssr = log_v - np.log(blur) msr += ssr / len(scales) return msr img = cv2.imread("night_sence.jpg") hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV).astype(np.float32) h, s, v = hsv[..., 0], hsv[..., 1], hsv[..., 2] v_retinex = msr_v_channel(v) v_retinex = (v_retinex - v_retinex.min()) / (v_retinex.ptp() + 1e-6) * 255.0 alpha = 0.7 v_new = alpha * v_retinex + (1 - alpha) * v v_new = np.clip(v_new, 0, 255) hsv[..., 0] = h hsv[..., 1] = s hsv[..., 2] = v_new result = cv2.cvtColor(hsv.astype(np.uint8), cv2.COLOR_HSV2BGR)

这段代码特别适合你跑通以后继续魔改。需要注意的是,v_retinex算出来以后一定要做线性拉伸,把动态范围拉开,否则噪声也会被一并放大。

5.4 融合增强的常见副作用与规避

HSV空间融合增强不是万能药,我实际用下来至少会遇到三个副作用。

第一个是光晕。在强光与暗部交界处,高斯核估计的入射光会跨越边界,导致暗部被提亮后边缘出现一圈“发光”。要弱化光晕,可以把高斯核的sigma减小,或者改用引导滤波替代高斯滤波估计光照。

第二个是噪点放大。Retinex把暗部细节提起来的时候,原始噪声也一起被放大了。解决思路是增强之前先对V通道做一次中值滤波或使用非局部均值降噪,或者在增强后的V上做一次轻微的保边滤波。

第三个是饱和度在转回RGB后感觉变低。低照度图像本来S通道的绝对值就偏低,V被拉高后,转回RGB会觉得颜色有点淡。这时可以在合并前对S通道做一个轻度拉伸,比如把S上限从100提到140,但一定要适度,拉到过饱和会出色彩断层。

6. 我把H通道踩过的坑都记在这

6.1 红色在色相环两端,单区间分割必然漏检

这是新手最常犯的错误之一。用OpenCV时很多人从网上抄一套HSV值,比如用h in [0, 10]去检测红色,发现一部分红颜色的像素永远检测不到,或者一个红色物体只检测到半个。

原因就是红色同时位于色相环的0度和360度,对应到OpenCV的H范围就是0-180这个“线性化”色相环的左右两端。直接用一个区间去套,等于从0到10一刀切,把355度到360度那部分红色拦在外面。正确做法是把红色拆成两个区间做或运算,或者把H通道整体做一次平移,让红色落在连续区间里。第二招在跑大量数据时更好用,但对很多人来说比较绕,所以最傻瓜的方法是开两个inRange再bitwise_or。

6.2 通道顺序陷阱:BGR与RGB导致的“蓝红互换”

只要你在OpenCV里用过COLOR_RGB2HSV而不是COLOR_BGR2HSV,大概率见过一个极其诡异的现象:分割一个蓝色的物体,掩膜上显示出的却是红色区域。本质上是红蓝通道互换后,参与色相计算的颜色完全变了,你以为是“蓝色”,实际上算法看到的是“红色”。

这类问题排查起来非常耗时间,因为转换本身不会报错,图像的轮廓也在,就是颜色对不上。建议队伍里统一规范:凡是OpenCV读图,一律先记清图片数据是BGR顺序,只允许在最末端做显示转换时使用img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB),后续所有颜色计算都用BGR,约定越死,踩坑越少。

6.3 低饱和度区域的H值会乱跳,噪点在色相通道上炸开花

当S接近0或者V接近0时,某些像素的RGB三个通道数值非常接近,max和min之间的距离非常小,传感器只要有一丁点噪声,会让“哪个通道最大”这个判定反复横跳,H自然也跟着剧烈跳动。

表现是什么?一个平坦的灰墙,在H通道直方图上看是满屏噪声,各个色相都有。所以做颜色识别时一定不要上来就用H阈值分割,先设S和V的下限,把灰区和极暗区滤掉,再去看H通道。这个过程相当于告诉算法:“我只在有颜色的区域里谈色相。”这比任何后处理都有效。

6.4 直接拿HSV三通道做欧氏距离排序的误区

还有一种需求是做变色检测时计算“颜色差异”。不少人图省事,直接把两个像素的(H, S, V)当成三维坐标算欧氏距离,结果非常不靠谱。两个原因:第一,H是角度,0和359在人眼看来几乎一样,欧氏距离却算出359的巨大差值;第二,H、S、V三者的量纲不同,V对距离的贡献会压过H,一个亮度差就把真正的色相差淹没了。

如果你确实要比较颜色差异,方案有几种:简单场景下,先把注意力放在H通道并手动处理环形接缝;追求感知一致,建议把颜色转换到CIELAB空间,用Delta E来度量;还有一种折中做法是转回RGB比较,虽然RGB也不是感知均匀空间,但至少没有环形量纲问题,特定业务下够用。

我刚接触HSV那阵子,最喜欢干的就是打开一张图,把H通道单独转成伪彩色显示出来看。你会惊讶地发现,很多在RGB下看起来“差不多”的区域,在H通道里差异明显得惊人——这说明颜色空间选对了,信息才能被正确放大。如果你也准备上手,建议先别急着用Retinex这种组合玩法,找一张颜色丰富的图,跑通转换、显示H/S/V单通道、再用inRange做一次分割,把阈值感先建立起来。等你能一眼判断“这个红色该用哪个区间”,你对HSV(HSB)和HSL颜色空间的理解就不再停留在公式层面了。

返回列表