什么是最好的图像缩放算法(质量方面) ?

我想找出哪种算法是最好的,可以用来缩小光栅图片。最好的我指的是那个给出最好看的结果。我知道双立方体,但还有更好的吗?例如,我从一些人那里听说 Adobe Lightroom 有某种专有算法,它比我使用的标准双立方体算法产生更好的结果。不幸的是,我想自己在软件中使用这个算法,所以 Adobe 精心保护的商业秘密不会起作用。

补充说明:

我查看了 Paint.NET,令我惊讶的是 Super Sampling 在缩小图片尺寸时似乎比 two ubic 更好。这让我怀疑插值算法到底是不是正确的方法。

它还让我想起了一个算法,这个算法是我自己“发明”的,但从未实现过。我想它也有一个名字(因为这种琐碎的事情不可能只是我一个人的想法) ,但是我无法在流行的名字中找到它。超级抽样是最接近的一个。

这个想法是这样的——对于目标图片中的每个像素,计算它在源图片中的位置。它可能会覆盖一个或多个其他像素。这样就可以计算出这些像素的面积和颜色。然后,为了得到目标像素的颜色,只需计算这些颜色的平均值,并将它们的面积作为“权重”。因此,如果目标像素覆盖黄色源像素的1/3,绿色源像素的1/4,我得到(1/3 * 黄色 + 1/4 * 绿色)/(1/3 + 1/4)。

这自然是计算密集型的,但它应该尽可能接近理想状态,不是吗?

这个算法有名字吗?

115027 次浏览

There is Lanczos sampling which is slower than bicubic, but produces higher quality images.

I saw an article on Slashdot about Seam Carving a while ago, it might be worth looking into.

Seam carving is an image resizing algorithm developed by Shai Avidan and Ariel Shamir. This algorithm alters the dimensions of an image not by scaling or cropping, but rather by intelligently removing pixels from (or adding pixels to) the image that carry little importance.

The algorithm you describe is called linear interpolation, and is one of the fastest algorithms, but isn't the best on images.

(Bi-)linear and (bi-)cubic resampling are not just ugly but horribly incorrect when downscaling by a factor smaller than 1/2. They will result in very bad aliasing akin to what you'd get if you downscampled by a factor of 1/2 then used nearest-neighbor downsampling.

Personally I would recommend (area-)averaging samples for most downsampling tasks. It's very simple and fast and near-optimal. Gaussian resampling (with radius chosen proportional to the reciprocal of the factor, e.g. radius 5 for downsampling by 1/5) may give better results with a bit more computational overhead, and it's more mathematically sound.

One possible reason to use gaussian resampling is that, unlike most other algorithms, it works correctly (does not introduce artifacts/aliasing) for both upsampling and downsampling, as long as you choose a radius appropriate to the resampling factor. Otherwise to support both directions you need two separate algorithms - area averaging for downsampling (which would degrade to nearest-neighbor for upsampling), and something like (bi-)cubic for upsampling (which would degrade to nearest-neighbor for downsampling). One way of seeing this nice property of gaussian resampling mathematically is that gaussian with very large radius approximates area-averaging, and gaussian with very small radius approximates (bi-)linear interpolation.

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.

Is there a name for this algorithm?

It might be referred as "box" or "window" resampling in literature. It is actually less computational expensive as you think.

It can also be used to create a intermediate bitmap that is subsequently used by bi-cubic interpolation to avoid aliasing when downsampled by more than 1/2.

If anyone's interested, here is my C++ implementation of area averaging scaling algorithm:

void area_averaging_image_scale(uint32_t *dst, int dst_width, int dst_height, const uint32_t *src, int src_width, int src_height)
{
// 1. Scale horizontally (src -> mid)
int mid_width  = dst_width,
mid_height = src_height;
float src_width_div_by_mid_width = float(src_width) / mid_width;
float mid_width_div_by_src_width = 1.f / src_width_div_by_mid_width;
std::vector<uint32_t> mid(mid_width * mid_height);
for (int y=0; y<mid_height; y++)
for (int x=0; x<mid_width; x++)
for (int c=0; c<4; c++) {
float f = x * src_width_div_by_mid_width;
int i = int(f);
float d = ((uint8_t*)&src[i + y*src_width])[c] * (float(i) + 1 - f);
float end = f + src_width_div_by_mid_width;
int endi = int(end);
if (end - float(endi) > 1e-4f) {
assert(endi < src_width);
d += ((uint8_t*)&src[endi + y*src_width])[c] * (end - float(endi));
}
for (i++; i < endi; i++)
d += ((uint8_t*)&src[i + y*src_width])[c];
int r = int(d * mid_width_div_by_src_width + 0.5f);
assert(r <= 255);
((uint8_t*)&mid[x + y*mid_width])[c] = r;
}


// 2. Scale vertically (mid -> dst)
float mid_height_div_by_dst_height = float(mid_height) / dst_height;
float dst_height_div_by_mid_height = 1.f / mid_height_div_by_dst_height;
for (int y=0; y<dst_height; y++)
for (int x=0; x<dst_width; x++)
for (int c=0; c<4; c++) {
float f = y * mid_height_div_by_dst_height;
int i = int(f);
float d = ((uint8_t*)&mid[x + i*mid_width])[c] * (float(i) + 1 - f);
float end = f + mid_height_div_by_dst_height;
int endi = int(end);
if (end - float(endi) > 1e-4f) {
assert(endi < mid_height);
d += ((uint8_t*)&mid[x + endi*mid_width])[c] * (end - float(endi));
}
for (i++; i < endi; i++)
d += ((uint8_t*)&mid[x + i*mid_width])[c];
int r = int(d * dst_height_div_by_mid_height + 0.5f);
assert(r <= 255);
((uint8_t*)&dst[x + y*dst_width])[c] = r;
}
}