How to Downsample Images Correctly

How to downsample images correctly?

ok, i've found a nice alternative, which i think should work for any kind of bitmap decoding.

not only that, but it also allows you to downscale using any sample size you wish, and not just the power of 2 . if you put more effort to it, you can even use fractions instead of integers for the downscaling.

the code below works for images from the res folder, but it can easily be done for any kind of bitmap decoding:

private Bitmap downscaleBitmapUsingDensities(final int sampleSize,final int imageResId)
{
final Options bitmapOptions=new Options();
bitmapOptions.inDensity=sampleSize;
bitmapOptions.inTargetDensity=1;
final Bitmap scaledBitmap=BitmapFactory.decodeResource(getResources(),imageResId,bitmapOptions);
scaledBitmap.setDensity(Bitmap.DENSITY_NONE);
return scaledBitmap;
}

i've tested it and it shows the downsampled images just fine. in the image below, i've shown the original image, and downscaling the image using teh inSampleSize method, and using my method.

it's hard to see the difference, but the one that uses the density actually doesn't just skip pixels, but uses all of them to take into account. it might be a bit slower, but it's more precise and uses a nicer interpolation.

Sample Image

the only disadvantage compared to using the inSampleSize seems to be speed, which is better on inSampleSize because inSampleSize skips pixels and because the densities method does extra calculations on the skipped pixels.

However, i think that somehow android runs both methods in about the same speed.

I think the 2 methods comparison is similar to the comparison between the nearest-neighbor downsampling and the bilinear-interpolation downsampling.

EDIT: i've found one downside of the method i've shown here, compared to the one Google has . the memory used during the process can be quite high, and i think it depends on the image itself. this means you should use it only on cases you think make sense.


EDIT: i've made a merged solution (both google's solution and mine) for those who wish to overcome the memory problem. it's not perfect, but it's better than what i did before, because it won't use as much memory as the original bitmap needs during the downsampling. instead, it will use the memory as used in google's solution.

here's the code:

    // as much as possible, use google's way to downsample:
bitmapOptions.inSampleSize = 1;
bitmapOptions.inDensity = 1;
bitmapOptions.inTargetDensity = 1;
while (bitmapOptions.inSampleSize * 2 <= inSampleSize)
bitmapOptions.inSampleSize *= 2;

// if google's way to downsample isn't enough, do some more :
if (bitmapOptions.inSampleSize != inSampleSize)
{
// downsample by bitmapOptions.inSampleSize/originalSampleSize .
bitmapOptions.inTargetDensity = bitmapOptions.inSampleSize;
bitmapOptions.inDensity = inSampleSize;
}
else if(sampleSize==1)
{
bitmapOptions.inTargetDensity=preferHeight ? reqHeight : reqWidth;
bitmapOptions.inDensity=preferHeight ? height : width;
}

so, in short, the pros and cons of both methods:

Google's way (using inSampleSize) uses less memory during decoding, and is faster.
However, it causes some graphical artifacts sometimes and it only supports downsampling to the power of 2, so the result bitmap might take more than what you wanted (for example size of x1/4 instead of x1/7) .

My way (using densities) is more precise, gives higher quality images, and uses less memory on the result bitmap.
However, it can use a lot of memory during the decoding (depends on the input) and it's a bit slower.


EDIT: another improvement, as I've found that on some cases the output image doesn't match the required size restriction, and you don't wish to downsample too much using Google's way :

    final int newWidth = width / bitmapOptions.inSampleSize, newHeight = height / bitmapOptions.inSampleSize;
if (newWidth > reqWidth || newHeight > reqHeight) {
if (newWidth * reqHeight > newHeight * reqWidth) {
// prefer width, as the width ratio is larger
bitmapOptions.inTargetDensity = reqWidth;
bitmapOptions.inDensity = newWidth;
} else {
// prefer height
bitmapOptions.inTargetDensity = reqHeight;
bitmapOptions.inDensity = newHeight;
}
}

So, for example, downsampling from 2448x3264 image to 1200x1200, it will become 900x1200

What is the best image downscaling algorithm (quality-wise)?

