HSV And H2SV Color Space

From ILabWiki

Table of contents

HSV Color Space

HSV color space (http://en.wikipedia.org/wiki/HSV_color_space) transforms standard RGB (http://en.wikipedia.org/wiki/RGB_color_space) (Red, Green, Blue) color space into a new color space comprised of Hue, Saturation and Intensity (Value).

  1. The Hue (http://en.wikipedia.org/wiki/Hue) component can be though of as the actual color of the object. That is, if you looked at some object what color would it seem to you.
  2. Saturation (http://en.wikipedia.org/wiki/Saturation_%28color_theory%29) is a measure of purity. Where as Hue would say that an object is green, Saturation would tell us how green it actually is.
  3. Intensity (http://en.wikipedia.org/wiki/Color_value), which is also referred more accurately as value tells us how light the color is.

We can see how RGB color is broken down into HSV color by inspecting the code below:

RGB to HSV

HSV is converted from RGB in a simple way

1. First compute the base H,S and V
1. H - Hue if Color1 is Max
H = ( Color2 - Color3 ) / ( Max - Min )
2. S - Saturation
S = Max - Min / Max;
3. V - Value
V = max of either R,G or B
2. Normalize the values
1.General H,S,V has ranges:
  • 0 <=
Hue <= 360
  • 0 <=
Sat <= 100
  • 0 <=
Val <= 255
3. H - Hue is set based on the dominant color. It has three different basic ranges based on what that color is.
1. if Red
H *= 60
2. if Green
H += 2; H *= 60
3. if Blue
H += 4; H *= 60
4. In essence this forces the recognizable 0-360 value seen in hue
5. S - May be S *= 100 , I like in some cases to keep it from 0 to 1
6. V - is V *= 255
This is a standard representation of HSV color space as a cone. As you move outward, S is larger and colors are more pure. As you move down, colors get more dark. Moving around the cone changes the Hue color along the rainbow. Image sourced from The Wikipedia.
Enlarge
This is a standard representation of HSV color space as a cone. As you move outward, S is larger and colors are more pure. As you move down, colors get more dark. Moving around the cone changes the Hue color along the rainbow. Image sourced from The Wikipedia.
  • there are some other bells and whistles to take care of divide by zero conditions and other such singularities. For instance if Max = Min then we need to fudge hue
  • Note: In this example we set NORM to true for normal HSV conversions. This will make the values use normal HSV range. Setting NORM to false forces H,S and V to range between 0 and 1. This is useful if next you will convert to H2SV color space.
  • R, G and B are assumed to be between 0 and 255.

HSV Transformation C / C++ Code

  • This can be taken out of C macro form (http://en.wikipedia.org/wiki/C_preprocessor) and placed inside a class or function fairly easily.
Replace:
#define PIX_RGB_TO_HSV_COMMON(R,G,B,H,S,V,NORM)
With something like
void pixRGBtoHSVCommon(const double R, const double G, const double B, double& H, double& S, double& V, const bool NORM)
and then get rid of all the forward slashes \.
  • Floats can be used in place of doubles. It depends on whether you want precision or speed.
  • The boolean value NORM is used to decide whether to output traditional HSV values where
0 <= S <= 100 and 0 <= V <= 255.
Else we keep the values at a norm where
0 <= S <= 1 and 0 <= V <= 1.
The latter is faster for executing your own code, but the former should be used for compatibility.
// ######################################################################
// T. Nathan Mundhenk
// mundhenk@usc.edu
// C/C++ Macro RGB to HSV
#define PIX_RGB_TO_HSV_COMMON(R,G,B,H,S,V,NORM)                        \

Blue Is the dominant color

if((B > G) && (B > R))                                                 \
{                                                                      \
Value is set as the dominant color
  V = B;                                                               \
  if(V != 0)                                                           \
  {                                                                    \
    double min;                                                        \
    if(R > G) min = G;                                                 \
    else      min = R;                                                 \
Delta is the difference between the most dominant color and the least dominant color. This will be used to compute saturation.
    const double delta = V - min;                                      \
    if(delta != 0)                                                     \
      { S = (delta/V); H = 4 + (R - G) / delta; }                      \
    else                                                               \
      { S = 0;         H = 4 + (R - G); }                              \
Hue is just the difference between the two least dominant colors offset by the dominant color. That is, here 4 puts hue in the blue range. Then red and green just tug it one way or the other. Notice if red and green are equal, hue will stick squerely on blue
 
    H *=   60; if(H < 0) H += 360;                                     \
    if(!NORM) V =  (V/255);                                            \
    else      S *= (100);                                              \
  }                                                                    \
  else                                                                 \
    { S = 0; H = 0;}                                                   \
}                                                                      \

Green is the dominant color

else if(G > R)                                                         \
{                                                                      \
  V = G;                                                               \
  if(V != 0)                                                           \
  {                                                                    \
    double min;                                                        \
    if(R > B) min = B;                                                 \
    else      min = R;                                                 \
    const double delta = V - min;                                      \
    if(delta != 0)                                                     \
      { S = (delta/V); H = 2 + (B - R) / delta; }                      \
    else                                                               \
      { S = 0;         H = 2 + (B - R); }                              \
    H *=   60; if(H < 0) H += 360;                                     \
    if(!NORM) V =  (V/255);                                            \
    else      S *= (100);                                              \
  }                                                                    \
  else                                                                 \
    { S = 0; H = 0;}                                                   \
}                                                                      \

Red is the dominant color

else                                                                   \
{                                                                      \
  V = R;                                                               \
  if(V != 0)                                                           \
  {                                                                    \
    double min;                                                        \
    if(G > B) min = B;                                                 \
    else      min = G;                                                 \
    const double delta = V - min;                                      \
    if(delta != 0)                                                     \
      { S = (delta/V); H = (G - B) / delta; }                          \
    else                                                               \
      { S = 0;         H = (G - B); }                                  \
    H *=   60; if(H < 0) H += 360;                                     \
    if(!NORM) V =  (V/255);                                            \
    else      S *= (100);                                              \
  }                                                                    \
  else                                                                 \
    { S = 0; H = 0;}                                                   \
}

HSV to RGB

  1. Get some easy work done:
    1. If Value V = 0, then we are done, color is black set R,G,B to 0
    2. If Saturation S = 0, no color is dominant, set to some gray color
  2. Hue is valued from 0 to 360, we chunk the space into 60 degree increments. At each 60 degrees we use a slightly different formula. In general we will assign and set R,G and B exclusively as:
    1. We set the most dominant color:
      1. If H is 300 -> 60 , set R = V
      2. If H is 60 -> 180, set G = V
      3. If H is 180 -> 300, set B = V
    2. The least dominant color is set as: pv = Value * ( 1 - Saturation )
    3. The last remaining color is set as either:
      1. qv = Value * ( 1 - Saturation * (Hue/60) - floor(Hue/60))
      2. tv = Value * ( 1 - Saturation * ( 1 - ((Hue/60) - floor(Hue/60))))
  3. Clean up, here we allow for i to be -1 or 6 just in case we have a very small floating point error otherwise, we have an undefined input.
  4. Normalize R,G and B from 0 to 255.
  • Note: S and V are normalized between 0 and 1 in this example. H is its normal 0 to 360.

HSV Transformation C / C++ Code

// ######################################################################
// T. Nathan Mundhenk
// mundhenk@usc.edu
// C/C++ Macro HSV to RGB
#define PIX_HSV_TO_RGB_COMMON(H,S,V,R,G,B)                          \
if( V == 0 )                                                        \
{ R = 0; G = 0; B = 0; }                                            \
else if( S == 0 )                                                   \
{                                                                   \
  R = V;                                                            \
  G = V;                                                            \
  B = V;                                                            \
}                                                                   \
else                                                                \
{                                                                   \
  const double hf = H / 60.0;                                       \
  const int    i  = (int) floor( hf );                              \
  const double f  = hf - i;                                         \
  const double pv  = V * ( 1 - S );                                 \
  const double qv  = V * ( 1 - S * f );                             \
  const double tv  = V * ( 1 - S * ( 1 - f ) );                     \
  switch( i )                                                       \
    {                                                               \

Red is the dominant color

    case 0:                                                         \
      R = V;                                                        \
      G = tv;                                                       \
      B = pv;                                                       \
      break;                                                        \

Green is the dominant color

    case 1:                                                         \
      R = qv;                                                       \
      G = V;                                                        \
      B = pv;                                                       \
      break;                                                        \
    case 2:                                                         \
      R = pv;                                                       \
      G = V;                                                        \
      B = tv;                                                       \
      break;                                                        \

Blue is the dominant color

    case 3:                                                         \
      R = pv;                                                       \
      G = qv;                                                       \
      B = V;                                                        \
      break;                                                        \
    case 4:                                                         \
      R = tv;                                                       \
      G = pv;                                                       \
      B = V;                                                        \
      break;                                                        \

Red is the dominant color

    case 5:                                                         \
      R = V;                                                        \
      G = pv;                                                       \
      B = qv;                                                       \
      break;                                                        \

Just in case we overshoot on our math by a little, we put these here. Since its a switch it won't slow us down at all to put these here.

    case 6:                                                         \
      R = V;                                                        \
      G = tv;                                                       \
      B = pv;                                                       \
      break;                                                        \
    case -1:                                                        \
      R = V;                                                        \
      G = pv;                                                       \
      B = qv;                                                       \
      break;                                                        \

The color is not defined, we should throw an error.

    default:                                                        \
      LFATAL("i Value error in Pixel conversion, Value is %d",i);   \
      break;                                                        \
    }                                                               \
}                                                                   \
R *= 255.0F;                                                        \
G *= 255.0F;                                                        \
B *= 255.0F;

H2SV Color Space

The primary difficulty with HSV color space (http://en.wikipedia.org/wiki/HSV_color_space) is that Hue (http://en.wikipedia.org/wiki/Hue) is modulus (http://en.wikipedia.org/wiki/Modulo). This allows red hues to be singular (http://en.wikipedia.org/wiki/Mathematical_singularity) around 0. This can be a problem for instance if one wants to obtain the mean color of an object. If the object is red-ish, then half the hue distribution will be between 330 to 360 and the other half will be between 0 to 30. Thus, without windowing, we have a bimodal distribution (http://en.wikipedia.org/wiki/Bimodal_distribution) where we should not have one. One solution, though not very elegant is to window around hue. Thus, we slid the scale to put red in the middle. However, this too has its problems particularly if we do in fact have a bimodal distribution. We can get around this by converting hue, which is in radial (polar) coordinates (http://en.wikipedia.org/wiki/Polar_coordinate_system) to Cartesian coordinates (http://en.wikipedia.org/wiki/Cartesian_coordinates). Thus, we break Hue into an x and y component. We can term these H2 and H1 respectively.

  • Note: Two variants on H2SV are described here, H2SV1 and H2SV2.
The transformation from Hue in HSV to H1 and H2 components is shown here. Additionally, it is shown that different types of biases can be applied to the transformation. For instance, a slight bias turns the H1 and H2 components into Red/Green (Hollow Box Line) and Blue/Yellow (Solid Box Line) color opponencies.
Enlarge
The transformation from Hue in HSV to H1 and H2 components is shown here. Additionally, it is shown that different types of biases can be applied to the transformation. For instance, a slight bias turns the H1 and H2 components into Red/Green (Hollow Box Line) and Blue/Yellow (Solid Box Line) color opponencies.

Additional Advantages of H2SV

  • H2SV Color space is designed with video streams and tracking in mind. The transformation that is used, moves smoothly with changes in the environment. So as an object moves around and its colors change slightly, the change is strongly linearly related to change in H2SV color space.
  • The H2SV2 variant which uses Red/Green and Blue/Yellow color opponents (http://en.wikipedia.org/wiki/Color_opponency) has the advantage of being invertible. As such, images can be transformed in the H2SV2 Red/Green Blue/Yellow opponents, under go some image processing and be transformed back to HSV and then to RGB color.


HSV to H2SV Transformation

HSV to H2SV1 Variant

We convert HSV to H2SV1 (or H2SV2 etc) by converting Hue which is in radial coordinates 0 - 360 and converting it to Cartesian coordinates The precise coordinate transform is flexible, so long as it is invertible here, we treat r (hue) as falling along a straight line. We could also in another method treat it as if it is on a circle.

  • All final values normalize from 0 to 1
// ######################################################################
// T. Nathan Mundhenk
// mundhenk@usc.edu
// C/C++ Macro HSV to H2SV
#define PIX_HSV_TO_H2SV1_COMMON(H,H1,H2)                   \
if(H > 180)                                                \
{                                                          \
  H2 = ((H - 180)/180);                                    \
  if(H > 270) H1 = ((H - 270)/180);                        \
  else        H1 = (1 - (H - 90)/180);                     \
}                                                          \
else                                                       \
{                                                          \
  H2 = (1 - H/180);                                        \
  if(H > 90) H1 = (1 - (H - 90)/180);                      \
  else       H1 = (0.5 + H/180);                           \
}

HSV to H2SV2 Variant

We convert HSV to H2SV1 (or H2SV2 etc) by converting Hue which is in radial coordinates 0 - 360 and converting it to Cartesian coordinates. The precise coordinate transform is flexible, so long as it is invertible here, we treat r (hue) as falling along a straight line. We could also in another method treat it as if it is on a circle.

This variant is designed to make H1 and H2 mimic Red/Green Blue/Yellow opponencies Thus:

  1. H1 is 0 at Blue and 1 at Yellow
  2. H2 is 0 at Green and 1 at Red
  • All final values normalize from 0 to 1
  • Note: We can also use these macros on HSL color space (http://en.wikipedia.org/wiki/HSL_color_space) since HSV and HSL have the exact same basis for hue.
// ######################################################################
// T. Nathan Mundhenk
// mundhenk@usc.edu
// C/C++ Macro HSV to H2SV
#define PIX_HSV_TO_H2SV2_COMMON(H,H1,H2)                  \
if(H > 120)                                               \
{                                                         \
  H2 = ((H - 120)/240);                                   \
  if(H > 240) H1 = ((H - 240)/180);                       \
  else        H1 = (1 - (H - 60)/180);                    \
}                                                         \
else                                                      \
{                                                         \
  H2 = (1 - H/120);                                       \
  if(H > 60) H1 = (1 - (H - 60)/180);                     \
  else       H1 = ((2.0/3.0) + H/180);                    \
}

H2SV to HSV Simple Transformation

A simple transformation will convert H1 and H2 back to HSV Hue. However, if you perform operations in H2SV color space such as image convolutions, H1 and H2 cannot be transformed into Hue using the simple transformation. For the, one needs the robust transformation shown below.

H2SV1 to HSV Simple

Convert H2SV1 to HSV using a simple quick method. This just reverse maps the H1 and H2 components back to Hue in HSV using a simple inversion.

// T. Nathan Mundhenk
// mundhenk@usc.edu
// C/C++ Macro HSV2 to HSV
#define PIX_H2SV1_TO_HSV_SIMPLE_COMMON(H1,H2,H)           \
if(H1 > 0.5)                                              \
  if(H2 > 0.5)  H = 180 * H1 - 90;                        \
  else          H = 90 + 180 * (1 - H1);                  \
else                                                      \
  if(H2 <= 0.5) H = 90 + 180 * (1 - H1);                  \
  else          H = 270 + 180 * H1;

H2SV2 to HSV Simple

Convert H2SV2 to HSV using a simple quick method. This just reverse maps the H1 and H2 components back to Hue in HSV using a simple inversion.

// T. Nathan Mundhenk
// mundhenk@usc.edu
// C/C++ Macro H2SV to HSV
#define PIX_H2SV2_TO_HSV_SIMPLE_COMMON(H1,H2,H)           \
if(H1 > 2.0/3.0)                                          \
  if(H2 > 0.5)  H = 180 * H1 - 120;                       \
  else          H = 60 + 180 * (1 - H1);                  \
else                                                      \
  if(H2 <= 0.5) H = 60 + 180 * (1 - H1);                  \
  else          H = 240 + 180 * H1;

H2SV to HSV Robust Transformation

In this example we show how to convert back from H2SV2 color space into HSV using a robust method. It becomes nessessary to use this method if H2SV colors are blended. This is because the x and y coordinates move off the track from with they were initially transformed. Thus, they do not perfectly backwards map. Instead, we now triangulate the x and y coordinate and map it back to where it should be in the simple H2SV scheme. That is, we figure out how to put it back on its track by projecting where is should be.

Here we convert H2SV2 to HSV using a robust method that allows us to deal with H1 and H2 having been adjusted independently. If they are never adjusted independently, then we can use the simple version

The main difference here from simple is that:

  1. We compute xp and replace H1 with it
  2. We compute a new saturation term S_NEW
  • What makes this robust is that the H1 and H2 coordinates do not have to match up with the original unit coordinates. Instead we compute a slope term and figure out where it should intersect the original unit coordinates.
  • Saturation may be reduced if we are too far from a viable color coordinate and have to transform to much.

General Computations:

m:

The slope of the H1/H2 lines

xp:

The Adjusted H1 (x value) - Useful if H1 was adjusted independent of H2

yp:

The Adjusted H2 (y value)

d:

The difference between the ideal (xp,yp) value and the real (H1,H2)

ad:

The length of (xp,yp) from the origin

sfac:

Adjustment to be applied to saturation if d is large - Useful if H1 and H2 are close to the origin, thus saturation should be reduced.

C / C++ Code for Robust Transformation

// ######################################################################
// T. Nathan Mundhenk
// mundhenk@usc.edu
// C/C++ Macro H2SV to HSV
#define PIX_H2SV2_TO_HSV_ROBUST_COMMON(H1,H2,S,H,S_NEW)         \
double sfac    = 1;                                             \
const double x = H1 - 2.0/3.0;                                  \

Hue is between red and green. That is, 0 to 120 on the Hue interval of 360.

if(x > 0)                                                       \
{                                                               \
  const double y = H2 - 0.5;                                    \
  const double m = y/x;                                         \
Hue is between 0 to 60 on the Hue interval of 360.
  if(y > 0)                                                     \
  {                                                             \
    const double xp = 0.5/(m + 3.0/2.0);                        \
    if(xp > x)                                                  \
    {                                                           \
      const double yp = 0.5 - 0.5*xp/(1.0/3.0);                 \
      const double d  = sqrt(pow(xp - x,2) + pow(yp - y,2));    \
      const double ad = sqrt(pow(xp,2)     + pow(yp,2));        \
      sfac            = (ad - d)/ad;                            \
    }                                                           \
    H = 180 * (xp + 2.0/3.0) - 120;                             \
  }                                                             \
Hue is between 60 to 120 on the Hue interval of 360.
  else                                                          \
  {                                                             \
    const double xp = -0.5/(m - 3.0/2.0);                       \
    if(xp > x)                                                  \
    {                                                           \
      const double yp = -1.0 * (0.5 - 0.5*xp/(1.0/3.0));        \
      const double d  = sqrt(pow(xp - x,2) + pow(yp - y,2));    \
      const double ad = sqrt(pow(xp,2)     + pow(yp,2));        \
      sfac            = (ad - d)/ad;                            \
    }                                                           \
    H = 60 + 180 * (1 - (xp + 2.0/3.0));                        \
  }                                                             \
}                                                               \

Hue is between green and red. That is, 120 to 360 on the Hue interval of 360.

else                                                            \
{                                                               \
  if(x != 0)                                                    \
  {                                                             \
    const double y = H2 - 0.5;                                  \
    const double m = y/x;                                       \
Hue is between 120 to 240 on the Hue interval of 360.
    if(y <= 0)                                                  \
    {                                                           \
      const double xp = -0.5/(m + 0.5/(2.0/3.0));               \
      if(xp < x)                                                \
      {                                                         \
        const double yp = -1.0 * (0.5 + 0.5*xp/(2.0/3.0));      \
        const double d  = sqrt(pow(xp - x,2) + pow(yp - y,2));  \
        const double ad = sqrt(pow(xp,2)     + pow(yp,2));      \
        sfac            = (ad - d)/ad;                          \
      }                                                         \
      H = 60 + 180 * (1 - (xp + 2.0/3.0));                      \
    }                                                           \
Hue is between 240 to 360 on the Hue interval of 360.
    else                                                        \
    {                                                           \
      const double xp = 0.5/(m - 0.5/(2.0/3.0));                \
      if(xp < x)                                                \
      {                                                         \
        const double yp = 0.5 + 0.5*xp/(2.0/3.0);               \
        const double d  = sqrt(pow(xp - x,2) + pow(yp - y,2));  \
        const double ad = sqrt(pow(xp,2)     + pow(yp,2));      \
        sfac            = (ad - d)/ad;                          \
      }                                                         \
      H = 240 + 180 * (xp + 2.0/3.0);                           \
    }                                                           \
  }                                                             \
  else                                                          \
    H = 240;                                                    \
}                                                               \
S_NEW = S * sfac;

External Links

Publication that describes H2SV color space Mundhenk et al. SPIE 2005 (http://www.mundhenk.com/publications/SPIE-2005-Real-Time-Tracking.pdf)

Comments? Suggestions?

Comments can be added in the discussion section of this page. Also, questions can be directed to either the iLab discussion board (http://ilab.usc.edu/cgi-bin/yabb/YaBB.pl) which is monitored by the authors or via email at: mailto:mundhenk@usc.edu


Copyright © 2008 by the University of Southern California, iLab and T. Nathan Mundhenk (http://www.mundhenk.com). All Rights Reserved.