HCL颜色转换为RGB和反向转换

22
我需要一个算法将 HCL 颜色转换为 RGB,以及将 RGB 颜色反向转换为 HCL,需要注意这些颜色空间具有不同的色域(需要将 HCL 颜色限制在可以在 RGB 颜色空间中再现的范围内)。请提供该算法(该算法旨在在仅原生支持 RGB 颜色的Wolfram Mathematica中实现)。我没有在工作中使用颜色空间的经验。
P.S. 一些关于 HCL 颜色的文章: M. Sarifuddin(2005年)。新的感知均匀的颜色空间和相关的基于内容的图像和视频检索颜色相似性度量。 Zeileis,Hornik和Murrell(2009):逃离RGBland:选择统计图形的颜色//计算统计与数据分析卷53,问题9,2009年7月1日,页3259-3270

更新: 正如Jonathan Jansson所指出的,在上述两篇文章中,“HCL”这个名称描述了不同的颜色空间:“第二篇文章使用LCh(uv),它与Luv*相同,但是用极坐标来描述,其中h(uv)是u*和v*坐标的角度,C*是该向量的大小”。因此,实际上我需要一种将RGB转换为Luv*和反向转换的算法。


3
你看过 http://w3.uqo.ca/missaoui/Publications/TRColorSpace.zip 吗?这似乎是由相同的作者(Sarifuddin / Missaoui)编写的,但包括双向转换算法。 - AakashM
@AakashM 感谢您引用这篇论文,但是这篇论文并没有包含完整的算法:例如 gamma 的值不清楚。 - Alexey Popkov
2
“gamma” 似乎是一个调整参数,需要根据(我认为的)整体光照水平进行调整。@Sjoerd 链接的代码使用了“3”。 - AakashM
3
第三页的某个位置写着:“γ 是一个修正因子,其数值(= 3)与 Lab* 空间中使用的数值相同。” - Ishtar
6个回答

14

我刚刚学习了HCL色彩空间。你问题中两篇文章使用的色彩空间似乎不同。

第二篇文章使用的是L*C*h(uv),它与L*u*v*相同,但使用极坐标来描述,其中h(uv)是u*和v*坐标的角度,C*是该向量的大小。

第一篇文章中的LCH色彩空间似乎描述了另一个颜色空间,其转换更具算法性。这里还有第一篇论文的另一个版本:http://isjd.pdii.lipi.go.id/admin/jurnal/14209102121.pdf

如果您想使用CIE L*u*v*,则需要先将sRGB转换为CIE XYZ,然后再转换为CIE L*u*v*。在大多数情况下,RGB实际上指的是sRGB,因此无需将RGB转换为sRGB。

需要的所有源代码

有关如何转换为XYZ的好文章

不错的在线转换器

但我无法回答如何将颜色限制在sRGB空间的问题。您可以在转换后丢弃超出0到1范围的RGB颜色。仅夹紧颜色可能会产生相当奇怪的结果。尝试进入转换器并将颜色RGB 0 0 255转换为L*a*b*(类似于L*u*v*),然后增加L*,例如70,并将其转换回来,结果肯定不再是蓝色了。

编辑:更正了URL 编辑:将另一个答案合并到这个答案中


好的观点!在这种情况下,我需要一个将RGB转换为Luv*和反向转换的算法。我已经更新了我的问题。顺便说一句,你回答中的链接已经失效了。 - Alexey Popkov
1
我将之前的答案合并到了这个答案中。可能没必要将RGB转换为sRGB。只是说RGB不是一个具体的标准,所以你可以假设它是sRGB。你需要阅读Mathematica的手册,并查看它们是否提到了这方面的内容。如果你知道你拥有的RGB颜色实际上是sRGB(或其他标准化的RGB变体),那么该颜色就是设备无关的。 - Jonathan Jansson

8

