# Getting the average HSB from pixels

**URL:** <https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965>\
**Category:** Coding Questions\
**Created:** [August 27, 2021, 8:47am UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965 "2021-08-27T08:47:57Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![paulstgeorge](https://avatars.discourse-cdn.com/v4/letter/p/53a042/32.png) [@paulstgeorge](https://discourse.processing.org/u/paulstgeorge)\
**Post date:** [August 27, 2021, 8:47am UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/1 "2021-08-27T08:47:57Z")

</div>

I have some simple code that is intended to find the average colour in a rectangle. That rectangle moves with the mouse.

All is fine until I try to average two colours as in the example below.

The Hue of half the sample is 247. The Hue of the other half is 1. So, the existing code (see below) gives an average of 124. This is obviously incorrect. The average should be of 247 and (360+1), so 304.

How do I change the code so this works? The images that I intend to analyse are more complex than this example so the solution should not rely on the image being two adjacent rectangles.

TIA

 ![02](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/9/99a0ece99a43ed95cc4a9878bfc0ef9ddc317170.jpeg)

02.jpg

```auto
// ( )
PImage img1; // the image to be investigated
PImage img2; // the detail of the image to be averaged

// ( )
int samplesize = 60; // an even number
int half = samplesize/2;

void setup() {
  colorMode(HSB, 360, 100, 100);
  size(1920, 1200);

  img1 = loadImage("02.jpg");

} 

void draw() {
  background(0, 0, 100);

  // (1) display the image under investigation
  image(img1, 0, 0); 

  // (2) the sample but in a new image
  img2 = get(mouseX-half, mouseY-half, samplesize, samplesize); 

  // (3) after averaged by function
  fill(extractColorFromImage(img2)); 
  rect(img1.width-200, img1.height, 200, 200);

  // (4) show border of sample
  rectMode(CENTER);
  stroke(0);
  noFill();
  rect(mouseX, mouseY, samplesize, samplesize); 
  rectMode(CORNER); // revert to default
  noStroke(); // revert to default
}

color extractColorFromImage(PImage img2) {

  img2.loadPixels();
  int h = 0, s = 0, b = 0;
  for (int i=0; i<img2.pixels.length; i++) {
    color c = img2.pixels[i];

    h += hue(c);
    s += saturation(c);
    b += brightness(c);
  }
  h /= img2.pixels.length;
  s /= img2.pixels.length;
  b /= img2.pixels.length;

  fill(0);
  text("HSB "+h+" "+s+" "+b, 20, img1.height+220);

  return color(h, s, b);
}

```

---

<div class="post-metadata">

**Author:** ![quark](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/quark/32/26_2.png) [@quark](https://discourse.processing.org/u/quark)\
**Post date:** [August 27, 2021, 9:52am UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/2 "2021-08-27T09:52:03Z")

</div>

Here is a simple sketch that includes a small function to calculate the average hue

```auto
void setup() {
  averageHue(247, 1);
  averageHue(1, 247);
  averageHue(30, 200);
  averageHue(200, 30);
  averageHue(20, 330);
  averageHue(330, 20);
}

int averageHue(int h0, int h1) {
  int hlow = min(h0, h1);
  int hhigh = max(h0, h1);
  int delta = hhigh - hlow;
  int ha = delta < 180 ? hlow + delta / 2 : hhigh + (360 - delta)/2;
  println(hlow, hhigh, ha);
  return ha;
}

```

---

<div class="post-metadata">

**Author:** ![paulstgeorge](https://avatars.discourse-cdn.com/v4/letter/p/53a042/32.png) [@paulstgeorge](https://discourse.processing.org/u/paulstgeorge)\
**Post date:** [August 27, 2021, 10:02am UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/3 "2021-08-27T10:02:10Z")

</div>

> [@quark](#):
>
> ```auto
> 
> int ha = delta < 180 ? hlow + delta / 2 : hhigh + (360 - delta)/2;
> 
> ```

Ha indeed! I guessed it would involve 180 (as this is similar to the interval between two angles) but I was struggling to get the code that clear.

How will this work when there are many values (one value for each pixel in the sample)?  
Thank you.

---

<div class="post-metadata">

**Author:** ![quark](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/quark/32/26_2.png) [@quark](https://discourse.processing.org/u/quark)\
**Post date:** [August 28, 2021, 10:20am UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/4 "2021-08-28T10:20:33Z")

</div>

> [@quark](#):
>
> ```auto
> int averageHue(int h0, int h1) {
> int hlow = min(h0, h1);
> int hhigh = max(h0, h1);
> int delta = hhigh - hlow;
> int ha = delta < 180 ? hlow + delta / 2 : hhigh + (360 - delta)/2;
> ha %= 360;
> println(hlow, hhigh, ha);
> return ha;
> }
> 
> ```

Slight change to my code above to prevent hues \>= 360

---

<div class="post-metadata">

**Author:** ![quark](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/quark/32/26_2.png) [@quark](https://discourse.processing.org/u/quark)\
**Post date:** [August 28, 2021, 10:49am UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/5 "2021-08-28T10:49:43Z")

</div>

Although the method I provided accurately calculates the average hue for 2 values it cannot be used consistently for more than two. For instance this sketch calculates the average for 3 values and gives 3 different averages depending on the order we calculate the intermediate values

```auto
void setup() {
  int[] hues = {330, 45, 190};
  println(averageHue(hues[0], averageHue(hues[1], hues[2])));
  println(averageHue(hues[1], averageHue(hues[0], hues[2])));
  println(averageHue(hues[2], averageHue(hues[0], hues[1])));
}

int averageHue(int h0, int h1) {
  int hlow = min(h0, h1);
  int hhigh = max(h0, h1);
  int delta = hhigh - hlow;
  return (delta < 180 ? hlow + delta / 2 : hhigh + (360 - delta) / 2) % 360;
}

```

`Output = 43 332 278`  
This image shows the output from a sketch that calculates the average hue of an image many times but accessing the pixels in a different order each time. The frequency graph shows a peak about 20/30 but has a wide range of values for the average hue.

 ![result](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/c/c534fdaf7b15dbc071dd8c7a1ef50dbc2838804f.jpeg)

In this situation a simple numerical average is probably the best solution because at least the result is repeatable.

---

<div class="post-metadata">

**Author:** ![glv](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/glv/32/18785_2.png) [@glv](https://discourse.processing.org/u/glv)\
**Post date:** [August 28, 2021, 11:54am UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/6 "2021-08-28T11:54:30Z")

</div>

@paulstgeorge

My version below:

```auto
void setup() 
  {
  //for (int h = 359; h>=0; h--)
  for (int h = 0; h<360; h++)
    {
    print(averageHue(0, h), '\t');
    println(averageHue2(0, h));
    } 
  }

// glv version: 
float averageHue2(int h0, int h1) 
  {
  float h2 = abs(h0+h1)/2f;
  float ha2 = abs(h1-h0)>180 ? h2+180 : h2;

  //if (abs(h1-h0)>180)
  // ha2 = h2+180;
  //else
  // ha2 = h2;  

  return ha2;
  } 

//quark version from https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/2  
int averageHue(int h0, int h1) 
  {
  int hlow = min(h0, h1);
  int hhigh = max(h0, h1);
  int delta = hhigh - hlow;
  int ha = delta < 180 ? hlow + delta / 2 : hhigh + (360 - delta)/2;
  return ha;
  }

```

I added quarks version to compare outputs.

`:)`

---

<div class="post-metadata">

**Author:** ![paulstgeorge](https://avatars.discourse-cdn.com/v4/letter/p/53a042/32.png) [@paulstgeorge](https://discourse.processing.org/u/paulstgeorge)\
**Post date:** [August 28, 2021, 12:59pm UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/7 "2021-08-28T12:59:23Z")

</div>

> [@glv](#):
>
> I added quarks version to compare outputs

That’s not fair (to either of you)! If Quark’s code is changed from `int` numbers to `float` it gives the same result as your code. But, if the setup is changed a little (see below) we get the real test, how does the code cope with two numbers like 247 and 1, where adding together and dividing by 2 would give the wrong result. Your code and Quark’s code both give the correct answer but you do it in fewer lines!!!

```auto
int s1 = 247;
int s2 = 1;

```

//correct result is (247+361)/2, or 304

```auto
void setup() {
    print("Q "+averageHue(s1, s2), '\t');
    println("G "+averageHue2(s1, s2));
  }

// glv version: 
float averageHue2(int h0, int h1) 
  {
  float h2 = abs(h0+h1)/2f;
  float ha2 = abs(h1-h0)>180 ? h2+180 : h2;

  return ha2;
  } 

//quark version from https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/2  
float averageHue(int h0, int h1) 
  {
  float hlow = min(h0, h1);
  float hhigh = max(h0, h1);
  float delta = hhigh - hlow;
  float ha = delta < 180 ? hlow + delta / 2 : hhigh + (360 - delta)/2;
  return ha;
  }

```

---

<div class="post-metadata">

**Author:** ![glv](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/glv/32/18785_2.png) [@glv](https://discourse.processing.org/u/glv)\
**Post date:** [August 28, 2021, 1:53pm UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/8 "2021-08-28T13:53:30Z")

</div>

> [@paulstgeorge](#):
>
> That’s not fair (to either of you)!

I was showing my method which I was working on as a personal challenge.

I looked at @quark’s after and compared outputs and added floats for that extra resolution which I thought may be of interest… I like the details.

That is all there is to that.

`:)`

---

<div class="post-metadata">

**Author:** ![paulstgeorge](https://avatars.discourse-cdn.com/v4/letter/p/53a042/32.png) [@paulstgeorge](https://discourse.processing.org/u/paulstgeorge)\
**Post date:** [August 28, 2021, 2:02pm UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/9 "2021-08-28T14:02:03Z")

</div>

Yeh, I know. I am showing that yours was actually even better if we compare like with like.

---

<div class="post-metadata">

**Author:** ![paulstgeorge](https://avatars.discourse-cdn.com/v4/letter/p/53a042/32.png) [@paulstgeorge](https://discourse.processing.org/u/paulstgeorge)\
**Post date:** [August 28, 2021, 2:32pm UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/10 "2021-08-28T14:32:19Z")

</div>

I am closing in on a solution and thought I would share now in case someone wants to swoop in to assist (or tell me I am wasting my time).

Treat the n numbers like angles between 0 and 360. Then think of the angles as simple vectors.

Get the sine of each angle. Get the mean of all the sines. s  
Get the cosine of each angle. Get the mean of all the cosines. c

Then get the atan or arc tangent of (s/c) to find the angle. a

if s \< 0 && c \> 0, a = a + 360

if c \< 0, a = a + 180

if s \> 0 && c \> 0, a

I am not sure if this is correct yet, but it looks like it might be. It works for any number of values.

@glv @quark

---

<div class="post-metadata">

**Author:** ![quark](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/quark/32/26_2.png) [@quark](https://discourse.processing.org/u/quark)\
**Post date:** [August 28, 2021, 4:34pm UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/11 "2021-08-28T16:34:06Z")

</div>

> [@paulstgeorge](#):
>
> I am not sure if this is correct yet, but it looks like it might be. It works for any number of values.

How will you know if the value produced by your algorithm is correct?

---

<div class="post-metadata">

**Author:** ![paulstgeorge](https://avatars.discourse-cdn.com/v4/letter/p/53a042/32.png) [@paulstgeorge](https://discourse.processing.org/u/paulstgeorge)\
**Post date:** [August 28, 2021, 4:35pm UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/12 "2021-08-28T16:35:11Z")

</div>

Good question! I plan to post something soon, then test it.

---

<div class="post-metadata">

**Author:** ![paulstgeorge](https://avatars.discourse-cdn.com/v4/letter/p/53a042/32.png) [@paulstgeorge](https://discourse.processing.org/u/paulstgeorge)\
**Post date:** [August 28, 2021, 4:57pm UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/13 "2021-08-28T16:57:03Z")

</div>

Here it is for the two test values. I first wanted to see if it gave the correct result for 247 and 1. Obviously I would use arrays, etc. but wanted to present the idea in a clear way so it can be pulled apart. I am not sure I have covered all bases in the `if then` part.

```auto
int a1 = 247;
int a2 = 1;

float sin1;
float sin2;

float cos1;
float cos2;

float meansin;
float meancos;

float ha;

//correct result is (247+361)/2, or 304

void setup() {

  // mean of sine of each angle
  sin1 = sin(radians(a1));
  sin2 = sin(radians(a2));

  meansin = (sin1 + sin2)/2.0;

  // mean of cosine of each angle
  cos1 = cos(radians(a1));
  cos2 = cos(radians(a2));

  meancos = (cos1 + cos2)/2.0;

  // find the angle from the arc tangent
  if (meansin < 0 && meancos > 0) {
    ha = 360 + degrees(atan(meansin/meancos));
  } else if (meancos < 0) {
    ha = 180 + degrees(atan(meansin/meancos));
  } else if (meansin > 0 && meancos > 0) {
    ha = degrees(atan(meansin/meancos));
  }

  println(ha);
}

```

---

<div class="post-metadata">

**Author:** ![quark](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/quark/32/26_2.png) [@quark](https://discourse.processing.org/u/quark)\
**Post date:** [August 28, 2021, 5:03pm UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/14 "2021-08-28T17:03:40Z")

</div>

The problem is not finding the average of 2 values, we already have an algorithm for that. In my previous post I showed that this could not be extended to work consistently with more than 2 values.

---

<div class="post-metadata">

**Author:** ![paulstgeorge](https://avatars.discourse-cdn.com/v4/letter/p/53a042/32.png) [@paulstgeorge](https://discourse.processing.org/u/paulstgeorge)\
**Post date:** [August 28, 2021, 5:04pm UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/15 "2021-08-28T17:04:41Z")

</div>

I know. I am inching forwards. Now doing for three values, then for any number of values.

---

<div class="post-metadata">

**Author:** ![quark](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/quark/32/26_2.png) [@quark](https://discourse.processing.org/u/quark)\
**Post date:** [August 28, 2021, 5:07pm UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/16 "2021-08-28T17:07:13Z")

</div>

When you do the three values try the algorithm see if you get the same result not matter what the order they are in e.g.  
240 30 200  
240 200 30  
200 30 240  
…

---

<div class="post-metadata">

**Author:** ![paulstgeorge](https://avatars.discourse-cdn.com/v4/letter/p/53a042/32.png) [@paulstgeorge](https://discourse.processing.org/u/paulstgeorge)\
**Post date:** [August 28, 2021, 5:08pm UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/17 "2021-08-28T17:08:50Z")

</div>

I will. Yes. But I think order will not matter for my algorithm. I am adding the values before I do anything to them. Addition is commutative.

---

<div class="post-metadata">

**Author:** ![paulstgeorge](https://avatars.discourse-cdn.com/v4/letter/p/53a042/32.png) [@paulstgeorge](https://discourse.processing.org/u/paulstgeorge)\
**Post date:** [August 28, 2021, 5:21pm UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/18 "2021-08-28T17:21:40Z")

</div>

Work in Progress.  
This gives the same results (no matter what order).

As I suspected, some work needs to be done on the conditions for choosing which of the three values is correct.  
50.985184 230.98518 410.9851

```auto
int a1 = 240;
int a2 = 30;
int a3 = 200;

float sin1;
float sin2;
float sin3;

float cos1;
float cos2;
float cos3;

float meansin;
float meancos;

float ha;

void setup() {

  // mean of sine of each angle
  sin1 = sin(radians(a1));
  sin2 = sin(radians(a2));
  sin3 = sin(radians(a3));

  meansin = (sin1 + sin2 + sin3)/3.0;

  // mean of cosine of each angle
  cos1 = cos(radians(a1));
  cos2 = cos(radians(a2));
  cos3 = cos(radians(a3));

  meancos = (cos1 + cos2 + cos3)/3.0;

  // find the angle from the arc tangent

  ha = degrees(atan(meansin/meancos));

  print(ha, ha+180, ha+360);
}

```

---

<div class="post-metadata">

**Author:** ![jeremydouglass](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/jeremydouglass/32/20_2.png) [@jeremydouglass](https://discourse.processing.org/u/jeremydouglass)\
**Post date:** [August 28, 2021, 5:50pm UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/19 "2021-08-28T17:50:26Z")

</div>

> [@paulstgeorge](#):
>
> Treat the n numbers like angles between 0 and 360. Then think of the angles as simple vectors.

If you have hues 0-360, then you can imagine them as degree angles, and solve the mean angle question – which also gives you a mean hue.

**EDIT** fixed typos:

```auto
float getMeanAngle(float... anglesDeg) {
  float x = 0.0;
  float y = 0.0;
  for (float angleD : anglesDeg) {
    float angleR = radians(angleD);
    x += cos(angleR);
    y += sin(angleR);
  }
  float avgR = atan2(y / anglesDeg.length, x / 
  anglesDeg.length);
  return degrees(avgR);
}

```

For more, see:

[https://rosettacode.org/wiki/Averages/Mean\_angle](https://rosettacode.org/wiki/Averages/Mean_angle)

If you want a simple approximation of _perceptual_, intuitive hue averaging, it will probably work more like either clustering or just peak detection. That is, some color distributions have one peak, some have two or three, and the correct answer simplifies to one _or more_ average hues.

Also, the pixel is usually the wrong unit for perceptual, because our eyes blend textures of color points into new colors – so hue averaging on pixels can give us really unintuitive answers if the image contains noise / dithered textures. You can change what you measure by first averaging your pixels into small neighborhoods blocks like 3x3 or 4x4, then computing the clustering / peak on those – or first using blur.

---

<div class="post-metadata">

**Author:** ![jeremydouglass](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/jeremydouglass/32/20_2.png) [@jeremydouglass](https://discourse.processing.org/u/jeremydouglass)\
**Post date:** [August 28, 2021, 7:54pm UTC](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965/20 "2021-08-28T19:54:35Z")

</div>

Here is a _simple_ mean HSB extractor on an image (pixels array). It doesn’t do clustering or suggest multiple hues, and it doesn’t do local averaging / blurring / scaling – so it gives satisfactory results on some images, and not on others. Still, it demonstrates the concept.

```auto
/**
 * MeanHSB
 * 2021-08-28 Jeremy Douglass - Processing 3.5.4 
 *
 * Compute the mean HSB (hue, saturation, brightness)
 * for an array of colors -- for example, the pixels
 * of an image. The bottom bar displays the HSB values
 * as a new color.
 *
 * Hue is calculated as a mean angle using getMeanDegrees().
 *
 * https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965
 */

PImage img;

void setup() {
  size(640, 480);
  img = loadImage("https://upload.wikimedia.org/wikipedia/commons/thumb/2/21/64_365_Color_Macro_%285498808099%29.jpg/640px-64_365_Color_Macro_%285498808099%29.jpg");
  img.resize(640, 480);
  noLoop();
}

void draw() {
  image(img, 0, 0);
  float[] meanHSB = getMeanHSB(img.pixels);
  colorMode(HSB, 360, 100, 100);
  fill(meanHSB[0], meanHSB[1], meanHSB[2]);
  print(meanHSB[0], meanHSB[1], meanHSB[2]);
  rect(0, height-50, width, height);
}

/**
 * Calculate the mean HSB values of a pixel array
 * using mean angle in degrees for hue (0-360)
 * and mean for saturation / brightness (0-100).
 * 
 * @param pixels the array of pixels to measure
 * @return a float array of three means, {H, S, B}
 */
float[] getMeanHSB(int... pixels) {
  double sumS = 0;
  double sumB = 0;
  float anglesDeg[] = new float[pixels.length];
  // convert 
  push();
  colorMode(HSB, 360, 100, 100);
  for(int i=0; i<pixels.length; i++){
    anglesDeg[i] = hue(pixels[i]);
    sumS += saturation(pixels[i]);
    sumB += brightness(pixels[i]);
  }
  pop();
  return new float[] {
    // convert list of hues into mean hue
    getMeanDegrees(anglesDeg),
    // convert sums of sat/bright into means
    (float)(sumS/pixels.length),
    (float)(sumB/pixels.length)
  };
}

/**
 * Calculate the mean angle from any number
 * of angles expressed in degrees 0-360.
 * 
 * @param anglesDeg a list of angles in degrees
 * @return the average angle in degrees (0-360)
 */
float getMeanDegrees(float... anglesDeg) {
  float x = 0.0;
  float y = 0.0;
  for (float angleD : anglesDeg) {
    float angleR = radians(angleD);
    x += cos(angleR);
    y += sin(angleR);
  }
  float avgR = atan2(y / anglesDeg.length, x / 
  anglesDeg.length);
  return degrees(avgR);
}

```

![screenshot--MeanHSB-1](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/6/6bcb11710c08eedbdba53944a48499582bb8366b.jpeg)

[Next page](https://discourse.processing.org/t/getting-the-average-hsb-from-pixels/31965.md?page=2)
