HEVC Picture Buffering Management

1 POC and DPB

HEVC 中的每个图片都分配有一个图片顺序计数 (picture order count, POC) 值,表示为 PicOrderCntVal。 它具有三个主要用途:唯一地标识图片,指示相对于同一 CVS 中其他图片的输出位置,以及在较低级别的 VCL 解码过程中执行运动矢量缩放。 同一CVS中的所有图片必须具有唯一的POC值。 来自不同CVS的图片可以共享相同的POC值,但是图片仍然可以被唯一地识别,因为不可能将来自一个CVS的图片与另一CVS的任何图片混合。 CVS 中允许 POC 值存在间隙,即,输出顺序连续的两个图片之间的 POC 值差异可以相差超过1(事实上,连续图片的 POC 值的差异量可以任意变化) 。

图片的 POC 值由slice header 中的 slice_pic_order_cnt_lsb 码字表示。 允许的 POC 值的范围是从 -231 到 231 – 1,因此为了节省 slice header 中的比特,仅用信号通知 POC 值的最低有效位 (POC LSB)。 用于 POC LSB 的位数可以在 4 到 16 之间,并在 SPS 中用信号通知。 由于在 slice header 中仅用信号通知 POC LSB,因此当前图片的最高有效 POC 值位 (POC MSB) 是从称为 prevTid0Pic 的先前图片导出的。 为了即使图片被移除,POC 推导也能以相同的方式工作,prevTid0Pic 被设置为时间层 0 的最近的先前图片,该图片不是 RASL 图片、RADL 图片或子层非参考图片。 解码器通过将当前图片的POC值与前一个图片的POC值进行比较来导出POC MSB值。

HEVC中的解码图片缓冲器(DPB)是包含解码图片的缓冲器。 除当前图片之外的解码图片可以存储在DPB中,因为它们需要参考,或者因为它们尚未输出,这是启用乱序输出所必需的。 请注意,当前解码的图片也存储在 DPB 中。 图 2.12 显示了两个示例引用结构,它们都需要至少三张图片的 DPB 大小。 图2.12a中的图片P1和P2都在P3之后输出,因此在P3解码时都需要将其存储在DPB中。 因此,DPB 需要能够同时存储 P1、P2 和 P3。 在图2.12b中,每个图片使用两个参考图片,因此DPB也需要足够大以同时存储三个图片。 图2.12b中的参考结构是所谓的低延迟B结构的示例,其中广泛使用双向预测而没有任何乱序输出。

解码器需要分配用于解码特定比特流的最小DPB大小由sps_max_dec_pic_buffering_minus1 码字来发信号通知,其可以针对序列参数集中的每个时间子层发送。 HEVC的第一版本中允许的最大可能的DPB大小是16,但是根据图片大小和所使用的解码能力的“级别”的组合,最大大小可以进一步受到限制。 请注意,HEVC 指定要包括在 DPB 中的当前图片,因此 DPB 大小为 1 将不允许任何参考图片。 如果DPB大小为1,则所有图片都必须进行帧内编码。

DPB中的图片被标记以指示它们的参考状态。 DPB中的每张图片都被标记为“不用于参考”、“用于短期参考”或“用于长期参考”。 通常将这些类型的图片分别称为非参考图片、短期图片和长期图片。

参考图片要么是短期图片,要么是长期图片。不同之处在于,长期图片可以在DPB中保存的时间比短期图片长得多。有一条规则决定了短期图片可以在DPB中停留多长时间。由当前图片、prevTid0Pic、DPB中的短期参考图片和DPB内等待输出的图片组成的图片集的POC跨度必须在POC LSB覆盖的POC范围的一半以内。该规则保证了POC MSB推导的正确性,并通过使解码器能够识别丢失的短期图片来提高误差鲁棒性。

非参考图片是不用作参考但如果以后需要输出时仍可以保留在DPB中的图片。

如图 2.13 所示,每个解码图像的图像标记都会发生变化。 图片被解码后,最初总是被标记为短期图片。 短期图片可以保持短期图片或改变为非参考图片或长期图片。 长期图片可能会保持长期图片或变成非参考图片,但它们永远不能再变成短期图片。 非参考图片永远不能再次成为参考图片。

DPB中的图片可以被保存以供将来输出,无论它是参考图片还是非参考图片。 当图片已被解码时,它通常等待输出,除非 slice header 中的pic_output_flag等于0或者图片是与CVS中的第一图片相关联的RASL图片。 如果是这种情况,则不会输出图片。

图片标记和图片输出是在单独的过程中完成的,但是当图片既是非参考图片并且不等待输出时,DPB中的图片存储缓冲器被清空并且可以用于存储将来的解码图片。 编码器负责管理图片标记和图片输出,以便 DPB 中的图片数量不超过 sps_max_dec_pic_buffering_minus1 指示的 DPB 大小。

SPS中与图片输出相关的另外两个码字是
sps_max_num_reorder_pics 和 sps_max_latency_increase_plus1,两者都可以针对每个时间子层发送。 sps_max_num_reorder_pics,此处表示为NumReorderPics,指示在解码顺序中可以在任何图片之前并且在输出顺序中在其之后的图片的最大数量。 图 2.12a 中的参考结构的 NumReorderPics 值为 2,因为图片 P3 有两个图片在解码顺序上位于其前面,但在输出顺序上位于其后面。 图 2.12b 的 NumReorderPics 为零,因为没有图片乱序发送。

sps_max_latency_increase_plus1 用于指示 MaxLatencyPictures,它指示在输出顺序中可以在任何图片之前并且在解码顺序中在该图片之后的图片的最大数量。 图 2.14 显示了 NumReorderPics 和 MaxLatencyPictures 之间的区别。 NumReorderPics 在此等于 1,因为 P1 是唯一乱序发送的图片。 MaxLatencyPictures 设置为 4,因为图片 P2、P3、P4 和 P5 在输出顺序上都先于 P1。 此参考结构的最小 DPB 大小为 3。

可以说NumReorderPics表示DPB中处理乱序图片所需的图片存储的最小数量,而MaxLatencyPictures表示由乱序图片引起的以图片为单位的最小编码延迟量。

对于低延迟应用,建议使用不会因乱序输出而导致编码延迟的参考结构。 这是通过确保解码顺序和输出顺序相同来实现的,这可以通过发信号通知 NumReorderPics 或 MaxLatencyPictures 或两者都等于 0 来表示。

2 Reference Picture Sets

将图片标记为“用于短期参考”、“用于长期参考”或“不用于参考”的过程是使用参考图片集(RPS)概念来完成的。 RPS是在每个slice header中用信号通知的一组图片指示符,并且由一组短期图片和一组长期图片组成。 在图片的第一个片头被解码之后,DPB中的图片被标记为由RPS指定的。

RPS的短期图片部分中指示的DPB中的图片被保留为短期图片。 将RPS中的长期图像部分中指示的DPB中的短期或长期图像转换为或保留为长期图像。 最后,DPB中RPS中没有指示符的图片被标记为未使用以供参考。 因此,可以用作对按照解码顺序的任何后续图片进行预测的参考的所有已经解码的图片必须被包括在RPS中。

RPS 由一组图片顺序计数 (POC) 值组成,用于识别 DPB 中的图片。 除了用信号发送 POC 信息之外,RPS 还为每张图片发送一个标志。 每个标志指示对应的图片是否可供当前图片参考。 注意,即使参考图片被信号通知为对于当前图片不可用,它仍然被保存在DPB中并且可以供稍后参考并用于解码未来的图片。 根据 POC 信息和可用性标志,可以创建如表 2.3 所示的五个参考图片列表。

列表RefPicSetStCurrBefore由可用于当前图片的参考并且具有低于当前图片的POC值的POC值的短期图片组成。 RefPicSetStCurrAfter 由可用的短期图片组成,其 POC 值高于当前图片的 POC 值。 RefPicSetStFoll是包含对于当前图片不可用但可以用作用于按解码顺序解码后续图片的参考图片的所有短期图片的列表。 最后,列表RefPicSetLtCurr和RefPicSetLtFoll分别包含可用于和不可用于当前图片的参考的长期图片。

图 2.15 和表 2.4 显示了使用三个时间子层的示例引用结构以及按解码顺序排列的每个图片的 RPS 列表的内容。

IDR图片重置编解码器,包括将DPB中的所有图片转变为非参考图片。 由于IDR图片的RPS是空的,因此没有为IDR图片用信号发送RPS语法。 因此表 2.4 中的所有列表对于 IDR 图片 I0 都是空的。 在图片B3处,图片P1被放入RefPicSetStFoll中,因为P1未被B3引用。 然而,P1 保留在 DPB 中,因为它用于将来的图片。 在图片B4处,本例中I0被制作为长期图片,因此由于B4没有引用它,因此将其放入RefPicSetLtFoll中。 在图片P5处,编码器通过根本不将图片B2和B3包括在RPS中来使图片B2和B3成为非参考图片。 同时,图片 I0 被移动到 RefPicSetLtCurr,因为它被 P5 引用。 此后,I0 保存在 RefPicSetLtFoll 中以供以后使用。

编码器需要确保RefPicSetStCurr和RefPicSettRoll中指示的每个图片都存在于DPB中。如果不是这种情况,解码器应该将其推断为比特流错误,并采取适当的行动。然而,如果在RPS中的条目的DPB中不存在被指示不用于当前图片的参考的对应图片,则解码器不采取任何动作,因为这种情况可能由于移除个别子层非参考图片或整个较高时间子层而发生。

尽管没有针对IDR图片发送RPS,但是CRA和BLA图片都可以在其RPS中具有图片,使得相关联的RASL图片可以使用RPS中的那些图片作为预测的参考。但是,对于CRA和BLA图片,要求RefPicSettCurr和RefPicSetLtCurr列表均为空。

3 Reference Picture Set Signaling

对于RPS中的每个图片,用信号发送三条信息:POC信息、可用性状态以及图片是短期图片还是长期图片。

短期图片的 POC 信息以两组方式发送。 组 S0 首先被发信号通知,它由 POC 值低于当前图片的所有短期图片组成。 该组之后是组 S1,其包含具有比当前图片更高的 POC 值的所有短期图片。 每组的信息按照相对于当前图片的POC距离顺序发送,从最接近当前图片的POC值的POC值开始。 对于每张图片,都会发出相对于前一张图片的 POC 增量信号。 当前图片充当每组中第一张图片的前一张图片,因为第一张图片没有前一张图片。

通过 POC 增量对长期图片进行编码可能会导致非常长的码字,因为长期图片可以在 DPB 中保留很长时间。 因此,长期图像而是通过其 POC LSB 值来表示。 用于slice header POC LSB 值的相同位数也用于长期图片。 解码器会将 RPS 中发信号通知的每个 POC LSB 值与 DPB 中图片的 POC LSB 值进行匹配。 由于 DPB 中可能有多个具有相同 POC LSB 值的图片,因此还可以选择发送长期图片的 POC MSB 信息。 当存在解码器无法正确识别图片的风险时,必须发送此 MSB 信息。 避免这种风险的一种方法是,当相应的 POC LSB 值已被两个或多个不同的长期图片使用时,始终用信号发送长期图片的 POC MSB 信息。

RPS 中每个图片的可用性状态由一位标志表示,其中“1”表示该图片可供当前图片参考,“0”表示不可用。

表 2.5 显示了图 2.15 中图片 B7 的示例 RPS 语法。 表中的第一列显示语法是否与发信号通知短期图片或长期图片相关。 第二列包含每个语法元素的 HEVC 规范名称。 第三列表示RPS中的相关图片,第四列表示示例中语法元素的值。 第五列显示语法元素的类型,其中“uvlc”表示通用可变长度代码,“flag”是一位二进制标志,“flc”是固定长度代码。 最后一列显示使用类型对每个语法元素的值进行编码的结果位。

图2.15中图片B7的POC值为5,其RPS包含3个短期图片; S0组中的图片P1和S1组中的图片B6和P5。 RPS 的前两个语法元素传达 S0 和 S1 中的图片数量; 这是由 uvlc 代码“010”和“011”编码的。 然后用信号发送组S0中的图像。 P1 的 POC 增量为 1,因为它的 POC 值为 4,并且当前图片的 POC 值等于 5。在编码之前,POC 增量减去 1,因此最终信号值为 0,这导致 uvlc 代码“1” 。 当前图片B7以图片P1为参考; 这是由标志“1”表示的。

接下来的语法元素涵盖了 S1 中的图片。 当前图片的 POC 值等于 5,因此代码字“1”用于 B6,因为其 POC 值等于 6。图片 B6 也用于由标志“1”指示的当前图片的参考。 S1 中的第二张图片是 P5,其 POC 值为 8。它是相对于 S1 中的前一张图片(B6,POC 值为 6)进行编码的。增量为 2,减 1 等于 1,编码为 ‘010’。 当前图片B7不参考图片P5; 这是由标志“0”表示的。

RPS 中的下一个码字表示 RPS 中长期图像的数量,它是 1,并且使用“010”进行 uvlc 编码。 然后用信号通知长期图像I0的POC LSB值。 这是通过代码字“00000000”假设 8 位用于发送 POC LSB 值。 长期图片I0不被当前图片B7参考; 这是用标志‘0’来表示的。 最后,由于 DPB 中没有其他图片共享相同的 POC LSB 值,因此不会针对该长期图片发送 POC MSB 信息; 这是用当前标志“0”来表示的。

出于弹性原因,RPS信息在每个 slice header 中用信号发送,但是对于每个 slice 按原样重复RPS信息可能会花费许多比特。不仅可以将图片分割成必须重复RPS的多个slice,比特流中的图片通常通过重复相同的GOP结构来编码,因此对于在GOP结构中共享相同位置的图片重复相同的RPS信息。

为了利用冗余并降低总体比特成本,可以在 SPS 中发送一些 RPS 语法在 slice header 中引用。 RPS的短期图像部分是相对于当前图像的POC值进行编码的。 这使得可以将多个短期RPS部分存储在SPS中的列表中,并且仅在 slice header 中用信号发送列表索引。 该列表可以包含用于GOP结构中的每个画面位置的一个RPS。 然后,SPS 中的 RPS 数量将等于 GOP 长度,并且每个片将仅需要发送列表索引以便用信号通知其短期图像部分。