HCL是一个非常通用的名称,有很多方法可以得到色相、色度和亮度。例如,Chroma.js有一个称为HCL的东西,它是将极坐标转换为Lab(当您查看实际代码时)。其他实现,甚至是从同一网站链接的实现,使用Polar Luv。由于您可以简单地借用L因子并通过转换为极坐标来推导色相,这两种方法都是获取这三个元素的有效方式。最好称它们为Polar Lab和Polar Luv,以避免混淆。

M. Sarifuddin(2005)的算法不是极坐标Luv或Polar Lab,计算较为简单(无需先导出Lab或Luv空间),可能更好。文中有些地方似乎是错误的,例如在CIE L*C*H*颜色空间应用欧几里得距离。使用色调意味着它必须是圆形的,并且将该数字直接塞入A²+B²+C²中会给您带来问题。同样的道理也适用于在D94或D00上应用基于色调的颜色空间,因为这些都是具有针对Lab颜色空间的内置修正的距离算法。除非我漏掉了什么,否则我会忽略第6-8图。我对图形中的拒绝容限表示怀疑。您可以设置一个更低的阈值并取得更好的效果,而且颜色空间之间的数值没有归一化。无论如何,尽管论文中有一些看似缺陷,但所描述的算法值得一试。如果RGB并不太重要,您可以选择对其进行欧几里得计算。但如果您正在寻找颜色距离算法,那么这里就有了。
这是M. Sarifuddin用Java实现的HCL。反复阅读论文后,我得出结论:在distance_hcl例程中,它将距离按色相变化比例缩放了0.16至180.16倍。这个因素非常深远,几乎完全不正确。这使得颜色匹配很糟糕。我已经注释掉了论文中的那行,并使用只有Al因子的行。通过将亮度缩放1.4倍,它并不会变得无法使用。如果没有任何缩放因子,它最终与cycldistance完全相同。http://w3.uqo.ca/missaoui/Publications/TRColorSpace.zip是该论文的修正和改进版本。
static final public double Y0 = 100;
static final public double gamma = 3;
static final public double Al = 1.4456;
static final public double Ach_inc = 0.16;

public void rgb2hcl(double[] returnarray, int r, int g, int b) {
    double min = Math.min(Math.min(r, g), b);
    double max = Math.max(Math.max(r, g), b);
    if (max == 0) {
        returnarray[0] = 0;
        returnarray[1] = 0;
        returnarray[2] = 0;
        return;
    }

    double alpha = (min / max) / Y0;
    double Q = Math.exp(alpha * gamma);
    double rg = r - g;
    double gb = g - b;
    double br = b - r;
    double L = ((Q * max) + ((1 - Q) * min)) / 2;
    double C = Q * (Math.abs(rg) + Math.abs(gb) + Math.abs(br)) / 3;
    double H = Math.toDegrees(Math.atan2(gb, rg));

    /*
    //the formulae given in paper, don't work.
    if (rg >= 0 && gb >= 0) {
        H = 2 * H / 3;
    } else if (rg >= 0 && gb < 0) {
        H = 4 * H / 3;
    } else if (rg < 0 && gb >= 0) {
        H = 180 + 4 * H / 3;
    } else if (rg < 0 && gb < 0) {
        H = 2 * H / 3 - 180;
    } // 180 causes the parts to overlap (green == red) and it oddly crumples up bits of the hue for no good reason. 2/3H and 4/3H expanding and contracting quandrants.
    */

    if (rg <  0) {
        if (gb >= 0) H = 90 + H;
        else { H = H - 90; }
    } //works


    returnarray[0] = H;
    returnarray[1] = C;
    returnarray[2] = L;
}

public double cycldistance(double[] hcl1, double[] hcl2) {
    double dL = hcl1[2] - hcl2[2];
    double dH = Math.abs(hcl1[0] - hcl2[0]);
    double C1 = hcl1[1];
    double C2 = hcl2[1];
    return Math.sqrt(dL*dL + C1*C1 + C2*C2 - 2*C1*C2*Math.cos(Math.toRadians(dH)));
}

