我如何创建即使运行一百万次也不会损坏图像的过滤器

为新的网络漫画《无限的喵》完成了网络架构的创建后,我决定该写一些早就应该过期的技术文章了。 本文将重点介绍我几年前开发的过滤器。 尽管在我看来值得这样做,但从未在视频压缩领域进行过讨论。

2011年,我开发了“半球过滤器”。 这是一种特殊的过滤器,它接收传入的图像,并且最有说服力的显示了当恰好移动半个像素时图像的样子。

您可能想知道为什么根本需要这样的过滤器。 实际上,它们在现代视频编解码器中很常见。 视频编解码器使用类似的过滤器来获取先前帧的片段,并在后续帧中使用它们。 较旧的编解码器一次只将帧数据移动整个像素,但是新的编解码器走得更远,允许移动一半甚至四分之一像素,以更好地传输较小的运动。

杰夫·罗伯茨Jeff Roberts)在分析传统半象限滤波器中的运动补偿算法的行为时,发现将其重复应用于顺序帧时,它们会迅速退化,从而迫使视频压缩器的其他部分使用更多的数据来校正伪像。 如果禁用这些校正并查看Halfpel过滤器的“原始”结果,则这是原始图像:


变成这样:


短短一秒钟的视频 应当将其移至侧面,因为每帧图像均移了半个像素。 但是结果看起来并不像原始图像的置换版本,而是严重失真。

实际上,在“一秒钟视频”过滤器中会多次应用过滤器-如果视频以每秒60帧的频率播放,则过滤器会过滤60次。 但理想情况下,我们需要能够抵抗这种失真的滤波器。 如果我们拥有它们,那么平滑滚动的视频就不会进行如此多的伪像校正编码,这会使它们变少或变好,或两者兼而有之。

如果您熟悉视频压缩领域,您可能想知道为什么我们甚至需要多次使用Halfpel滤波器。 最后,如果我们两次应用半像素滤镜,那么我们已经将整个像素移动了一个像素,那么为什么不只使用帧中的数据并取它们呢?

答案不是那么简单。 首先,我们需要对数据进行编码的数据越多,获得的压缩就越少。 因此,如果我们开始编码而不需要太多数据(例如“从哪一帧获取数据”),则视频将无法很好地压缩。

但这不是最重要的。 主要问题是,如果我们需要从先前的帧中获取信息, 则必须将其存储 。 要保留前两帧而不是前一帧,您需要猜测您拥有两倍的内存。 对于现代CPU而言,这不是一个特殊的问题,它们具有大量的内存,并且这样的琐事不会打扰他们。 但这对您来说是个问题 ,如果您想创建一种快速,可移植,使用广泛的视频格式,该格式应在内存量较小的设备(手机,内置电子设备等)中使用。

我们真的不想存储几帧来补偿运动,以免使用半像素滤镜。 因此,我被指示找出此处到底发生了什么,并弄清是否可以创建没有此类问题的过滤器。

在此之前,我从未使用过过滤器,也不知道它们通常是如何开发的。 奇怪的是,事实证明这是对我有利的,因为我必须在没有偏见的情况下研究这个问题。

基础知识


我很快意识到,最流行的半像素滤镜具有相似的结构:对于输出图像中的每个像素,将获取输入图像的2至8个像素,并对其进行采样并与某些系数混合。 不同的滤镜仅在采样的源像素的数量(在滤镜开发人员的术语中通常称为抽头)和像素混合因子方面有所不同。 这些系数通常称为“过滤器内核”,这是完整描述过滤器所需要的。

如果您熟悉图像的任何一种采样或重新采样(例如缩放图像),那么您应该很清楚。 本质上,过滤器执行相同的操作。 由于视频压缩是一个广泛的领域,可以进行各种研究,因此很明显,除了简单的滤波以外,还有许多其他方式可以补偿运动。 但是常见的编解码器通常使用带有半像素滤波器的运动补偿过程,该过程与图像缩放滤波器基本相同:它们只获取原始像素,将它们乘以一定的权重,相加后得到输出像素。

需要“锐度”


因此,我们需要将图像移动半个像素。 如果您是图形程序员,但对过滤不是特别熟悉,您可能会认为:“我也有问题,只需使用双线性过滤器即可。” 当我们需要计算两个传入数据元素之间的中间值时,这是处理图形的标准过程,就像这里发生的那样。

通过以下滤镜核心,可以轻松地描述用于将像素精确移动一半的双线性滤镜:

// NOTE(casey): Simple bilinear filter BilinearKernel[] = {1.0/2.0, 1.0/2.0}; 

这将起作用,但并非没有问题。 如果您的目标是高质量的图像,并且在视频压缩的情况下,目标就是那样,那么双线性滤波器不是最佳解决方案,因为它会使结果增加不必要的模糊。 它不是很多 ,但是比其他过滤器创建的更多。

