Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

For an jpg image file get 3-4 average main colors

I would like to be able to detect the 3-4 main colors of a jpg image file.

Example images and sample code below:

image example 1 - red, black, white

image example 2 - white, green, pink

image example 3 - blue, yellow, black

I have modified some code to get the below, but still has trouble grouping colors.

    public static int RoundColorToGroup(int i)
    {
        int r = ((int)Math.Round(i / 10.0)) * 10;
        if (r > 255)
            r = 255;
        return r;
    }

    [TestMethod]
    public void AverageColorTest_WebExample()
    {          
        Bitmap bm = new Bitmap("C:\\Users\\XXXX\\Desktop\\example1.jpg");

        int width           = bm.Width;
        int height          = bm.Height;
        int red             = 0;
        int green           = 0;
        int blue            = 0;
        int minDiversion    = 15; // drop pixels that do not differ by at least minDiversion between color values (white, gray or black)
        int dropped         = 0; // keep track of dropped pixels                

        int bppModifier     = bm.PixelFormat == System.Drawing.Imaging.PixelFormat.Format24bppRgb ? 3 : 4; // cutting corners, will fail on anything else but 32 and 24 bit images
        BitmapData srcData  = bm.LockBits(new System.Drawing.Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadOnly, bm.PixelFormat);
        int stride          = srcData.Stride;
        IntPtr Scan0        = srcData.Scan0;

        Dictionary<string, Int64> dicColors = new Dictionary<string, long>(); // color, pixelcount i.e ('#FFFFFF',100);

        unsafe
        {
            byte* p = (byte*)(void*)Scan0;

            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    int idx = (y * stride) + x * bppModifier;
                    red     = p[idx + 2];
                    green   = p[idx + 1];
                    blue    = p[idx];

                    red     = RoundColorToGroup(red);
                    green   = RoundColorToGroup(green);
                    blue    = RoundColorToGroup(blue);

                    if (Math.Abs(red - green) > minDiversion || Math.Abs(red - blue) > minDiversion || Math.Abs(green - blue) > minDiversion)
                    {
                        string htmlColorGroup = ColorTranslator.ToHtml(Color.FromArgb(red, green, blue));

                        if (dicColors.ContainsKey(htmlColorGroup))
                        {
                            dicColors[htmlColorGroup]++;
                        }
                        else
                        {
                            dicColors.Add(htmlColorGroup, 1);
                        }
                    }
                    else
                    {
                        dropped++;
                    }
                }
            }
        }

        dicColors = dicColors.OrderByDescending(x => x.Value).ToDictionary(pair => pair.Key, pair => pair.Value);

        Console.WriteLine(dicColors.ElementAt(0).Key); // should ouput main color 1
        Console.WriteLine(dicColors.ElementAt(1).Key); // should ouput main color 2
        Console.WriteLine(dicColors.ElementAt(2).Key); // should ouput main color 3

    }
  • output for example1.jpg is (#FF6E8C, #FF6482, #FA6E8C) - 3 shades of red/pink - should be red, black and white
  • output for example2.jpg is (#F0C8C8,#C8DC6E,#E6C8C8) - 2 shades of pinkand green - should be light pink, green, white
  • output for example3.jpg is (#FFDC50,#640A28,#8C1E3C) - 3 shades of blue - should be blue, yellow, black

Ideally needs to ignore background color (#FFFFFF) and black outlining/shading.

Can copy paste html colors online here

like image 234
Dere_2929 Avatar asked Nov 06 '13 09:11

Dere_2929


2 Answers

I've done such any Color Analyzer App where you can see how it works. You are fully on the right track. Scale down the image if needed to small size like 128x128 to keep performance.

Color Analyzer

Then, you need to convert the RGB value into Color Angle using C#.

Now you need to create any sorting of Color Count. I fully recommend to scale down the color angles from 360° = 360 items into about 36 color items only for example. Now you can just pickup the first color in sorted list with most color counts.

like image 180
Nasenbaer Avatar answered Nov 11 '22 03:11

Nasenbaer


Many thanks to your hints, and this article.

Here is solution:

  • GetWebColors() - Populates list of all 'Named' Web Colors (http://www.w3schools.com/html/html_colornames.asp)

  • GetNamedWebColor_NearestMatch(Color) - For any given color (there are 16,777,216!) returns nearest 'Named' web color (1 of 140)

  • GetMainXColors() - Returns first X many colors that differ by min HSL properties passed in (this will stop 3 shades of same color being passed back)

Tuning: ammend below code values for this (i.e for hue, we want 10 degree min change between colors.)

    float minHueDiff        = (float)10;  // 0 to 360
    float minBrightDiff     = (float)0.1; // 0 to 1
    float minSatDiff        = (float)0.1; // 0 to 1

Now Outputs

  • output for example1.jpg is: Gainsboro, Crimson, DarkSlateGray : now close to white, red, black

  • output for example2.jpg is: Gainsboro, DarkKhaki, Pink : now close to white, green, pink

  • output for example3.jpg is: Black, DarkSlateBlue, Gold : correct

Code:

public void AverageColorTest_WebExample_FineTuned()
{                
    Bitmap bm = new Bitmap("C:\\Users\\XXX\\Desktop\\example1.jpg");

    int width = bm.Width;
    int height = bm.Height;
    int red = 0;
    int green = 0;
    int blue = 0;
    float minDiversion = 30 / 100; // drop pixels that do not differ by at least minDiversion between color values (white, gray or black)
    int dropped = 0; // keep track of dropped pixels                

    int bppModifier = bm.PixelFormat == System.Drawing.Imaging.PixelFormat.Format24bppRgb ? 3 : 4; // cutting corners, will fail on anything else but 32 and 24 bit images
    BitmapData srcData = bm.LockBits(new System.Drawing.Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadOnly, bm.PixelFormat);
    int stride = srcData.Stride;
    IntPtr Scan0 = srcData.Scan0;

    Dictionary<Color, Int64> dicColors = new Dictionary<Color, long>(); // color, pixelcount i.e ('#FFFFFF',100);

    unsafe
    {
        byte* p = (byte*)(void*)Scan0;

        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                int idx = (y * stride) + x * bppModifier;
                red = p[idx + 2];
                green = p[idx + 1];
                blue = p[idx];

                if (red == 255 && green == 255 && blue == 255)
                continue;

                Color GroupedColor = GetNamedWebColor_NearestMatch(red, green, blue);

                if (dicColors.ContainsKey(GroupedColor))
                {
                    dicColors[GroupedColor]++;
                }
                else
                {
                    dicColors.Add(GroupedColor, 1);
                }
            }
        }
    }

    // sort dictionary of colors so that most used is at top
    dicColors = dicColors.OrderByDescending(x => x.Value).ToDictionary(pair => pair.Key, pair => pair.Value);

    List<Color> MainColors  = null;
    Int16 numberOf          = 3;
    float minHueDiff        = (float)10;
    float minBrightDiff     = (float)0.1;
    float minSatDiff        = (float)0.1;

    MainColors = GetMainXColors(dicColors.Keys.ToList(), numberOf, minHueDiff, minBrightDiff, minSatDiff);

    foreach (Color MainColor in MainColors)
    {
        Console.WriteLine(ColorTranslator.ToHtml(MainColor)); // should ouput main colors
    }

}   

/// <summary>
/// returns first x many colors that differ by min HSL properties passed in
/// </summary>
/// <param name="listIn"></param>
/// <param name="ReturnMaxNumberOfColors"></param>
/// <param name="minHueDiff"></param>
/// <param name="minBrightDiff"></param>
/// <param name="minSatDiff"></param>
/// <returns></returns>
private static List<Color> GetMainXColors(List<Color> listIn, Int32 ReturnMaxNumberOfColors, float minHueDiff, float minBrightDiff, float minSatDiff)
{
    List<Color> response = new List<Color>();

    Int32 i = 0;
    while (response.Count < ReturnMaxNumberOfColors && i < listIn.Count)
    {
        bool  blnUniqueMainColor = true; // want main colors ie dark brown, gold, silver, not 3 shades of brown
        Color nextColor          = listIn[i];

        float brightness    = nextColor.GetBrightness();
        float sat           = nextColor.GetSaturation();
        float hue           = nextColor.GetHue();

        for (Int32 j = 0; j < response.Count; j++)
        {

            float brightnessOther   = response[j].GetBrightness();
            float satOther          = response[j].GetSaturation();
            float hueOther          = response[j].GetHue();

            // hue is 360 degrees of color, to calculate hue difference                        
            // need to subtract 360 when either are out by 180 (i.e red is at 0 and 359, diff should be 1 etc)
            if (hue - hueOther > 180) hue -= 360;
            if (hueOther - hue > 180) hueOther -= 360;

            float brightdiff        = Math.Abs(brightness - brightnessOther);
            float satdiff           = Math.Abs(sat - satOther);
            float huediff           = Math.Abs(hue - hueOther);
            int matchHSL            = 0;

            if (brightdiff <= minBrightDiff)
                matchHSL++;

            if (satdiff <= minSatDiff)
                matchHSL++;

            if (huediff <= minHueDiff) 
                matchHSL++;

            if (matchHSL != 0  & satdiff != 1))
            {
                blnUniqueMainColor = false;
                break;
            }
        }
        if (blnUniqueMainColor)
        {       // color differs by min ammount of HSL so add to response
            response.Add(nextColor);
        }
        i++;
    }
    return response;
}   

private static List<Color> WebColors;
/// <summary>
/// Returns the "nearest" color from a given "color space"
/// </summary>
/// <param name="input_color">The color to be approximated</param>
/// <returns>The nearest color</returns>        
public static Color GetNamedWebColor_NearestMatch(double dbl_input_red, double dbl_input_green, double dbl_input_blue)
{
    // get the colorspace as an ArrayList
    if (WebColors == null) 
        WebColors = GetWebColors();
    // the Euclidean distance to be computed
    // set this to an arbitrary number
    // must be greater than the largest possible distance (appr. 441.7)
    double distance = 500.0;
    // store the interim result
    double temp;
    // RGB-Values of test colors
    double dbl_test_red;
    double dbl_test_green;
    double dbl_test_blue;
    // initialize the result
    Color nearest_color = Color.Empty;
    foreach (Color o in WebColors)
    {
        // compute the Euclidean distance between the two colors
        // note, that the alpha-component is not used in this example                
        dbl_test_red    = Math.Pow(Convert.ToDouble(((Color)o).R) - dbl_input_red, 2.0);
        dbl_test_green  = Math.Pow(Convert.ToDouble(((Color)o).G) - dbl_input_green, 2.0);
        dbl_test_blue   = Math.Pow(Convert.ToDouble(((Color)o).B) - dbl_input_blue, 2.0);
        temp            = Math.Sqrt(dbl_test_blue + dbl_test_green + dbl_test_red);
        // explore the result and store the nearest color
        if (temp < distance)
        {
            distance = temp;
            nearest_color = (Color)o;
        }
    }            
    return nearest_color;
}

/// <summary>
/// Returns an ArrayList filled with "WebColors"
/// </summary>
/// <returns>WebColors</returns>
/// <remarks></remarks>
private static List<Color> GetWebColors()
{
    List<string> listIgnore = new List<string>();
    listIgnore.Add("transparent");

    Type color = (typeof(Color));
    PropertyInfo[] propertyInfos = color.GetProperties(BindingFlags.Public | BindingFlags.Static);
    List<Color> colors = new List<Color>();

    foreach (PropertyInfo pi in propertyInfos)
    {
        if (pi.PropertyType.Equals(typeof(Color)))
        {
            Color c = (Color)pi.GetValue((object)(typeof(Color)), null);
            if (listIgnore.Contains(c.Name.ToLower()))
            continue;
            colors.Add(c);
        }
    }
    return colors;
}
like image 24
Dere_2929 Avatar answered Nov 11 '22 05:11

Dere_2929