到目前为止,无论是图片还是视频,主流的标准都是将内容先切分为一系列的编码单元,然后按光栅扫描顺序逐一进行编码处理,在解码的时候逆向还原编码过程。这个框架如此稳定地存在了30多年了,证明了它的成功性,也有少量框架突破了这个设计(平衡了算法复杂度、内存消耗、硬件实现难易度等因素)。今天我们借这个周末回顾一下编码单元这块的历史演进过程,并尝试跟大家继续分享一些个人认知,求同存异,有机会多讨论。
如前文所讲,YCbCr被应用到编码中是从JPEG年代就开始了的,因为人眼对亮度的敏感度远大于色差分量的敏感度,所以4:2:0的信号源就变成最常用的信号源。当然有专业的信号,为了保持图像运动的效果好,会使用4:2:2的信源,或是4:4:4的信源,处理原理上极类似。我们只把4:2:0的信号源作为主要考虑的对象。
从JPEG、H.261的年代,因为技术成熟度的问题以及当时硬件条件的约束,整体算法架构都设计的非常简单朴实(因此也建议对于该方向入门者,使用更老的标准来熟悉框架和技术,由浅入深,其实会非常省力)。我们前文[1]把各标准分代,其实也是因为同一代中的算法架构与设计逻辑相对接近。
对JPEG,H.261这个阶段的标准,频域转换只设计了一个8x8的DCT算法,因此在设计最小编码单元的时候,也是完全遵照这个逻辑来配套定义的。因此历史也就是这么开始的。接下来我们来推演下发展到现在的过程。
JPEG最小的编码单元叫MCU(Minimum Coding Unit,最小编码单元),对于4:2:0的输入来讲,一个最小单元要保证各通道最小运算尺幅为8x8,以匹配8x8的DCT算法,因此一个MCU的组成为16x16的正方形区域,包括了16x16的Y分量信号和两个8x8的Cb, Cr通道色差分量信号。
在H.261开始,这个设计被第一次定义为一个宏块(Macroblock),不管YCbCr是4:2:0,还是其他的格式,都一率按16x16正方形区域为一个宏块,即一个最小的编码单元来处理。对Y来讲,一个16x16的区域,被切分为四个8x8的方形进行DCT处理,而CbCr则刚好是一个8x8的DCT运算,非常简单直白。
真正发生实质性变化的一代,是在H.264/AVC出现的时候,宏块还是那个宏块,16x16的方形区域,但是最小编码单元可以继续往下切分为16x8, 8x16, 8x8, 4x8, 8x4, 4x4等多种结构的组合,这样做可以按图像结构属性进行图像切分。有了这样的结构,帧内和帧间的预测精确度大幅度提升,同时在转换阶段,也支持了4x4的DCT变换进行匹配,整体上达成了对上一代的性能碾压。
到了H.265, H.266的年代,视频应用的主战场已经面向2k, 4k这种超高清尺幅的情况了,显然画面中大片可预测区域会继续变多,16x16宏块这种粒度明显过于小了,于是在大量的实验和测试后,引入了CTU(Coding Tree Unit)的概念,这样一个编码单元可以是64x64,32x32或16x16。一个CTU又可以被分为多个CTB(Coding Tree Block),一个CTB可以被切为多个CU(Coding Unit),一个CU又可以由多个PU(Prediction Unit)和TU(Transform Units),这样每一个CTU往下,可以切分到最小4x4的CU单元,可以说各种切分、预测、变换的组合情况越来越多。
在一个技术框架下优化,总会是这样,选择越多,精细度越多,效率就越高,复杂度也越高。事实上在这个框架上还可以继续复杂下去,编码质量提升也还是继续可达的。
最大的问题,Block Artifact。
因为每一个最小编码单元都是独立处理的,块与块之间是欠缺相关性的。尤其对于一些设计没那么好的码率控制算法来讲,相邻块的量化损失差异如果过大,就会在每个编码块之间造成明显的块效应,也会直接造成主观质量的下降。在专业领域里,这个问题会更明显一些。挑剔的鹰眼们在电影出片前会逐帧地寻找每一个细微的颜色缺陷,一次次的复压也灼烤着每一个还没到工业化成熟的编码方案。
因此现代编码器对后处理的要求越来越高,现在的编码结构都要求先进行一次deblocking filter,然后进行一次SAO filter来尽可能降低编码后图像的算法缺陷,这些filter设计也越做越复杂,对于编解码的性能压力提升也还是非常大的。
编码单元这个概念,其实并不只是视频编码界才提出并涉及到的。把一个图像内容切块表示或处理,是一个计算机图形学、计算机视觉里的常规操作。
在图形学的范畴里,这种操作叫像素化(Pixelation),尤其早些年,渲染能力还没有那么强的时候,图像渲染都在较低的分辨率下执行,因此为了实时渲染,不得不妥协渲染效果,大量使用近邻法进行采样,因此在显示效果上大量的像素化风格在当时的主机里被大量应用。但是美学这个东西,就很有趣,也并不是说像素化了,大众就无法接受了。像素化风格也因为一些特别好的场景,也被大部分人所喜爱,比如超级玛丽、我的世界这些经典的游戏,以至于让大家不但喜欢这个风格,甚至不少人还拒绝放弃这种风格。
另外一些APP也把像素化风格作为主风格提供给用户,老二次元用户也是喜闻乐见,比如时间块应用等视觉效果和使用体验还真的不错的。
随着渲染能力以及硬件能力、显示设备的提升,逐渐的支持的渲染越来越强,通过双线性滤波器、双边滤波器等更复杂算法,可以让显示效果越来越平滑,现在大型游戏有时已经到可以与真实场景无法区分的状态了,采样方法也是其中很重要的一个因素(当然,还有光照、材质、渲染算法等大量其他的优化综合的结果)。
最典型的应用,马赛克效果,主要处理一些敏感画面来用的。这个大家在各种场合见得比较多了,实现方法很多种,比如区域内色值平均,颜色替换等。
其实逻辑上跟编码算法的逻辑一样的。选定一个需要打马赛克的区域,然后按最小处理单元设定对区域进行切分,将每个区域内的颜色设置为一个函数的输出(一般跟区域内像素有关,比如颜色平均等)。最小处理单元越小,马赛克效果越不明显;最小处理单元越大,马赛克效果越明显。
图像的像素化,必会造成大量的信息丢失,这样对于理解原始内容难度就变得极大,当然这也是设计初衷。但近些年,随着AI算法的不断发展,通过一些恢复模型或超分辨率算法,很多马赛克后的内容可以被很高质量的还原回来。当然如果是娱乐属性的应用,也许还算可以接受。但对于一些特殊场景(如关键视觉信息隐藏要求高时),可能会造成严重的后果。所以后期时,选择合适的马赛克算法会变得越来越重要,绝不能大意。
知识永远是相通的,尤其编码算法和计算视觉两者现在高度融合,哪怕是在MPEG-2, H.264年代,也有很多简单的取巧的玩法,很多时候就看你想不想认真玩。
比如是否有可能在信源不做处理的情况下做到马赛克效果。传统的马赛克效果,都是靠解码视频之后的后处理来完成的,这也是标准的、通用的视觉处理架构。但是在编码界,也有另外一种做法,就是在解码阶段,直接把DCT的结果AC参数完全丢弃,只使用DC参数进行解码。这样复用解码流程就可以实现一定的马赛克效果。因为DC参数相当于把一个编码单元区域进行了平滑化,而丢弃了区域颜色细节,这样本质上与马赛克生成算法有一定的接近,计算量和流程复杂度下降了不少。
当然因为预测算法的准确度越来越高,马赛克的效果相比后处理会稍弱一些,但作为一个小技巧,也是不断提醒我们算法的路线其实很多条的,未必某一种就是固定的做法。
另外一个能走通的路径是视频内容预览,作为高清内容的简单浏览、一些特殊效果的合成、或是一些特殊用途,你也可以选择在解码时,将最小编码单元缩小在长宽方向都缩小一半。对转换残差部分也按一样的逻辑进行丢弃,这样解码复杂度大致相当于完整内容的四分之一,解码内容与原画内容是相似的,但可预见的有不少损失(其实最终结果有点像是把视频内容像素化了一些),这样可以实现低功耗的高复杂度码流的在小屏上的预览算法,当时有一些预览设备在用类似的技巧进行加速,也是从压缩算法出发解决问题的一手奇招。
世界是属于创新者的,即使在这么基础的话题上,我觉得可以往深想的事情仍然非常多。比如:
从JPEG的MCU,H.261以后的Macroblock,H.265以后的CTU,都是方形区域的最小编码单元应用,目前还没有看到特别成型的大的突破口,只不过在块越来越大的路上停不下来,比如AV1,superblock最大可以到128x128,思路框架还是一致的。
比较大差异的架构可以参考JPEG 2000,一个编码单元定义为一个tile,一个tile可以是整张图像,然后使用小波进行整体变换,经过几层小波变换,图像就自然变为几个layer,不同层的layer是可以独立解码显示的,这也是把小波的优势最大化应用出来。
但是JPEG 2000在应用中并不算非常成功,在图像编码的领域也只是被应用在一些特定的场景中。所以还是期待有一些新的脑洞大开的思路被提出来,也可能在未来某一天会弯道超车,突然比现有框架性能提升比较多。
对于光栅化编码这个事情,在06年的时候就一直觉得有不少提升空间。我们只从帧内编码这个角度来想这个问题,如果编码的起始点不是从左上角开始,而是可以从任一位置开始,那编码越往后,预测的模式选择空间越大,编码性能提升的可能性越高。另外,一张图的编码路径可以有非常多条,光栅化顺序只是最直观,选择难度最小的一种做法,是否存在一种分析方法,它很灵活但是可以取全局(或是相对的)最优预测和编码路径,目前其实也没有定论,需要后续继续深入研究和实验才能得知。
每周末这一两个小时的梳理,感觉是非常的愉悦的。不用那么关心表述的对错,只是梳理思维脉络。希望有写错的地方大家留言指正,也希望大家跟我一起多开脑洞,的确这是一个非常有趣的专业方向,还有太多可以往深挖掘的,而到现在,只是刚开了个头。
[1]
前文: 我对视频标准的理解,以及为什么这可能会影响所有人