Unfortunately, I cannot find a link to the original survey, but as Hollywood cinematographers moved from film to digital images, this question came up a lot, so someone (maybe SMPTE, maybe the ASC) gathered a bunch of professional cinematographers and showed them footage that had been rescaled using a bunch of different algorithms. The results were that for these pros looking at huge motion pictures, the consensus was that Mitchell (also known as a high-quality Catmull-Rom) is the best for scaling up and sinc is the best for scaling down. But sinc is a theoretical filter that goes off to infinity and thus cannot be completely implemented, so I don't know what they actually meant by 'sinc'. It probably refers to a truncated version of sinc. Lanczos is one of several practical variants of sinc that tries to improve on just truncating it and is probably the best default choice for scaling down still images. But as usual, it depends on the image and what you want: shrinking a line drawing to preserve lines is, for example, a case where you might prefer an emphasis on preserving edges that would be unwelcome when shrinking a photo of flowers.

There is a good example of the results of various algorithms at Cambridge in Color.

The folks at fxguide put together a lot of information on scaling algorithms (along with a lot of other stuff about compositing and other image processing) which is worth taking a look at. They also include test images that may be useful in doing your own tests.

Now ImageMagick has an extensive guide on resampling filters if you really want to get into it.

It is kind of ironic that there is more controversy about scaling down an image, which is theoretically something that can be done perfectly since you are only throwing away information, than there is about scaling up, where you are trying to add information that doesn't exist. But start with Lanczos.

Image downsampling algorithms

Skipping pixels will result in aliasing, where high frequency changes (such as alternating light/dark bands) will convert to low frequencies (such as constant light or dark).

The quickest way to downsize to half without aliasing is to average 2x2 pixels into a single pixel. Better results can be had with more sophisticated reduction kernels, but they will come at the expense of speed.

Here are some examples of the techniques discussed so far.

Skipping every other pixel - you can see that the results aren't very good by looking at the legend on the left side. It's almost unreadable:

Skipping every other pixel

Averaging every 2x2 grid - The text is now sharp and readable:

Average 2x2

Gaussian blur, as suggested by R. - a little blurrier, but more readable up to a point. The amount of blur can be adjusted to give different results:

Gaussian blur followed by pixel selection

R. is also correct about the Gamma curve affecting the results, but this should only be visible in the most demanding applications. My examples were done without gamma correction.

Edit: And here is an example of a more sophisticated but slow kernel, a Lanczos-5 performed in a linear (not gamma-adjusted) color space.

Lanczos-5 in linear space

The contrast in the lettering is lower, probably because of the conversion of color spaces. But look at the coastline detail.

Downsampling of fMRI image with FSL

If I understand correctly then you have images whose spatial extent are (a) 9 x 6 x 1.2 cm^3 and (b) 1.6 x 0.7 x 0.6 cm^3? Those are quite small and quite different. I can imagine that if image 2 covers a very specific sub-region of image 1 (because it is much smaller) you may need to give a good starting estimate to get a correct result.

if input_image has the dimensions (b) and good_size_image has the dimensions (a) then with the call

flirt -in input_image 
-ref good_size_image
-out output_image
-applyxfm

(init option is not strictly necessary in this case),

your output will be the image showing the much smaller space in much larger voxels. I assume the output image would have the size of the reference image (that is the idea of the reference image) but most of it would be empty. Switching (a) and (b) would not make sense because the space of the reference image can then only cover a tiny part of the input. You would need to register first.

You may want to experiment with the option applyisoxfm which resamples an image to cubic voxels of a given size.

flirt -in small_image -ref small_image -out small_1mm -applyisoxfm 1

will resample the image that covers the space (b) to 1x1x1 mm^3. Again, that is probably to coarse a resolution for so small a space. maybe resample both images to 0.5 mm isotropic and then register?

For these problems the FSL mailing list is a much better
place to find help.

Downsample an Image

There's not much wrong in your code -- basically just keep proper track of the read/write pointer locations (remember to update with strides). This requires using 2 nested loops one way or another. (+ fix the divider to 4).

I've found the following approach useful: processing one row at a time has not much speed penalty, but allows easier integration of various kernels.

iptr=input_image;  in_stride = in_width;
optr=output_image; out_stride = out_width;
for (j=0;j<out_height;j++) {
process_row(iptr, optr, in_width); // in_stride is needed
// as the function requires access to iptr+in_stride
iptr+=in_stride * 2;
optr+=out_stride;
}


Related Topics



Leave a reply



Submit