Getting the dominant color of an image

edited January 2016 in How To...

Hi,

im trying to get the most dominant color of an image (perfect case: getting the 5 dominant colors sorted by most used).

Is there a way to make that in processing? I tried already a code i found but with that im only getting the average color:

color extractColorFromImage(PImage img) {
    img.loadPixels();
    int r = 0, g = 0, b = 0;
    for (int i=0; i<img.pixels.length; i++) {
        color c = img.pixels[i];
        r += c>>16&0xFF;
        g += c>>8&0xFF;
        b += c&0xFF;
    }
    r /= img.pixels.length;
    g /= img.pixels.length;
    b /= img.pixels.length;

    return color(r, g, b);
}

So its not really that what i need.

I already read that i could do it with HSV, k-means and so on.... and any way to do it in processing?

Example: Here i want to get the color red as the dominant color, with the example above im getting a dark orange.

capture1

Answers

  • that is averaging the colours, you can see it in the code. it's adding the red green and blue up and dividing by count.

    you need to get the colour for each pixel and keep a count of each separate value, which is more complicated, especially for photographs which can have a lot of colours.

    for each pixel in image
        is this a new colour?
            yes: create a new entry for colour with count = 1
            no: increment the count for the colour entry
        end
    end loop
    

    then you can sort the entries by count and keep the top 5

    look up ArrayList in the reference. or IntDict (which has an increment() method)

  • I think you can group colors based on their distance (deltaE).
    Color Distance: https://en.wikipedia.org/wiki/Color_difference
    For some code see: https://forum.processing.org/two/discussion/14185/palette-colors#latest

  • edited January 2016

    Easiest way I've come up w/ was to dump the whole pixels[] into an IntDict via increment():

    static final IntDict countColorsIntoDict(final color... colors) {
      final IntDict iDict = new IntDict();
      for (color c : colors)  iDict.increment(str(c & ~#000000));
      return iDict;
    }
    

    Then invoke ValuesReverse() in order to have the highest counter color keys at the top of the IntDict:

    pic = createImage(width, height, ARGB);
    sortedDict = countColorsIntoDict(pic.pixels);
    sortedDict.sortValuesReverse();
    

    And as a final touch, collected the 1st 5 entries into a regular color[] array and another IntDict:

    for (int i = 0; i != QTY; ++i) {
      final String k = sortedDict.key(i);
      dominantDict.set(k, sortedDict.value(i));
      dominantArr[i] = int(k) | #000000;
    }
    

    The only con is that IntDict stores its keys as a String rather than an int or color.
    Therefore it needs many conversions w/ str() or int() down the road. :|
    In addition of not being as efficient & economic as an int! 3:-O

    Well, here's the full sketch. Enjoy! Any doubts about it, just ask: :bz

    /**
     * Dominant Color Sort I (v1.03)
     * GoToLoop (2016-Jan-11)
     * forum.Processing.org/two/discussion/14393/getting-the-dominant-color-of-an-image
     */
    
    static final int QTY = 5, VARIATION = 50;
    final color[] dominantArr  = new color[QTY];
    final IntDict dominantDict = new IntDict(QTY);
    
    IntDict sortedDict;
    PImage pic;
    int len;
    
    void setup() {
      size(800, 600);
      noLoop();
    
      pic = createImage(width, height, ARGB);
      len = pic.pixels.length;
    }
    
    void draw() {
      background(randomPixels(pic, VARIATION));
    
      sortedDict = countColorsIntoDict(pic.pixels);
      sortedDict.sortValuesReverse();
    
      dominantDict.clear();
      java.util.Arrays.fill(dominantArr, 0);
    
      final int dictSize = sortedDict.size(), limit = min(QTY, dictSize);
      println("\nUnique colors found:", dictSize, "\tfrom:", len, ENTER);
    
      for (int i = 0; i != limit; ++i) {
        final String k = sortedDict.key(i);
        dominantDict.set(k, sortedDict.value(i));
        dominantArr[i] = int(k) | #000000;
      }
    
      for (int i = 0; i != QTY; ++i)
        println(i, "->", hex(dominantArr[i], 6), "\tcount:", dominantDict.value(i));
    
      println();
      println(dominantDict, ENTER);
      println(dominantArr);
    }
    
    void mousePressed() {
      redraw = true;
    }
    
    PImage randomPixels(final PImage img, final int v) {
      final color p[] = img.pixels, d = 0400 - min(0400, abs(v));
    
      for (int i = 0; i != p.length; p[i++] = color(
        (color) random(d, 0400), 
        (color) random(d, 0400), 
        (color) random(d, 0400)));
    
      img.updatePixels();
      return img;
    }
    
    static final IntDict countColorsIntoDict(final color... colors) {
      final IntDict iDict = new IntDict();
      for (color c : colors)  iDict.increment(str(c & ~#000000));
      return iDict;
    }
    
  • edited January 2016

    In order to be more efficient & saving than an IntDict, we need to use some sorta Map<Integer, Integer>.

    Problem is such structure's got no means to sort() neither its keys nor its values.
    Actually it doesn't have any notion of ordered entries! @-)

    However, not all is lost. In the form of LinkedHashMap, it can at least keep its order of insertion. #:-S

    That is, if we manage to sort() all of its entries in another container which allows ordering, like for example a regular array or some List, we can put() everything back into the LinkedHashMap and be assured that when we traverse it, it's gonna be iterated exactly by the order of the put() insertion. *-:)

    In this more complex sketch, I've created a function called sortMapByValues() as a replacement for IntDict's ValuesReverse().
    It's based on this link btW: http://StackOverflow.com/questions/34381536/sort-a-hashmap-by-the-integer-value-desc

    It also invokes Array.sort() passing the array along w/ an instance of Comparator.
    Only when it's sorted by most dominant color, it is dumped via put() into the LinkedHashMap, keeping insertion order. ~O)

    Well, it's too daunting to explain it all. Better see it for yourself: (:|

    /**
     * Dominant Color Sort II (v1.03)
     * GoToLoop (2016-Jan-11)
     *
     * forum.Processing.org/two/discussion/14393/getting-the-dominant-color-of-an-image
     * StackOverflow.com/questions/34381536/sort-a-hashmap-by-the-integer-value-desc
     */
    
    import java.util.Arrays;
    import java.util.Comparator;
    
    import java.util.Map;
    import static java.util.Map.Entry;
    import java.util.LinkedHashMap;
    
    static final int QTY = 5, VARIATION = 50;
    final color[] dominantArr = new color[QTY];
    final Map<Integer, Integer> dominantMap =
      new LinkedHashMap<Integer, Integer>(QTY, 1.0);
    
    Map<Integer, Integer> map, sortedMap;
    PImage pic;
    int len;
    
    void setup() {
      size(800, 600);
      noLoop();
    
      pic = createImage(width, height, ARGB);
      len = pic.pixels.length;
    }
    
    void draw() {
      background(randomPixels(pic, VARIATION));
    
      map = countColorsIntoMap(pic.pixels);
      sortedMap = sortMapByValues(map);
    
      dominantMap.clear();
      Arrays.fill(dominantArr, 0);
    
      println("\nUnique colors found:", map.size(), "\tfrom:", len, ENTER);
    
      int idx = 0;
      for (final Entry<Integer, Integer> colors : sortedMap.entrySet()) {
        dominantMap.put(dominantArr[idx] = colors.getKey(), colors.getValue());
        if (++idx == QTY)  break;
      }
    
      idx = 0;
      for (final Entry<Integer, Integer> colors : dominantMap.entrySet())
        println(idx++, "->", hex(colors.getKey(), 6), "\tcount:", colors.getValue());
    
      println();
      println(dominantMap, ENTER);
      println(dominantArr);
    }
    
    void mousePressed() {
      redraw = true;
    }
    
    PImage randomPixels(final PImage img, final int v) {
      final color p[] = img.pixels, d = 0400 - min(0400, abs(v));
    
      for (int i = 0; i != p.length; p[i++] = color(
        (color) random(d, 0400), 
        (color) random(d, 0400), 
        (color) random(d, 0400)));
    
      img.updatePixels();
      return img;
    }
    
    static final Map<Integer, Integer> countColorsIntoMap(final color... colors) {
      final Map<Integer, Integer> map = new HashMap<Integer, Integer>();
    
      for (color c : colors) {
        final Integer count = map.get(c |= #000000); // c &= ~#000000
        map.put(c, count == null? 1 : count + 1);
      }
    
      return map;
    }
    
    static final <K extends Comparable<K>, V extends Comparable<V>>
      Map<K, V> sortMapByValues(final Map<K, V> map)
    {
      final int len = map.size(), capacity = ceil(len/.75) + 1;
      final Entry<K, V>[] arr = map.entrySet().toArray(new Entry[len]);
    
      Arrays.sort(arr, new Comparator<Entry<K, V>>() {
        @ Override public int compare(final Entry<K, V> e1, final Entry<K, V> e2) {
          final int sign = e2.getValue().compareTo(e1.getValue());
          return sign != 0? sign : e1.getKey().compareTo(e2.getKey());
        }
      });
    
      final Map<K, V> sortedMap = new LinkedHashMap<K, V>(capacity);
      for (final Entry<K, V> entry : arr)
        sortedMap.put(entry.getKey(), entry.getValue());
    
      return sortedMap;
    }
    
  • edited January 2016 Answer ✓

    As a bonus, an alternative version using List<Map.Entry<K, V>> in place of Map.Entry<K, V>[].
    And consequently, it's relying on Collections.sort() in place of Arrays.sort(). >-)

    Both K & V represent an Integer instance.
    K is the color key and V is the value for how many times it's shown up. :-bd

    This time it's based on this following article: http://JavaRevisited.BlogSpot.com.br/2012/12/how-to-sort-hashmap-java-by-key-and-value.html

    /**
     * Dominant Color Sort III (v1.03)
     * GoToLoop (2016-Jan-11)
     *
     * forum.Processing.org/two/discussion/14393/getting-the-dominant-color-of-an-image
     * JavaRevisited.BlogSpot.com/2012/12/how-to-sort-hashmap-java-by-key-and-value.html
     */
    
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.List;
    
    import java.util.Map;
    import static java.util.Map.Entry;
    import java.util.LinkedHashMap;
    
    static final int QTY = 5, VARIATION = 50;
    final color[] dominantArr = new color[QTY];
    final Map<Integer, Integer> dominantMap =
      new LinkedHashMap<Integer, Integer>(QTY, 1.0);
    
    Map<Integer, Integer> map, sortedMap;
    PImage pic;
    int len;
    
    void setup() {
      size(800, 600);
      noLoop();
    
      pic = createImage(width, height, ARGB);
      len = pic.pixels.length;
    }
    
    void draw() {
      background(randomPixels(pic, VARIATION));
    
      map = countColorsIntoMap(pic.pixels);
      sortedMap = sortMapByValues(map);
    
      dominantMap.clear();
      java.util.Arrays.fill(dominantArr, 0);
    
      println("\nUnique colors found:", map.size(), "\tfrom:", len, ENTER);
    
      int idx = 0;
      for (final Entry<Integer, Integer> colors : sortedMap.entrySet()) {
        dominantMap.put(dominantArr[idx] = colors.getKey(), colors.getValue());
        if (++idx == QTY)  break;
      }
    
      idx = 0;
      for (final Entry<Integer, Integer> colors : dominantMap.entrySet())
        println(idx++, "->", hex(colors.getKey(), 6), "\tcount:", colors.getValue());
    
      println();
      println(dominantMap, ENTER);
      println(dominantArr);
    }
    
    void mousePressed() {
      redraw = true;
    }
    
    PImage randomPixels(final PImage img, final int v) {
      final color p[] = img.pixels, d = 0400 - min(0400, abs(v));
    
      for (int i = 0; i != p.length; p[i++] = color(
        (color) random(d, 0400), 
        (color) random(d, 0400), 
        (color) random(d, 0400)));
    
      img.updatePixels();
      return img;
    }
    
    static final Map<Integer, Integer> countColorsIntoMap(final color... colors) {
      final Map<Integer, Integer> map = new HashMap<Integer, Integer>();
    
      for (color c : colors) {
        final Integer count = map.get(c &= ~#000000); // c |= #000000
        map.put(c, count == null? 1 : count + 1);
      }
    
      return map;
    }
    
    static final <K extends Comparable<K>, V extends Comparable<V>>
      Map<K, V> sortMapByValues(final Map<K, V> map)
    {
      final int len = map.size(), capacity = ceil(len/.75) + 1;
      final List<Entry<K, V>> entries = new ArrayList<Entry<K, V>>(map.entrySet());
    
      Collections.sort(entries, new Comparator<Entry<K, V>>() {
        @ Override public int compare(final Entry<K, V> e1, final Entry<K, V> e2) {
          final int sign = e2.getValue().compareTo(e1.getValue());
          return sign != 0? sign : e1.getKey().compareTo(e2.getKey());
        }
      });
    
      final Map<K, V> sortedMap = new LinkedHashMap<K, V>(capacity);
      for (final Entry<K, V> entry : entries)
        sortedMap.put(entry.getKey(), entry.getValue());
    
      return sortedMap;
    }
    
Sign In or Register to comment.