How can I scale/interpolate an image with indexed values smoothly?

Viewed 49

I am wanting to scale grayscale images (input masks, really) with discrete values up smoothly. The values in these images are indexes that represent arbitrary concepts (e.g. "terrain types"; they are usually indices into a table), rather than values on a continuous scale, so they can't be averaged or blended in any way.

Do there exist algorithms that can do this with a more pleasing result than nearest-neighbour, which results in a very blocky, pixelated result? I am looking for something that will at least produce more rounded, more fluid results. The kind of thing that would be ideal would be a whitepaper, or a library (preferably in Java).

I've researched the subject, but I can't find anything. There is plenty about linear or cubic interpolation, etc., but that won't work for indexed values. The only algorithm I ever see mentioned that does not try to average values is nearest-neighbour. But there must be more?

diagram of desired result

Using colour here for clarity. I do of course understand that the preferred result here is impossible; I'm not asking for something that reconstitutes destroyed information, just hoping for something that will at least guestimate something smoother than the first result.

3 Answers

Scan the destination image and for every corresponding source pixel (non-integer coordinates) check if the colors of the four surrounding pixels are the same. If yes, assign that color.

If not, perform as many bilinear interpolations as there are different colors. For this assign the weight 1 for a given color (each in turn) and 0 for the others, and interpolate the weight. Finally, keep the color with the largest weight.

By analytical geometry, one can show that in bilinear interpolation, the iso-weight curves are arcs of hyperbola. If your magnification is large, you will see them. G1 continuity is not guaranteed. If this is an annoyance, you can work with G1 bicubic interpolation instead.

If this still does not satisfy you, you can try smooth approximating surfaces rather than interpolating ones. But the principle of keeping the color of maximum weight remains.


If there aren't many distinct colors and you want to use ready-made functions, you can work this out as follows:

  • split the image in several binary images (white for a chosen color, black for background);

  • magnify all images (to grayscale) using the favorite method;

  • now implement yourself a function that assigns every pixel the color that has the largest value among the magnified images.


You can also apply a smoothing filter to the binary images before or after magnification.

For the sake of illustration, here is what you would get with two colors at a time (but this easily generalizes).

Color source image:

enter image description here

Smoothing applied to the binary equivalents:

enter image description here

enter image description here

Magnified:

enter image description here

enter image description here

Maximum weight decision:

enter image description here

I guess that smoothing the shape for each value individually is a way to avoid undesired mixed value.

To handle values individually, here, I started with your nearest-neighbour image v, and create 3 image { A.bmp, B.bmp, C.bmp } by hand. (each image has only 1 color region and background is black. e.g. A.bmp is below:)

enter image description here

After smoothing the shape for each image, draw these shapes to one result image buffer with different color.

//I use C++ and OpenCV
int main()
{
    const std::string FileNames[3] = { "A.bmp", "B.bmp", "C.bmp" };
    const cv::Scalar ResultShowColor[3] = { cv::Scalar(0,255,255), cv::Scalar(0,255,0), cv::Scalar(0,0,255) };
    cv::Mat Imgs[3];

    const int KernelSize = 15;
    for( int i=0; i<3; ++i )
    {
        Imgs[i] = cv::imread( FileNames[i], cv::IMREAD_GRAYSCALE );
        if( Imgs[i].empty() )return 0;

        cv::threshold( Imgs[i], Imgs[i], 32, 255, cv::THRESH_BINARY );
        cv::GaussianBlur( Imgs[i], Imgs[i], cv::Size(KernelSize,KernelSize), 0 );
        cv::threshold( Imgs[i], Imgs[i], 255*0.5, 255, cv::THRESH_BINARY );
        cv::imshow( FileNames[i], Imgs[i] );
    }

    cv::Mat ResultImg = cv::Mat::zeros( Imgs[0].size(), CV_8UC3 );
    for( int i=0; i<3; ++i )
    {
        ResultImg.setTo( ResultShowColor[i], Imgs[i] );
    }
    cv::imshow( "ResultImg", ResultImg );

    if( cv::waitKey() == 's' ){ cv::imwrite( "ResultImg.png", ResultImg );  }
    return 0;
}

This is result:

enter image description here

Yes, this result is not enough. Gaps exist at the boundaries of shapes. Therefore some ingenuity is required... but I post this because it might be some hint for you.

One thing you could try is to extract a polygon for the boundary of each uniformly-colored region, then upscale and draw the polygon in the output image. You won’t create neatly rounded edges, but you will avoid the stair-case effect of the nearest neighbor interpolation. Upscaling polygons should avoid gaps between the regions too.

Related