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

Posted

技术标签:

【中文标题】什么是最好的图像缩小算法(质量方面)?【英文标题】:What is the best image downscaling algorithm (quality-wise)? 【发布时间】:2010-09-27 22:51:02 【问题描述】:

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

添加:

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

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

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

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

这个算法有名字吗?

【问题讨论】:

您描述了超级采样的具体工作原理。它并不比双三次好,因为双三次从源图像中获取更多像素。 我投票重新提出这个非常古老的问题,因为它是一个很好的问题。 “看起来最好”听起来很主观,但研究这一点的人对其进行了充分量化,以获得良好的、非主观的和一致的答案。 @tom10 - 坦率地说,我认为 Lanczos 选项对于大多数用途来说已经足够了。 【参考方案1】:

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

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

fxguide 的人们将a lot of information 放在一起讨论缩放算法(以及许多其他关于合成和其他图像处理的内容),值得一看。它们还包括可能对您自己进行测试有用的测试图像。

现在 ImageMagick 有一个extensive guide on resampling filters,如果你真的想进入它的话。

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

【讨论】:

我想指出的是,sinc 滤波器可以在不截断有限范围的信号的情况下实现。如果我们假设在我们知道的区域之外,所有样本都为零,那么 Whittaker-Shannon 插值公式中的额外项就会消失,我们会得到一个有限和。这是对原始数据的有效解释,即使它可能不正确(在我们的视野之外,世界并不是黑色的)。这个过滤器仍然不能用于实时音频和视频,因为它不是因果关系,而是用于无关紧要的图像。 我迟到了,但这是我对此的看法。只有一种适当的方法可以缩小图像,它是两种方法的组合。 1)缩小x2,继续缩小,直到下一个缩小尺寸小于目标尺寸。在每次缩放时,每个新像素 = 4 个旧像素的平均值,所以这是保留的最大信息量。 2) 从最后一个按比例缩小 2 的步骤开始,使用 BILINEAR 插值缩小到目标大小。这很重要,因为双线性根本不会引起任何振铃。 3)(奖励)在线性空间中进行缩放(degamma->scale down->regamma)。 @Alex 没有普遍“正确”的方式来缩小图像,因为对于图像中的“重要”和应该保留的内容与“不重要”的内容没有统一的定义,并且可以被丢弃。您的算法可能适用于某些图像,但它会将黑白线条图变成浅灰色模糊。 好吧,我说的是照片,但我认为它也能更好地处理线条图。你肯定知道不会有响铃。比如,零。没有过滤器可以匹配这个。但是,是的,对于某些图像,最好使用最近邻或其他比通用算法更合适的方法。 @Alex:此外,您没有考虑诸如“sinc”之类的算法所具有的过滤效果。使用数码相机拍摄的许多照片都会有噪点(分布相当均匀),尤其是在以高 ISO 拍摄照片时。这可以在缩小图像时进行过滤。【参考方案2】:

Lanczos sampling 比双三次要慢,但可以生成更高质量的图像。

【讨论】:

.NET 中是否已经为此实现了?会节省我的时间。 :) @Vilx- github.com/dlemstra/Magick.NET 我已经使用过它,并且使用 (MagickImage image = new MagickImage(path)) image.FilterType = ImageMagick.FilterType.Lanczos;图像.调整大小(145,145); // 将图像保存为 tiff image.Write("c:/workbackup/jay_Lanczos.png"); 【参考方案3】:

(Bi-)linear 和 (bi-)cubic 重采样不仅丑陋,而且在缩小小于 1/2 的因子时非常不正确。它们将导致非常糟糕的混叠,类似于如果您将其缩减 1/2 倍然后使用最近邻缩减采样,则会得到类似的结果。

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

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

【讨论】:

半径很重要。双三次在缩小时经常失败的原因是没有调整半径,并且用于缩小尺寸的相同半径用于缩小尺寸。这根本行不通,并且在极端情况下会比最近的邻居更糟糕。如果半径调整得当,它应该比区域平均提供更好的结果。 立方过滤器绝对没有任何固有的东西将其限制为 4 个样本,如果您将其扩大并除以权重之和,该公式就可以正常工作。事实上 Catmull-Rom 与 Lanczos-2 类似,可以调整到几乎相同。 可能是这样,但数学并不在乎。有时间试试看。 在研究了最好看的缩小方法后,我还发现面积法可以产生最好的结果。结果不令人满意的一种情况是将图像缩小一个小倍数。在那种特殊情况下,区域方法通常会模糊图像,但最近邻法可以表现得非常好。使用高斯缩小的有趣之处在于,它或多或少相当于首先模糊图像,然后使用最近邻缩小图像。 将高斯模糊应用于彩色图像时可能会出错,因为 RGB 值是实际亮度强度的平方根。因此,如果一个人天真地/线性地模糊了 RGB 颜色,则混合颜色将不对应于我们的眼睛本能地感知为混合颜色的内容。一种解决方案是像这样应用模糊:sqrt(gauss(image^2)).【参考方案4】:

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

接缝雕刻是一种调整图像大小的方法 由 Shai Avidan 和 阿里尔·沙米尔。该算法改变 图像的尺寸不是 缩放或裁剪,而是通过 智能地从(或 将像素添加到)携带的图像 不重要。

【讨论】:

我见过这个。不完全是我的想法,但这肯定是一个好主意!谢谢!该算法是否在某处公开可用? 实际上,缝雕是重定向,而不是缩放。它们产生不同的结果。 @Vilx:是的,这里有一个 GIMP 插件:liquidrescale.wikidot.com 这是一个 dotNET 实现:blogs.msdn.com/mswanson/archive/2007/10/23/… 请注意,接缝雕刻重定向算法已进入 Photoshop 4,如果该算法有沉重的专利负担,我不会感到惊讶。 Seamcarving 与 Gimp 的液体缩放和 Photoshop CS4 的内容感知缩放的想法相同。它不是用于缩放,而是用于更改图像的纵横比而不使其看起来被拉伸。【参考方案5】:

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

【讨论】:

除了 OP 会像子像素字体渲染那样考虑子像素的空间位置。这可能是一种获得一点分辨率的非常酷的方法,但也可能导致奇怪的图像效果,并且还取决于给定的子像素架构。 不,线性插值是一种卷积算法。在真正的超级采样中描述。 @AdamTolley 我严重怀疑普通图像的亚像素 AA 看起来是否可以接受。它适用于文本,因为只有两种颜色,即使有任何其他颜色而不是白色也是一个问题 @itzJanuary 我认为当边界颜色与子像素方案匹配时会很好,但这只会发生在某些时候,导致最好的情况是不一致的有用性,最坏的情况是破坏像素采样的基本频率创建奇怪的感知伪影【参考方案6】:

这个算法有名字吗?

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

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

【讨论】:

【参考方案7】:

如果有人感兴趣,这是我的面积平均缩放算法的 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;
            

【讨论】:

请在您的答案中添加一些解释,以便其他人可以从中学习

以上是关于什么是最好的图像缩小算法(质量方面)?的主要内容,如果未能解决你的问题,请参考以下文章

基于opencv的纸张表面质量检测算法中

图片加载优化方案

缩小 32 位 RGB 图像的最快算法

SGD的动量(Momentum)算法

kineticjs (HTML5 Canvas) - 缩小时平滑图像

最佳语音压缩算法/格式