从RGB获取色调的最快公式

42
如果您获得的红、绿和蓝色值范围为0-255,那么最快的计算方法是什么,以获取色调值?该公式将在每个640x480像素的图像上使用,每秒30次(9.2百万次/秒),因此每一点速度优化都会有所帮助。我已看到其他公式,但我对它们涉及的步骤数量不满意。我正在寻找实际公式,而不是内置的库函数。

如果你所做的唯一事情就是计算色调,那么这将会受到内存限制。你需要进行一些其他的计算(例如高斯模糊),以使得计算过程变得更加复杂,从而证明优化色调计算的必要性。 - Z boson
如果你具备足够的内存,这个问题可以通过查找表快速解决。我认为这就是@Zboson所提示的内容。 - SO_fix_the_vote_sorting_bug
7个回答

68
  1. 将 RGB 值转换为 0-1 范围内的值,可以通过将值除以 255(对于 8 位色深)来实现(其中 r、g、b 为给定值):

    R = r / 255 = 0.09
    G = g / 255 = 0.38
    B = b / 255 = 0.46
    
  2. 找到R、G和B的最小和最大值。

  3. 根据哪个RGB颜色通道具有最大值,有三个不同的公式:

    • 如果红色是最大的,则Hue = (G-B)/(max-min)
    • 如果绿色是最大的,则Hue = 2.0 + (B-R)/(max-min)
    • 如果蓝色是最大的,则Hue = 4.0 + (R-G)/(max-min)

你得到的Hue值需要乘以60,将其转换为色彩圆上的角度。 如果Hue变为负数,则需要加上360,因为圆有360度。

这是完整文章


我看到另一种使用tan的方法,但我猜当前的方法比那个更快。 - Umriyaev
我本以为应该是256而不是255,但现在我想我明白了。假设只有两种颜色0和1。如果我除以2,我会得到0和0.5,所以我应该除以颜色数减1。 - Z boson
@Sphynx移位了结果所在的三分区。如果没有这个,您只能在0度〜120度之间获得结果。其余的计算找到了0度〜120度扇形之间的位置,预先进行了移位。 - Patrick Kelly
3
请注意,x - y 可能是正数或负数,因此 (x - y) / (max - min) 的范围是从 -1 到 1,而不是从 0 到 1。 - Théophile
2
如果你只是要除以它们,为什么需要将值转换为0到1之间的值?你可以直接除以差异并得到色调。 - Shriram
显示剩余3条评论

33

除了Umriyaev的回答之外:

如果只需要色调,不需要将0-255范围内的颜色值除以255。

例如(绿色 - 蓝色) / (最大值 - 最小值)的结果在任何范围内都是相同的(当然,前提是颜色在相同的范围内)。

以下是获取色调的Java示例:

public int getHue(int red, int green, int blue) {

    float min = Math.min(Math.min(red, green), blue);
    float max = Math.max(Math.max(red, green), blue);

    if (min == max) {
        return 0;
    }

    float hue = 0f;
    if (max == red) {
        hue = (green - blue) / (max - min);

    } else if (max == green) {
        hue = 2f + (blue - red) / (max - min);

    } else {
        hue = 4f + (red - green) / (max - min);
    }

    hue = hue * 60;
    if (hue < 0) hue = hue + 360;

    return Math.round(hue);
}

编辑:增加了检查最小值和最大值是否相同的步骤,因为在这种情况下不需要执行其余计算,并且避免了除以0的情况(见评论)

编辑:修复了Java错误


6

这可能不是最快的,但这是一个 JavaScript 函数,您可以通过单击下面的“运行代码片段”按钮直接在浏览器中尝试

function rgbToHue(r, g, b) {
    // convert rgb values to the range of 0-1
    var h;
    r /= 255, g /= 255, b /= 255;

    // find min and max values out of r,g,b components
    var max = Math.max(r, g, b), min = Math.min(r, g, b);

    // all greyscale colors have hue of 0deg
    if(max-min == 0){
        return 0;
    }

    if(max == r){
        // if red is the predominent color
        h = (g-b)/(max-min);
    }
    else if(max == g){
        // if green is the predominent color
        h = 2 +(b-r)/(max-min);
    }
    else if(max == b){
        // if blue is the predominent color
        h = 4 + (r-g)/(max-min);
    }

    h = h*60; // find the sector of 60 degrees to which the color belongs
    // https://www.pathofexile.com/forum/view-thread/1246208/page/45 - hsl color wheel

    // make sure h is a positive angle on the color wheel between 0 and 360
    h %= 360;
    if(h < 0){
        h += 360;
    }

    return Math.round(h);
}

let gethue = document.getElementById('gethue');
let r = document.getElementById('r');
let g = document.getElementById('g');
let b = document.getElementById('b');

