I am supposed to write an algorithm responsible for Color Quantization that uses the Popularity Algorithm method. However I couldn't find any good explanation of.
So the steps as I understand are as follows:
Getting K most frequently occurring colors
- To obtain them there is a need to create RGB histograms
- Then check colors at local maxima of the histograms
For each pixel find the closest most occurring color and replace its color with a new one
Are those steps correct?
If so then I am still in need. I have already created a method responsible for obtaining 3 histograms (R,G,B) and as far as I understand now it is time to get K most frequently occurring colors using them. If I was correct then it should be done by finding local maxima? So should I find them for each histogram independently and then collect all those values together, sort them and group them into a smaller group containing K colors?
At the moment I don't have idea how to find the closest most occurring color, but first I guess I need to success with previous steps.
Thank you.
UPDATE
Well, this my current version of the algorithm. It works. I have changed it so it uses unsafe code instead of SetPixel(...)/GetPixel(...). It does its job somehow faster now. One time it occurred that I got some type of memory error, but can't recall what exactly it was.
However it still takes some time to process pixels (it also depends on what image if want to quantize) when there are more K colors in the input. Do you see any way to make the code run faster? I have observed that the most consuming part is the last one, mainly changing the bitmap, as getting K most frequent colors works pretty fast.
class PopularityQuantization
{
public unsafe BitmapImage PopularityQuantizationApply(BitmapImage FilteredImage, int colorsNum)
{
Bitmap bitmap = ImageConverters.BitmapImage2Bitmap(FilteredImage);
Dictionary<Color, long> histogram = Histogram.GetHistogram(bitmap);
List<KeyValuePair<Color, long>> myList = histogram.ToList();
myList.Sort((pair1, pair2) => pair1.Value.CompareTo(pair2.Value));
List<Color> topColors = new List<Color>();
int i = myList.Count() - 1;
int counter = 0;
while (counter < colorsNum)
{
topColors.Add(myList[i].Key);
counter++;
i--;
}
#region lockbits
BitmapData bData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, bitmap.PixelFormat);
int bitsPerPixel = Bitmap.GetPixelFormatSize(bitmap.PixelFormat);
byte* scan0 = (byte*)bData.Scan0.ToPointer();
#endregion // lockbits
for (int x = 0; x < bitmap.Width; ++x)
for(int y = 0; y < bitmap.Height; ++y)
{
int r, g, b;
byte* data = scan0 + x * bData.Stride + y * bitsPerPixel / 8;
r = data[2];
g = data[1];
b = data[0];
Color currentColor = Color.FromArgb(r, g, b);
Color newColor = FindNearestNeighborColor(x, y, currentColor, topColors);
data[2] = newColor.R;
data[1] = newColor.G;
data[0] = newColor.B;
}
bitmap.UnlockBits(bData);
return ImageConverters.Bitmap2BitmapImage(bitmap);
}
private Color FindNearestNeighborColor(int x, int y, Color currentColor, List<Color> candidateColors)
{
int dMin = int.MaxValue;
Color nearestCandidate = currentColor;
foreach(Color candidateColor in candidateColors)
{
int distance = CalculateDistance(currentColor, candidateColor);
if (distance < dMin)
{
dMin = distance;
nearestCandidate = candidateColor;
}
}
return nearestCandidate;
}
private int CalculateDistance(Color currentColor, Color candidateColor)
{
int distR = candidateColor.R - currentColor.R;
int distG = candidateColor.G - currentColor.G;
int distB = candidateColor.B - currentColor.B;
return (int)(Math.Pow(distR, 2) + Math.Pow(distG, 2) + Math.Pow(distB, 2));
}
}