87

我想找出可用于缩小光栅图片大小的最佳算法。最好我的意思是能给出最好看的结果的那个。我知道双三次,但还有更好的东西吗?例如,我从一些人那里听说,Adobe Lightroom 有某种专有算法,它比我使用的标准双三次产生更好的结果。不幸的是,我想在我的软件中自己使用这个算法,所以 Adob​​e 精心保护的商业机密是行不通的。

添加:

我检查了 Paint.NET,令我惊讶的是,在缩小图片尺寸时,超级采样似乎比双三次更好。这让我想知道插值算法是否完全可行。

它还让我想起了我自己“发明”但从未实现过的算法。我想它也有一个名字(因为这个微不足道的东西不可能是我一个人的想法),但我在流行的名字中找不到它。超级采样是最接近的。

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

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

这个算法有名字吗?

4

7 回答 7

82

不幸的是,我找不到原始调查的链接,但是随着好莱坞电影摄影师从胶片转向数字图像,这个问题出现了很多,所以有人(也许是 SMPTE,也许是 ASC)聚集了一群专业的电影摄影师并向他们展示了镜头已经使用一堆不同的算法重新调整。结果是,对于这些观看大型电影的专业人士来说,一致认为Mitchell(也称为高质量 Catmull-Rom)最适合放大,而sinc最适合缩小。但是 sinc 是一个理论上的滤波器,它会趋于无穷大,因此无法完全实现,所以我不知道“sinc”实际上是什么意思。它可能指的是 sinc 的截断版本。兰佐斯是 sinc 的几个实用变体之一,它试图改进只是截断它,并且可能是缩小静止图像的最佳默认选择。但像往常一样,这取决于图像和您想要的内容:例如,缩小线条图以保留线条是一种情况,您可能更喜欢强调保留边缘,而这在缩小花朵照片时会不受欢迎。

在剑桥的 Color中有一个很好的例子来说明各种算法的结果。

fxguide的人们汇总了很多关于缩放算法的信息(以及很多关于合成和其他图像处理的其他内容),值得一看。它们还包括可能对您自己的测试有用的测试图像。

现在 ImageMagick 有一个关于重采样过滤器的广泛指南,如果你真的想进入它。

具有讽刺意味的是,关于缩小图像存在更多争议,理论上这是可以完美完成的事情,因为你只是丢弃信息,而不是关于放大,你试图添加没有的信息'不存在。但从兰佐斯开始。

于 2011-05-30T02:49:09.513 回答
22

Lanczos 采样比双三次采样要慢,但会产生更高质量的图像。

于 2008-12-21T21:48:34.093 回答
16

(双)线性和(双)立方重采样不仅丑陋,而且在缩小小于 1/2 的因子时非常不正确。它们将导致非常糟糕的混叠,类似于您将得到的结果降低 1/2 倍然后使用最近邻下采样。

就我个人而言,我建议对大多数下采样任务进行(区域)平均样本。它非常简单、快速且接近最佳。高斯重采样(选择的半径与因子的倒数成正比,例如,半径 5 用于下采样 1/5)可能会以更多的计算开销提供更好的结果,并且在数学上更合理。

使用高斯重采样的一个可能原因是,与大多数其他算法不同,只要您选择适合重采样因子的半径,它对于上采样和下采样都可以正常工作(不会引入伪影/混叠)。否则,要支持两个方向,您需要两种单独的算法 - 用于下采样的区域平均(对于上采样将降级为最近邻),以及用于上采样的(双)立方(对于下采样将降级为最近邻)。从数学上看高斯重采样的这种良好特性的一种方法是,具有非常大半径的高斯近似于区域平均,而具有非常小的半径的高斯近似于(双)线性插值。

于 2010-10-24T04:23:27.867 回答
7

不久前我在 Slashdot 上看到一篇关于Seam Carving的文章,可能值得一看。

接缝雕刻是由 Shai Avidan 和 Ariel Shamir 开发的图像大小调整算法。该算法不是通过缩放或裁剪来改变图像的尺寸,而是通过智能地从图像中删除(或添加像素)不重要的像素来改变图像的尺寸。

于 2008-12-21T22:20:47.617 回答
4

您描述的算法称为线性插值,是最快的算法之一,但在图像上并不是最好的。

于 2009-09-18T17:55:15.603 回答
3

这个算法有名字吗?

它在文献中可能被称为“盒子”或“窗口”重采样。实际上,它的计算成本比您想象的要低。

它还可以用于创建中间位图,随后由双三次插值使用,以避免在下采样超过 1/2 时出现混叠。

于 2011-06-08T15:40:40.713 回答
0

如果有人感兴趣,这是我的面积平均缩放算法的 C++ 实现:

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;
            }
}
于 2020-03-08T04:01:20.597 回答