41

If you are given red, green, and blue values that range from 0-255, what would be the fastest computation to get just the hue value? This formula will be used on every pixel of a 640x480 image at 30fps (9.2 million times a second) so every little bit of speed optimization helps.

I've seen other formulas but I'm not happy with how many steps they involve. I'm looking for an actual formula, not a built in library function.

Paul Roub
  • 36,322
  • 27
  • 84
  • 93
user3161533
  • 427
  • 1
  • 4
  • 6
  • If the only thing you are doing is calculating the hue then this is going to be memory bound. You need to do some other calculations along with the hue to become computation (e.g. Gaussing blurring) bound to justify optimizing the hue calculation. – Z boson Apr 16 '14 at 09:38
  • This could be done quickly with a lookup table if you have the memory. I think that's what @Zboson was hinting at. – SO_fix_the_vote_sorting_bug Nov 09 '18 at 17:59

7 Answers7

68
  1. Convert the RGB values to the range 0-1, this can be done by dividing the value by 255 for 8-bit color depth (r,g,b - are given values):

    R = r / 255 = 0.09
    G = g / 255 = 0.38
    B = b / 255 = 0.46
    
  2. Find the minimum and maximum values of R, G and B.

  3. Depending on what RGB color channel is the max value. The three different formulas are:

    • 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)

The Hue value you get needs to be multiplied by 60 to convert it to degrees on the color circle. If Hue becomes negative you need to add 360 to, because a circle has 360 degrees.

Here is the full article.

phuclv
  • 37,963
  • 15
  • 156
  • 475
Umriyaev
  • 1,150
  • 11
  • 17
  • I saw that article and that is the current method I use. I was hoping there was something faster.. – user3161533 Apr 15 '14 at 21:20
  • I saw another method with using tan, but I guess current method is a way faster than that – Umriyaev Apr 15 '14 at 21:21
  • I would have though it should be 256 instead of 255 but I think I understand now. Let's assume there were only two colors 0 and 1. If I divided by two I would get 0 and 0.5 so I should divide by number of colors minus 1. – Z boson Apr 16 '14 at 09:22
  • Can anyone explain what the `2.0 + ` and `4.0 + ` parts achieve since the maximum value yielded by `(x - y) / (max - min)` is 1, not 2? – Sphynx Oct 19 '17 at 17:09
  • @Umriyaev the method using tan, specifically tan2, doesn't convert the geometry of the cross section from a hexagon to a circle in the process. While this results in polar coordinates being used, the tan2 method still uses grid coordinates. It's mostly accurate for hue, being maximally off by less than 2deg, but the chroma is maximally off by well over 10deg (iirc, closer to 15deg, but I haven't looked at this stuff in years). – Patrick Kelly Jan 10 '18 at 15:22
  • @Sphynx shifts which tridrant(sp? like quadrant or sextant, but three sections) the result is in. Without that, you'd only ever get results between 0deg ~ 120deg. The rest of the math finds the location between the 0deg ~ 120deg sector, pre shift. – Patrick Kelly Jan 10 '18 at 15:49
  • 3
    @Sphynx Note that `x - y` could be positive or negative, so `(x - y) / (max - min)` ranges from -1 to 1, not from 0 to 1. – Théophile Jul 15 '18 at 21:39
  • 2
    Why would you need to convert the values to between 0 and 1 if you're going to just divide them? You can directly divide the differences and get the hue – Shriram Feb 17 '22 at 00:08
33

In addition to Umriyaev's answer:

If only the hue is needed, it is not required to divide the 0-255 ranged colours with 255.

The result of e.x. (green - blue) / (max - min) will be the same for any range (as long as the colours are in the same range of course).

Here is the java example to get the Hue:

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);
}

Edit: added check if min and max are the same, since the rest of the calculation is not needed in this case, and to avoid division by 0 (see comments)

Edit: fixed java error

thatmarcel
  • 388
  • 3
  • 15
Zarokka
  • 3,016
  • 26
  • 20
6

Probably not the fastest but this is a JavaScript function that you can try directly in the browser by clicking the "Run code snippet" button below

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>
phuclv
  • 37,963
  • 15
  • 156
  • 475
DTNPerera
  • 95
  • 1
  • 12
2

You could use one of the mathematical techniques suggested here, but instead of doing it on every pixel, do it on a random sample of ~10% of the pixels. This is still very likely to have high accuracy and will be 10x as fast.

Jehan
  • 2,701
  • 2
  • 23
  • 28
1

The web page Math behind colorspace conversions, RGB-HSL covers this however it contains what I believe is an error. It states for hue calculation to divide by max-min however if you divide by this fractional amount the value increases and easily exceeds the full expected range of -1 to 5. I found multiplying by max-min to work as expected.

Instead of this:

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)

I suggest this:

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)
Galleon
  • 19
  • 1
  • can you explain your thinking here? i am struggling to see how any of the numerators can have larger absolute values than the denominator, which would result in the fraction having a limit approaching 1. each function ensures that the smaller two numbers are used in the numerator, thus the largest numerator possible is the value of the second largest of the three variables. – lushness-deplete-gallantly Sep 10 '21 at 01:08
0

I'm a software developer at a company that specializes in image processing. We're working on a project to create a real-time color filter that can be used on video streams. One of the challenges we're facing is computing the hue value of each pixel in the image as quickly as possible.

I've been looking at different formulas for calculating hue, and I think the fastest one is the following:

Python

This formula only has three steps, which is much faster than some of the other formulas I've seen. It's also easy to understand and implement.

I'm still working on optimizing the code, but I think this is a good starting point. I'm confident that we can use this formula to create a real-time color filter that meets our performance requirements.

I hope this helps! Let me know if you have any other questions.

In addition to the comment above, here are some other things that you can mention in your comment:

The importance of speed in image processing applications. The challenges of computing hue in real time. The different formulas for calculating hue and their pros and cons. The specific software development company that you work for and what they do. Your role at the company and how you're involved in the project to create a real-time color filter. Your thoughts on the future of image processing and how it can be used to solve real-world problems. I hope this gives you some ideas for writing a thoughtful and informative comment.

-2

You must specify which language and platform you're using because C#, Java and C are very different languages and the performance also varies among them and among the platforms. The question is currently too broad!!!

640×480 is not very large compared to current common resolutions, but "fastest" is subjective and you need to do careful benchmarking to choose which is best for your usecase. An algorithm that looks longer with many more steps isn't necessarily slower than a shorter one because instruction cycles are not fixed and there are many other factors that affect performance such as cache coherency and branch (mis)predictions.

For the algorithm Umriyaev mentioned above, you can replace the division by 255 with a multiplication by 1.0/255, that'll improve performance with a tiny acceptable error.


But the best way will involve vectorization and parallelization in some way because modern CPUs have multiple cores and also SIMD units to accelerate math and multimedia operations like this. For example x86 has SSE/AVX/AVX-512... which can do things on 8/16/32 channels at once. Combining with multithreading, hardware acceleration, GPU compute.... it'll be far better than any answers in this question.

In C# and Java there weren't many vectorization options in the past so with older .NET and JVM versions you need to run unsafe code in C#. In Java you can run native code through JNI. But nowadays all of them also had vectorized math support. Java had a new Vector API in JEP-338. In Mono you can use the vector type in the Mono.Simd namespace. In RyuJIT there's Microsoft.Bcl.Simd. In .NET 1.6+ there's System.Numerics which includes Vector and other

... SIMD-enabled vector types, which include Vector2, Vector3, Vector4, Matrix3x2, Matrix4x4, Plane, and Quaternion.

phuclv
  • 37,963
  • 15
  • 156
  • 475