Reputation: 4785
I am trying to find the dominant color of an image. I am using ColorMine to do the color comparison. Basically I have a template of colors that I compare each pixel against.
The color with the least distance gets elected from the template as the representative of that pixel.
public class ColorItem
{
public enum Colors
{
White ,
Black ,
Gray ,
Red ,
Orange ,
Yellow ,
Green ,
Cyan ,
Blue ,
Magenta,
Pink ,
Brown ,
None,
}
public ColorItem(Color color, Colors colorType)
{
this.color = color;
this.colorType = colorType;
}
public Colors colorType;
public Color color;
}
//The color template that I am comparing against, which I cannot help but to
//Think that this is the issue
public class ColorTemplate
{
public static Color white = Color.FromArgb(255,255,255);
public static Color black = Color.FromArgb(0, 0, 0);
public static Color gray = Color.FromArgb(150, 150, 150);
public static Color red = Color.FromArgb(255, 0, 0);
public static Color orange = Color.FromArgb(255, 150, 0);
public static Color yellow = Color.FromArgb(255, 255, 0);
public static Color green = Color.FromArgb(0, 255, 0);
public static Color cyan = Color.FromArgb(0, 255, 255);
public static Color blue = Color.FromArgb(0, 0, 255);
public static Color magenta = Color.FromArgb(255, 0, 255);
public static Color pink = Color.FromArgb(255, 150, 255);
public static Color brown = Color.FromArgb(150, 90, 25);
}
private static List<ColorItem> _template = new List<ColorItem>
{
new ColorItem(ColorTemplate.black, ColorItem.Colors.Black),
new ColorItem(ColorTemplate.blue, ColorItem.Colors.Blue),
new ColorItem(ColorTemplate.brown, ColorItem.Colors.Brown),
new ColorItem(ColorTemplate.cyan, ColorItem.Colors.Cyan),
new ColorItem(ColorTemplate.gray, ColorItem.Colors.Gray),
new ColorItem(ColorTemplate.green, ColorItem.Colors.Green),
new ColorItem(ColorTemplate.magenta, ColorItem.Colors.Magenta),
new ColorItem(ColorTemplate.orange, ColorItem.Colors.Orange),
new ColorItem(ColorTemplate.pink, ColorItem.Colors.Pink),
new ColorItem(ColorTemplate.red, ColorItem.Colors.Red),
new ColorItem(ColorTemplate.white, ColorItem.Colors.White),
new ColorItem(ColorTemplate.yellow, ColorItem.Colors.Yellow)
};
public bool GetDominantColor(string filePath, out List<ColorPercentages> domColor)
{
domColor = new List<ColorPercentages>();
Bitmap bmp = null;
try
{
bmp = new Bitmap(filePath);
}
catch (Exception)
{
}
if (bmp == null)
return false;
//Used for tally
var total = 0;
var countWhite = 0;
var countBlack = 0;
var countGray = 0;
var countRed = 0;
var countOrange = 0;
var countYellow = 0;
var countGreen = 0;
var countCyan = 0;
var countBlue = 0;
var countMagenta = 0;
var countPink = 0;
var countBrown = 0;
for (var x = 0; x < bmp.Width; x++)
{
for (int y = 0; y < bmp.Height; y++)
{
total++;
var clr = bmp.GetPixel(x, y);
var near = FindNearestColor(clr);
switch (near)
{
case ColorItem.Colors.Black:
countBlack++;
break;
case ColorItem.Colors.Blue:
countBlue++;
break;
case ColorItem.Colors.Brown:
countBrown++;
break;
case ColorItem.Colors.Cyan:
countCyan++;
break;
case ColorItem.Colors.Gray:
countGray++;
break;
case ColorItem.Colors.Green:
countGreen++;
break;
case ColorItem.Colors.Magenta:
countMagenta++;
break;
case ColorItem.Colors.Orange:
countOrange++;
break;
case ColorItem.Colors.Pink:
countPink++;
break;
case ColorItem.Colors.Red:
countRed++;
break;
case ColorItem.Colors.White:
countWhite++;
break;
case ColorItem.Colors.Yellow:
countYellow++;
break;
}
}
}
domColor.Add(new ColorPercentages((int)(((double)countWhite / (double)total) * 100), ColorItem.Colors.White));
domColor.Add(new ColorPercentages((int)(((double)countBlack / (double)total) * 100), ColorItem.Colors.Black));
domColor.Add(new ColorPercentages((int)(((double)countGray / (double)total) * 100), ColorItem.Colors.Gray));
domColor.Add(new ColorPercentages((int)(((double)countRed / (double)total) * 100), ColorItem.Colors.Red));
domColor.Add(new ColorPercentages((int)(((double)countOrange / (double)total) * 100), ColorItem.Colors.Orange));
domColor.Add(new ColorPercentages((int)(((double)countYellow / (double)total) * 100), ColorItem.Colors.Yellow));
domColor.Add(new ColorPercentages((int)(((double)countGreen / (double)total) * 100), ColorItem.Colors.Green));
domColor.Add(new ColorPercentages((int)(((double)countCyan / (double)total) * 100), ColorItem.Colors.Cyan));
domColor.Add(new ColorPercentages((int)(((double)countBlue / (double)total) * 100), ColorItem.Colors.Blue));
domColor.Add(new ColorPercentages((int)(((double)countMagenta / (double)total) * 100), ColorItem.Colors.Magenta));
domColor.Add(new ColorPercentages((int)(((double)countPink / (double)total) * 100), ColorItem.Colors.Pink));
domColor.Add(new ColorPercentages((int)(((double)countBrown / (double)total) * 100), ColorItem.Colors.Brown));
domColor.Sort(new SortColorPercentagesDescending());
return true;
}
private ColorItem.Colors FindNearestColor(Color input)
{
ColorItem.Colors nearest_color = ColorItem.Colors.None;
var distance = 255.0;
Rgb inColoRgb = new Rgb {R = input.R, G = input.G, B = input.B};
Lab inColorLab = inColoRgb.To<Lab>();
foreach (var colorItem in _template)
{
Rgb templateColorRgb = new Rgb {R = colorItem.color.R, G = colorItem.color.G, B = colorItem.color.B};
Lab templateColorLab = templateColorRgb.To<Lab>();
var target = new CieDe2000Comparison();
var tempRes = inColoRgb.Compare(templateColorRgb, target);
if (tempRes == 0.0)
{
nearest_color = colorItem.colorType;
break;
}
else if (tempRes < distance)
{
distance = tempRes;
nearest_color = colorItem.colorType;
}
}
return nearest_color;
}
public class SortColorPercentagesDescending : Comparer<ColorPercentages>
{
public override int Compare(ColorPercentages x, ColorPercentages y)
{
if (x == null || y == null)
return -1;
if (x.Percentage > y.Percentage)
return -1;
if (x.Percentage < y.Percentage)
return 1;
return 0;
}
}
public class ColorPercentages
{
public int Percentage;
public ColorItem.Colors Color;
public ColorPercentages(int percentage, ColorItem.Colors color)
{
Percentage = percentage;
Color = color;
}
}
The two main functions here are GetDominantColor
and FindNearestColor
. The first loads an image file in a bitmap and iterates over each pixel, finds the nearest color and increments that color's count. Once it goes over all the pixels it calculate the occurrence percentage of each color and returns them in a list to the caller sorted by the colors' percentages.
The FindNearestColor
function compares each pixel to a hardcoded template using ColorMine's CieDe2000Comparison implementation and returns the closest template color as the pixel's color.
Now, I cannot help but think that the issue is with the template of colors I am using. I am not sure how to adjust those to give accurate results. I tried halving the RGB values and the results got somewhat better yet not to the point where it is accurate. For example, Yellow images returns the dominant color as White. Dark Green Images returns the dominant color as Black and so on.
How can I make this work
EDIT: I am not looking to find the average color of the image. What I am looking for is a way to categorize/map the color of each pixel to a specific pre-defined color and then find the most repeated color.
Upvotes: 2
Views: 9936
Reputation: 91
I had to do something similar. As input colors I used all values in System.Drawing.KnownColor, but you can change that part pretty easily.
The code below gets the color most often used in an image and then tries to match it a known color.
class Program
{
static void Main(string[] args)
{
var image = (Bitmap)Image.FromFile(@"C:\temp\colorimage3.bmp");
var mostUsedColor = GetMostUsedColor(image);
var color = GetNearestColor(mostUsedColor);
Console.WriteLine(color.Name);
Console.ReadKey();
}
private static Color GetNearestColor(Color inputColor)
{
var inputRed = Convert.ToDouble(inputColor.R);
var inputGreen = Convert.ToDouble(inputColor.G);
var inputBlue = Convert.ToDouble(inputColor.B);
var colors = new List<Color>();
foreach (var knownColor in Enum.GetValues(typeof(KnownColor)))
{
var color = Color.FromKnownColor((KnownColor) knownColor);
if (!color.IsSystemColor)
colors.Add(color);
}
var nearestColor = Color.Empty;
var distance = 500.0;
foreach (var color in colors)
{
// Compute Euclidean distance between the two colors
var testRed = Math.Pow(Convert.ToDouble(color.R) - inputRed, 2.0);
var testGreen = Math.Pow(Convert.ToDouble(color.G) - inputGreen, 2.0);
var testBlue = Math.Pow(Convert.ToDouble(color.B) - inputBlue, 2.0);
var tempDistance = Math.Sqrt(testBlue + testGreen + testRed);
if (tempDistance == 0.0)
return color;
if (tempDistance < distance)
{
distance = tempDistance;
nearestColor = color;
}
}
return nearestColor;
}
public static Color GetMostUsedColor(Bitmap bitMap)
{
var colorIncidence = new Dictionary<int, int>();
for (var x = 0; x < bitMap.Size.Width; x++)
for (var y = 0; y < bitMap.Size.Height; y++)
{
var pixelColor = bitMap.GetPixel(x, y).ToArgb();
if (colorIncidence.Keys.Contains(pixelColor))
colorIncidence[pixelColor]++;
else
colorIncidence.Add(pixelColor, 1);
}
return Color.FromArgb(colorIncidence.OrderByDescending(x => x.Value).ToDictionary(x => x.Key, x => x.Value).First().Key);
}
}
Upvotes: 2
Reputation: 12310
Its difficult to interpret what 'dominant color' really means in your question without a more precise definition or knowing what the application is.
The Image Color Summarizer web service examples show that a straight pixel average is pretty good. You can try some of the images you're working with on that site to get some idea of whether average/median stats are enough for your purpose.
This answer suggests using k-means quantization which looks pretty good too.
Upvotes: 1
Reputation: 2519
It looks to me like your starting values for ColourTemplate
is a little off what you expect them to be.
One solution would be to categorise all a whole range of colours (try googling "CIE colour"), and seeing where you agree and disagree with your programs categorisation.
Pseudo code:
Image CategoriseImage (Image input) {
Image result = new Image(input.height, image.width);
foreach (pixel in input) {
colour c = FindNearestColour(pixel);
result.SetColour(c, pixel.x, pixel.y);
}
return result;
}
Then you can save the image or display it. It might involve a little trial-and-error, but you should be able to move the seed values in ColourTemplate
around until you are happy with what they categorise each colour as.
Upvotes: 1