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


78

我想找出哪种算法最适合用于缩小光栅图片。最好,我的意思是给出最佳外观的产品。我知道双三次,但是还有更好的东西吗?例如,我从某些人那里听说Adobe Lightroom具有某种专有算法,该算法比我使用的标准双三次方产生更好的结果。不幸的是,我想在自己的软件中自己使用该算法,因此Adobe谨慎保护的商业机密将无法实现。

添加:

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

这也让我想起了自己“发明”但从未实现的算法。我想它也有一个名字(因为这个琐碎的东西不能仅凭我一个主意),但是我在流行的名字中找不到它。超级采样是最接近的一种。

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

这自然会占用大量计算资源,但应尽可能接近理想值,不是吗?

这个算法有名字吗?


1
您描述了超级采样是如何工作的。它不比三次立方更好,因为考虑到三次立方从源图像获取更多像素。
homm 2014年

1
我投票重新讨论这个非常老的问题,因为这是一个好问题。“看起来最好”听起来很主观,但是研究此内容的人对其进行了充分的量化,以得到良好的,非主观的和共识性的答案。
tom10

@ tom10-坦率地说,我认为Lanczos选项对于大多数用途已经足够好了。
Vilx-

Answers:


75

不幸的是,我找不到与原始调查的链接,但是随着好莱坞摄影师从电影到数字图像的转变,这个问题浮出水面,所以有人(也许是SMPTE,也许是ASC)聚集了一大批专业摄影师并向他们展示了镜头使用一系列不同的算法进行了重新缩放。结果是,对于这些观看大型电影的专业人士而言,共识是Mitchell(也称为高品质Catmull-Rom)是放大效果最好的,sinc是缩小效果最好的。但是,sinc是一个理论滤波器,会变为无穷大,因此无法完全实现,因此我不知道它们实际上对“ sinc”的含义。它可能是指Sinc的截短版本。兰佐斯是Sinc的几种实用变体之一,它试图在截断后进行改进,并且可能是缩小静态图像的最佳默认选择。但是,通常情况下,它取决于图像和所需内容:例如,缩小线条图以保留线条是一种情况,在这种情况下,您可能更希望保留在缩小花朵照片时不受欢迎的边缘。

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

在乡亲fxguide放在一起了很多资料上缩放算法,这是值得考虑看看(用约合成等图像处理很多其他的东西一起)。它们还包括测试图像,这些图像可能对您进行自己的测试有用。

现在,如果您真的想使用ImageMagick,则可以获取有关重新采样滤镜的详尽指南

具有讽刺意味的是,关于缩小图像还有更多争议,从理论上讲,这是可以完美实现的,因为您只丢掉信息,而不是在放大时尝试添加不符合要求的信息。不存在。但从Lanczos开始。


我想指出的是,sinc滤波器是可以实现的,而不会在有限范围内截断信号。如果我们假设在已知区域之外,所有样本均为零,那么Whittaker-Shannon插值公式中的多余项就会消失,并且得到有限的总和。这是对原始数据的有效解释,即使它可能是错误的(在我们的视野范围内世界也不是黑色的)。该滤镜仍然不能用于实时音频和视频,因为它不是因果关系,而是用于无关紧要的图像中。
蒂姆·塞吉恩

我参加聚会迟到了,但这是我的看法。只有一种适当的方法可以按比例缩小图像,并且这是两种方法的组合。1)按x2缩小,保持缩小直到下一次缩小将小于目标大小。在每次缩放时,每个新像素= 4个旧像素的平均值,因此这是保留的最大信息量。2)从最后的按比例缩小2步,使用BILINEAR插值将其缩小到目标大小。这很重要,因为双线性根本不会引起任何振铃。3)(加成)在线性空间中进行缩放(degamma-> scale down-> regamma)。
Alex

@Alex没有普遍适用的“按比例缩小”图像的方法,因为没有统一定义图像中“重要”的内容,应该相对于“不重要”保留并可以丢弃。您的算法可能对某些图像非常有用,但是它将使黑白线图变成浅灰色的模糊。
旧版Pro