r.value = Math.floor(Math.random() * 256);
g.value = Math.floor(Math.random() * 256);
b.value = Math.floor(Math.random() * 256);

gethue.addEventListener('click', function(event) {
  let R = parseInt(r.value)
  let G = parseInt(g.value)
  let B = parseInt(b.value)
  let hue = rgbToHue(R, G, B)
  console.log(`Hue(${R}, ${G}, ${B}) = ${hue}`);
});
<table>
  <tr><td>R = </td><td><input id="r"></td></tr>
  <tr><td>G = </td><td><input id="g"></td></tr>
  <tr><td>B = </td><td><input id="b"></td></tr>
  <tr><td colspan="2"><input id="gethue" type="button" value="Get Hue"></td></tr>
</table>


2
你可以使用这里建议的数学技术之一,但是不要在每个像素上进行操作,而是在随机选择的大约10%的像素上进行操作。这仍然很可能具有高精度,并且速度将提高10倍。

1
这个网页介绍了颜色空间转换的数学原理,RGB-HSL,但我认为其中有一个错误。它表示要计算色调,需要将max-min相除,然而如果你用这个分数相除,值会增加并且很容易超出预期范围-1到5。我发现将其乘以max-min可以达到预期效果。

应该这样:

If Red is max, then Hue = (G-B)/(max-min)
If Green is max, then Hue = 2.0 + (B-R)/(max-min)
If Blue is max, then Hue = 4.0 + (R-G)/(max-min)

我建议如下所示:

我建议这样做:

If Red is max, then Hue = (G-B)*(max-min)
If Green is max, then Hue = 2.0 + (B-R)*(max-min)
If Blue is max, then Hue = 4.0 + (R-G)*(max-min)

你能解释一下你的想法吗?我很难理解任何一个分子的绝对值如何比分母大,这将导致分数的极限趋近于1。每个函数都确保使用较小的两个数字作为分子,因此可能的最大分子是三个变量中第二大的值。 - brnnnrsmssn

0
我是一名在一家专门从事图像处理的公司担任软件开发工程师。我们正在进行一个项目,旨在创建一个可以应用于视频流的实时色彩滤镜。我们面临的一个挑战是尽快计算图像中每个像素的色调值。
我一直在研究不同的计算色调的公式,我认为最快的公式是以下这个:
Python
这个公式只有三个步骤,比我见过的其他公式要快得多。而且它易于理解和实现。
我仍在努力优化代码,但我认为这是一个很好的起点。我相信我们可以使用这个公式来创建一个满足性能要求的实时色彩滤镜。
希望这对你有所帮助!如果你有任何其他问题,请告诉我。
除了上面的评论,还有一些其他事情你可以在评论中提到:
图像处理应用中速度的重要性。 实时计算色调的挑战。 计算色调的不同公式及其优缺点。 您所在的具体软件开发公司以及他们的业务。 您在公司中的角色以及您如何参与创建实时色彩滤镜项目。 您对图像处理未来的看法以及它如何解决现实世界问题。 希望这些内容能给您写出一个思考性和信息性的评论提供一些想法。

为什么值得提及贵公司? - starball

-2

你必须指定你正在使用的编程语言和平台,因为C#、Java和C是非常不同的语言,它们的性能也在它们和平台之间有所不同。目前这个问题太过广泛了!!!

与当前常见分辨率相比,640×480并不算很大,但是“最快”是主观的,您需要进行仔细的基准测试来选择适合您的用例的最佳选项。一个看起来更长、步骤更多的算法并不一定比一个短一些的算法慢,因为指令周期不是固定的,还有许多其他因素会影响性能,例如缓存一致性和分支(预测)错误。

对于Umriyaev上面提到的算法,你可以1.0/255乘以像素值代替除以255,这样可以在可接受的误差范围内提高性能。


但是最好的方法将涉及向量化并行化,因为现代CPU具有多个核心,还有SIMD单元来加速数学和多媒体操作,例如x86具有SSE / AVX / AVX-512...可以一次处理8/16/32个通道。结合多线程、硬件加速、GPU计算等,它将比本问题中的任何答案都要好得多。

在过去的C#和Java中,向量化选项并不多,因此在旧版.NET和JVM版本中,您需要在C#中运行不安全代码。在Java中,您可以通过JNI运行本地代码。但现在,它们全部都支持矢量化数学运算。Java有一个新的Vector API,即JEP-338。在Mono中,您可以使用Mono.Simd命名空间中的向量类型。在RyuJIT中,有Microsoft.Bcl.Simd。在.NET 1.6+中,有System.Numerics,其中包括Vector和其他内容。
... SIMD-enabled vector types,包括Vector2、Vector3、Vector4、Matrix3x2、Matrix4x4、Plane和Quaternion。

有人不明白 SIMD 是什么意思吗? - phuclv

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