public double distance_hcl(double[] hcl1, double[] hcl2) {
    double c1 = hcl1[1];
    double c2 = hcl2[1];
    double Dh = Math.abs(hcl1[0] - hcl2[0]);
    if (Dh > 180) Dh = 360 - Dh;
    double Ach = Dh + Ach_inc;
    double AlDl = Al * Math.abs(hcl1[2] - hcl2[2]);
    return Math.sqrt(AlDl * AlDl + (c1 * c1 + c2 * c2 - 2 * c1 * c2 * Math.cos(Math.toRadians(Dh))));
    //return Math.sqrt(AlDl * AlDl + Ach * (c1 * c1 + c2 * c2 - 2 * c1 * c2 * Math.cos(Math.toRadians(Dh))));
}

1
我真的弄不明白为什么作者们认为他们可以用(dH + 8/50)乘以整个距离公式来解决色调的8度变化比50的色度变化更小的问题。这只是在将距离部分按照 ~(0-180) 的比例缩放,并使贫弱的亮度部分变得无意义。 - Tatarize
1
粗糙的数学计算对色调也没有什么作用。即使是“//works”部分,仍然只有较少的区域发生碰撞。无论你如何调整它,使用atan((G-B)/(R-G))几乎无法产生完整的颜色范围。如果R == G,则无论是灰色128,128,128、蓝色1,1,255还是黄色255,255,1,都会得到一个角度0。 - Tatarize
你如何使用你的修改将其转换回来? - user151496
1
我不会这么做。就颜色距离算法或者颜色空间而言,它们都是垃圾。认真地说,如果你想要更好的结果,使用LAB,如果你想要快速而脏的但最终非常好的结果,使用redmean。http://www.compuphase.com/cmetric.htm距离中的修复可以忽略。距离不会调整颜色空间。至于实际的颜色空间本身,您可以使用极坐标来找到在圆柱体中的位置,然后使用该位置来找出应使用哪些RGB颜色。但是,真的不必费心。这只是RGB,他们伪造了他们的结果。Lab很棒。 - Tatarize
我们都知道Lab或Luv很棒,但计算成本太高了。对于实时应用来说太昂贵了。 - user151496
显示剩余2条评论

6
如其他答案中所述,有很多方法可以实现HCL颜色空间并将其映射为RGB。
我最终使用了HSLuv,它在C、C#、Go、Java、PHP和其他几种语言中都有MIT许可证的实现。它类似于CIELUV LCh但完全映射到RGB。这些实现可在GitHub上获得
以下是网站上描述HSLuv颜色空间的简短图形,右侧两个面板显示了实现输出:

HSLuv color space examples compared to HSL and CIELUV


6
我熟悉许多颜色空间,但这个我还不熟悉。遗憾的是,Mathematica的ColorConvert也不知道它。
我在这里找到了一个rgb2hcl例程(链接),但没有相反方向的例程。
更全面的颜色空间转换包可以在这里找到。它似乎能够实现各种颜色空间之间的转换。在colorspace_1.1-0.tar.gz\colorspace_1.1-0.tar\colorspace\src中寻找colorspace.c文件。请注意,在此软件包中,HCL被称为PolarLUV。

从第一个链接中,代码中有一个指向http://w3.uqo.ca/missaoui/Publications/TRColorSpace.zip的链接,其中包含两种方法的算法/方程。 - Chris
@Chris,请查看我在问题下AakashM的评论中的留言。 - Alexey Popkov

5

我想在网络上进行颜色插值,发现HCL是最合适的颜色空间,但无法找到任何库使转换变得简单且高效,因此我编写了自己的库。

有许多常数在起作用,其中一些根据您从哪里获取它们而变化很大。