是的,我在谈论照片,但我认为它也可以更好地处理线条图。当然,您知道不会响铃。像零 没有过滤器可以与此匹配。但是,是的,对于某些图像,最好做最近邻居或比通用算法更合适的其他事情。
Alex

@Alex:此外,您没有考虑诸如“ sinc”之类的算法具有的过滤效果。用数码相机拍摄的许多照片都会有噪点(分布相当均匀),尤其是在以高iso拍摄时。缩小图像时可以过滤。
待定


14

当缩小比例小于1/2时,(双)线性和(双)三次重采样不仅难看,而且非常不正确。它们将导致非常糟糕的混叠,类似于如果您向下缩水1/2倍,然后使用最近邻居向下取样,您将得到的混叠效果很差。

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

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


2
半径很重要。bicubic在缩小时经常失败的原因是未调整半径,而缩小时使用了与放大相同的半径。这根本行不通,并且在极端情况下会变得比最近的邻居更糟。如果适当地调整半径,它将比区域平均得到更好的结果。
Mark Ransom 2014年

3
三次过滤器将其限制为4个样本绝对没有任何内在条件,如果您将其加宽并除以权重之和,该公式就可以正常工作。实际上,Catmull-Rom与Lanczos-2类似,并且可以调整为几乎相同。
Mark Ransom 2014年

1
@MarkRansom:三次滤波器的定义是曲线多项式的近似值,三次多项式由曲线上的任意4个点唯一定义。
R .. GitHub停止帮助ICE 2014年

2
可能是这样,但是数学无关紧要。尝试一下,看看。
Mark Ransom 2014年

3
在研究了最佳外观的缩小尺寸方法之后,我还发现了面积法可以产生最佳效果。结果不令人满意的一种情况是将图像按比例缩小一小部分。在那种特殊情况下,区域方法通常会使图像模糊,但是最近的邻居可以执行出奇的效果。关于使用高斯缩减的有趣之处在于,它或多或少相当于先模糊处理图像,然后再使用最近的邻居进行缩减。
贾胡,2015年

7

不久前,我在Slashdot上看到了一篇有关缝缝的文章,可能值得研究。

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


我看过了 并非完全是我的初衷,但是研究它当然是一个好主意!谢谢!该算法在某个地方可以公开获得吗?
Vilx-

3
实际上,接缝雕刻是重新定位,而不是缩放。他们产生不同的结果。@Vilx:是的,这里有一个GIMP插件:liquidrescale.wikidot.com
Can BerkGüder08年


请注意,接缝雕刻重新定位算法已进入Photoshop 4,如果该算法有沉重的专利负担,我也不会感到惊讶。
拉瑟五世卡尔森

3
Seamcarving与Gimp的液体重新缩放和Photoshop CS4的内容感知缩放相同。它不是用于缩放,而是用于更改图像的宽高比,而不会使其看起来被拉伸。
mk12'2

4

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


除了OP会像子像素字体渲染一样考虑子像素的空间位置。这可能是获得一点分辨率的一种很酷的方法,但也可能导致奇怪的图像效果,并且还取决于给定的子像素体系结构。
亚当·托利

不,线性插值是一种卷积算法。描述为真正的超级采样。
homm 2014年

@AdamTolley我严重怀疑正常图像的亚像素AA看起来是否可以接受。它可与文本一起使用,因为只有两种颜色,甚至还有除白色以外的任何其他颜色都是一个问题
RecursiveExceptionException

1
@itzJanuary,我认为边界颜色与子像素方案匹配时会很好,但是这种情况只会在某些时候发生,从而导致最好的使用效果不一致,并且最坏的情况是像素采样的基本频率受到干扰,从而产生了奇特的感知伪影
Adam Tolley

2

这个算法有名字吗?

在文献中可能将其称为“框”或“窗口”重采样。您实际上认为它的计算成本更低。

它还可以用于创建中间位图,随后该位图将由双三次插值使用,以避免在降频采样率超过1/2时发生混叠。


-1

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

3
请在回答中添加一些解释,以便其他人可以从中学习
Nico Haase
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.