发送每个GOP位置的RPS信息可能需要SPS中相对较多的比特,比如对于一些GOP结构存在数百比特。 为了节省 SPS 中的比特,HEVC 中可以选择使用 RPS 预测。 这要求以 GOP 解码顺序发送 RPS,并利用每个 RPS 与前一个类似的事实。 可以按照解码顺序将图片参考从一个RPS移除到下一个RPS,但是相对于前一RPS最多只能添加一张新图片。 HEVC 中的 RPS 预测机制正在利用此属性,相对于显式信令,可以实现 SPS 中 RPS 比特数最多减少 50%。

4 Reference Picture Lists

当P或B slice的 slice header 已被解码时,解码器为当前slice设置参考图片列表。 有两个参考图片列表,L0和L1。 L0 用于 P 和 B slice,而 L1 仅用于 B slice。 图2.16示出了使用五个参考图片进行预测的图片B7的参考图片列表示例。

首先,构建 L0 和 L1 的临时列表。 临时列表L0以RPS列表RefPicSetStCurrBefore中的图片开始,按POC值降序排序。 这些都是可供当前图片参考并且具有小于当前图片的POC值的POC值的短期图片。 因此图2.16中图片B7的临时列表L0以P1开始,随后是B2。 接下来是按 POC 值升序排序的 RPS 列表 RefPicSetStCurrAfter 中的图片,即图 2.16 中的 B6 和 P5。 最后添加可用的长期图片,因此图片I0是最后添加的图片。 图片B7的最终临时列表L0则为{P1、B2、B6、P5、I0}。 临时列表L1的设置与L0类似,但是RefPicSetStCurrBefore和RefPicSetStCurrAfter的顺序交换。 因此,图片B7的临时列表L1变为{B6、P5、P1、B2、I0}。

临时列表用于构建最终的L0和L1列表。L0和L1的长度在PPS中用信号发送,但可以被slice header 中的可选码字覆盖。列表的最大长度为15。如果L0或L1的指定长度分别小于临时列表L0或L2的长度,则通过截断相应的临时列表来构建最终列表。如果指定的长度大于相应临时列表的长度,则从临时列表中重复图片。例如,如果长度为2,L0等于{P1、B2},如果长度是9,则等于{P1,B2、B6、P5、I0、P1、B2、B6、P5}。启用比临时列表长的列表的原因是加权预测,以使不同的加权因子能够应用于相同的参考图片。

从临时列表构建 L0 和 L1 的另一种方法是通过显式列表信令。 在这种情况下,列表中的每个条目都有一个代码字,用于指定要使用临时列表中的哪个图片。 因此,如果指定L0的长度等于3,则有3个码字用于指定L0。 例如,如果这三个码字等于1、1、0,则当P1是临时列表L0中的第一图片且B2是第二图片时,列表L0变为{B2、B2、P1}。

最终列表L0和L1用于运动补偿。 对于单预测,为块指示一个运动向量和一个参考图片索引。 例如,如果块的信号索引等于 0,则 L0 中的第一张图片是用于运动补偿的图片。

二值化过程(Binarization process)

VVC标准中使用了4种二值化方案:截断莱斯码(truncated Rice (TR)), 截断二进制码(truncated binary (TB)), k阶指数哥伦布码(k-th order Exp-Golomb (EGk)), 定长码(fixed-length (FL))。

TR码

TR码由前缀和后缀组成,后缀可能不存在。TR码有3个输入,分别是:语法元素值 symbolVal、门限值 cMax 和莱斯参数 cRiceParam。语法元素值 symbolVal 的前缀值 prefixVal 定义如下:

prefixVal = symbolVal >> cRiceParam

如果 prefixVal 小于 cMax >> cRiceParam,则前缀码由 prefixVal 个1和0组成。下表给出了这种一元二值化的码表:

如果 prefixVal 大于等于 cMax >> cRiceParam,则前缀码由 cMax >> cRiceParam 个1组成。

cMax 大于 symbolVal cRiceParam 大于0时,TR码的后缀存在,后缀值推导如下:

suffixVal = symbolVal − ( prefixVal << cRiceParam )

TR 码的后缀通过定长码(FL)为 suffixVal 进行二值化,cMax 值等于 ( 1 << cRiceParam ) − 1。

TB码

TB码的输入有两个,分别为语法元素值 synVal 和门限值 cMax。TB码的推导过程如下:

n = cMax + 1
k = Floor( Log2( n ) )
u = ( 1 << ( k + 1 ) ) − n

如果 synVal 小于 u,则通过定长码(FL)为 synVal 进行二值化,其中 cMax 值等于 ( 1 << k ) − 1。否则,通过定长码(FL)为 (synVal+u) 进行二值化来导出TB码,其中 cMax 值等于(1<<(k+1))−1。

k阶指数哥伦布码

k阶指数哥伦布码包括前缀码和后缀码,输入为语法元素值 symbolVal 和阶数 k,输出的二进制串定义如下,其中函数put(X)是将X放在二进制串的末尾。

absV = Abs( symbolVal )
stopLoop = 0
do
if( absV >= ( 1 << k ) ) {
put( 0 )
absV = absV − ( 1 << k )
k++
} else {
put( 1 )
while( k−− )
put( ( absV >> k ) & 1 )
stopLoop = 1
}
while( !stopLoop )

下表给出了0阶指数哥伦布码的前缀码和后缀码

下表给出了无符号0阶指数哥伦布码,对应语法元素表中的ue(v)

对于有符号的0阶指数哥伦布码,对应语法元素表中的se(v),VVC标准中采用了映射的方法,映射表为:

FL码

FL 码是通过使用符号值 symbolVal 的固定长度位无符号整数二进制串来构造的,其中fixedLength = Ceil( Log2( cMax + 1 ) )。 FL 码的 bin 索引使得 binIdx = 0 与最高有效位相关,并且 binIdx 的值向最低有效位递增。

深入理解 Affine-Motion Compensation

1 The six-parameter affine motion model

H. Huang, J. Woods, Y. Zhao, and H. Bai, “Control-point representation and differential coding affine-motion compensation,” IEEE Transactions on Circuits and Systems for Video Technology, vol. 23, no.10, pp.1651–1660, Oct. 2013.

2D 仿射变换被描述为(1-1):

