Getting the most common color of an image
Asked Answered
S

7

10

I'd like to get the most common color from an image. I use Java and I want to have the most predominant color. Are there any cbir java library to make this?

Thanks

Slingshot answered 13/12, 2010 at 9:4 Comment(2)
Does this answer your question? How can i find dominant color of an image?Selfabasement
Also see How can I get the average color of an image?Selfabasement
B
10

How accurate do you want this to be? You can use Bozhos's approach and loop over the entire image but this could be slow for large images. There are 16777216 possible RGB values and keeping counters for them in a Map is not very efficient.

An alternative is to resample the image using getScaledInstance to scale it down to a smaller version e.g. a 1x1 image and then use getRGB to get the colour of that pixel. You can experiment with different resampling algorithms such as SCALE_REPLICATE and SCALE_AREA_AVERAGING to see what works best for you.

Bookbindery answered 13/12, 2010 at 9:33 Comment(3)
Note that this approach would give a different result than Bozhos' approach. In the latter, the color that appears most frequently in the image is determined while your approach tries to find something like "the average color" - an ill-defined notion, but it's clear that the returned color may not even appear anywhere in the original image. I'm not saying one approach is better than the other, I guess the original poster has to clarify what s/he's looking for.Encrinite
Yes, I understand that, hence why I asked how much accuracy was required. If you use the ReplicateScaleFilter you will get a colour which appears in the original image because it "omits rows and columns of pixels to scale down". It doesn't do any "blending" like the AreaAveragingScaleFilter.Bookbindery
Where did you get the number 16581375 from? If we're talking about 8 bits per channel, there are 2^24 = 16777216 possible RGB values.Sporocarp
A
4

Thanks for the answers. Here is a practical example of Bozho's method. It also filters out white/grays/black.

import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;


public class ImageTester {


    public static void main(String args[]) throws Exception {
        File file = new File("C:\\Users\\Andrew\\Desktop\\myImage.gif");
        ImageInputStream is = ImageIO.createImageInputStream(file);
        Iterator iter = ImageIO.getImageReaders(is);

        if (!iter.hasNext())
        {
            System.out.println("Cannot load the specified file "+ file);
            System.exit(1);
        }
        ImageReader imageReader = (ImageReader)iter.next();
        imageReader.setInput(is);

        BufferedImage image = imageReader.read(0);

        int height = image.getHeight();
        int width = image.getWidth();

        Map m = new HashMap();
        for(int i=0; i < width ; i++)
        {
            for(int j=0; j < height ; j++)
            {
                int rgb = image.getRGB(i, j);
                int[] rgbArr = getRGBArr(rgb);                
                // Filter out grays....                
                if (!isGray(rgbArr)) {                
                        Integer counter = (Integer) m.get(rgb);   
                        if (counter == null)
                            counter = 0;
                        counter++;                                
                        m.put(rgb, counter);                
                }                
            }
        }        
        String colourHex = getMostCommonColour(m);
        System.out.println(colourHex);
    }


    public static String getMostCommonColour(Map map) {
        List list = new LinkedList(map.entrySet());
        Collections.sort(list, new Comparator() {
              public int compare(Object o1, Object o2) {
                return ((Comparable) ((Map.Entry) (o1)).getValue())
                  .compareTo(((Map.Entry) (o2)).getValue());
              }
        });    
        Map.Entry me = (Map.Entry )list.get(list.size()-1);
        int[] rgb= getRGBArr((Integer)me.getKey());
        return Integer.toHexString(rgb[0])+" "+Integer.toHexString(rgb[1])+" "+Integer.toHexString(rgb[2]);        
    }    

    public static int[] getRGBArr(int pixel) {
        int alpha = (pixel >> 24) & 0xff;
        int red = (pixel >> 16) & 0xff;
        int green = (pixel >> 8) & 0xff;
        int blue = (pixel) & 0xff;
        return new int[]{red,green,blue};

  }
    public static boolean isGray(int[] rgbArr) {
        int rgDiff = rgbArr[0] - rgbArr[1];
        int rbDiff = rgbArr[0] - rgbArr[2];
        // Filter out black, white and grays...... (tolerance within 10 pixels)
        int tolerance = 10;
        if (rgDiff > tolerance || rgDiff < -tolerance) 
            if (rbDiff > tolerance || rbDiff < -tolerance) { 
                return false;
            }                 
        return true;
    }
}
Albright answered 6/6, 2011 at 9:25 Comment(2)
looks like an expensive way of doing by iterating over each pixel. It will easily die for a 5MP photoSzabadka
If the image is massive, resize it first.Albright
D
3