考虑到我的目标是Web,我认为最好匹配Chromium源代码。以下是使用TypeScript编写的最小化代码段,sRGB XYZ矩阵已经预计算并且所有常数都内联。

const rgb255 = (v: number) => (v < 255 ? (v > 0 ? v : 0) : 255);
const b1 = (v: number) => (v > 0.0031308 ? v ** (1 / 2.4) * 269.025 - 14.025 : v * 3294.6);
const b2 = (v: number) => (v > 0.2068965 ? v ** 3 : (v - 4 / 29) * (108 / 841));
const a1 = (v: number) => (v > 10.314724 ? ((v + 14.025) / 269.025) ** 2.4 : v / 3294.6);
const a2 = (v: number) => (v > 0.0088564 ? v ** (1 / 3) : v / (108 / 841) + 4 / 29);

function fromHCL(h: number, c: number, l: number): RGB {
    const y = b2((l = (l + 16) / 116));
    const x = b2(l + (c / 500) * Math.cos((h *= Math.PI / 180)));
    const z = b2(l - (c / 200) * Math.sin(h));
    return [
        rgb255(b1(x * 3.021973625 - y * 1.617392459 - z * 0.404875592)),
        rgb255(b1(x * -0.943766287 + y * 1.916279586 + z * 0.027607165)),
        rgb255(b1(x * 0.069407491 - y * 0.22898585 + z * 1.159737864)),
    ];
}

function toHCL(r: number, g: number, b: number) {
    const y = a2((r = a1(r)) * 0.222488403 + (g = a1(g)) * 0.716873169 + (b = a1(b)) * 0.06060791);
    const l = 500 * (a2(r * 0.452247074 + g * 0.399439023 + b * 0.148375274) - y);
    const q = 200 * (y - a2(r * 0.016863605 + g * 0.117638439 + b * 0.865350722));
    const h = Math.atan2(q, l) * (180 / Math.PI);
    return [h < 0 ? h + 360 : h, Math.sqrt(l * l + q * q), 116 * y - 16];
}

这里有一个以上代码片段的游乐场。
它包括 d3 的 interpolateHCL 和浏览器原生的 css 过渡,用于比较。
https://svelte.dev/repl/0a40a8348f8841d0b7007c58e4d9b54c

这是一个将任何 Web 颜色格式转换为 HCL 颜色空间并在其中插值的要点。
https://gist.github.com/pushkine/c8ba98294233d32ab71b7e19a0ebdbb9


1
老兄,我一直在到处寻找这个(也是为了颜色插值)。你太棒了。 - Nitsan BenHanoch

0

我认为

if (rg <  0) {
    if (gb >= 0) H = 90 + H;
    else { H = H - 90; }
} //works

不是很必要,因为可以使用 atan2(,) 代替 paper 中的 atan(/)(但我对 Java 的 atan2(,) 不太了解)


该算法将rg和gb视为互补色,但它们不是互补色,而是三刺激值。它使用atan2生成介于+90°和-90°之间的值。然后,再用该代码绕过其他部分。它非常接近于正常情况下atan2()所做的规范化,但它对特定象限进行了更正。该论文试图将象限I -> 象限III和象限II -> 象限IV移动。但是,atan2()仅在象限I和象限III中产生结果。它重叠了色调的部分,然后重新检查以移动它们的象限。 - Tatarize
2
不得不说,它很糟糕。即使修复了他们拙劣的工作和错误的数学,并且需要在某些地方不使用他们有缺陷的工作并尽最大努力。它仍然只是RGB的修改,而且不是很好的修改。他们论文中的大多数表格都是谎言。http://godsnotwheregodsnot.blogspot.com/2012/09/hcl-new-color-space-for-pack-of-lies.html-- 如果您想要好的东西,请尝试Hunter Lab。计算简单,结果与LAB DE2000差不多。(http://www.easyrgb.com/index.php?X=MATH&H=02#text2) - Tatarize

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