0

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:

  1. 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
  2. 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));
    }
}
  • 1
    You need to create a 3D histogram, not 3 separate histograms. – Cris Luengo Jun 11 '20 at 14:41
  • @CrisLuengo What do you mean by 3D? Something like one list of Dictionary? –  Jun 11 '20 at 15:22
  • 1
    You could try a Dictionary that collects and counts all colors. – TaW Jun 11 '20 at 15:25
  • 1
    More like `hist = np.array((256,256,256))`. Then, for each pixel, `hist[r,g,b] += 1`. Usually it's enough to do 64x64x64 bins, indexing with `r//4` etc. – Cris Luengo Jun 11 '20 at 15:37
  • @CrisLuengo All right, got it. I guess now after I've created a histogram I should get K most frequently occurring colors, so in that case K elements in the long[,,] array that have the greatest value. The last step after that will be to replace pixels color with the closest neighboring one right? –  Jun 11 '20 at 15:54
  • 1
    Yes, indeed, that's it. I don't know exactly what your exercise requires, but you can either take the bins with the largest values, or first find local maxima, and take the local maxima with the largest values. Though there are more advanced techniques that produce better results, those are likely not the target of your exercise. – Cris Luengo Jun 11 '20 at 15:55
  • @CrisLuengo Well, I have observed that creating a histogram using Dictionary with Color as a key makes things easier as it can be sorted in an easier way. So there is the last thing to do now, do you have any advice how to find a new color for pixels using the K colors that I have collected? –  Jun 11 '20 at 16:36
  • 1
    I think a dictionary will be a lot more expensive, but it probably doesn't matter for your exercise. Once you have your reduced set of colors, simply compute a distance from a pixel's (r,g,b) value to each of the (r,g,b) values of the reduced set. The smallest distance wins! – Cris Luengo Jun 11 '20 at 17:16
  • @CrisLuengo All right, thank you. I've finished the algorithm, but now it's time for some optimization. The longest time is being spend on getting pixel/calc. distance/setting pixel process. I guess using unsafe code and lockbits might help a bit? –  Jun 12 '20 at 08:55

0 Answers0