\(\left\{\begin{array}{l}x^{\prime}=a x+b y+e \\ y^{\prime}=c x+d y+f\end{array}\right.\)

其中\((x, y)\)和\(\left(x^{\prime}, y^{\prime}\right)\)分别是当前帧和参考帧中的一对对应位置,\(a 、b、c、d、e\) 和 \(f\) 是仿射参数。 令 \((v_{x}, v_{y})=\) \((x-x^{\prime}, y-y^{\prime})\)为当前帧中位置 \((x, y)\) 的运动,则可以得出(1-2)

\(\left\{\begin{array}{l}
v_{x}=(1-a) x-b y-e \\
v_{y}=(1-c) x-d y-f
\end{array}\right.\)

这称为仿射运动模型(affine motion model)。 与传统的块匹配算法相比,允许参考帧中的匹配块扭曲或变形(warped or deformed),以获得更好的运动补偿预测。 代价是传输更多的运动参数,每块 6 个而不是 2 个。 在视频编码中,运动参数作为辅助信息传输,在较低的总比特率下可能很重要。 因此已经发现,该运动信息的压缩对于提高整体编码性能是至关重要的。 由此产生的运动参数数量的增加可能会抵消仿射运动补偿的优点。因此,运动参数的有效编码对于仿射运动模型变得更加关键。

给定一个大小为S × S的正方形区域\(\chi\),设置坐标系如图2所示。顶部和左侧3个角,即\((0,0),(S,0)\)和\( (0,S)\)可以作为控制点,记为\((x_i,y_i),i=0,1,2\)。设它们的平移运动向量为\(\vec{v_i}=(v_{x_i}, v_{y_i}),i=0,1,2\)。矩阵\(\mathbf{V} =(\vec{v}_0,\vec{v}_1,\vec{v}_2)\)称为 affne运动矩阵。 使 \(\chi\) 变形的位移控制点为 \((x_i^{\prime},y_i^{\prime})=(x_i-v_{x_i},y_i-v_{y_i})\)。用\((v_{x_i},v_{y_i})\) 和 \( (x_i,y_i)\)代入(1-2)中的 \((v_x,v_y)\)和\((x,y)\),我们将有六个方程,其中有六个未知数 \(a,b,c,d,e\) 和 \(f\)。 求解这些方程,我们有(1-3):

\(\left\{\begin{array}{l}a=1-\frac{v_{x_1}-v_{x_0}}S,b=\frac{v_{x_0}-v_{x_2}}S,e=-v_{x_0}\\c=1-\frac{v_{y_1}-v_{y_0}}S,d=\frac{v_{y_0}-v_{y_2}}S,f=-v_{y_0}\end{array}\right.\)

通过 (1-2) 和 (1-3),我们得到(1-4):

\(\left\{\begin{array}{l}v_x=\frac{v_{x_1}-v_{x_0}}Sx+\frac{v_{x_2}-v_{x_0}}Sy+v_{x_0}\\v_y=\frac{v_{y_1}-v_{y_0}}Sx+\frac{v_{y_2}-v_{y_0}}Sy+v_{y_0}\end{array}\right.\)

在解码器端接收仿射运动矩阵V,\(\chi\)上的运动场可由(1-4) 导出。

2 The four-parameter affine motion model

L. Li, H. Li, D. Liu, Z. Li, H. Yang, S. Lin, H. Chen, and F. Wu, “An Efficient Four-Parameter Affine Motion Model for Video Coding,” IEEE Transactions on Circuits and Systems for Video Technology, Apr. 2017.

仿射运动模型可以表征平移、旋转、缩放、错切(translation, rotation, zooming, shear mapping)等。 然而,日常视频中最常见的运动仅包括六种典型摄像机运动(camera track, boom, pan, tilt, zoom, and roll)和典型物体运动(平移、旋转和缩放),其中六模型参数超出必要。 需要说明的是,这里的旋转是指物体在与相机平行的二维平面内旋转。 此外,只有当物体与相机之间的相对距离保持不变或者物体具有平坦表面时,才可以使用仿射运动模型来表征物体缩放。 由于本文关注的是局部仿射模型,因此只要局部块足够小,我们就可以假设局部块具有平坦的表面。 下面,将以典型的物体运动为例来解释所提出的四参数仿射运动模型的物理解释。

事实上,如图1(a)所示,如果只需要对旋转和平移的组合进行表征,则同一像素变换前后的坐标关系可以描述为(2-2):

\(\left\{\begin{array}{l}x’=\cos\theta\cdot x+\sin\theta\cdot y+c\\y’=-\sin\theta\cdot x+\cos\theta\cdot y+f\end{array}\right.\)

其中 θ 是旋转角度。 此外,如图1(b)所示,如果仅表征缩放和平移的组合,则其关系可以描述为(2-3):

\(\left\{\begin{array}{c}x’=\rho\cdot x+c\\y’=\rho\cdot y+f\end{array}\right.\)

其中 ρ 分别是 x 和 y 方向上的缩放因子。

旋转/缩放和平移的组合都需要三个参数来表征。 如果我们将旋转、缩放和平移结合在一起,则需要四个参数,其关系可以描述为(2-4):

\(\left\{\begin{array}{l}x^{\prime}=\rho\cos\theta\cdot x+\rho\sin\theta\cdot y+c\\y^{\prime}=-\rho\sin\theta\cdot x+\rho\cos\theta\cdot y+f\end{array}\right.\)

如果我们将 ρcos θ 和 ρsin θ 替换为 (1 + a) 和 b,则 (2-4) 可以重写为(2-5):

\(\left\{\begin{array}{l}MV_{(x,y)}^h=x^{\prime}-x=ax+by+c\\MV_{(x,y)}^v=y^{\prime}-y=-bx+ay+f\end{array}\right.\)

其中\(MV_{(x,y)}^h\)和\(MV_{(x,y)}^v\) 是位置 (x, y) 的 MV 的水平和垂直分量。 等式(2-5)是采用的四参数仿射运动模型,能够精确表征旋转、缩放和平移的组合。

六参数仿射运动模型与四参数仿射运动模型进行比较,可以明显看出,在四参数仿射运动模型下,每个块计算的参数较少,因此解码复杂度可以稍微降低。 此外,由于稍后将介绍的快速仿射ME算法可以在四参数仿射运动模型下更快地收敛,因此编码复杂度也将降低。 由于标头信息的比特节省,四参数仿射运动模型可以为大多数自然序列带来更好的 R-D 性能。

根据式(2-5),有四个未知模型参数。 除了这四个参数,我们还可以使用两个MV来等效地表示模型,因为使用MV更符合现有的视频编码框架。 可以在当前块的任何位置选择这两个 MV 来表示运动模型。 我们选择当前块的左上和右上位置的MV,因为这两个位置与先前重建的块相邻,并且可以更准确地预测对应的MV。 在如图 2 所示的典型 S × S 块中,如果我们将左上角像素 (0, 0) 的 MV 表示为 MV0,将右上像素 (S − 1, 0) 的 MV 表示为 MV1,则模型参数a、b、c、f可根据式(5)求解如下(2-6):

\(\left\{\begin{array}{ll}a=\frac{MV_1^h-MV_0^h}{S-1}&c=MV_0^h\\b=-\frac{MV_1^v-MV_0^v}{S-1}&f=MV_0^v\end{array}\right.\)

3 Affine Motion Eestimate

下面给出VTM中4参数和6参数Affine ME的迭代步骤:

步骤1:寻找ME过程的初始CPMV。 测试若干个候选的CPMV组合,对于每个候选,通过将CPMV设置为候选MV来执行当前块的AMC,然后计算AMC预测块的SATD成本。 初始CPMV候选设置为等于提供最小 SATD 成本的候选者。

步骤2:从仿射参数Pi推导Pi+1。那么当使用Pi+1进行AMC时当前块的总误差可以计算为(3-3)

\(D=\sum_{(x,y)\in B}\lVert org(x,y)-ref(W(x,y,P_{i+1}))\rVert^2\)

其中对于6参数模型(3-4):

\(\left.W=\left[\begin{array}{c}ax+by+e\\cx+dy+f\end{array}\right.\right]=\left[\begin{array}{cccccc}x&0&y&0&1&0\\0&x&0&y&0&1\end{array}\right]\left[\begin{array}{ccccc}a&c&b&d&e&f\end{array}\right]^T\)

对于6参数模型(3-5):

\(\left.W=\left[\begin{matrix}ax+by+e\\-bx+ay+f\end{matrix}\right.\right]=\left[\begin{matrix}x&y&1&0\\-y&x&0&1\end{matrix}\right]\left[\begin{array}{cccc}a&b&e&f\end{array}\right]^T\)

将(3-3)改写为(3-6):

\(D=\sum_{(x,y)\in B}\lVert org(x,y)-ref(W(x,y,P_i+\Delta P))\rVert^2\)

将\(ref(W(x,y,P_i+\Delta P))\)泰勒展开得(3-7):

\(ref(W(x,y,P_i+\Delta P))\approx ref(W(x,y,P_i))+\frac{\partial ref}{\partial W}\frac{\partial W}{\partial P}\Delta P\)

其中\(\frac{\partial ref}{\partial W}\)为参考图像对位置的导数,即梯度\(\begin{matrix}G=[G_x&G_y]\end{matrix}\); \(\frac{\partial W}{\partial P}\)可以由(3-4)(3-5)推导,因此对于6参数模型(3-8):

\(Q(x,y)=\frac{\partial ref}{\partial W}\frac{\partial W}{\partial P}=\begin{matrix}[xG_x&xG_y&yG_x&yG_y&G_x&G_y]\end{matrix}\)

对于4参数模型(3-9):

\(Q(x,y)=\frac{\partial ref}{\partial W}\frac{\partial W}{\partial P}=\begin{matrix}[xG_x-yG_y&yG_x+xG_y&G_x&G_y]\end{matrix}\)

带入(3-8)或(3-9)到(3-6)中得(3-9):

\(D=\sum_{(x,y)\in B}\lVert E(x,y)-Q(x,y)\Delta P\rVert^2\)

其中\(E(x,y)=org(x,y)-ref(W(x,y,P_i)\)。

求D的最小值即可得到\(\Delta P\),我们对D求导并令导数为0得(3-10):

\(Q^TQ\Delta P=Q^TE\)

其中Q是尺寸为m×6或者m×4的矩阵,E是长度m×1的向量,m是CU内的像素总数。使用高斯消元法可得到\(\Delta P\),则可推导出\(P_{i+1}=P_i+\Delta P\)。

步骤3:使用\(P_{i+1}\)执行当前块的AMC,然后计算AMC预测块的SATD成本C(i+1)。 如果 C(i+1) < C*,则 C*设置为等于 C(i+1)

步骤4:若\(P_{i+1}\)与\(P_i\)不相同且i小于最大迭代次数,则设i=i+1,转步骤2; 否则,终止ME过程。

HEVC码流中的NALU

翻译自 High Efficiency Video Coding (HEVC) Algorithms and Architectures

HEVC的“high-level syntax”部分包括适用于位流的一个或多个完整Slice或图像的高级信息的信令。例如,高级语法表示视频的空间分辨率,使用了哪些编码工具,并描述了比特流的随机访问功能。除了语法元素的信号外,与语法元素相关的高级工具解码过程也被认为包含在标准的高级语法部分中。示例高级语法解码过程包括参考图片管理和解码图片的输出。

图2.1显示了一个HEVC编码器和解码器。输入图片被送入编码器,编码器将图片编码成比特流。HEVC位流由称为网络抽象层(NAL)单元的数据单元序列组成,每个单元包含一个整数字节。NALU的前两个字节构成NALU头,而NALU的其余部分包含有效载荷数据。一些NALU携带参数集,其中包含应用于一张或多张完整图片的控制信息,而其他NALU携带单个图片中的编码样本。

NALU由解码器解码以产生解码器输出的解码图像。编码器和解码器都将图片存储在已解码的图片缓冲区(DPB)中。这个缓冲区主要用于存储图片,以便以前编码过的图片可以用来生成预测信号,在编码其他图片时使用。这些存储的图片称为参考图片(reference pictures)。

The NAL Unit Header and the HEVC Bitstream

在HEVC中有两类NALU——视频编码层(VCL) NALU和非VCL NALU。每个VCL NALU携带编码图像数据的一个Slice段,而非VCL NALU包含通常与多个编码图像相关的控制信息。一个编码图像,连同与编码图像相关联的非VCL NALU,称为HEVC访问单元(access unit, AU)。没有要求AU必须包含任何非VCL NALU,并且在一些应用程序(如视频会议)中,大多数AU不包含非VCL NALU。但是,由于每个AU都包含一个编码图像,因此它必须由一个或多个VCL NALU组成——编码图像被分割成的每个Slice对应一个单元。

The NAL Unit Header

图2.2显示了NALU Header的结构,它有两个字节长。所有HEVC NALU Header,对于VCL和非VCL NALU,都从这个两字节的NALU头开始,旨在使解析NALU的主要属性变得容易;它是什么类型,以及它属于什么层和时态子层。

NALU报头的第一个位总是设置为’0’,以防止在遗留的MPEG-2系统环境中产生可能被解释为MPEG-2启动代码的位模式。接下来的六位包含NALU的类型,标识NALU中携带的数据类型。6位表示有64种可能的NAL单位类型值。这些值在VCL和非VCL NALU之间平均分配,因此它们各有32种类型。

下面的6位包含一个层标识符,表示NALU属于哪个层,用于将来的可扩展和分层扩展。虽然第一版HEVC于2013年6月发布,支持时间可扩展性,但它不包括任何其他可扩展或分层编码,因此第一版中所有NALU的层标识符(层ID)始终设置为“00000o”。在以后的HEVC版本中,层ID有望用于识别NAL属于哪个空间可扩展层、质量可扩展层或可扩展多视图层。

NALU Header的最后三位包含NALU的时间标识符,表示七个可能的值,其中一个值是禁止的。HEVC中的每个AU都属于一个时间子层,由时间ID表示。由于每个AU属于一个时间子层,所有属于同一AU的VCL NALU在其NALU头中必须具有相同的时间ID。

图2.3显示了编码视频序列中图片的两个不同示例参考结构,两个时态子层都对应于0和1的时态ID值。Slice类型在图中使用I、P和B表示,箭头显示了图片如何参考其他图片。例如,图2.3a中的图B2是使用bi-prediction的图片,参考图片Io和Pl。

VCL NAL Unit Types

表2.1显示了所有32个VCL NALU类型及其NALU类型(图2.2中的NALType)在NAL单元头中的值。所有VCL相同AU的NALU必须具有相同的NALU类型值,该值定义了AU的类型及其编码图。例如,当一个AU的所有VCL NALU的NALU类型都等于21时,则该AU称为CRA AU,编码图像称为CRA图像。在HEVC中有三种基本类型的图像: intra random access point (IRAP) pictures, leading pictures, and trailing pictures.

IRAP Picture

IRAP图像类型由NAL单元类型16-23组成。这包括IDR, CRA和BLA图片类型以及类型22和23,这些类型目前保留供将来使用。所有IRAP图片必须属于时间子层0,并且不使用任何其他图片的内容作为参考数据进行编码(即仅使用intra编码)。注意,但未标记为IRAP的帧内编码图片允许在比特流中出现。IRAP图片类型用于在比特流中提供可能开始解码的点。因此,IRAP图片本身不允许依赖于比特流中的任何其他图片。

比特流的第一张图片必须是IRAP图片,但在整个比特流中可能有许多其他IRAP图片。IRAP图像还提供了切换比特流的可能性,例如当开始观看TV或从一个TV频道切换到另一个频道时。IRAP图片还可以用于在视频内容中实现时间定位——例如,通过使用视频播放器的控制条来移动视频程序中的当前播放位置。最后,IRAP图片还可以、用于在压缩域中从一个视频流无缝切换到另一个视频流。这被称为比特流切换或拼接,它可以发生在两个直播视频流之间,一个直播流和一个存储的视频文件之间,或者两个存储的视频文件之间。从IRAP图像解码并按输出顺序输出任何后续图像始终是可能的,即使按解码顺序在IRAP图像之前的所有图像都从比特流中丢弃。

当为存储和稍后的播放或广播应用程序编码内容时,IRAP图片通常均匀分布,以在整个比特流中提供相似频率的随机接入点。在实时通信应用中,随机访问功能不是那么重要,或者发送IRAP图片所需的相对大量的比特是一个显著的负担,会增加通信延迟,IRAP图片可能很少发送,或者只有当一些反馈信号表明视频数据已经损坏,需要刷新场景时才发送。

Leading and Trailing Pictures

leading picture 是在解码顺序上跟随特定IRAP图并在输出顺序上先于它的图。trailing picture 是在解码顺序和输出顺序上都遵循特定IRAP图片的图片。图2.4展示了leading picture和trailing picture 的例子。在解码顺序上,leading picture和trailing picture 被认为与最接近的IRAP图像相关联,如图2.4中的I1图像。trailing picture必须使用trailing picture NAL单元类型0-5中的一种。特定IRAP图片的trailing picture不允许依赖于任何leading picture或之前IRAP图片的 trailing picture;相反,它们只能依赖于相关的IRAP图片和同一IRAP图片的其他 trailing picture 图片。此外,IRAP图像的所有leading picture必须在解码顺序中位于与同一IRAP图像相关联的所有trailing picture之前。这意味着关联图片的解码顺序总是:(1)IRAP图片,(2)关联的leading picture(如果有),然后(3)关联的trailing picture(如果有)。

HEVC的trailing picture有三种类型:时间子层访问(TSA)图像、逐级时间子层访问(STSA)图像和普通trailing picture (TRAIL)。

Temporal Sub-layer Access (TSA) Pictures

TSA图像是显示时间子层切换点的trailing picture。TSA图片类型只能用于没有在解码顺序中TSA图片之前的,其时间ID大于或等于TSA图片本身的时间ID,用于预测TSA的图片或用于预测与TSA图片在相同或更高的时间子层中的任何后续(解码顺序)图片。例如,图2.5中的图片P6可以使用TSA图片类型,因为只使用时间子层0中的前一张图片来按照解码顺序预测TSA图片本身和后续图片。

当解码器解码位流中时间子层的子集并且遇到时间子层的TSA图像类型正好在它正在解码的最大时间子层之上时,解码器可以切换到并解码任意数量的附加时间子层。对于图2.5中的示例,仅对TSA 图像的时间子层0进行解码的解码器(1)只解码时域子层0,(2)决定开始解码时域子层1和子层0,或(3)开始解码所有三个子层。对于只转发最低的时间子层的网络节点(例如由于先前的网络拥塞情况),可能会有类似的动作。网络节点可以检查具有时间ID等于1的传入图片的NAL单元类型。这并不需要大量的计算资源,因为NAL单元类型和时间ID可以在NAL单元头中找到,并且很容易解析。当遇到时序子层1的TSA图像时,网络节点可以切换到按照解码顺序转发继TSA图像之后的任何时序子层图像,而不会存在解码器由于没有所有必要的参考图像而无法正确解码的风险。

Step-wise Temporal Sub-layer Access (STSA) Pictures

STSA图片类型与TSA图片类型类似,但它只保证STSA图片本身以及在解码顺序上与它后面的STSA图片具有相同时间ID的图片不参考在解码顺序上与它前面的STSA图片具有相同时间ID的图片。因此,STSA图像可以用来标记比特流中可以切换到具有与STSA图像相同时间ID的子层的位置,而TSA图像可以标记比特流中可以切换到任何更高子层的位置。图2.5中STSA图的一个例子是图P2。这张图片不可能是TSA图片,因为P3参考了P1。但图片P2可以是STSA图片,因为P6没有参考任何子层1的图片,解码顺序在P2之后的任何子层1的图片也没有参考解码顺序在P2之前的任何子层1的图片。TSA和STSA图片的时间ID都必须大于0。

还要注意的是,由于在HEVC中禁止从较高的时间子层预测到较低的时间子层,因此在任何图片上总是有可能向下切换到较低的时间子层,无论图片类型或时间子层。

Ordinary Trailing (TRAIL) Pictures

普通trailing图片用枚举类型TRAIL表示。trailing图片可以属于任何时间子层。它们可以参考关联的IRAP图片和与同一IRAP图片关联的其他trailing图片,但它们不能参考leading图片(或与同一IRAP图片关联的任何其他非trailing图片的图片)。在按解码顺序输出下一张IRAP图片后,它们也不能输出。请注意,所有TSA和STSA图片都可以标记为TRAIL图片,所有TSA图片都可以标记为STSA图片。然而,为了表明比特流中存在的所有可能的时间子层切换点,建议跟踪图片应使用最具限制性的类型。

Instantaneous Decoding Refresh (IDR) Pictures

IDR图像是一个完全刷新解码过程并开始一个新的CVS的帧内图像。这意味着,无论是IDR图像还是按解码顺序在IDR图像之后的任何图像,都不能依赖于按解码顺序在IDR图像之前的任何图像。IDR图像有两种子类型,IDR_W_RADL类型可能有关联的随机访问可解码leading(RADL)图像,IDR_N_LP类型没有任何leading图像。请注意,即使IDR图片没有任何leading图片,编码器也允许(但不推荐)使用IDR_W_RADL类型。但是,禁止将IDR_N_LP类型用于具有先导图像的IDR。使用两种不同的IDR图片类型的原因是为了使系统层能够在随机访问时知道IDR图片是否是要输出的第一个图片。IDR图像的POC值总是等于零。因此,与IDR图像相关联的leading图像(如果有的话)都具有负的POC值。

Clean Random Access (CRA) Pictures

CRA图片是一个帧内图片,与IDR图片不同,它不会刷新解码器,也不会开始一个新的CVS。这使得CRA图像的leading图像以解码顺序依赖于在CRA图像之前的图像。允许这样的leading图像通常使包含CRA图像的序列比包含IDR图像的序列更具压缩效率(约6%)。

CRA图像的随机访问是通过对CRA图像进行解码,其leading图像在解码顺序上不依赖于CRA图像之前的任何图像,以及在解码和输出顺序上都遵循CRA的所有图像。注意,CRA图片不一定有关联的leading图片。

Random Access Decodable Leading (RADL) and Random Access Skipped Leading (RASL) Pictures

leading图像必须使用RADL或RASL NAL单元类型发出信号。RADL和RASL图片可以属于任何时间子层,但不允许被任何trailing图片引用。RADL图片是保证在对关联的IRAP图片进行随机访问时可解码的leading图片。因此,RADL图片只允许引用关联的IRAP图片和同一IRAP图片的其他RADL图片。

当从关联的IRAP图像执行随机访问时,RASL图像是可能无法解码的先导图像。图2.6显示了两张RASL图片,由于图片P2在解码顺序上位于CRA图片之前,因此这两张图片都是不可解码的。由于其在解码顺序上的位置,在CRA图片的位置随机访问不会解码P2图片,因此无法解码这些RASL图片并将其丢弃。尽管对于可解码的leading图,如图2.6中的RADL图,不禁止使用RASL类型,但为了更加网络友好,建议在可能的情况下使用RADL类型。只有其他RASL图片被允许依赖于一个RASL图片。这意味着依赖于RASL图像的每个图像也必须是RASL图像。RADL和RASL图像可以按解码顺序混合,但不能按输出顺序混合。RASL图片在输出顺序上必须在RADL图片之前。

IDR_W_RADL图片的所有前导图片必须是可解码的,并且使用RADL类型。RASL图片不允许与任何IDR图片相关联。CRA图可能同时具有关联的RADL和RASL图,如图2.6所示。允许RASL图片引用在相关IRAP图片的IRAP图片,也可以参考在解码顺序中遵循该IRAP图片的其他图片,但不能引用解码顺序中更早的图片。图2.6中的RASL图片无法引用图片P0。

在HEVC中有三个约束,目的是在执行随机访问时消除图像输出不均匀。其中两个约束依赖于为每张图片设置的变量PicOutputFlag,它指示图片是否输出。当一个被称为pic_output的标志出现在slice header中并且等于0时,或者当当前图片是RASL图片并且关联的IRAP图片是CVS中的第一张图片时,这个变量被设置为0。否则,PicOutputFlag被设置为1。

第一个约束是,任何具有PicOutputFlag等于1的图片,在解码顺序上位于IRAP图片之前,必须在输出顺序上位于IRAP图片之前。图2.7a中的结构不受此约束,因为图像P1在解码顺序上位于CRA之前,但在输出顺序上位于它之后。如果允许这样做,并且对CRA图片进行随机访问,则会丢失图片P1,导致输出不均匀。

第二个约束是,任何具有PicOutputFlag等于1的图像,在解码顺序上位于IRAP图像之前,必须在输出顺序上位于与IRAP图像相关联的任何RADL图像之前。一个引用结构,即如图2.7b所示,因为P1在I2之前,但在输出顺序上在P3之后。如果允许这种引用结构,并且对CRA图片进行随机访问,则缺少P1图片将导致输出不均匀。

第三个约束是所有RASL图片必须在输出顺序上先于任何RADL图片。由于RASL图片在随机访问时被丢弃,而RADL图片没有被丢弃,因此在RADL图片之后显示的任何RASL图片在随机访问时可能会导致输出不均匀。

其他类型用的比较少,这里就不写了。

HEVC Core Transform Design

翻译自论文 “Core Transform Design in the High Efficiency Video Coding (HEVC) Standard”

A. Use of Transforms in Block-Based Video Coding

在基于块的混合视频编码方法中,变换应用于帧间或帧内预测产生的残差信号,如图 1 所示。在编码器处,帧的残差信号被划分大小为\(N×N\)的方形块, 其中\(N=2^M\)和M是一个整数。 然后将每个残差块 (\(U\)) 输入到二维\(N×N\)正向变换。 通过分别对每行和每列应用\(N\)点一维变换,可以将二维变换实现为可分离变换。 然后对得到的变换系数(\(coeff\))进行量化(相当于除以量化步长\(Q_{step}\))以获得量化变换系数(\(level\))。 在解码器处,量化的变换系数随后被反量化(这相当于乘以\(Q_{step}\))。 最后,将二维可分离逆变换应用于反量化变换系数(\(coeff_Q\)),从而产生量化样本的残余块,然后将其添加到帧内或帧间预测样本以获得重建块。

通常,正向变换矩阵和逆变换矩阵是彼此转置的,并且被设计为在没有中间量化和去量化步骤的情况下连接时实现输入残差块的近乎无损重建。

在 HEVC 等视频编码标准中,指定了反量化过程和逆变换,而正向变换和量化过程则由实施者选择(受到比特流的约束)。 然而,在下文中,除非另有说明,我们将根据正向变换矩阵讨论 HEVC 核心变换的设计和属性。 逆变换在HEVC标准中被指定为对应的转置矩阵。

B. Discrete Cosine Transform

应用于输入样本\(u_i\)的\(N\)点 1D DCT 的变换系数\(w_i\)可以表示为:

这里\(i=0,…,N-1\),\(c_{ij}\)为DCT变换矩阵\(C\)的系数,定义如下:

这里\(i,j=0,…,N-1\), \(A = 1\) or \(2^{1/2}\) for \( i = 0 \) or \(i > 0\),DCT的基向量\(c_i\)定义为\(c_i=[c_{i0},…,c_{i(N-1)}]^T, i=0,…,N-1\)

DCT 具有多个被认为对于压缩效率和高效实现都有用的属性:

  1. 基向量是正交的,即\(c^T_ic_j=0\)。 该属性对于通过实现不相关的变换系数来提高压缩效率是理想的。
  2. DCT 的基向量已被证明可以提供良好的能量压缩,这对于压缩效率来说也是理想的。
  3. DCT 的基向量具有相等的范数,即\(c^T_ic_i=1\)。 该属性对于简化量化/去量化过程是有利的。 假设需要量化误差的相等频率加权,则基向量的相等范数消除了对量化/去量化矩阵的需要。
  4. 让\(N=2^M\)。 大小为\(2^M×2^M\) DCT 矩阵的元素是 \(2^{(M+1)}×2^{(M+1)}\) DCT 矩阵元素的子集。更具体地,较小矩阵的基向量等于较大矩阵的偶数基向量的前半部分。 此属性对于降低实施成本很有用,因为相同的乘法器可以重复用于各种变换大小。
  5. DCT矩阵可以通过使用少量的唯一元素来指定。 通过检查 (2) 的元素\(c_{ij}\),可以看出大小为 \(2^M×2^M\)的 DCT 矩阵中唯一元素的数量等于 \(2^M-1\)。
  6. DCT的偶数基向量是对称的,而奇数基向量是反对称的。 此属性对于减少算术运算的数量很有用。
  7. DCT 矩阵的系数具有一定的三角关系,可以减少算术运算的数量,超出利用(反对)对称性质所能实现的数量。

C. Finite Precision DCT Approximations

HEVC 的核心变换矩阵是 DCT 矩阵的有限精度近似。 在视频编码标准中使用有限精度的好处是,实值 DCT 矩阵的近似值在标准中指定,而不是依赖于实现。 这可以避免制造商使用略有不同的浮点表示实现 IDCT 所导致的编码器-解码器不匹配。 另一方面,使用近似矩阵元素的缺点是第 B 节中讨论的 DCT 的一些属性可能不再满足。 更具体地说,在与对矩阵元素使用高位深度相关的计算成本和满足第 B 节的一些条件的程度之间存在权衡。

确定 DCT 矩阵元素的整数近似值的一种直接方法是用某个大数(通常在\(2^5\)和\(2^{16}\)之间)缩放每个矩阵元素,然后舍入到最接近的整数。 然而,这种方法并不一定能产生最佳的压缩性能。 如第 D 节所示,对于给定的矩阵元素位深度,近似 DCT 矩阵元素的不同策略会导致第 B 节的一些属性之间的不同权衡。

D. HEVC Core Transform Design Principles

用于 HEVC 核心变换的 DCT 近似是根据以下原则选择的。 首先,B 部分的属性 4、5 和 6 得到满足,没有任何妥协。 这种选择确保了 DCT 的几个实现友好的方面得以保留。 其次,对于属性 1、2、3 和 7,在用于表示每个矩阵元素的位数和满足每个属性的程度之间存在权衡。

为了测量属性 1、2 和 3 的近似程度,为整数点 DCT 近似定义了以下测量,其缩放矩阵元素等于\(d_{ij}\),基向量\(d_i=[d_{i0},…,d_{i(N-1)}]^T, i=0,…,N-1\)

  1. 正交性度量:\(o_{ij}=d^T_id_j/d^T_0d_0, i≠j\)
  2. 与 DCT 的相似性度量:\(m_{ij}=|αc_{ij}-d_{ij}|/d_{00}\)
  3. 范数度量:\(n_i=|1-d^T_id_j/d^T_0d_0|\)

比例因子\(α\)定义为 \(d_{00}N^{1/2}\)。

经过仔细研究,决定用8位(包括符号位)来表示每个矩阵系数,并选择第一基向量的元素等于64(即 ,\(d_{0j}=64\))。 请注意,与正交 DCT 相比,这会导致 HEVC 变换矩阵的比例因子为\(2^{(6+M/2)}\)。 其余矩阵元素进行手动调整(在属性 4、5 和 6 的约束内),以实现属性 1、2 和 3 之间的良好平衡。手动调整执行如下。 首先,推导出实值缩放 DCT 矩阵元素\(αc_{ij}\) 。

接下来,对于结果矩阵中的每个唯一数字,检查\(αc_{ij}\)区间 [-1.5, 1.5] 周围的每个整数值,并计算 \(o_{ij}\)、 \(m_{ij}\)和 \(n_{ij}\)值。 由于变换矩阵中只有 31 个唯一数字(参见第 E 节),因此可以系统地检查各种排列(尽管不是详尽的)。 选择最终的整数矩阵元素是为了在所有测量 \(o_{ij}\)、 \(m_{ij}\)和 \(n_{ij}\)之间提供良好的折衷。 所得到\(o_{ij}\)、 \(m_{ij}\)和 \(n_{ij}\)的最坏情况值显示在表 I 的第二列中。范数被认为足够接近 1(即范数测量\(n_{ij}\)足够接近 0),以证明不使用非平坦默认值是合理的 HEVC 中的去量化矩阵(即所有变换系数均等缩放)。

为了比较的目的,表I的第三列中列出了将实值DCT矩阵元素与\(2^{(6+M/2)}\)相乘并舍入到最接近的整数时的结果测量。从表中可以看出,虽然HEVC变换的矩阵元素距离缩放后的DCT矩阵元素较远,但它们具有更好的正交性和范数性质。

最后,仅使用 8 位表示,B 节的属性 7(矩阵元素之间的三角关系)不容易保留。 作者不知道 HEVC 核心变换的任何三角特性可用于将算术运算数量减少到低于使用(反)对称特性时所需的数量。

E. Basis Vectors of the HEVC Core Transforms

指定32点正向变换的32×32矩阵的左半部分如图2所示。

右半部分可以通过使用基向量的(反)对称性质(B节的性质6)来导出。HEVC的逆变换矩阵被定义为图中矩阵的转置。32×32矩阵包含多达31个唯一数字,如下所示:

这些唯一数字是正向变换矩阵的第一列的元素1至31。请注意,尽管数字90出现了三次,但这是偶然的,通常不是真的。

此外,较小变换矩阵\(N=4,8,16\)的系数可以从 32×32 变换矩阵的系数导出为:

让\(D_4\)表示 4×4 变换矩阵。 利用式(4)和图2,\(D_4\)可得:

8×8 变换矩阵和16×16 变换矩阵可以类似地从32×32 变换矩阵获得,如图2所示,其中使用不同的颜色来突出显示嵌入的16×16、8×8 和4×4 正向变换矩阵。 此属性允许使用相同的架构来实现不同的变换大小,从而促进不同变换大小之间的硬件共享。

请注意,根据 (3) 的唯一数属性和(反对)对称属性,\(D_4\)也等于:

F. Intermediate Scaling

由于与正交DCT变换相比,HEVC矩阵是按比例\(2^{(6+M/2)}\)缩放的,并且为了通过正向和反向二维变换保持残差块的范数,需要应用额外的比例因子\(S_{T1}\)、\(S_{T2}\)、\(S_{IT1}\)、\(S_{IT2}\),如图3所示。请注意,图3基本上是图1中变换和量化的定点实现。虽然HEVC标准规定了逆变换的比例因子(即\(S_{IT1}\)、\(S_{IT2}\)),但HEVC参考软件也规定了前向变换的相应比例因子(例如\(S_{T1}\)、\(S_{T2}\))。比例因子是在以下限制条件下选择的:

  1. 所有缩放因子应为 2 的幂,以允许缩放以右移的方式实现。
  2. 假设输入残差块的全范围(例如,所有样本都具有最大幅度的DC块),每个变换阶段之后的位深度应等于16位(包括符号位)。 这被认为是准确性和实施成本之间的合理权衡。
  3. 由于HEVC矩阵按比例\(2^{(6+M/2)}\)缩放,2D正向变换和逆变换的级联将导致1D行正向变换、1D列正向变换、1D列逆变换和1D行逆变换中的每一个的按比例\(2^{(6+M/2)}\)缩放。因此,为了通过二维正变换和逆变换保持范数,所有比例因子的乘积应等于\((1/2^{(6+M/2)})^4=2^{-24}2^{-2M}\)。

图4以4×4正向变换为例说明了选择正向变换比例因子的过程。当视频具有B bit位深度时,残差将在需要(B+1)位来表示的范围\([-2^B+1,2^B-1]\)内。 在下面的worst case 位深度分析中,我们将假设残差块的所有样本的最大振幅等于正向变换第一阶段的输入。 我们相信这是一个合理的假设,因为所有基向量都具有几乎相同的范数。

还要注意,在worst case 分析中,我们使用\(-2^B\)而不是\(-2^B+1\)或\(2^B-1\),因为它是2的幂。由于所有比例因子都是2的幂,所以假设输入为\(-2^B\)(仍然适合(B+1)位),则比例因子推导变得更简单。对于这种worst case的输入块,输出样本的最大值将为\(-2^B×N×64\)。这对应于第一基向量(长度为N,所有值均等于64)与由等于\(-2^B\)的值组成的输入向量的点积。因此,对于\(N=2^M\),为了使输出适合于16位(即,\(2^{-15}\)的最大值)内,需要\(1/(2^B×2^M×2^6×2^{-15})\)的缩放。因此,第一变换阶段之后的比例因子被选择为\(S_{T1}=2^{-(B+M-9)}\)。

正向变换的第二阶段由第一变换阶段的结果与\(D^T_4\)相乘组成。正向变换第二阶段的输入是第一阶段的输出,该输出是一个矩阵,第一行中的所有元素都具有 \(2^{-15}\)的值。所有其他元素将为零,如图 4(b) 所示。 与\(D^T_4\)相乘的输出将是一个矩阵,其中仅 DC 值等于 \(2^M×2^6×2^{-15}\),所有其余值等于 0。这意味着第二阶段变换后所需的缩放为 \(S_{T2}=2^{-(M+6)}\),以便输出适合 16 位。

逆变换的第一阶段由正变换的结果与\(D^T_4\)相乘组成。逆变换第一阶段的输入是正变换的输出矩阵,该矩阵是仅 DC 元素等于\(2^{-15}\)的矩阵。 与\(D^T_4\)相乘的输出将是一个矩阵,其第一列元素等于 \(2^6×2^{-15}\)。因此,在逆变换的第一阶段之后,为了使输出适合 16 位,所需的缩放是\(S_{IT1}=2^{-6}\)。

逆变换的第二阶段包括将逆变换的第一阶段的结果与\(D_4\)相乘。 逆变换第二级的输入是逆变换第一级的输出矩阵,该矩阵的第一列元素等于\(2^{-15}\)。与\(D_4\)相乘的输出将是所有元素等于\(2^6×2^{-15}\)的矩阵。 因此,在第二阶段逆变换之后,为了使输出值进入 \([-2^B,2^B-1]\)的原始范围所需的缩放是 \(S_{IT1}=2^{-21-B}\)。

总之,本节中施加的约束会在不同的变换阶段后产生以下比例因子:

  • 在第一个正向变换阶段之后:\(S_{T1}=2^{-(B+M-9)}\)
  • 在第二个正向变换阶段之后:\(S_{T2}=2^{-(M+6)}\)
  • 在第一个逆变换阶段之后:\(S_{IT1}=2^{-6}\)
  • 在第二个逆变换阶段之后:\(S_{IT1}=2^{-21-B}\)
    其中B是输入/输出信号的位深度(例如 8 位), 是\(M=log2(N)\)。

在没有量化/去量化的情况下,比例因子的这种选择可确保在所有变换阶段之后的位深度为 16 位。 然而,量化/反量化过程引入的量化误差可能会将每个逆变换阶段之前的动态范围增加到超过 16 位。 例如,考虑 B = 8 和正向变换的所有输入样本等于 255 的情况。在这种情况下,正向变换的输出将是值等于 \(255<<7 =32640\) 的 DC 系数。对于高 QP 值并使用向上舍入量化器,每个逆变换级的输入很容易超出 [-32768, 32767] 允许的 16 位动态范围。 虽然在去量化器之后clip到 16 位范围被认为是微不足道的,但在第一个逆变换阶段之后被认为是不受欢迎的。 为了允许一定程度的量化误差,同时将两个逆变换级之间的动态范围限制为 16 位,逆变换比例因子的选择最终修改如下:

  • 在第一个逆变换阶段之后:\(S_{IT1}=2^{-7}\)
  • 在第二个逆变换阶段之后:\(S_{IT1}=2^{-20-B}\)

逆变换比例因子的使用如图 5 所示,以 4×4 逆变换为例,假设输入是图 4 的最终输出。

表 II 和表 III 分别总结了与正交 DCT 相比正向变换和反向变换的不同缩放因子。

HEVC规范指定在缩放之前添加的偏移值以进行舍入。 该偏移值等于比例因子除以 2。图 3-5 中未明确显示该偏移。

最后,使用 8 位系数并将中间数据的位深度限制为 16 位的两个有用结果是,所有乘法都可以用具有 16 位或更少的乘法器来表示,并且右移之前的累加器可以用更少的位来实现。 所有变换阶段都超过 32 位。

G. Quantization and De-Quantization

量化由除以量化步长(\(Q_{step}\))组成,逆量化由乘以量化步长组成。类似于H.264/AVC,量化参数(QP)用于确定HEVC中的量化步长。可以取从0到51的52个值。1的增加意味着量化步长增加了大约12%(即\(2^{1/6}\))。增加6导致量化步长增加2倍。除了指定两个连续QP值的步长之间的相对差之外,还需要定义与QP值的范围相关联的绝对步长。这是通过为\(Q_{step}=1\)选择QP=4来完成的。

正交变换的等效量化步长之间的结果关系现在由下式给出:

式(7)也可表示为:

HEVC 量化和反量化基本上是(8)的定点近似。 如图 3 所示,引入了额外的比例因子 \(S_Q\)和 \(S_{IQ}\),以恢复残差块的范数,该范数由于(8)的定点实现中使用的缩放而被修改。

HEVC 中 (8) 的定点近似由下式给出:

这导致

对于量化器输出、level,去量化器在 HEVC 标准中指定为:

这里\(shift1=(M-9+B)\), \(offset_{IQ}=1<<(M-10+B)\).

图3的比例因子\(S_{IQ}\)等于\(2^{-shift1}\),如下所示:当QP=4时,图3中的逆变换和反量化的组合比例在相乘时应产生1的乘积,以通过逆变换和逆量化保持残差块的范数,即

这导致\(S_{IQ}=2^{-(M-9+B)}\)导致\(shift1\)等于右移 \((M-9+B)\) 。 (13) 中的比例因子 D 从表 III 中获得。

对于正向变换的输出样本, coeff,可以按如下方式实现简单的量化方案:

这里\(shift2=29-M-B\),

注意 \(f_{QP\%6} \approx 2^{14}/G_{QP\%6}\)。shift2的值是通过对正向变换和量化器中的组合缩放施加类似的约束来获得的,如(13)中,即 \(S_Q×f_4×2^{(15-B-M)}=1\),这里\(S_Q=2^{-shift2}\).

最后,选择 \(offset_Q\)来实现所需的舍入。

总而言之,选择量化器乘法器\(f_i\)和去量化器乘法器\(g_i\)以满足以下条件

1) 确保\(g_i\)可以用有符号的 8 位数据类型表示。
2) 确保步长从一个QP值到下一个QP值的增加几乎相等(大约 12%)
3) 通过量化和反量化过程确保近似单位增益
4) 为QP=4提供所需的量化步长绝对值。

Sign Hiding

在HEVC中,对于每个变换单元,量化的变换系数以16个系数的组进行熵编码。在sign-hiding 的情况下,对于满足一定条件的系数组,沿扫描路径的第一非零系数的符号不在比特流中显式传输,而是从解码器处该系数组中的所有非零系数之和的奇偶性来推断。为了保证隐藏符号与所有非零系数之和的奇偶性匹配,编码器采用了基于率失真优化或失真最小化的奇偶性调整方法。

1 隐藏符号位的基本原理

在视频编码中,数据隐藏是一种已知的技术,通常用于将一个语法元素隐藏到部分比特流中以提供更好的编码效率。例如,用于帧内预测的MPM索引嵌入量化系数之和的奇偶性中;使用量化系数的奇偶性来传送变换模式的信息。

在将语法元素隐藏到其他语法元素的特定特征中时,必须考虑要隐藏哪个语法元素(源信号)以及要将其隐藏到哪里(目标信号)。为了产生率失真增益,必须考虑几个标准。

首先,从因果关系的角度来看,隐藏必须是可行的。在编码期间,当编码目标信号时源信号必须可用,在解码期间,必须在实际使用源信号之前解码目标信号。

其次,源信号必须代表压缩比特流中不可忽略的部分。从先前关于压缩数据隐藏的研究可知,隐藏数据时可以获得净增益,然而,以速率增益度量,它将仅表示隐藏信号的总速率的一部分。因此,如果数据隐藏要产生任何有价值的增益,则源信号应代表压缩比特流的相当大的比例。

此外,如果用于嵌入信息的目标和源信号特征不相关,则为了从隐藏源信号中获得最大增益,必须考虑其分布条件。当将二进制元素隐藏到目标信号的二进制函数中(例如将位隐藏到奇偶校验中)时,在大约50%的情况下,目标信号将需要改变。因此,由这种变化引起的率失真损失是近似恒定的,并且与源信号的分布无关:例如,隐藏始终等于0的二进制语法元素会导致相同的损失,因为奇偶校验仍然必须改变一半的时间。另一方面,当源信号在比特流中编码时达到其每符号1比特(在二进制元素的情况下)的编码上限时,通过省略源信号的传输而获得的增益是最大的。因此,必须为源信号选择一个等概率语法元素。

目标信号的一个重要条件在于能够找到对率失真性能影响最小的变化,因为在大约一半的情况下需要进行变化。因此,最好使用较大的目标信号,从而增加发现这种变化的机会。例如,将二元元素隐藏到单个量化系数的奇偶性中将在需要改变该系数时产生高率失真损失,当将其隐藏在较大的量化系数集合的和的奇偶性中时,增加了找到非常接近其量化区间边界并且其中量化值的改变将导致中等率失真损失的量化系数的机会。­

最后,希望源信号和目标信号大致成比例:当有更多的数据要隐藏时,应该有更多的目标信号嵌入其中。

量化系数可以做sign-hiding的原因:

  • 在编码器中,当要隐藏的符号已知时,可以修改量化系数的电平,并且在解码器中,在符号比特实际用于解码信号之前,可以使用这些电平。
  • 符号位代表压缩比特流的很大一部分(在HEVC通用测试条件下为15%到20%)。
  • 符号位是等概率的。
  • 在一组量化系数中嵌入符号位可以提供足够的机会找到一个量化系数,该量化系数在修改时会导致中等的率失真损失。
  • 当有更多的符号位要隐藏(源信号)时,有更多的剩余数据(目标信号)要嵌入它们。

2 量化和系数编码

可参考文章:

率失真优化量化 (Rate-Distortion Optimized Quantization RDOQ) – 我受到了惊吓 (mmedia-t.cn)

变换系数编码 – 我受到了惊吓 (mmedia-t.cn)

3 Sign Hiding

3.1 在解码器处推断隐藏符号位

对于每个CG,解码器统计沿扫描路径的第一个非零系数和最后一个非零系数之间的系数数目,并将其与阈值进行比较。如果超过阈值,则从所有非零系数之和的奇偶性推断第一个非零系数的符号。特别地,如果和为偶数,则该符号被推断为正;如果和为奇数,则该符号被推断为负。在这种情况下,不需要为该系数显式传输符号位。否则,以常规方式(每个符号1位)从比特流解码符号位。稍后在讨论编码器侧奇偶校验调整方案时,将详细说明应用这种标准以排除某些CG应用符号隐藏技术的原因。

上述多个符号位隐藏的解码处理针对一个TU内的每个CG重复。由于每个CG具有16个系数,因此对于一个4×4、8×8、16×16或32×32 TU,可分别隐藏多达1、4、16或32个符号位。­

3.2 编码器中隐藏符号位

为了避免失配,编码器执行与解码器相同的检查,以比较具有相同阈值的每个CG的第一非零系数和沿扫描过程的最后非零系数之间的系数数目,以确定是否要编码第一非零系数的符号。如果该数字不大于预定义阈值,则以常规方式编码符号;否则,不显式传输第一个非零系数的符号,因此每CG节省一位。注意,所有非零系数之和的奇偶性并不总是与第一个非零系数的符号匹配。当发生这种情况时,调整量化系数的幅度,以便奇偶性匹配符号。

将一个系数组中的原始变换系数表示为\(c={c_0, c_1, … , c_{N-1}}\),其中N是系数组中的系数总数。将量化和去量化函数分别表示为\(Q(·)\)和\(Q^{-1}(·)\)。量化输出或量化变换系数表示为\(u=Q(c)={u_0, u_1, … , u_{N-1}}\)。设k是沿扫描路径的第一个非零系数的索引,即\(u_k≠0\)且\(u_i=0, 0≤i<k\)。为了优化奇偶调整后的率失真性能,可将多符号位隐藏方案表述为以下优化问题:­­

\(D(c, \hat{c})\)是c与其重构\(\hat{c}\)之间的均方失真,\(R(u)\)表示编码系数向量u所需的速率,sign(x)是符号函数,λ是拉格朗日乘子,表征了距离和编码速率之间的折衷,以及

等式(3)中的问题本质上是矢量量化问题,对于该问题,需要设计矢量量化器以使给定速率约束以及和约束的奇偶性的失真最小化。因此可以应用一般的矢量量化器设计方法,然而通常需要迭代方法并且具有高设计复杂度。

在HEVC参考编码器中,当分别使用RDOQ或HDQ时,采用两种非迭代奇偶调整方案来解决等式(3)中的问题。首先通过RDOQ或HDQ对变换系数进行量化。在和的奇偶性与要隐藏的符号不匹配的情况下,编码器执行线性搜索,并将量化系数之一的幅度调整+1或-1,以使调整后的系数之和的奇偶校验与符号匹配。

第一个奇偶校验调整方案遵循与RDOQ类似的原则,即选择系数和调整方向以最小化调整后的总体率失真代价。第二种方案在不需要准确率信息的情况下,仅使失真最小化,适用于HDQ,且不太复杂。­

SAO Implementation Aspects and Parameters Estimation

由于SAO需要样本级操作来将每个样本分类为编码器和解码器中的边带或类别,因此需要尽可能减少每个样本的操作数量,以降低总体计算复杂性。在编码器端,有许多SAO类型需要测试,以在合理的计算复杂性下实现更好的速率失真性能。本文将讨论一些有效的编码器算法。

关于SAO的介绍可以参考:样本自适应偏移 Sample Adaptive Offset (SAO) – 我受到了惊吓 (mmedia-t.cn)

Fast Edge Offset Sample Classification

可以通过使用以下函数和方程以更有效的方式实施EO样本分类:

其中,“c”是当前样本,“a”和“b”是两个相邻样本。作为进一步的加速,上一步骤中获得的数据可以在下一个样本的分类中重复使用。例如,假设EO类为0(即,一维水平图案),并且CTB中的样本按照光栅扫描顺序进行处理。当前样本的“sign3(c-a)”等于左侧相邻样本的“sign3(c-b)”。同样,当前样本的“sign3(c-b)”可以被右侧的相邻样本重用。在软件实现中,sign3(x)函数可以通过使用逐位操作或查找表来实现,以避免使用if-else操作,这在某些平台上可能是耗时的。

Fast Band Offset Sample Classification

样本范围在BO中平均分为32个波段。由于32等于2的5次幂,所以BO样本分类可以实现为使用每个样本的五个最高有效位作为分类结果。通过这种方式,BO的复杂性降低了,特别是在硬件中,只需要电线连接而不需要逻辑门来从样本值获得分类结果。为了减少软件解码运行时间,可以通过使用逐位操作或查找表来实现BO分类,以避免使用if-else操作。

Distortion Estimation for Encoder

速率失真优化过程需要多次计算原始和重建样本值之间的失真。一个简单的SAO实现将通过向de-blocking后的样本添加偏移,然后计算得到的样本和原始样本之间的失真。为了减少存储器访问和操作次数,可以如下实现快速失真估计方法。设k, s(k)和x(k)分别是样本位置、原始样本和重建样本,其中k属于C,CTB内属于特定SAO类型(即BO或EO)起始边带位置或EO类别以及特定边带或类别的样本集合。原始样本和重建样本之间的失真可以通过以下等式描述:

原始样本和SAO修改的样本之间的失真可以通过以下等式描述

其中h是样本集的偏移量。失真变化由以下方程定义:

其中N是集合中的样本数,E是原始样本和重建样本(SAO之前)之间的差值之和,如以下等式所定义:

请注意,样本分类和(7.28)可以在去块滤波后输入样本变得可用后立即计算。因此,N和E只计算一次并存储。然后,ΔJ定义如下:

其中λ是拉格朗日乘数,R表示估计的比特。

对于具有特定SAO类型(即BO或EO)、起始频带位置或EO类别以及特定频带或类别的给定CTB,测试接近E/N的几个h值(偏移),并选择使ΔJ最小化的偏移。在选择了所有边带或类别的偏移之后,将32个BO边带中的每个边带或五个EO类别中的每个的ΔJ相加,以获得整个CTB的速率失真成本的增量(变化)。使用零偏移和EO类别0的BO边带的失真可以通过(7.25)预先计算,并存储以供后续重复使用。当SAO降低整个CTB的成本(即,增量成本为负)时,该CTB启用SAO。类似地,通过快速失真估计可以找到最佳SAO类型和最佳起始位置或EO类。

Slice-Level On/Off Control

HM参考软件通用测试条件使用分级量化参数(QP)设置。作为示例,在随机接入条件下,GOP大小为8。根据帧在GOP中的位置,帧可以属于不同的层次结构级别。通常,帧仅从具有较小或相同层次结构的帧中预测。具有较高层次结构的帧可能会被赋予较高的QP。

如下提供Slice级开/关判定算法。对于层次结构级别0的帧,Slice header中始终启用SAO。给定具有非零层次级别N的当前帧,先前帧被定义为解码顺序中具有层次级别(N-1)的上一个图片。如果在前一张图片中超过75%的CTB中禁用SAO,HM参考编码器将在当前图片的所有切片标头中禁用SAO,并跳过SAO编码过程。这种编码器技术不仅可以减少要解析的语法数量,还可以提高0.5%的BD速率。请注意,亮度和色度SAO可以在Slice header中单独启用或禁用。

SAO Parameters Estimation and Interaction with Deblocking

在HM参考编码器中,估计每个CTU的SAO参数。由于SAO被应用于去块滤波器的输出,所以在去块样本可用之前,不能精确地确定SAO参数。然而,当前编码树块(CTB)中的右列和底行的解块样本可能不可用,因为右侧的CTU和当前CTU下方的CTU可能尚未被重建(在单通道编码器中)。这一限制可以通过两个选项中的一个来克服。第一个选项估计可用CTB样本上的SAO参数,即除了三个底行亮度样本、一个底行Cb和Cr样本、最右侧四列亮度样本、最右边两列Cb和Cr样本之外的CTB样本。当使用64×64 CTU大小时,所提出的方法不会引起显著的编码效率损失。然而,对于较小的CTU大小,SAO参数估计中未使用的样本百分比较高,这可能导致显著的编码效率损失。在这种情况下,第二选项在SAO参数估计期间使用去块之前的样本,而不是不可用的去块样本,这可以减少较小CTU大小的编码效率损失。

样本自适应偏移 Sample Adaptive Offset (SAO)

样本自适应偏移的关键功能是衰减振铃效应,当使用较大的变换大小时,振铃效应更可能出现。SAO通过首先使用所选分类器将区域中的样本分类为多个类别并根据其类别向每个样本添加特定偏移量来减少样本失真。在比特流中用信号通知每个区域的分类器索引和偏移。

HEVC使用两种SAO类型:边界偏移(Edge Offset, EO)和边带偏移(Band Offset, BO)。在EO中,样本的分类基于其邻域,即当前样本与其相邻样本之间的比较。在BO中,分类基于样本值。

边界偏移 Edge Offset

图1显示了吉布斯现象,可用于解释图像和视频编码中振铃效应的出现。横轴表示沿一维线的采样位置,纵轴表示采样值。虚线曲线表示原始样本,而实线曲线表示当由于变换系数的量化而丢弃信号中的最高频率时的重构样本。

局部峰、凸边/角、凹边/角和局部谷用实心圆标记,从图中可以观察到,通过对局部峰值和凸角应用负偏移,对凹角和凹谷应用正偏移,可以减少失真。

EO使用四种单向模式进行样本分类:水平、垂直、135°对角线和45°对角线,如图2所示,其中标签“c”表示当前样本,标签“a”和“b”表示两个相邻样本。这四个样本模式形成四个EO类别。每个启用EO的CTB只能选择一个EO类。基于率失真优化,选择一个EO类,并且在比特流中用信号通知指示选择哪个EO类的索引。

对于具有特定方向的给定EO类别,CTB内的每个样本被分为五类之一。标记为“c”的当前采样值将沿选定的一维模式与其两个相邻值进行比较。下表总结了每个样本的类别分类规则。类别1和4分别与所选一维模式的局部谷和局部峰相关联。类别2和3分别与凹角和凸角相关联。如果当前样本不属于EO类别1至4中的任何一类,则将其分配至类别0,且不应用SAO。请注意,类别是互斥的,一个样本只能属于一个类别。

正负边界偏移的影响如图3所示,解释如下。类别1和类别2的正偏移会导致平滑,因为局部凹谷和凹角会变得更平滑,而这些类别的负偏移会导致锐化。相反,对于类别3和4,负偏移导致平滑,正偏移导致锐化。在HEVC中,不允许在EO中锐化。因此,编码器针对每个EO类别用一个信号通知四个特定偏移的绝对值,偏移的符号从相应的EO类别中隐式导出。EO和BO都使用四个偏移量,这限制了偏移量的数量,以减少对行缓冲器的要求。

边带偏移 Band Offset

HEVC SAO工具使用的另一个偏移是边带偏移(BO)。将向值属于同一边带(值范围)的所有采样添加一个偏移。样本值范围分为32个相等的边带。对于0到255范围内的8位样本,边带的宽度为8。因此,8k到8k+7的样本值属于边带k,其中k的范围为0到31。可以向解码器发送边带中原始样本和重构样本之间的差异(即,边带的偏移)。BO的偏移符号没有限制。

图4展示了BO如何补偿区域的样本强度偏移。横轴表示样本位置,纵轴表示样本值。虚线曲线表示原始样本,而实线曲线表示重建样本,受预测残差的量化误差和相位偏移的影响,因为编码运动矢量偏离真实运动。

如图4所示,如果重建的运动向量和“真实”运动向量之间存在相移(差异),则与原始信号相比,具有梯度的平滑区域可能会偏移一定值。在该示例中,与原始样本相比,重构样本被向左移动,这导致可以通过BO针对边带k、k+1、k+2和k+3校正的系统负误差,其中范围从k*8到((k+1)*8)-1的样本被分类为属于边带k,并且可以通过使用相应的偏移值来修改。

在HEVC中,只有四个连续边带的偏移和当前区域的起始(或最小)边带位置被发信号给解码器。在BO中用信号通知四个偏移量,这等于EO中用信号发送的偏移量的数量(偏移量的数目被限制以减少线缓冲器的需求)。仅发出四个边带的信号的原因是CTB形成的区域中的样本值范围可能非常有限。

因此,通过发信号通知当前区域的起始边带位置,BO可以识别当前区域中要补偿的最小采样值,以便解码器可以恢复它,如图5中的示例所示。这对于色度CTB尤其如此。在自然图像中,色度分量通常由窄带信号表示,这意味着通过几个边带偏移,编码器可以恢复该区域中的大多数样本。

SAO Parameters Signaling

序列参数集(SPS)中用信号通知的语法元素sample_adaptive_offset_enabled_flag指示在当前视频序列中是否启用了SAO。在Slice header中,两个语法元素slice_sao_luma_flag和slice_sio_chroma_flag指示当前Slice中是否分别为亮度和色度启用了SAO。

低延迟应用可以使用基于编码树单元(CTU)的SAO编码算法。如图6所示,CTU包括其对应的luma CTB、Cb CTB和Cr CTB。语法方面,SAO参数自适应的基本单元始终是一个CTU。如果在当前Slice中启用SAO,则将每个CTU的SAO参数交织到Slice数据中。比特流中的SAO数据在每个CTU的开头用信号通知。CTU级SAO参数包括SAO merge信息、类型信息和偏移信息。

A. SAO Parameters Merging

CTU可以使用三个选项来发送SAO参数:重用left CTU的SAO参数(通过将语法元素SAO_merge_left_flag设置为1),重用top CTU的SAO参数(设置语法元素
sao_merge_up_flag设置为1)或通过发送新的SAO参数。SAO Parameters Merging 由所有三个颜色分量共享。当SAO merge_left或SAO merge_up模式被指示时,来自左侧或上方CTU的所有SAO参数都将被复制,并且当前CTU没有更多信息。这种基于CTU的SAO Parameters Merging 有效地减少了需要用信号通知的SAO消息。

B. SAO Type and Offsets Signaling

如果不使用SAO Parameters Merging,则如图7所示发送当前CTU的信息。首先发送亮度分量的语法元素,然后发送Cb语法元素,再发送Cr语法元素。对于每个颜色分量,发送SAO类型(SAO_type_idx_luma或SAO_type_idx_chrma),表示EO、BO或未应用(SAO已关闭)。如果选择了BO或EO,则传输四个偏移。如果选择了BO,则发信号通知起始边带位置(sao_band_position)。否则,如果选择EO,则用信号通知EO类(sao_EO_class_luma或sao_EO_class_chrma)。

Cb和Cr共享SAO类型(SAO_type_idx_chroma)和EO类(SAO_EO_class_chrma)语法元素,以通过在某些平台上实现更高效的内存访问来减少辅助信息并加快SAO处理。因此,这些语法元素仅针对Cb进行编码。码字(包括“关闭”、“EO类别选择索引”和“BO频带位置索引”)的设计基于概率分布,以减少信息。

C. CABAC Contexts and Bypass Coding

使用基于上下文的自适应二进制算术编码(CABAC)对包括SAO merge信息、SAO类型信息和偏移信息的所有CTU级SAO语法元素进行编码。只有SAO类型的第一个bin(指定在当前CTU中SAO是打开还是关闭)以及SAO merge_left和merge-up标志使用CABAC上下文。所有其他箱都在旁路模式下进行编码,这显著增加了CABAC中的SAO解析吞吐量,而没有很大的编码效率损失。

率失真优化量化 (Rate-Distortion Optimized Quantization RDOQ)

率失真优化量化 (RDOQ) 是一种编码优化技术,可以对其进行修改,而不会影响比特流是否符合标准。

在视频编码器操作中,量化步骤是唯一负责通过量化和减少变换系数的数量来减少有损数据的步骤。因此,量化变换系数的计算对压缩效率具有显著影响。利用标量量化的编码器所提供的压缩效率(在速率失真方面)可以通过选择更复杂的量化变换系数的计算方式来显著提高(它不是标准化的,可以以任何方式执行)。

在视频压缩技术的发展过程中,已经开发了许多旨在改进量化系数计算的方法。由于其效率,RDOQ被选为HEVC测试模型开发中两种可能的量化方法之一。

改进的量化器可以考虑量化误差和传输变换系数所需的位数,并确定量化变换系数的最佳集合。可以对每个变换系数块(即HEVC中的变换单元(TU))执行优化,并且通过最小化拉格朗日函数来计算最优成本。

该方法的一般思想是找到与最低RD成本相对应的量化变换系数的最佳集合。理论上,确定量化系数的最佳集合需要通过评估所有可能的组合进行穷举搜索。由于编码器的极端计算复杂性,穷举方法是不切实际的。因此,引入了快速次优方法。此外,在[1] “速率失真优化量化(RDOQ)”这一名称已被提出,并被广泛采用。

RDOQ的目的是找到表示编码块中的残差数据的量化变换系数的最佳或次最佳集合。RDOQ计算编码块中的图像失真(由变换系数的量化引入)和编码相应量化变换系数所需的比特数。基于这两个值,编码器通过计算RD成本来选择更好的系数值。

率失真优化量化过程

RDOQ的主要思想是确定TB中的第i个系数(表示为\(c_i\))的最优量化水平,或者在两个候选量化水平之间[上限舍入水平(表示为\(l_{i,ceil}\))和下限舍入水平(称为\(l_{i,floor}\))],或者特别地,在三个候选之间,或者通过在 \(l_{i,ceil}=2\)的情况下额外考虑 0,\(l_{i,ceil}\)和\(l_{i,floor}\)的值计算如下:

其中QStep是对应于量化参数(QP)的量化步长。RDOQ过程需要所有候选量化级别的RD代价,其中产生最小RD代价的量化级别被选为最优级别。每个候选级别的RD代价导出为:

其中\(D(c_i,r_{li})\)是系数\( c_i \)被量化为\( l_i \)时的量化误差,\( r_{li} \)是其重构(即反量化),\(R(l_i)\) 是量化级别 \(l_i\) 的熵编码产生的码率。 λ是对应于QP的拉格朗日乘数。

注意,(2)中的量化误差\(D(c_i,r_{li})\)可以表示为

其中 \(l_{i,float} \)是由 \(|c_i|/QStep \)计算的量化中的实际浮点数。 现在, \(R(li) \)被计算为:

如(4)中,\(R(li) \)需要 \(R_{li}^{Sig}\)、 \(R_{li}^{1}\)、 \(R_{li}^{2}\)、 \(R_{li}^{Rem}\),分别为与量化级别相关的4个语法元素(SE)的各自速率[3],即coeff_sig_flag、coeff_abs_lev_greater1_flag、coeff_abs_lev_greater2_flag、coeff_abs_remaining 。这四个 SE 的含义如表 I 所示。

请注意,在 HEVC 中,coeff_sig_ falg、coeff_abs_lev_greater1_falg和 coeff_abs_lev_greater2_ falg的 bin 是上下文编码的,而 coeff_abs_remaining 的 bin 是旁路编码的 [4]。为了估计 SE 的个体速率,需要执行实际的熵编码和上下文更新过程。然而,在 HEVC 参考软件中,速率 \(R_{li}^{Sig}\)、 \(R_{li}^{1}\)、 \(R_{li}^{2}\)是根据级别重要性、位置和上下文从预定义的查找表中估计的;通过使用 Golomb–Rice 代码和 Exp-Golomb 代码对低复杂度估计进行二值化 coeff_abs_remaining(表示为 Rem)来估计速率 \(R_{li}^{Rem}\)。 Rem 的值计算如下,其中 absLevel 是量化级别的绝对值:

表 II 显示了 coeff_abs_remaining 的二值化。 coeff_abs_remaining 二值化后,\(R_{li}^{Rem}\) 计算如下:

其中\({Length}^{prefix}\)和\({Length}^{suffix}\)分别是二值化后前缀和后缀的长度。由于HEVC变换系数左移15以表示整数域中速率的分数部分,并且还使用缩放系数估计失真,因此对\(R_{li}^{Rem}\) 左移15。

The RDOQ in HEVC

RDOQ已包含在HEVC参考软件(HM)中,并在HEVC开发和性能期间广泛使用。本节介绍了适用于HEVC的RDOQ算法。

HM中RDOQ的自适应与HEVC残差编码技术密切相关。在HEVC中,变换单元的大小可以从4×4到32×32像素不等,并且只允许方形单元。变换和扫描后,系数被划分为包含16个变换系数的系数组(CG)(图3)。HEVC变换系数编码的详细描述见[2]。

编码器中的RDOQ操作可分为三个阶段:变换系数的量化、系数组(CG)的消除和最后一个非零系数的选择。

A. 变换系数的量化

在该阶段中,编码器分别对每个变换系数执行计算。在第一步中,编码器通过使用无死区的均匀量化器量化变换系数的幅度来计算值Level。在下一步中,编码器考虑分析的量化系数的两个附加幅度:1级和0级。对于每一个提到的系数幅度,编码器计算用所选幅度编码系数的RD成本,并选择RD成本最低的一个。

值得一提的是,当与等于Level的系数幅度值进行比较时,将幅度设置为0值。与设置较低幅度值(级别1)相比,允许更显著的比特率降低。然而,通过将幅度设置为0来消除所选变换系数可能会导致显著失真。

B. 消除系数组

在该阶段中,编码器对每个变换系数组(CG)执行计算。编码器计算消除整个CG的RD成本。整个CG的消除是通过将CG中的所有系数量化为零来执行的。编码器计算所分析CG的消除的RD成本,如果消除允许降低成本,则消除所选择的CG。

整个CG的消除可以导致显著的比特率降低(不需要为CG内的每个系数发送sig_coeff_flag),同时向重建图像引入显著的失真。

C. 最后一个非零系数的选择

RDOQ的最后阶段是在步骤A和B之后针对TU中的所有剩余CG执行的。RDOQ算法分析系数以找到最佳的(短期RD成本)最后非零系数位置。包括此步骤是因为编码器必须编码比特流中最后一个非零系数的(x,y)坐标。

D. RD cost 的计算

在RDOQ操作期间,编码器必须计算所考虑的每一组变换系数或系数组的成本。可以通过考虑编码所选系数CG或TU所需的比特数(B)、引入的失真(D)以及通过拉格朗日乘数对两个值进行加权来计算该成本(RD_cost):

\(RD\_cost = D+\lambda \cdot B\)

在不同的实现中,编码器可以使用引入的失真的精确或估计值以及编码所选变换系数、系数组或变换单元所需的比特数。使用估计值会导致最佳系数集选择中的一些错误,并导致压缩性能下降。然而,速率和失真的估计可以加快编码器的操作。

Reference

[1] M. Karczewicz, Y. Ye, I. Chong, “Rate Distortion Optimized Quantization”, ITU-T SG 16/Q 6 VCEG, document: VCEG-AH21, Jan. 2008.
[2] J. Sole, R. Joshi, N. Ngyuen, T. Ji, M. Karczewicz, G. Clare, F. Henry, A. Duenas, “Transform Coefficient Coding in HEVC”, IEEE Transactions on Circuits and Systems for Video Technology, vol. 22, no. 12, pp. 1765-1777, Dec. 2012.
[3]J. Sole et al., “Transform coefficient coding in HEVC,” IEEE Trans. Circuits Syst. Video Technol., vol. 22, no. 12, pp. 1765–1777, Dec. 2012.
[4]V. Sze and M. Budagavi, “High throughput CABAC entropy coding in HEVC,” IEEE Trans. Circuits Syst. Video Technol., vol. 22, no. 12, pp. 1778–1791, Dec. 2012.

Merge模式候选列表构建

Megre模式是HEVC编码标准引入的一项帧间预测编码技术,在VVC编码标准中对Merge模式进行了扩展。包括对候选列表的扩展,增加了HMVP和 Pair-wise average candidates。编码工具新增CIIP (combined inter-intra prediction)、MMVD(merge mode with MV difference)、GEO(geometric partitioning mode)。VVC新提出的affine也存在merge模式,不过affine merge的候选推导和上面几个编码工具是不一样的。本文主要讨论Merge模式候选列表构建。

1 HEVC Merge候选列表构建

HEVC的Merge候选个数最大为5个,构建过程如图1。

图1

为了构建空域Merge候选,在位于图2所示位置的候选中选择最多四个Merge候选。

图2

构建的顺序是A1→B1→B0→A0→B2。仅当位置 A1、B1、B0 和 A0 的任何 PU 不可用(例如它属于另一个Slice或Tile)或者不是inter mode时才考虑位置 B2。

在A1位置的候选加入后,剩余候选的加入会进行冗余校验,确保将具有相同运动信息的候选排除在列表之外,以提高编码效率。为了降低计算复杂度,仅比较下图3的箭头链接的对,并且仅当候选通过冗余检查时才将其添加到列表中。

图3

在 HEVC 中,一个 CU 可能会被划分为多个 PU,这可能会对Merge模式带来冗余。图4描绘了从 CU 中分别按 N × 2N 和 2N × N 模式划分的“第二个 PU”。

图4

当第二个 PU 从一个 CU 划分 N × 2N 时,位置 A1 的候选者不考虑用于列表构建。事实上,通过选择这个候选者,两个 PU 将共享相同的运动信息,这对于 CU 中只有一个 PU 的情况是多余的。 类似地,当第二个 PU 从一个 CU 被分割为 2N × N 时,不考虑位置 B1。

在时域Merge候选的推导中,TMVP 候选是从存储在位置 H 或 C 的 MV 推导出来的,如图 1 所示的并置图片,类似于 AMVP 模式的 TMVP 候选。 对于Merge候选列表中的 TMVP 候选,MV 将被缩放到相应参考帧列表中具有参考索引 0 的参考帧。

除了时空Merge候选之外,还有两种附加类型的Merge候选:组合的双向预测Merge候选和具有 (0, 0) 运动向量的零运动候选。 组合的双向预测Merge候选是通过仅利用 B Slice的时空Merge候选来生成的。 通过组合具有参考列表0的第一Merge候选和具有参考列表1的第二Merge候选来生成组合双向预测候选,其中第一和第二Merge候选根据预定义顺序从Merge候选列表中的可用Merge候选中选择。这两个MV将形成新的双向预测候选。如果未满足Merge候选列表,则将向列表添加零运动候选以填充列表。

2 VVC Merge候选列表构建

2.1 空域候选推导

VVC中空域Merge候选的推导与HEVC相同,只是前两个Merge候选的位置交换了。在位于图 5 所示位置的候选中,最多选择四个Merge候选。推导顺序为 B1、A1、B0、A0 和 B2。仅当位置 B0、A0、B1、A1 的一个或多个 CU 不可用(例如它属于另一个Slice或Tile)或者不是inter mode时,才考虑位置 B2。在A1位置的候选加入后,剩余候选的加入进行冗余校验,保证将具有相同运动信息的候选排除在列表之外,从而提高编码效率。为了降低计算复杂度,在提到的冗余校验中并未考虑所有可能的候选对,与HEVC一样仅考虑与图 3 中的箭头链接的对,并且仅当用于冗余校验的相应候选具有不同的运动信息时,才将候选添加到列表中。

图5

2.2 时域候选推导(TMVP)

在此步骤中,仅将一个候选者添加到列表中。 特别地,在该时域Merge候选的推导中,基于属于并置参考图片的 co-located CU来缩放运动矢量。 用于推导位于同一位置的 CU 的参考帧列表和参考索引会被写入slice header中。 时域Merge候选的缩放运动向量如图 6中的虚线所示,它是从 co-located CU 的运动向量缩放的。

图6

如图 7 所示,在候选 C0 和 C1 之间选择时域候选的位置。如果位置 C0 处的 CU 不可用、被帧内编码或位于当前 CTU 行之外,则使用位置 C1。 否则,在时域Merge候选的推导中使用位置 C0。

图7

2. 3 HMVP候选

在 HEVC 中,有两种类型的 MVP,即时域 MVP 和空域 MVP,它们利用来自空间相邻或时间块的运动信息。在 VVC 中,引入了一种新型 MVP,即 基于历史的 MVP(HMVP)。 HMVP 的基本思想是进一步使用先前编码的 MV 作为 MVP,这些 MV 与相对于当前块的相邻或不相邻块相关联。为了跟踪可用的 HMVP 候选者,在编码器和解码器处都维护了一个 HMVP 候选者表,并动态更新。每当新的 CTU 行开始时,表就会重置以简化并行编码。

HMVP 表中最多有5个候选者。在对一个不处于子块模式(包括仿射模式)或 GPM 的帧间预测块进行编码之后,通过将关联的运动信息附加到表的末尾作为新的 HMVP 候选者来选择性地更新表。应用受限的先进先出 (FIFO) 规则来管理表,其中首先应用冗余检查以查找表中是否存在相同的HMVP。 如果找到,则从表中删除相同的 HMVP,然后将所有 HMVP 候选向前移动,并将相同的 HMVP 插入到表的最后一个条目中。使用 HMVP,即使编码块在空间上不与当前块相邻,先前编码块的运动信息也可以用于更有效的运动矢量预测。

为了减少冗余校验操作的数量,引入了以下简化:

  1. 表中的最后两个条目分别对 A1 和 B1 空域候选进行冗余检查。
  2. 一旦可用Merge候选的总数达到最大允许Merge候选减1,则终止来自HMVP的Merge候选列表构建过程。

2.4 Pair-wise average merge candidates derivation

VVC 中的Pair-wise average Merge候选取代了 HEVC 中的组合双预测Merge候选。Pair-wise average 候选是通过使用前两个Merge候选对现有Merge候选列表中预定义的候选对进行平均来生成的。 第一个Merge候选定义为 p0Cand,第二个Merge候选定义为 p1Cand。 根据 p0Cand 和 p1Cand 的运动向量的可用性分别计算每个参考列表的平均运动向量。 如果两个运动矢量都在一个列表中,则即使它们指向不同的参考帧,这两个运动矢量也会被平均,并将其参考图片设置为p0Cand之一; 如果只有一个运动矢量可用,则直接使用一个; 如果没有可用的运动矢量,则保持此列表无效。 此外,如果 p0Cand 和 p1Cand 的半像素插值滤波器索引不同,则将其设置为 0。

当添加Pair-wise average Merge候选后Merge列表未满时,则将向列表添加零运动候选以填充列表。

2.5 VTM代码分析

删去了代码中和GDR相关的和一些不重要的内容。

空域:代码里的符号和上面图中的符号不一致,但是仔细看顺序还是一样的。

// above
  const PredictionUnit *puAbove = cs.getPURestricted(posRT.offset(0, -1), pu, pu.chType);

  bool isAvailableB1 = puAbove && isDiffMER(pu.lumaPos(), posRT.offset(0, -1), plevel) && pu.cu != puAbove->cu && CU::isInter(*puAbove->cu);

  if (isAvailableB1)
  {
    miAbove = puAbove->getMotionInfo(posRT.offset(0, -1));

    // get Inter Dir
    mrgCtx.interDirNeighbours[cnt] = miAbove.interDir;
    mrgCtx.useAltHpelIf[cnt] = miAbove.useAltHpelIf;
    // get Mv from Above
    mrgCtx.bcwIdx[cnt] = (mrgCtx.interDirNeighbours[cnt] == 3) ? puAbove->cu->bcwIdx : BCW_DEFAULT;
    mrgCtx.mvFieldNeighbours[cnt << 1].setMvField(miAbove.mv[0], miAbove.refIdx[0]);

    if (slice.isInterB())
    {
      mrgCtx.mvFieldNeighbours[(cnt << 1) + 1].setMvField(miAbove.mv[1], miAbove.refIdx[1]);
    }
    cnt++;
  }

  //left
  const PredictionUnit* puLeft = cs.getPURestricted(posLB.offset(-1, 0), pu, pu.chType);

  const bool isAvailableA1 = puLeft && isDiffMER(pu.lumaPos(), posLB.offset(-1, 0), plevel) && pu.cu != puLeft->cu && CU::isInter(*puLeft->cu);

  if (isAvailableA1)
  {
    miLeft = puLeft->getMotionInfo(posLB.offset(-1, 0));

    if (!isAvailableB1 || (miAbove != miLeft))
    {
      // get Inter Dir
      mrgCtx.interDirNeighbours[cnt] = miLeft.interDir;
      mrgCtx.useAltHpelIf[cnt]       = miLeft.useAltHpelIf;
      mrgCtx.bcwIdx[cnt]             = (mrgCtx.interDirNeighbours[cnt] == 3) ? puLeft->cu->bcwIdx : BCW_DEFAULT;
      // get Mv from Left
      mrgCtx.mvFieldNeighbours[cnt << 1].setMvField(miLeft.mv[0], miLeft.refIdx[0]);

      if (slice.isInterB())
      {
        mrgCtx.mvFieldNeighbours[(cnt << 1) + 1].setMvField(miLeft.mv[1], miLeft.refIdx[1]);
      }
      cnt++;
    }
  }

  // above right
  const PredictionUnit *puAboveRight = cs.getPURestricted( posRT.offset( 1, -1 ), pu, pu.chType );

  bool isAvailableB0 = puAboveRight && isDiffMER( pu.lumaPos(), posRT.offset(1, -1), plevel) && CU::isInter( *puAboveRight->cu );

  if( isAvailableB0 )
  {
    miAboveRight = puAboveRight->getMotionInfo( posRT.offset( 1, -1 ) );

    if( !isAvailableB1 || ( miAbove != miAboveRight ) )
    {

      // get Inter Dir
      mrgCtx.interDirNeighbours[cnt] = miAboveRight.interDir;
      mrgCtx.useAltHpelIf[cnt] = miAboveRight.useAltHpelIf;
      // get Mv from Above-right
      mrgCtx.bcwIdx[cnt] = (mrgCtx.interDirNeighbours[cnt] == 3) ? puAboveRight->cu->bcwIdx : BCW_DEFAULT;
      mrgCtx.mvFieldNeighbours[cnt << 1].setMvField( miAboveRight.mv[0], miAboveRight.refIdx[0] );

      if( slice.isInterB() )
      {
        mrgCtx.mvFieldNeighbours[( cnt << 1 ) + 1].setMvField( miAboveRight.mv[1], miAboveRight.refIdx[1] );
      }
      cnt++;
    }
  }

  //left bottom
  const PredictionUnit *puLeftBottom = cs.getPURestricted( posLB.offset( -1, 1 ), pu, pu.chType );

  bool isAvailableA0 = puLeftBottom && isDiffMER( pu.lumaPos(), posLB.offset(-1, 1), plevel) && CU::isInter( *puLeftBottom->cu );

  if( isAvailableA0 )
  {
    miBelowLeft = puLeftBottom->getMotionInfo( posLB.offset( -1, 1 ) );

    if( !isAvailableA1 || ( miBelowLeft != miLeft ) )
    {
      // get Inter Dir
      mrgCtx.interDirNeighbours[cnt] = miBelowLeft.interDir;
      mrgCtx.useAltHpelIf[cnt]       = miBelowLeft.useAltHpelIf;
      mrgCtx.bcwIdx[cnt]             = (mrgCtx.interDirNeighbours[cnt] == 3) ? puLeftBottom->cu->bcwIdx : BCW_DEFAULT;
      // get Mv from Bottom-Left
      mrgCtx.mvFieldNeighbours[cnt << 1].setMvField( miBelowLeft.mv[0], miBelowLeft.refIdx[0] );

      if( slice.isInterB() )
      {
        mrgCtx.mvFieldNeighbours[( cnt << 1 ) + 1].setMvField( miBelowLeft.mv[1], miBelowLeft.refIdx[1] );
      }
      cnt++;
    }
  }

  // above left
  if ( cnt < 4 )
  {
    const PredictionUnit *puAboveLeft = cs.getPURestricted( posLT.offset( -1, -1 ), pu, pu.chType );

    bool isAvailableB2 = puAboveLeft && isDiffMER( pu.lumaPos(), posLT.offset(-1, -1), plevel ) && CU::isInter( *puAboveLeft->cu );

    if( isAvailableB2 )
    {
      miAboveLeft = puAboveLeft->getMotionInfo( posLT.offset( -1, -1 ) );

      if( ( !isAvailableA1 || ( miLeft != miAboveLeft ) ) && ( !isAvailableB1 || ( miAbove != miAboveLeft ) ) )
      {
        // get Inter Dir
        mrgCtx.interDirNeighbours[cnt] = miAboveLeft.interDir;
        mrgCtx.useAltHpelIf[cnt]       = miAboveLeft.useAltHpelIf;
        mrgCtx.bcwIdx[cnt]             = (mrgCtx.interDirNeighbours[cnt] == 3) ? puAboveLeft->cu->bcwIdx : BCW_DEFAULT;
        // get Mv from Above-Left
        mrgCtx.mvFieldNeighbours[cnt << 1].setMvField( miAboveLeft.mv[0], miAboveLeft.refIdx[0] );

        if( slice.isInterB() )
        {
          mrgCtx.mvFieldNeighbours[( cnt << 1 ) + 1].setMvField( miAboveLeft.mv[1], miAboveLeft.refIdx[1] );
        }
        cnt++;
      }
    }
  }

TMVP

  if (slice.getPicHeader()->getEnableTMVPFlag() && (pu.lumaSize().width + pu.lumaSize().height > 12))
  {
    //>> MTK colocated-RightBottom
    // offset the pos to be sure to "point" to the same position the uiAbsPartIdx would've pointed to
    Position posRB = pu.Y().bottomRight().offset( -3, -3 );
    const PreCalcValues& pcv = *cs.pcv;

    Position posC0;
    Position posC1 = pu.Y().center();
    bool C0Avail = false;
    bool boundaryCond = ((posRB.x + pcv.minCUWidth) < pcv.lumaWidth) && ((posRB.y + pcv.minCUHeight) < pcv.lumaHeight);
    const SubPic& curSubPic = pu.cs->slice->getPPS()->getSubPicFromPos(pu.lumaPos());
    if (curSubPic.getTreatedAsPicFlag())
    {
      boundaryCond = ((posRB.x + pcv.minCUWidth) <= curSubPic.getSubPicRight() &&
                      (posRB.y + pcv.minCUHeight) <= curSubPic.getSubPicBottom());
    }
    if (boundaryCond)
    {
      int posYInCtu = posRB.y & pcv.maxCUHeightMask;
      if (posYInCtu + 4 < pcv.maxCUHeight)
      {
        posC0 = posRB.offset(4, 4);
        C0Avail = true;
      }
    }

    Mv        cColMv;
    int       refIdx      = 0;
    int       dir         = 0;
    unsigned  arrayAddr   = cnt;
    bool      existMV     = (C0Avail && getColocatedMVP(pu, REF_PIC_LIST_0, posC0, cColMv, refIdx, false))
                   || getColocatedMVP(pu, REF_PIC_LIST_0, posC1, cColMv, refIdx, false);
    if (existMV)
    {
      dir     |= 1;
      mrgCtx.mvFieldNeighbours[2 * arrayAddr].setMvField(cColMv, refIdx);
    }

    if (slice.isInterB())
    {
      existMV = (C0Avail && getColocatedMVP(pu, REF_PIC_LIST_1, posC0, cColMv, refIdx, false))
                || getColocatedMVP(pu, REF_PIC_LIST_1, posC1, cColMv, refIdx, false);
      if (existMV)
      {
        dir     |= 2;
        mrgCtx.mvFieldNeighbours[2 * arrayAddr + 1].setMvField(cColMv, refIdx);
      }
    }

    if( dir != 0 )
    {
      bool addTMvp = true;
      if( addTMvp )
      {
        mrgCtx.interDirNeighbours[arrayAddr] = dir;
        mrgCtx.bcwIdx[arrayAddr]             = BCW_DEFAULT;
        mrgCtx.useAltHpelIf[arrayAddr]       = false;
        if (mrgCandIdx == cnt)
        {
          return;
        }

        cnt++;
      }
    }
  }

HMVP

bool PU::addMergeHMVPCand(const CodingStructure &cs, MergeCtx &mrgCtx, const int &mrgCandIdx,
                          const uint32_t maxNumMergeCandMin1, int &cnt, const bool isAvailableA1,
                          const MotionInfo miLeft, const bool isAvailableB1, const MotionInfo miAbove,
                          const bool ibcFlag, const bool isGt4x4

)
{
  const Slice& slice = *cs.slice;
  MotionInfo miNeighbor;

  auto &lut = ibcFlag ? cs.motionLut.lutIbc : cs.motionLut.lut;

  const int numAvailCandInLut = (int) lut.size();

  for (int mrgIdx = 1; mrgIdx <= numAvailCandInLut; mrgIdx++)
  {
    miNeighbor = lut[numAvailCandInLut - mrgIdx];

    if ( mrgIdx > 2 || ((mrgIdx > 1 || !isGt4x4) && ibcFlag)
      || ((!isAvailableA1 || (miLeft != miNeighbor)) && (!isAvailableB1 || (miAbove != miNeighbor))) )
    {
      mrgCtx.interDirNeighbours[cnt] = miNeighbor.interDir;
      mrgCtx.useAltHpelIf      [cnt] = !ibcFlag && miNeighbor.useAltHpelIf;
      mrgCtx.bcwIdx[cnt]             = (miNeighbor.interDir == 3) ? miNeighbor.bcwIdx : BCW_DEFAULT;

      mrgCtx.mvFieldNeighbours[cnt << 1].setMvField(miNeighbor.mv[0], miNeighbor.refIdx[0]);
      if (slice.isInterB())
      {
        mrgCtx.mvFieldNeighbours[(cnt << 1) + 1].setMvField(miNeighbor.mv[1], miNeighbor.refIdx[1]);
      }

      if (mrgCandIdx == cnt)
      {
        return true;
      }
      cnt ++;

      if (cnt  == maxNumMergeCandMin1)
      {
        break;
      }
    }
  }

  if (cnt < maxNumMergeCandMin1)
  {
    mrgCtx.useAltHpelIf[cnt] = false;
  }

  return false;
}

pairwise-average candidates

if (cnt > 1 && cnt < maxNumMergeCand)
    {
      mrgCtx.mvFieldNeighbours[cnt * 2].setMvField( Mv( 0, 0 ), NOT_VALID );
      mrgCtx.mvFieldNeighbours[cnt * 2 + 1].setMvField( Mv( 0, 0 ), NOT_VALID );
      // calculate average MV for L0 and L1 seperately
      unsigned char interDir = 0;

      mrgCtx.useAltHpelIf[cnt] = (mrgCtx.useAltHpelIf[0] == mrgCtx.useAltHpelIf[1]) ? mrgCtx.useAltHpelIf[0] : false;
      for( int refListId = 0; refListId < (slice.isInterB() ? 2 : 1); refListId++ )
      {
        const short refIdxI = mrgCtx.mvFieldNeighbours[0 * 2 + refListId].refIdx;
        const short refIdxJ = mrgCtx.mvFieldNeighbours[1 * 2 + refListId].refIdx;

        // both MVs are invalid, skip
        if( (refIdxI == NOT_VALID) && (refIdxJ == NOT_VALID) )
        {
          continue;
        }

        interDir += 1 << refListId;
        // both MVs are valid, average these two MVs
        if( (refIdxI != NOT_VALID) && (refIdxJ != NOT_VALID) )
        {
          const Mv &mvI = mrgCtx.mvFieldNeighbours[0 * 2 + refListId].mv;
          const Mv &mvJ = mrgCtx.mvFieldNeighbours[1 * 2 + refListId].mv;

          // average two MVs
          Mv avgMv = mvI;
          avgMv += mvJ;
          avgMv.roundAffine(1);

          mrgCtx.mvFieldNeighbours[cnt * 2 + refListId].setMvField( avgMv, refIdxI );
        }
        // only one MV is valid, take the only one MV
        else if( refIdxI != NOT_VALID )
        {
          Mv singleMv = mrgCtx.mvFieldNeighbours[0 * 2 + refListId].mv;
          mrgCtx.mvFieldNeighbours[cnt * 2 + refListId].setMvField( singleMv, refIdxI );
        }
        else if( refIdxJ != NOT_VALID )
        {
          Mv singleMv = mrgCtx.mvFieldNeighbours[1 * 2 + refListId].mv;
          mrgCtx.mvFieldNeighbours[cnt * 2 + refListId].setMvField( singleMv, refIdxJ );
        }
      }

      mrgCtx.interDirNeighbours[cnt] = interDir;
      if( interDir > 0 )
      {
        cnt++;
      }
    }

Zero Mv

uint32_t arrayAddr = cnt;

  int numRefIdx = slice.isInterB() ? std::min(slice.getNumRefIdx(REF_PIC_LIST_0), slice.getNumRefIdx(REF_PIC_LIST_1))
                                   : slice.getNumRefIdx(REF_PIC_LIST_0);

  int r = 0;
  int refcnt = 0;
  while (arrayAddr < maxNumMergeCand)
  {
    mrgCtx.interDirNeighbours[arrayAddr] = 1;
    mrgCtx.bcwIdx[arrayAddr]             = BCW_DEFAULT;
    mrgCtx.mvFieldNeighbours[arrayAddr << 1].setMvField(Mv(0, 0), r);
    mrgCtx.useAltHpelIf[arrayAddr] = false;

    if (slice.isInterB())
    {
      mrgCtx.interDirNeighbours[arrayAddr] = 3;
      mrgCtx.mvFieldNeighbours[(arrayAddr << 1) + 1].setMvField(Mv(0, 0), r);
    }

    arrayAddr++;

    if (refcnt == numRefIdx - 1)
    {
      r = 0;
    }
    else
    {
      ++r;
      ++refcnt;
    }
  }
  mrgCtx.numValidMergeCand = arrayAddr;

代码和上文描述的是一致的,代码里包含了更多的细节,这里就不再讨论了。