Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Preview: HSV - Hue, Saturation, Value Colorspace

Stough, 202-

The HSV and closely related HSL colorspaces are ways of considering ‘color’ that separate the brightness of a color from its tone and purity. This is useful because we as humans are highly attuned to brightness changes in our perception. So if the RGB space speaks to the mechanism by which we receive a color stimulus--activation of red, green, and blue-centered cones--then the HSV speaks more to how we perceive or speak about color.

Viewing the planes of the HSV space.

Here I’ll make a little interactive plot on the HSV space for various saturation, thanks to HYRY’s stackoverflow answer. We first stack together an HSV image, then convert to RGB so that we can display it.

There is a bit of a problem with these demos. The plt.ioff() and plt.ion() are required to avoid duplication of plots when putting together an interactive demo. plt.close() is used to make sure those plots don’t regenerate later in the notebook.

<Figure size 600x300 with 1 Axes>

Drag the saturation slider down and watch what happens: every hue fades toward gray, and at SHSV=0S_{HSV}=0 the whole plot is just a vertical grayscale ramp, the same at every hue. With no saturation, hue has no effect at all. Remember this; it comes back to bite us below.

Note also that red appears at both the left (H=0H=0) and right (H=360H=360) edges. Hue is an angle, so 0°0° and 360°360° are the same color. That’s why the HSL/HSV diagrams above draw hue around a circle.

Viewing an image as RGB and HSV for comparison

<Figure size 800x600 with 9 Axes>
((1200, 1600, 3), dtype('float64'), np.float64(0.0), np.float64(1.0))

 

How the conversion works

rgb2hsv (like most libraries) works with RGB scaled to [0,1][0,1] and returns HH, SS, VV also each in [0,1][0,1], so H=1H=1 means 360°360°. For a pixel, let M=max⁡(R,G,B)M = \max(R,G,B) and m=min⁡(R,G,B)m = \min(R,G,B). Then

V=M,S={M−mMM>00M=0V = M, \qquad S = \begin{cases} \frac{M - m}{M} & M > 0 \\ 0 & M = 0 \end{cases}

and the hue is an angle determined by which channel is largest, and by how the other two compare:

H=16×{G−BM−m(mod6)M=RB−RM−m+2M=GR−GM−m+4M=BH = \frac{1}{6} \times \begin{cases} \frac{G - B}{M - m} \pmod 6 & M = R \\ \frac{B - R}{M - m} + 2 & M = G \\ \frac{R - G}{M - m} + 4 & M = B \end{cases}

So value is simply the brightest channel, saturation is how far apart the channels are relative to that, and hue says which part of the color wheel (red at 0, green at 13\frac{1}{3}, blue at 23\frac{2}{3}) the pixel leans toward. We can confirm the first of these directly:

True

Now look back at the figure. A few things to notice:

  • V looks like a grayscale version of the image, but not quite the one you’d expect. The snow, clouds, and the saturated flowers are all bright, since each has at least one channel near the max.

  • S is dark where the image is colorless (snow, clouds, the bright reflections), and bright for the deep blue sky, the green meadow, and the flowers.

  • H cleanly separates blue sky, green grass and pink/yellow flowers. But look at the snow and clouds: the hue there is a noisy speckle. When M−mM - m is tiny, which is exactly when SS is near 0, the hue formula divides small, noisy differences by a small number. For a gray pixel hue is simply undefined (skimage reports 0). Hue is only meaningful where there’s enough saturation to have a color in the first place.

That last point matters whenever you use hue to find things in an image, e.g. “find all the green pixels”. You almost always want to also require a minimum saturation (and value).

 

Let’s visualize in 3D

Given the HSV graph above, The Hue and Saturation can be seen in polar coordinates, with Hue as the angle θ\theta and Saturation as the radius RR in. The Value coordinate (brightness) is the Z. Easy. So then, how to plot in polar coords? We’ve provided a nice library method vis_hsv_cube for just this. Read the link and understand the code.

<Figure size 400x400 with 1 Axes>

 

Problem: Manipulating the HSV components of an image...

In the below interactive visualization we can manipulate the hue of an image. Try to complete the application by adding saturation and value sliders to this image manipulation tool.

Thanks XKCD:

changeHue shifts every pixel’s hue by the same amount, the difference between the requested hue and the image’s mean hue. Because hue is an angle, we can’t just add and clip: a hue of 0.9 shifted by 0.2 should wrap around to 0.1, not saturate at 1.0. np.fmod(..., 1) does that wrap-around. Saturation and value are untouched, so the shadows, highlights, and textures of the scene all stay put while the colors rotate around the wheel.

<Figure size 800x300 with 2 Axes>