为了清楚地说明这一点,以下是在最简单的滤镜一次应用后海象眼睛与原始图像的近似图像:


左边是原稿,右边是双线性过滤。 在它们之间是使用最广泛的视频编解码器的半像素滤波器。 如果仔细观察,您会发现几乎所有图像看起来都相似, 除了双线性的图像(稍微模糊些)之外。 尽管没有太多模糊,但是如果您的主要目标是图像质量,则足以选择其他滤镜而不是双线性滤镜。

那么其他滤镜如何“保持”清晰度并避免模糊呢? 让我们记住双线性模糊的核心是什么样的:

 BilinearKernel[] = {1.0/2.0, 1.0/2.0}; 

这很简单。 要将图像移动半个像素,我们需要一个像素并将其与相邻像素混合50%。 仅此而已。 可以想象这是如何“模糊”图像的,因为在那些明亮的白色像素与深黑色相邻的地方,这两个像素在双线性滤波过程中被平均,从而创建了一个“软化”边界的灰色像素。 每个像素都会发生这种情况,因此实际上每个区域的颜色或亮度都有明显差异。 顺利了。

这就是为什么在高质量编解码器中,不将双线性滤波用于运动补偿的原因(尽管在其他情况下也可以使用它)。 而是使用保留清晰度的滤镜,例如:

 // NOTE(casey): Half-pel filters for the industry-standard h.264 and HEVC video codecs h264Kernel[] = {1.0/32.0, -5.0/32.0, 20.0/32.0, 20.0/32.0, -5.0/32.0, 1.0/32.0}; HEVCKernel[] = {-1.0/64.0, 4.0/64.0, -11.0/64.0, 40.0/64.0, 40/64.0, -11.0/64.0, 4.0/64.0, -1.0/64.0}; 

如您所见,在双线性过滤仅考虑两个像素的情况下,这些过滤器考虑了六个(h.264)甚至八个(HEVC)像素。 另外,它们不仅计算这些像素的常规加权平均值,而且对某些像素使用权重以从其他值中减去这些像素。

他们为什么要这样做?

实际上,这并不难理解:使用正值和负值,并考虑更宽的“窗口”,滤波器可以考虑相邻像素之间的差异 ,并模拟两个最近像素相对于最远像素的清晰度。 这样,您就可以在像素与其相邻像素显着不同的那些地方保持图像结果的清晰度,同时仍然使用平均来创建“半像素”偏移的可信值,该值必须反映来自传入图像的像素组合。

不稳定的过滤


那么,问题解决了吗? 是的,可以,但是如果您只需要做一个半像素偏移。 但是,这些“锐化的”滤波器(我在这里故意使用了这个术语)实际上会做一些危险的事情, 本质上类似于双线性滤波。 他们只是更好地知道如何隐藏它。

双线性过滤会降低图像清晰度,而这些标准过滤器会提高图像清晰度,就像某些图形程序中的锐化操作一样。 锐化的数量非常小,因此,如果仅执行一次过滤器,我们将不会注意到这一点。 但是,如果多次执行过滤,那么这将变得非常明显。

而且,不幸的是,由于这种锐化是程序性的并且取决于像素之间的差异,因此它会创建一个反馈循环 ,该循环将不断反复地对相同的边界进行锐化,直到破坏图像为止。 您可以通过具体示例来说明这一点。

上图-原始图像,下图-具有双线性过滤,执行了60帧:



如您所料,模糊只会继续降低图像的清晰度,直到变得非常模糊为止。 现在,原始图像将位于顶部,而h.264编解码器半像素滤镜将在底部运行60帧:



看到所有这些垃圾? 该滤镜的效果与双线性滤镜的“模糊”效果相同, 反之亦然 -它“提高了图像的清晰度”,因此所有细节都变成了严重扭曲的明暗模式。

使用8像素的HEVC编解码器性能是否更好? 好吧,它绝对比h.264更好:


但是如果我们将时间从60帧(1秒)增加到120帧(2秒),我们仍然会看到有反馈并且图像被破坏了:


为了喜欢信号处理的人,我将添加一个窗口正弦滤波器(称为Lanczos滤波器)以供参考:

 // NOTE(casey): Traditional 6-tap Lanczos filter LanczosKernel[] = {0.02446, -0.13587, 0.61141, 0.61141, -0.13587, 0.02446}; 

我不会在本文中解释为什么有人可能会对“ window sinc”感兴趣,但是可以说这个滤镜由于理论原因而受欢迎,因此请看一下它在处理60帧(1秒)时的外观:


处理120帧(2秒)时:


比h.264更好,与HEVC差不多。

稳定的过滤


我们如何比h.264,HEVC和window sinc获得更好的结果? 他们能有多好?

希望在视频压缩的文献中看到类似的问题,并且压缩专家应该知道这些问题,但是实际上(至少在2011年如此),我没有找到至少指出这是问题的人。 因此,我不得不独自提出一个解决方案。

