Best result for downscaling and color reduction

Hello
I’m working on a graphic editor for colorization of virtual pinball score displays. These are dot matrix displays like https://www.youtube.com/watch?v=4J52dEtfGBQ.
The resolution is 95% 128x32, 5% 192x64.
The author who are working with it would like me to add image import to the editor.
It would mean resizing a high res image to the res given above AND converting the image to a 64-color paletted image.
1/ What would be the best way to do it: resize THEN reduce the number of colors or vice versa? Or perhaps do it at once.
2/ Could anyone provide the code to perform that the best way, please?
Thank you very much
David

cv::resize to resize
try kmeans to reduce number of colors

I don’t understand. You want us to solve your problem and write the code?

The first question was about the order “resize THEN reduce the number of colors or vice versa? Or perhaps do it at once.”

You have to choose carrefully interpolation flag in resize function. It depends of your context

My context is exactly the one explained:

  • I have a high res RGB24 picture (let’s say 1920x1080 pix)
  • I want to convert it to a paletted n-colors (2<=n<=64) low res (128x32 mainly) image
    So I would like to know what would be the way for the best result, please.
    (and perhaps a little bit of help on the code as the kmeans function is like chinese to me)
    Thanks

For downscaling, I’ve read that Lanczos is the best and that resizing AFTER color reduction would be better.
Just a little help on this step (color reduction to n colors with kmeans) would be appreciated, please?

OK, I succeeded doing that:

    int h = image.rows;
    int w = image.cols;
    Mat samples(h * w, 3, CV_32F);
    int count = 0;

    for (int x = 0; x < h; x++) {
        for (int y = 0; y < w; y++) {
            samples.at<float>(count, 0) = image.at<Vec3b>(x, y)[0];
            samples.at<float>(count, 1) = image.at<Vec3b>(x, y)[1];
            samples.at<float>(count, 2) = image.at<Vec3b>(x, y)[2];
            count++;
        }
    }

    Mat labels, centers;
    kmeans(samples, ncolors, labels, TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 10000, 0.0001), 1, KMEANS_RANDOM_CENTERS, centers);
    centers.convertTo(centers, CV_8U);
    Mat res(image.size(), image.type());
    for (int i = 0; i < image.rows; ++i) {
        for (int j = 0; j < image.cols; ++j) {
            int label = labels.at<int>(i * image.cols + j);
            res.at<Vec3b>(i, j) = centers.row(label);
        }
    }

The number of colors is right and the image OK.
The problem is that, if I do the downscaling after using resize(res, ...,cv::INTER_LANCZOS4), the number of color rises again.
Is there a way to apply the lanczos downscaling keeping the same colors as the one in res, please?

Avoid loop over pixel in opencv it is time consumming. Try

    Mat srcColor;
    image.convertTo(srcColor, CV_32FC3);
    Mat samples = srcColor.reshape(0,image.cols*image.rows);

may be don’t apply lanczos4, prefer INTER_NEAREST
Why don’t you apply kmean after resize?

Thanks for your answers
I prefer to do the less operations possible in low res.

maybe apply the quantization AFTER resampling.

whoever told you to first quantize and then resample either thought you wanted a visually pleasing result (non-aliased edges) or they have no clue. that order of operations was guaranteed to reintroduce convex combinations of your quantized colors. only way to avoid that would be INTER_NEAREST, but…

I wouldn’t recommend INTER_NEAREST for downsampling. that will cause frequency aliasing, i.e. it can produce moire patterns.

INTER_AREA is a good choice, and so is Lanczos. if you need downsampling by a power of two, just use pyrDown.

if you have to handle a photo of a LED matrix display, INTER_NEAREST might be a good idea after all. if you do it right, the samples perfectly hit the center each LED… if you do it right. so, perhaps just go with INTER_AREA.

note that INTER_AREA only works with resize(). the “warp” functions don’t do that. if you need it, you can first warp to a comparable resolution (examine eigenvalues and vectors of the warp matrix to assess “comparable”), then downscale with pyrDown.

Thanks a lot for your answer.

So you think that doing it in the reverse order would not lead to something ugly?

It is not downsampling by a power of two. My editor lets you select a part of an image (so the cropped image could be 517 * 183… any dimension) and the number of colours you wish in its 64 palette. The program then must fit the selected image in a selected SUB part of the DMD, so it can even be less than 128 * 32, with that number of colors.

I just wanted to thank you for your help. So I did as you suggested to downscale then reduce the colors and the DMD colorization authors who made their first tests with it are really happy of the result:

2 Likes