我使用双线性纹理过滤已经有二十多年了,距离我撰写双线性重采样已经有几个月了,但是距离发现我的一个与之相关的错误只有两天了。 😅同样,就在上周,一位同事要求在CPU上非常快速地实现双线性,这引起了一系列问题“哪种双线性?”。
因此,我认为这是另一篇简短的博客文章的机会–关于双线性过滤,但涉及下采样/上采样。我们将在这里介绍GPU半像素偏移,对齐像素网格,Tensorflow中的错误/混淆,对双线性操作期间发生的情况进行更深入的信号处理分析,以及对著名的“魔术核”的魔术性进行分析。
我强烈推荐我的上一篇文章作为该主题的入门,因为我将从那里开始使用一些工具和术语,但这并不是严格要求的。我们走吧!
经常使用术语双线性上采样和下采样,但这是什么意思?
我想在这篇文章中传达的几个想法之一是,双线性上采样/下采样在此术语的使用上没有单一含义或共识。对于一直使用的面包和黄油类型的图像处理操作来说,这是令人惊讶的!
即使是图像处理专业人员,也很难做到这一点,而且长期存在bug和顶级库的混乱(我知道一些由Tensorflow不一致引起的实际生产bug)!
编辑:一篇博客文章标题为“ Tensorflow的tf.image.resize如何偷走了我60天的生命”,并描述了同样的问题。我知道我的一些同事花了几个月的时间在Tensorflow 2中修复它-想像一下如何修复不正确的用法并“修复”已经受过此错误训练的模型。
它的某些部分(如相移)是如此棘手,以至于著名的“魔术内核”博客文章每隔几年出现一次,专家们重新阅读了几次以弄清楚到底发生了什么,而作者只是重新发现了双线性! (重要说明:我不想选择作者,因为他是一个非常聪明和博学的人,并且乐于分享见解总是值得尊重的。“魔术内核”只是为什么它如此很难谈论“双线性”。我也尊重他如何多次修改和改进该职位。但是没有“魔术内核”。)
因此,让我们看一下问题所在。在这里,我将仅专注于2倍向上/向下采样,并希望在此建议和使用的某些思想框架对您有益,也有助于您研究和分析不同的(和非整数因素)。
由于双线性的可分离性,当再次应用于一维信号时,我将再次滥用该表示法并称其为“双线性”滤波器,通常,我的很多分析将在一维中进行。
让我们从最简单的解释开始,而不是一成不变:创建一个分辨率更高的图像,其中每个样本都是通过对分辨率较低的图像进行双线性过滤而创建的。
对于双线性下采样,情况会变得有些混乱。它使用双线性滤波器来防止在抽取输入图像时出现信号混叠–嗯,很多技术术语。我会回头再说,但首先要解决第一个常见的困惑。
当对图像进行2倍降采样时,我们经常互换使用术语盒滤波器和双线性滤波器。两者都是正确的。怎么会这样?
我们可以看到2抽头盒滤波器与2抽头双线性滤波器相同。原因是在这种情况下,两个滤镜都位于像素之间的中心。离散化它们之后(在采样点评估滤波器权重),没有区别,因为我们不再知道生成它们的公式是什么,以及滤波器内核在评估点之外的外观。
进行双线性下采样的最典型方法与盒下采样相同。交替使用这两个名称进行2倍下采样都是正确的! (旁注:当进行大约2倍的下采样时,情况会发散。对于另一篇博客文章,这可能是个不错的话题。)对于1D信号,这意味着将每两个元素平均在一起,对于2D图像,将4个元素平均产生一个。
您可能已经注意到了我隐含的假设-像素中心偏移了半个像素,并且边缘/角对齐。
显然,这也是一个线性帐篷,它不会移动像素中心。所得的滤波器权重[0.25 0.5 0.25]也称为[1 2 1]滤波器,或者是二项式滤波器的最简单情况,非常接近高斯滤波器。 (要了解原因,请查看当试算次数达到无穷大时二项式分布会发生什么!)。这可能是我在工作中使用最多的过滤器,但我离题了。 🙂
为什么第二种方法没有使用太多?这是设计使然,也是GPU坐标/采样器中半像素的像素移动的原因,您可能已经注意到了问题–高分辨率阵列的最后一个像素被丢弃。但是,让我们不要超越自己,首先,我们可以看一下与上采样的关系。
如果要设计双线性上采样算法,则有几种解决方法。
让我从一个可能存在问题的“天真”开始。我们可以获取每个原始像素,并在它们之间放置其他像素的平均值。
是双线性/帐篷吗?是的,它是对零插入图像的帐篷过滤器(稍后会详细介绍)。它具有不寻常的特性;一些像素变得模糊,一些像素保持“清晰”(原始复制)。
但更重要的是,如果您如上所述进行框/双线性下采样,然后对图像进行上采样,则图像会发生偏移:
或更确切地说,它无法校正下采样所造成的半个像素偏移。
但是,使用第二种方法进行下采样也可以。第二种方法是对每个输出像素进行插值;全部插值:
进行双线性上采样的另一种方法最初可能最初并不直观:每个像素是一个像素的0.75,另一个像素的0.25,交替“向左”和“向右”。当您将纹理上采样2倍时,GPU正是这样做的:
对于那些“替代”权重,有两种简单的解释。第一个最简单的方法就是查看此方案中的“帐篷”:
我将看看这个过滤器的第二种解释–伪装成[️0.125 0.375 0.375 0.125],但首先,我想是时候提出主要声明/声明了:我们需要谨慎在讨论不同分辨率的图像时使用相同的参考坐标系。
您的上采样操作应该知道什么是下采样操作,以及它们如何定义像素网格偏移,反之亦然!
内部化的重要一件事是信号滤波器可以具有奇数或偶数个采样。如果我们有偶数个样本,那么这种滤波器就没有“中心”,因此必须将整个信号沿任一方向移动半个像素。相比之下,对称奇数滤波器可以移动特定频率,但不能移动整个信号:
如果您知道信号处理,那么它们就是I型和II型线性相位滤波器。
这是为什么重要的视觉演示。使用不同序列处理的柯达数据集图像,首先从框下采样开始:
如果我的帖子中只有一课,我希望是这样:上面的双线性上/下采样中的两个“取材”都可以是有效和正确的,您只需要选择一个适合您的用途即可:案例和整个代码/框架/库中使用的约定;始终对下采样和上采样使用一致的坐标约定。当您看到“双线性”一词时,请务必仔细检查其含义!因此,我实际上很想重新实现它们,并确保我保持一致…
也就是说,我认为对于普通用例而言,“盒式”双线性下采样和“替代权重”是更好的选择。第一个原因可能是主观的/次要的(因为双线性下采样/上采样本质上是低质量的,所以我不建议在质量远不只是简单性/性能时使用它)。如果我们目视检查升采样操作,则可以在奇/奇组合中看到更多的剩余混叠(仅看对角线边缘):
第二个原因,IMO更重要的一个原因是它们对齐图像的容易程度。这就是GPU采样具有“臭名昭著”的半像素偏移的原因。
好的,所以我最喜欢的部分开始了–半像素偏移!痛苦,沮丧,误解的根源,也是表示纹理和像素坐标的一种超级合理而强大的方法。如果您是最近才开始进行图形编程(DX10 +时代)的人,或者您不是图形程序员,那么这对您来说并不重要。但基本上,使用较旧的图形API时,帧缓冲区坐标没有texel偏移的一半,而纹理采样器却希望如此,因此您必须手动添加它。有时人们将其添加到顶点着色器中,有时将其添加到像素着色器中,有时在CPU上设置制服。这几乎是每天都有无尽错误的来源,尤其是在通过多种平台/ API发行的视频游戏中!
它们可以是[0,1,2,3]。但是GPU使用半像素偏移的惯例,因此它们最终为[0.5、1.5、2.5、3.5]。这将转换为UV或“归一化”坐标[0.5 / 4、1.5 / 4、2.5 / 4、3.5 / 4],其范围为[0.5 / width,1 – 0.5 / width]。
起初,这种表示方式似乎违反直觉,但是它为我们提供了一个保证和约定,即图像角位于[0和1]归一化,或[0,宽度]未归一化。
当半个像素对齐像素角时,向下/上采样的另一种方式来自对齐图像中的第一个像素中心。
现在,让我们看一下如何在半像素移位惯例中计算双线性上采样权重:
该约定使权重从何处变得非常简单明了,而且一旦对齐网格角,计算就变得多么简单。即使在GPU着色器领域之外的API中,我也亲自使用它-一切都变得更加容易。如果添加和删除0.5会增加性能成本,则可以在微优化阶段将其删除,但通常没关系。
对于处理不同分辨率图像的任何图像处理代码,GPU约定中针对像素和纹理像素使用的像素中心的半个像素偏移是一个合理的默认值。
在处理不同分辨率的纹理(例如,2幂的非幂的mip贴图)时,这特别重要。 9纹素而不是4的纹理?没问题:
它确保网格对齐,并且上/下采样操作“正常工作”。要获得盒/双线性下采样,您只需对源纹理进行一次双线性抽头,与上采样相同。
使用它是如此简单,以至于当您开始图形编程时,您很少考虑它。这是一把双刃剑–既很适合初学者入门,也很容易引起混淆,一旦您开始深入了解它并分析正在发生的事情或进行小数或最近邻降采样(例如创建非采样)。 -可插入的深度图金字塔…)。
即使没有其他原因,这也是为什么我建议在讨论双线性时将移相箱下采样和[0.25 0.75] / [0.75 0.25]上采样器作为默认设置的原因。
优点:将texel坐标偏移0.5意味着如果要获取整数坐标(例如texelFetch指令),则无需四舍五入。地板/截断(在某些设置下可以更便宜的操作)为您提供最接近的像素整数坐标以进行索引!
注意:Tensorflow弄错了。 “ align_corners”参数对齐…角落像素的中心???这是一个非常糟糕的命名加上设计选择,将[0.0 1.0]乘以2会产生[0,1/3,2/3,1],这是完全出乎意料的,并且与任何一种约定都不同我在这里描述了。
我喜欢写关于信号处理和在频域中分析信号的文章,所以让我在这里解释如何在EE /信号处理框架中对双线性上/下采样建模。
如果您从未听说过这种查看方式(尤其是零插入),则很可能是因为实际上没有人(至少在图形或图像处理中)实现这种方式,这样做非常浪费。这样的顺序。 🙂
零插入是一个有趣的,违反直觉的操作。您在每个元素之间插入零(通常将原始元素乘以2x以保留信号中的恒定/平均能量;或者我们可以在以后的滤波器中将此乘积折叠),再获得2x的样本,但它们并不是非常“有用” 。您的图像主要由“孔”组成...
从该图可以立即看到零插入时有很多高频不存在!所有这些零都会由于原始信号和零之间的交替和“振荡”而产生大量高频。被“扩张”并在系数之间为零的滤波器(如a-trous /扩张卷积)被称为梳状滤波器-因为它们类似于梳齿!
让我们从光谱分析来看一下。零插入会复制频谱:
原始信号的每个频率都是重复的,但是我们知道在较小分辨率的图像中不存在这样的频率。奈奎斯特上方不可能代表任何东西!为了解决这个问题,我们需要在此操作之后使用低通滤波器将其过滤掉:
我故意显示了一些余下的频率内容,因为通常很难做到“完美”的低通滤波(如果我们想要的话,例如振铃问题,这也值得怀疑)。
这是经过逐步滤波的一维信号的样子,注意到高频和“梳”消失了:
这是2D图片上的模糊/过滤动画,以及如何使这张零插入的图片变得越来越像正确地进行过采样处理:
看起来很像图像融合,但它只是融合滤镜-imo非常酷。 😎
显然,模糊(或技术上为低通)滤波器的选择非常重要。一些有趣的联系:如果将这个零插入信号与对称[0.5,0.5](如果在插入零时不将信号乘以2的话,则为1,1)进行卷积该怎么办?
有趣的是,我们有点“改造”了最近的邻居过滤器!一秒钟之后,这应该很直观。零样本会从单个非零邻居获得贡献,就像一个副本,而非零样本会被两个零包围,并且它们不会对其产生影响。
我们可以在频谱/傅立叶图上看到最近的相邻硬边和后混叠来自哪里(图的红色部分):
最近的邻居上采样也会移动信号(因为它是采样数的偶数),并且可以很好地撤消盒下采样滤波器,这符合复制采样的通常直觉,即盒滤波的“反向”并且不会引起移位问题。
让我们看一下如何在此框架中表示“保留一个样本,在两个样本之间进行插值”的策略。
这等效于使用[0.25 0.5 0.25]过滤器过滤零过采样的图像。
问题在于,在这种设置中,如果我们将权重乘以2(以使平均信号保持相同),然后再乘以零(其中信号为零),则会得到交替的[0.0 1.0 0.0]和[0.5 0.0 0.5]滤波器,具有非常不同的频率响应和方差减小...我将在这里再次参考我以前的博客文章,但是基本上您会交替获得原始信号方差的1.0和0.5(有效权重平方的总和)。
权重为[0.25 0.75]的第二种方法可以简单地看做:最近邻居上采样–过滤[0.5 0.5],然后过滤[0.25 0.5 0.25]!
两个卷积的序列在零插入图像上为我们提供了[0.125 0.375 0.375 0.125]的有效核,因此,如果将其乘以2,则只需交替交替[0.25 0.0 0.75 0.0]和[0.0 0.75 0.0 0.25]。角对齐的双线性上采样(GPU上的标准双线性上采样)与“魔术核”完全相同! also这也是我答应的第二个更复杂的双线性0.25 0.75权重的解释。
这样做的优点是,在交替像素上的有效权重为[0.25 0.75]和[0.75 0.25](忽略零),它们具有相同的过滤量和0.625的方差减小–非常重要!
两者都不是完美的,但是偶数通常会减少您的“问题”。
相比之下,进行过一些计算机图形或图像处理并了解这种情况下别名的读者应该对下采样过程更为熟悉。
下采样包括两个相反的步骤:1.过滤信号。 2.通过丢弃其他所有样本来抽取信号。
排序和第1步很重要,因为第二步,抽取等效于(重新)采样。如果我们不对新分辨率所能代表的频率之上的信号频谱进行滤波,那么我们将最终产生混叠,将频率回落到前一半奈奎斯特之上:
我们要分析的第一个抗锯齿滤波器是我们的老朋友“线性伪装”滤波器[0.5,0.5]。这绝对是不完美的,我们可以看到模糊和残留的混叠:
图形社区不久前就意识到了这一点–在进行一系列后期处理的下采样时,例如光晕/眩光;在这种情况下,默认的框/帐篷/双线性过滤器非常糟糕。当这样的小混叠“吹”到整个屏幕时,尤其是在运动中,这种情况确实很糟糕。这甚至是Siggraph演示文稿中的很大一部分,例如我的朋友Jorge Jimenez的出色演讲。
在我职业生涯的早期,我也很想解决这个问题,甚至描述了这个想法–奇怪的交叉滤镜(因为它在GPU上速度很快)–请不要这样做,这是一个坏主意,而且已经过时了! 🙂
相比之下,奇数双线性滤波器(不会移动相位)看起来有点不同:
更少的锯齿,更模糊。在许多情况下可能会更好,但是从破坏半像素/角点对齐约定的取舍是IMO不能接受的。而且价格也更高(无法一次点击2倍下采样)。
为了获得更好的结果->您将需要更多样本,其中一些样本带有负瓣。您还可以设计一个包含更多样本的均匀滤波器,例如Lanczos:
在某些情况下,我发生的一件有趣的事情是,对于低采样率和上采样率的权衡取舍是不同的。如果您使用“完美”的上采样低通滤波器,则会导致讨厌的振铃。
下采样通常不是这种情况。因此,在降采样时,您可以选择较锐利的滤镜,而在升采样时,则可以选择较不锐利的滤镜,这也是Photoshop的建议:
我希望我的博客文章有助于澄清使用相同的非常广泛的术语来表示某些不同操作所引起的一些常见混淆。
有几种方法可以进行双线性上采样和下采样。确保您使用的任何内容都使用相同的约定,并且在下采样/上采样后不会偏移图像。
半像素中心偏移是一个非常方便的约定。它可以确保图像的边界和角对齐。它是GPU上的默认设置,并且会自动发生。在CPU / DSP上工作时,值得使用相同的约定。
不同的上采样/下采样方式具有不同的频率
......