幸运的是,问题的陈述非常简单:创建一个可以多次应用的过滤器,以使图像看起来与开始时的外观相同。

我将此定义称为“稳定过滤”,因为我认为可以将其视为过滤器属性。 如果过滤器不属于其反馈回路,则它是“稳定的”,也就是说,它可以重复应用而不会产生伪像。 如果滤镜产生伪影,该伪影会因重复使用而被放大,并最终破坏图像,则它是“不稳定的”。

我再说一遍,我不明白为什么在视频编解码器或图像处理的文献中没有考虑这个主题。 也许它使用了不同的术语,但是我还没有满足。 在处理声音的领域中,“反馈”的概念已经确立。 但不是图像处理中的重要问题。 也许是因为通常过滤器应该只应用一次?

如果我是该领域的专家,那么我很可能会对这个问题有一个看法,也许我什至会知道那些已经有解决这个问题的专门文献的角落,这是鲜为人知的。 但是,正如我在文章开头所说的那样,我以前从未能够创建过滤器,所以我只搜索了一些知名文章(尽管值得注意的是,至少有一个文献中众所周知的人也没有听说过类似的信息)

因此,早上他们告诉我我们需要这个过滤器,并且整天我都试图创建它。 我的方法很简单:我创建了一个程序,该程序执行了数百次过滤器,最后生成了一个图像,以便可以看到长时间运行的结果。 然后,我尝试了不同的滤波器系数并观察了结果。 从字面上看,这是一个定向试验和错误过程。

大约一个小时后,我获得了适合此任务的最佳滤波器系数(但它们有一个缺陷,我将在本文的第二部分中讨论):

 MyKernel[] = {1.0/32.0, -4.0/32.0, 19.0/32.0, 19.0/32.0, -4.0/32.0, 1.0/32.0}; 

此核心即将锐化和模糊。 由于锐化总是会导致产生生动而明显的伪像的反馈,因此该滤镜核心更喜欢一点模糊,以便图像看起来更“暗淡”。

这是60帧后的外观。 作为参考,我按此顺序显示了所有滤镜:原始图像(无滤镜),我的滤镜,双线性,Lanczos,h.264,HEVC:







如您所见,我的滤镜比锐化滤镜产生的模糊效果略多,但60帧后没有不可接受的锐度伪像。 但是,您可能更喜欢使用模糊伪像来锐化伪像,因此可以在最佳锐化滤镜(Lanczos)和我的锐化之间进行选择。 但是,如果我们将数量增加到120帧,那么我的过滤器就无法竞争了:







300帧之后,除我的以外的所有滤镜都变成了一个恶作剧:







600帧之后,这个笑话变得更加残酷:







您甚至不必说900帧后会发生什么:







它的稳定性如何?


在这个阶段,自然会感到奇怪:我的滤波器真的很稳定,还是只是非常缓慢的模糊,比双线性滤波慢得多? 也许经过数千次重复,我的滤镜会逐渐使图像模糊?

令人惊讶的是,答案似乎是否定的。 尽管在大约一百个第一叠加的过程中添加了一些模糊,但看起来滤镜会收敛到图像的稳定表示,然后再不降级。 这是海象眼睛的另一个放大图像:


从左到右:原始图像,我的滤镜应用了60次,120次,300次,600次和900次。 如您所见,模糊会收敛到稳定状态,即使经过数百个滤镜叠加,模糊也不会降低。 相比之下,将它与相同数量的样本(抽头)与窗口同步进行比较,看看有多糟(而且快!)这些工件形成了反馈并产生了无用的结果:


我的滤镜看起来非常稳定,并且与我所见过的所有滤镜相比,它在重复使用后可产生最佳效果。 似乎它具有某种“渐近”特性,其中数据快速收敛到(有限的)平滑图像,然后保存该平滑图像,并且不执行无限降级以完成垃圾处理。

我什至尝试将滤镜应用一百万次,而且看起来在前几百个叠加层之后,它不会进一步退化。 如果没有更好的数学分析(我还没有找到能够确切证明它的数学解决方案,但我确定知道它在某处),我不能肯定地说数十亿或数万亿个叠加层之后-它不会破裂。 在合理的测试范围内,我无法检测到进一步的降级。

它是六个抽头的最佳稳定的Halfpel过滤器吗?


在这个阶段,提出这个问题是合乎逻辑的:这真的是最好的吗? 直觉告诉我们事实并非如此,因为我完全不了解过滤器的开发,并且几乎没有研究文献,所以我在一个小时内就拿起了这个过滤器。 至少可以假定 ,在进行了如此简短的研究之后,我将找不到一个确定的“最好的所有征服者”过滤器。

这个假设是真的吗? 如果为真, 那么最终的最佳过滤器将是什么 ? 我将在本文的第二部分中对此进行更详细的讨论。

Source: https://habr.com/ru/post/zh-CN468501/


All Articles