What if you consider your image as a big linear array of pixels, and after that all what you have to do is just sort it? When you have it sorted, you can count the longest part of same values.

Dibromide answered 13/12, 2010 at 10:16 Comment(0)
P
3

Depending on how exact you need the color value to be, you might want to consider "color buckets" collecting similar colors to avoid memory issues. This would mean to partition the color space into "intervals" of colors, where all colors which are similar (i.e. close together) enough are counted as the same color. By changing the interval size you have a means of directly manipulating the trade-off between accuracy and memory consumption.


Edit: What you want is basically a histogram (go look that up). There are most probably well established standard solutions for efficiently calculating one of those.

Paly answered 13/12, 2010 at 10:17 Comment(1)
Yes, just count each color (pretty easy to use the color value as the index for an array of integers) that you can increment quickly. The array will be smaller than the image being analyzed and incrementing an integer is pretty cheap in most (all?) programming languages.Duomo
P
2

You can loop the BufferedImage (two loops - one from 0 to width, and one from 0 to height), and get the call getRgb(x, y). Then count each different value. You can use a Map for that (key = color, value = number of occurences).

Porterporterage answered 13/12, 2010 at 9:11 Comment(2)
Really? There could be up to 16,581,375 colours.Bookbindery
Yes. Might just be my PC, but I got an OOM after about 4 million colours in my map.Bookbindery
D
1

I would calculate the hue of each pixel and then the cardinality of each hue (creates a histogram). Perhaps weighting by saturation. Then, apply a low-pass filter, and find the maximum. Finally convert from hue back to RGB.

This assumes that if you had just the red plane of an image, you'd want the result to be "red", not some shade of pink.

Disk answered 13/12, 2010 at 14:56 Comment(0)
V
0

Andrew Dyster code is working fine, Quick response in android

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import android.graphics.Bitmap;

public class ImageTester {

    public interface ImageColor {
        void onImageColor(int r, int g, int b);
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static void getMostCommonColour(final Bitmap image,
            final ImageColor heColor) {
        new Thread(new Runnable() {
            private int rgb;

            @Override
            public void run() {
                int height = image.getHeight();
                int width = image.getWidth();
                Map m = new HashMap();
                int boderWid = width / 4;
                int borderHeigh = height / 4;

                for (int i = boderWid; i < width - boderWid;) {
                    for (int j = borderHeigh; j < height - borderHeigh;) {
                        try {
                            rgb = image.getPixel(i, j);

                        } catch (Exception e) {
                            continue;
                        }finally{
                            i += 20;
                            j += 20;
                        }
                        int[] rgbArr = getRGBArr(rgb);
                        // Filter out grays....
                        if (!isGray(rgbArr)) {
                            Integer counter = (Integer) m.get(rgb);
                            if (counter == null)
                                counter = 0;
                            counter++;
                            m.put(rgb, counter);

                        }

                    }
                }
                List list = new LinkedList(m.entrySet());
                Collections.sort(list, new Comparator() {
                    public int compare(Object o1, Object o2) {
                        return ((Comparable) ((Map.Entry) (o1)).getValue())
                                .compareTo(((Map.Entry) (o2)).getValue());
                    }
                });
                Map.Entry me = (Map.Entry) list.get(list.size() - 1);
                int[] rgb = getRGBArr((Integer) me.getKey());
                heColor.onImageColor(rgb[0], rgb[1], rgb[2]);

            }
        }).start();
    }

    public static int[] getRGBArr(int pixel) {
        int red = (pixel >> 16) & 0xff;
        int green = (pixel >> 8) & 0xff;
        int blue = (pixel) & 0xff;
        return new int[] { red, green, blue };

    }

    public static boolean isGray(int[] rgbArr) {
        int rgDiff = rgbArr[0] - rgbArr[1];
        int rbDiff = rgbArr[0] - rgbArr[2];
        int tolerance = 10;
        if (rgDiff > tolerance || rgDiff < -tolerance)
            if (rbDiff > tolerance || rbDiff < -tolerance) {
                return false;
            }
        return true;
    }
}
Venation answered 17/6, 2014 at 14:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.