8

我希望能够检测 jpg 图像文件的 3-4 种主要颜色。

下面的示例图像和示例代码:

图片示例 1- 红、黑、白

图像示例 2- 白色、绿色、粉色

图像示例 3- 蓝色,黄色,黑色

我已经修改了一些代码以获得以下内容,但仍然无法对颜色进行分组。

    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

    }
  • example1.jpg 的输出是 (#FF6E8C, #FF6482, #FA6E8C) - 3 种红色/粉色阴影 - 应该是红色、黑色和白色
  • example2.jpg 的输出是 (#F0C8C8,#C8DC6E,#E6C8C8) - 2 种粉色和绿色 - 应该是浅粉色、绿色、白色
  • example3.jpg 的输出是 (#FFDC50,#640A28,#8C1E3C) - 3 种蓝色 - 应该是蓝色、黄色、黑色

理想情况下需要忽略背景颜色 (#FFFFFF) 和黑色轮廓/阴影。

可以在这里在线复制粘贴html颜色

4

4 回答 4

1

我已经完成了这样的任何颜色分析器应用程序,您可以在其中看到它是如何工作的。你完全在正确的轨道上。如果需要,将图像缩小到 128x128 等小尺寸以保持性能。

颜色分析仪

然后,您需要使用 C# 将 RGB 值转换为颜色角度

现在您需要创建任何颜色计数排序。例如,我完全建议将颜色角度从 360° = 360 个项目缩小为大约 36 个颜色项目。现在,您可以只拾取排序列表中颜色计数最多的第一个颜色。

于 2013-11-08T11:04:36.900 回答
1

非常感谢您的提示和这篇文章

这是解决方案:

  • GetWebColors() - 填充所有“命名”网络颜色的列表 ( http://www.w3schools.com/html/html_colornames.asp )

  • GetNamedWebColor_NearestMatch(Color) - 对于任何给定的颜色(有 16,777,216 个!)返回最接近的“命名”网络颜色(140 个中的 1 个)

  • GetMainXColors() - 返回前 X 多种颜色,这些颜色与传入的最小 HSL 属性不同(这将阻止 3 种相同颜色的阴影被传回)

调整:为此修改以下代码值 (即对于色调,我们希望颜色之间的最小变化为 10 度。)

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

现在输出

  • example1.jpg 的输出是:Gainsboro、Crimson、DarkSlateGray:现在接近白色、红色、黑色

  • example2.jpg 的输出是:Gainsboro、DarkKhaki、Pink:现在接近白色、绿色、粉色

  • example3.jpg 的输出为:Black、DarkSlateBlue、Gold:正确

代码:

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;
}
于 2013-11-08T14:30:21.013 回答
0

我认为您应该使用一些图像处理库,例如 Imagemagick 或 Opencv。这些库中有提取像素颜色的方法。您只需要将这些库链接到您的项目。我使用 imagemagick 从图像中提取主要颜色,但那是一个 cpp 应用程序。我认为 .net 接口也适用于 imagemagick

谢谢

于 2013-11-08T06:21:00.020 回答
0

我认为如果您不想使用某些库,则应该使用 HSL 而不是 RGB 进行计算http://en.wikipedia.org/wiki/HSL_and_HSV

于 2013-11-08T10:53:40.533 回答