深度解析H.266/VVC帧间编码

帧间预测是利用视频帧与帧之间的相关性,去除视频帧间的时间冗余信息。统计表明,帧间差绝对值超过3的像素平均不到一帧像素的4%,因此,采用高效的帧间编码方式,可以很大程度上提高视频压缩效率。

目前,主流视频编码标准中采用的基于块的帧间编码方式,基本原理是通过运动估计(Motion Estimate)从相邻参考重建帧中寻找和当前块差别最小的参考块,将其重建值作为当前块的预测块。其中参考块到当前块的位移称为运动矢量(Motion Vector),将重建值作为预测值的过程称为运动补偿(Motion Compensation)在这里插入图片描述

VVC帧间预测编码概述

MV的预测和获取

MV预测技术继承和发展了H.265的merge和AMVP技术,拓展了MV预测选择范围,提高MV的表示精度。

(1)Merge模式

在MVP候选列表的构造上,保留了空域和时域候选,去除了组合MVP候选,增加了基于历史的MV预测(HMVP)和成对平均MVP候选,并且改变了空域候选的检查顺序。

  • 联合帧内帧间预测技术(CIIP)
  • 带有运动矢量差的Merge技术(MMVD)
  • 几何划分帧间预测技术(GPM)

(2)AMVP模式

保留原AMVP列表构造方式的同时,引入了HMVP候选的构造。
在双向AMVP模式的选择上,增加对称运动矢量差分编码AMVD技术。
引入了CU级的自适应运动矢量精度(AMVR)技术,允许多种不同亚像素精度来编码MVD。

(3)基于子块的MV表示

新增了基于子块的帧间预测技术,可以一次性表示一个编码块中多个子块不同MV的信息。

基于子块的时域MV预测(SbTMVP),每个子块使用同位图像中对应位置块的运动信息来预测MV。
仿射运动补偿预测(AMCP)技术,针对缩放、旋转等运动场景。

(4)解码端MV细化

为提升双向MV预测的准确性,VVC采用了一种双向MV修正技术,解码端运动会矢量细化(DMVR),在解码端通过基于双边匹配的局部小区域运动搜索,将MV微调后再用于运动补偿。

  1. Merge模式

一、Merge模式

Merge模式是HEVC中提出的新技术,直接利用一个或者一组MVP,推断得到当前编码块的MV信息,通常是利用空域和时域相邻块的MV对当前块的MV进行预测直接得到,不存在MVD。因此不需要传输MVD和参考帧索引,是一种高效的MV编码方法。

编解码端会使用相同的方式构建MV候选列表,在编码端通过率失真准则选出最优的MV索引,只需将该索引传给解码端即可。在VVC中,对于Merge模式下的每个CU的最佳候选索引使用截断一元二值化进行编码即可,最佳索引的第一个bin使用上下文编码,其他bin使用旁路编码。进行运动补偿获取帧间预测块,然后对预测残差进行编码。

HEVC中Merge模式利用时域或空域相邻块构建MV候选列表,且列表中只有5个候选MV。
VVC在HEVC的基础上进行了扩展,MV候选列表中最多可以有6个候选MV,候选MV有5种类型,顺序如下:

  • 基于相邻块的空域MVP
  • 基于同位块的时域MVP
  • 基于历史信息构建的FIFO表的MVP
  • 成对的平均MVP
  • 零MV

Merge估计区域

Merge估计区域(MER)允许为同一Merge估计区域中的CU独立推导Merge候选列表。对于当前CU的Merge候选列表推导过程中,不包括与当前CU在同一MER内的候选块,即判断相邻块可用时,仅使用不同MER内的相邻块。在编码器侧选择MER大小,并在序列参数集中以log2_parallel_merge_level_minus2表示。另外,仅当( xCb + cbWidth ) >> Log2ParMrgLevel 大于 xCb >> Log2ParMrgLevel 且 ( yCb + cbHeight ) >> Log2ParMrgLevel 大于 ( yCb >> Log2ParMrgLevel )时更新HMVP候选列表。

Merge列表构建过程

Merge list的列表长度为6,首选寻找空域候选项(最多选择4个),然后寻找时域候选项(最多选择1个),然后选择基于历史信息的候选项,最后是平均候选项(最多选择1个),如果Merge list通过以上几个步骤,还未被填满,则使用零向量进行填充。在上面这些步骤当中,如果在中途某个步骤当中merge list被填满则提前终止,不进行后面的步骤。

VVC中的Merge list构建与HEVC中Merge list构建最大的不同在于,HEVC中的Merge list只包括空域候选项和时域候选项
当前CU的merge list构建完毕后,遍历它的6个候选项进行率失真代价计算,选择率失真代价最小的候选项直接作为当前CU的MV
注意:对于B slice,由于是双向预测,因此每个候选项包括两个MV(一个前向MV一个后向MV)。在候选列表中,每个候选项包括MV信息和参考帧索引信息。

构建Merge列表的代码及注释如下(基于VTM10.0):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
void PU::getInterMergeCandidates( const PredictionUnit &pu, MergeCtx& mrgCtx,
int mmvdList,
const int& mrgCandIdx )
{
const unsigned plevel = pu.cs->sps->getLog2ParallelMergeLevelMinus2() + 2;
const CodingStructure &cs = *pu.cs;
const Slice &slice = *pu.cs->slice;
const uint32_t maxNumMergeCand = pu.cs->sps->getMaxNumMergeCand();//该变量表示Merge候选列表的最大长度
CHECK (maxNumMergeCand > MRG_MAX_NUM_CANDS, "selected maximum number of merge candidate exceeds global limit");
for (uint32_t ui = 0; ui < maxNumMergeCand; ++ui)
{
mrgCtx.BcwIdx[ui] = BCW_DEFAULT;
mrgCtx.interDirNeighbours[ui] = 0;
mrgCtx.mrgTypeNeighbours [ui] = MRG_TYPE_DEFAULT_N;
mrgCtx.mvFieldNeighbours[(ui << 1) ].refIdx = NOT_VALID;
mrgCtx.mvFieldNeighbours[(ui << 1) + 1].refIdx = NOT_VALID;
mrgCtx.useAltHpelIf[ui] = false;
}

mrgCtx.numValidMergeCand = maxNumMergeCand;
// compute the location of the current PU 计算当前PU的位置

int cnt = 0;

const Position posLT = pu.Y().topLeft();//当前PU左上方的位置
const Position posRT = pu.Y().topRight();//当前PU右上方的位置
const Position posLB = pu.Y().bottomLeft();//当前PU左下方的位置
MotionInfo miAbove, miLeft, miAboveLeft, miAboveRight, miBelowLeft; //记录当前PU5个相邻块的运动信息

// above
const PredictionUnit *puAbove = cs.getPURestricted(posRT.offset(0, -1), pu, pu.chType);
// ===========================空域MV候选列表的构建=======================
bool isAvailableB1 = puAbove && isDiffMER(pu.lumaPos(), posRT.offset(0, -1), plevel) && pu.cu != puAbove->cu && CU::isInter(*puAbove->cu);
// 右上方B1块可用
if (isAvailableB1)
{
miAbove = puAbove->getMotionInfo(posRT.offset(0, -1)); //获取B1块的运动信息

// get Inter Dir
mrgCtx.interDirNeighbours[cnt] = miAbove.interDir; //该变量表示merge list中已候选项的数目
mrgCtx.useAltHpelIf[cnt] = miAbove.useAltHpelIf;
// get Mv from Above 从上方块获得运动矢量
// puAbove->cu->BcwIdx表示当前PU所属的CU维护的HMVP表中候选数目
mrgCtx.BcwIdx[cnt] = (mrgCtx.interDirNeighbours[cnt] == 3) ? puAbove->cu->BcwIdx : BCW_DEFAULT;
mrgCtx.mvFieldNeighbours[cnt << 1].setMvField(miAbove.mv[0], miAbove.refIdx[0]); //使用前向参考帧的MV

if (slice.isInterB())
{ //如果是B帧,则还需要获得相邻块后向参考帧的MV
mrgCtx.mvFieldNeighbours[(cnt << 1) + 1].setMvField(miAbove.mv[1], miAbove.refIdx[1]);
}
if (mrgCandIdx == cnt)
{
return;
}

cnt++;
}

// early termination 如果当前merge list中候选的数目已经达到了最大值,则停止merge list列表的构建
if (cnt == maxNumMergeCand)
{
return;
}

//left 检查左侧块A1运动信息是否可用
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]);
}
if (mrgCandIdx == cnt)
{
return;
}

cnt++;
}
}

// early termination
if( cnt == maxNumMergeCand )
{
return;
}

// above right 检查右上相邻块B0运动信息是否可用
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] );
}

if (mrgCandIdx == cnt)
{
return;
}

cnt++;
}
}
// early termination
if( cnt == maxNumMergeCand )
{
return;
}

//left bottom 检查左下相邻块A0运动信息是否可用
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] );
}

if (mrgCandIdx == cnt)
{
return;
}

cnt++;
}
}
// early termination
if( cnt == maxNumMergeCand )
{
return;
}

// above left 如果前四个相邻块由一个不可用, 则继续检查左上角的B2块是否可用
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] );
}

if (mrgCandIdx == cnt)
{
return;
}

cnt++;
}
}
}
// early termination
if (cnt == maxNumMergeCand)
{
return;
}

// =======================时域MVP候选列表的构建==============================
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
// 偏移pos,确保“指向”uiAbsPartIdx将指向的相同位置
Position posRB = pu.Y().bottomRight().offset( -3, -3 );
const PreCalcValues& pcv = *cs.pcv;

Position posC0;//C0位当前PU的右下方再移位(1,1)
Position posC1 = pu.Y().center(); //C1为当前PU的中心位置
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()); //获得当前PU所在的子图
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; //C0位置处的像素可用
}
}
Mv cColMv;
int iRefIdx = 0;
int dir = 0;
unsigned uiArrayAddr = cnt;
//检查是否存在同位MV,若存在则获取相应的MV到cColMv
bool bExistMV = ( C0Avail && getColocatedMVP(pu, REF_PIC_LIST_0, posC0, cColMv, iRefIdx, false ) )
|| getColocatedMVP( pu, REF_PIC_LIST_0, posC1, cColMv, iRefIdx, false );
if (bExistMV)
{
dir |= 1;
mrgCtx.mvFieldNeighbours[2 * uiArrayAddr].setMvField(cColMv, iRefIdx);
}

if (slice.isInterB())
{ //对于B帧,相应的获取两个MV
bExistMV = ( C0Avail && getColocatedMVP(pu, REF_PIC_LIST_1, posC0, cColMv, iRefIdx, false ) )
|| getColocatedMVP( pu, REF_PIC_LIST_1, posC1, cColMv, iRefIdx, false );
if (bExistMV)
{
dir |= 2;
mrgCtx.mvFieldNeighbours[2 * uiArrayAddr + 1].setMvField(cColMv, iRefIdx);
}
}

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

// early termination 提前终止
if (cnt == maxNumMergeCand)
{
return;
}

//=================================基于历史信息构建MV列表===========================
int maxNumMergeCandMin1 = maxNumMergeCand - 1;
if (cnt != maxNumMergeCandMin1) //检查Merge列表是否到达最大数目减1,如果到达则不需要进行HMV候选
{
bool isGt4x4 = true;
bool bFound = addMergeHMVPCand(cs, mrgCtx, mrgCandIdx, maxNumMergeCandMin1, cnt, isAvailableA1, miLeft,
isAvailableB1, miAbove, CU::isIBC(*pu.cu), isGt4x4);

if (bFound)
{
return;
}
}
// ================================成对平均候选MV==================================
// 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 分别计算L0和L1的平均MV
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++ )
{
// 使用列表中最前面的两个MV
const short refIdxI = mrgCtx.mvFieldNeighbours[0 * 2 + refListId].refIdx;
const short refIdxJ = mrgCtx.mvFieldNeighbours[1 * 2 + refListId].refIdx;

// both MVs are invalid, skip 两个MVs都无效,跳过
if( (refIdxI == NOT_VALID) && (refIdxJ == NOT_VALID) )
{
continue;
}

interDir += 1 << refListId;
// both MVs are valid, average these two MVs 两个MVs都有效,平均这两个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;
roundAffineMv(avgMv.hor, avgMv.ver, 1);
mrgCtx.mvFieldNeighbours[cnt * 2 + refListId].setMvField( avgMv, refIdxI );
}
// only one MV is valid, take the only one MV 只有一个MV有效,只取一个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++;
}
}

// early termination 提前终止
if( cnt == maxNumMergeCand )
{
return;
}
}

uint32_t uiArrayAddr = cnt;

int iNumRefIdx = 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;
// =================================零MV============================
while (uiArrayAddr < maxNumMergeCand)
{
mrgCtx.interDirNeighbours [uiArrayAddr ] = 1;
mrgCtx.BcwIdx [uiArrayAddr ] = BCW_DEFAULT;
mrgCtx.mvFieldNeighbours [uiArrayAddr << 1].setMvField(Mv(0, 0), r);
mrgCtx.useAltHpelIf[uiArrayAddr] = false;

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

if ( mrgCtx.interDirNeighbours[uiArrayAddr] == 1 && pu.cs->slice->getRefPic(REF_PIC_LIST_0, mrgCtx.mvFieldNeighbours[uiArrayAddr << 1].refIdx)->getPOC() == pu.cs->slice->getPOC())
{
mrgCtx.mrgTypeNeighbours[uiArrayAddr] = MRG_TYPE_IBC;
}

uiArrayAddr++;

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

从HMVP列表获得Merge候选列表的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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; //HMVP列表
int num_avai_candInLUT = (int)lut.size();//HMVP列表的长度

for (int mrgIdx = 1; mrgIdx <= num_avai_candInLUT; mrgIdx++) //遍历HMVP列表
{
miNeighbor = lut[num_avai_candInLUT - mrgIdx]; //先取HMVP列表中的后面几项

// 冗余性检查
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) //如果Merge列表长度到达maxNumMergeCandMin1,则停止加入列表
{
break;
}
}
}

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

return false;
}

1.1 常规Merge模式

直接将MVP运动矢量信息(参考图像索引、运动矢量)作为当前CU运动矢量信息的帧间模式编码。需要建立一个MVP候选列表(MergeMVP),选择一个MVP作为当前CU的MV,就可以得到当前CU的像素预测值,进一步编码预测残差。

MergeMVP列表构造较为复杂,是Merge模式最关键的环节,候选最多为6个。依次包含
VVC在HEVC的基础上,扩展了Merge模式构造Merge List的方法,Merge List最多可以包含6个候选MV,构造方法如下:

  • 基于空间相邻块的空域MVP(最多提供4个候选MV)
  • 基于同位块的时域MVP(最多提供1个候选MV)
  • 基于历史信息的MVP(不限个数,直到填充到Merge List包含5个候选MV)
  • 成对平均MVP(由Merge List中前两个候选MV平均得到)
  • 零MV

1.1.1 空域候选MVP:

空域候选列表建立方式和HEVC一样。如下图所示,A1为当前CU左下处的相邻CU,B1为当前CU右上处的相邻CU,B2为当前CU的左上相邻CU,A0和B0分别为距离A1和B1处最近的CU。VVC中空域最多提供4个候选MV,即最多使用这5个候选块中的4个候选块的运动信息,候选列表按照B1->A1->B0->A0->(B2)的顺序建立,其中B2是替补,只有前4个有一个或多个不存在时(例如不再同一个slice或tile或使用帧内编码)才将B2加入候选列表。

在这里插入图片描述

当候选列表加入B1后,后面新加入候选项时要进行冗余性检查以免新加入的候选项的运动信息和已有项的相同。为了减少计算复杂度,冗余性检查时不会和已有的每一项进行比较,只比下图中较箭头连接的项。
冗余检查
这里的图和代码中并不完全一致,实际上,因为先进入列表的是B1,所以对于B1项,不需要进行冗余性检查,对于A1项,检查是否和B1冗余。其余项的检查和图中一致,注意对于B2项,要同时检查和B1、A1项是否冗余。
检查冗余性时,所检查的是运动信息,包括MV、参考帧索引等运动信息,如下代码所示:在代码重载了==和!=运算符,通过调用!=,进行冗余性检查
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
struct MotionInfo
{
bool isInter;
bool isIBCmot;
char interDir;
bool useAltHpelIf;
uint16_t sliceIdx;
Mv mv [ NUM_REF_PIC_LIST_01 ];
int16_t refIdx [ NUM_REF_PIC_LIST_01 ];
uint8_t BcwIdx;
Mv bv;
MotionInfo() : isInter(false), isIBCmot(false), interDir(0), useAltHpelIf(false), sliceIdx(0), refIdx{ NOT_VALID, NOT_VALID }, BcwIdx(0) { }

MotionInfo(int i) : isInter(i != 0), isIBCmot(false), interDir(0), useAltHpelIf(false), sliceIdx(0), refIdx{ 0, 0 }, BcwIdx(0) { CHECKD(i != 0, "The argument for this constructor has to be '0'"); }

bool operator==( const MotionInfo& mi ) const
{
if( isInter != mi.isInter ) return false;
if (isIBCmot != mi.isIBCmot) return false;
if( isInter )
{
if( sliceIdx != mi.sliceIdx ) return false;
if( interDir != mi.interDir ) return false;

if( interDir != 2 )
{
if( refIdx[0] != mi.refIdx[0] ) return false;
if( mv[0] != mi.mv[0] ) return false;
}

if( interDir != 1 )
{
if( refIdx[1] != mi.refIdx[1] ) return false;
if( mv[1] != mi.mv[1] ) return false;
}
}

return true;
}

bool operator!=( const MotionInfo& mi ) const
{
return !( *this == mi );
}
};

1.1.2 时域候选MVP:

利用当前CU在时域邻近已编码图像中对应位置CU来确定,叫做同位图像(ColPic),但是需要按照位置关系进行比例伸缩调整。参考时域距离(图片顺序技术POC)。最多提供一个时域MVP。

时域最多提供1个候选MV,时域候选列表的构建方式和HEVC一样,与空域的构建方式不同,时域候选列表不能直接使用候选块的运动信息,需要根据位置关系进行相应比例地伸缩。如下图所示
时域列表
其中tb表示当前图像和参考图像之间的距离(用POC度量),td是同位图像和其参考图像之间的距离。时域最多提供一个MV,计算方法如下所示:
在这里插入图片描述

1.1.3 基于历史的候选MVP

将先前已编码块的运动信息存储在一个最大长度为5的HMVP列表中,随着编码过程不断更新,按照先进先出的原则保持列表控制冗余,每个CTU行重新置0。如果MergeMVP列表还有空域位置,就按照HMVP列表顺序添加不同的选项。在空域候选和时域候选推导完成之后,将HMVP的Merge候选项加入Merge候选列表。

HMVP候选项来自于一个先进先出的表,表的长度为6,这个表通过已经编码块的运动信息构建,每到新的一行CTU时,这个表就要重置,即清空操作。每遇到一个帧间编码的CU(非子块)时,它相关的运动信息就会加到这个表的最后一项成为一个新的HMVP候选项。每当插入一个新的候选项时,首先要进行冗余性检查即检查待插入的项的运动信息和表中已有项的运动信息是否相同,如果不相同,则按照先进先出的规则进行插入操作;如果相同,则将相同的HMVP从维护的FIFO(先进先出)表中移除,其后所有的项都向前移动一位,将待插入的候选项插入到HMVP的表的末端。

在使用HMVP候选项构建Merge候选列表时,按顺序检查HMVP列表中最新的HMVP候选(从后向前检查),在和Merge列表中现存的候选检查完冗余之后将不重复的HMVP候选添加到Merge列表中去。直到候选列表的长度达到5个。
在这里插入图片描述
为了减少冗余性检查操作,进行了以下的简化操作:

用于构建Merge list的HMVP候选项数量设为 (N<=4)?M:(8-N),其中N表示Merge list中已有项的数目,M表示HMVP表中候选项数目。一旦Merge list中候选项的数目达到了最大允许候选项数目减一(即6-1=5),则停止从HMVP生成merge候选项的过程
HMVP列表由cs控制

1
2
3
4
5
struct LutMotionCand
{
static_vector<MotionInfo, MAX_NUM_HMVP_CANDS> lut;
static_vector<MotionInfo, MAX_NUM_HMVP_CANDS> lutIbc;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void CodingStructure::addMiToLut(static_vector<MotionInfo, MAX_NUM_HMVP_CANDS> &lut, const MotionInfo &mi)
{
size_t currCnt = lut.size();

bool pruned = false;
int sameCandIdx = 0;

for (int idx = 0; idx < currCnt; idx++) //判断HMVP列表中是否存在当前模式信息
{
if (lut[idx] == mi)
{
sameCandIdx = idx;
pruned = true;// 如果存在,则将其从列表删除
break;
}
}
// 如果当前模式信息已经在模式列表中,则删除
// 或者当前模式列表已满,则删除最前面的模式
if (pruned || currCnt == lut.capacity())
{
lut.erase(lut.begin() + sameCandIdx);
}

lut.push_back(mi); //将模式信息加入到HMVP列表中
}

1.1.4 成对平均候选MVP

如果还有位置,就使用平均候选AvgCnd来补充列表
使用Merge候选列表中前两个候选项对来生成成对平均候选mvp。Merge列表中的第一项和第二项分别定义为p0Cand和p1Cand。对于每个参考帧列表,分别根据p0Cand和p1Cand的MV是否可用来计算平均MV。即:

– 如果两个MV在一个列表中可用,则即使这两个MV指向不同的参考帧,也对它们进行平均,并且其参考帧被设置为p0Cand对应的参考帧;
– 如果只有一个MV可用,直接使用那一个;
– 如果没有可用的运动矢量,则保持此列表无效。
– 如果p0Cand和p1Cand的半像素插值滤波器指数不同,则将其滤波器指数设置为0。

1.1.5 零值MVP:

还剩下就将单向、双向、值为(0,0)的MV补充道MergeMVP列表的末端
如果计算完平均候选项后Merge还未填满,则用0MV填充候选列表,直到候选列表达到最大长度。

1.2 SKIP模式

Skip由cu_skip_flag标记,是一种特殊的Merge模式。直接用MVP信息作为当前CU的运动矢量,除了不编码MVD信息,也不编码预测残差信息。只需要MVP信息索引,编码比特数很少。相应的在解码器只需要解析出该索引下的运动信息,然后通过运动补偿得到的预测像素值直接作为重建像素值。Skip模式可以大大减少传输比特数。Skip模式也可以是MMVD\GPM\SbTMVP和仿射Merge等的拓展模式。
Skip模式

1.3 带有运动矢量差的Merge模式(MMVD)

因为视频相邻区域CU往往有不同的运动特性,常规merge降低了MV编码比特数,但也因为不准确的MV产生大的预测误差。AMVP技术编码运动矢量的预测残差MVD,与MVP一起表示运动矢量。

但是AMVP模式下的MVD通常需要较多的比特表达,所以VVC引入了一种带有运动矢量差的merge技术MMVD,设定了包含多个固定值的M_MVd集合,选取集合中的一个值作为当前CU的MVD。MVD和MVP都是通过索引获得,MVP必须为mergeMVD列表前2项候选项。M_MVd对应4种方向上8种偏移步长,共284=64个新的运动矢量候选项。具体单项预测和双向预测,需要根据时间信息进行缩放。

为了进一步提高Merge/Skip模式下预测MV的准确性,VVC在常规Merge模式基础上,增加了“运动矢量差的”Merge模式。该模式是将常规Merge模式构建得到的Merge列表中的前两个候选MV作为初始MV,在上下左右四个方向上,进行8种步长的搜索,即在初始MV基础上加上一定的偏移MVD得到2x4x8=64个MV,并从中选出率失真代价最小的MV作为最终的细化MV。在编码端,将初始MV在Merge List中的索引、搜索方向索引和搜素步长索引传给解码端。

在这里插入图片描述
在这里插入图片描述
具体细节和代码参考:带有运动矢量差的Merge技术(Merge mode with MVD)

1.4 联合帧内帧间预测模式(CIIP)

利用帧内预测值和帧间预测值的加权平均得到当前CU预测值的技术。限定帧内预测只采用planar模式,帧间预测只采用常规merge模式,CU的尺寸和形状有限定。VVC对于Merge模式编码的CU,加入了帧内和帧间联合预测技术,即使用常规Merge模式通过运动补偿得到帧间预测信号Pinter ; 通过planar模式得到帧内预测信号Pintra 。 然后,使用加权平均对帧内和帧间预测信号进行组合,计算公式如下所示:
在这里插入图片描述
其中权重取决于上相邻块和左相邻块的编码模式。

CIIP使用条件:
Merge模式编码的CU
CU包含至少64个亮度像素(即CU宽度乘以CU高度等于或大于64),并且CU宽度和CU高度均小于128,
具体细节和代码参考:帧间和帧内联合预测(Combined inter and intra prediction, CIIP)

1.5 几何划分预测模式(GPM)

VVC中,CTU可以经过二叉、三叉、四叉树划分,但是实际视频内容和多样,当物体运动具有非水平或垂直边缘的时候,不能有效匹配,所以引入几何帧间预测模式,允许使用非水平或垂直直线对矩形CU进行划分,每个子区域可以使用不同的运动信息进行运动补偿。从而提升预测准确度。为merge拓展模式。针对运动物体的边缘部分,VVC引入了一种几何划分模式,该模式可以将图像中运动物体的边缘部分,采用更灵活的表示方法。具体地,对于图像中运动物体的边缘CU,可以将其划分为两个非矩形的子CU分别进行单向预测,并进行加权融合,得到整个CU的预测值。

使用GPM模式时,通过几何定位的直线将CU划分为两部分(下图所示)。

在这里插入图片描述CU划分后的子分区中包含单独的运动信息,每个子分区仅允许单向预测,如下图所示,当前CU的右侧部分来自参考帧P0的MV0预测,左侧部分来自参考帧P1的MV1预测。最终通过使用整数融合矩阵W0和W1进行边缘融合生成最终的GPM预测PG。
在这里插入图片描述
为了简化运动信息编码,GPM两个分区的运动信息使用Merge模式编码,GPM模式的候选列表是由常规Merge模式推导而来的。
具体细节和代码参考:带有运动矢量差的Merge技术(Merge mode with MVD)

原理
为了区分不同的物体,可能在物体边缘产生大量小块,消耗过多头信息,且划分边缘与实际边缘不符合,无法有效表示非垂直边缘。

GPM使用直线将矩形CU划分成2个不规则子区域,拥有自己的运动矢量信息,并进行相应的单向运动补偿。划分线可以用角度和偏移量表示,使用法线和点到直线的距离来表示。VVC中角度和偏移量只允许选择部分预定义的离散数值。各区域分别利用不同运动信息获得补偿,并对划分线附近区域以软混合的方式进行加权融合,以模拟自然场景中柔和的边缘过度。

几何划分:共支持64种划分方式。
子区域运动矢量:两个子区域都只使用单项预测,并以merge模式编码,运动矢量由GPM模式专用的单向MVP候选列表、列表索引获得。

运动补偿加权融合:为了避免划分线附近像素值的突变,距离话分享较近像素的权重渐变,融合了两个方向的预测值,模拟柔和过渡。

运动信息存储:1、当子块中心位置到划分线距离大于等于2时,存储运动矢量为中心所在子区域的运动矢量2、当划分线小于等于2的时候,如果两个子区域运动矢量所在参考列表不同,则作为双向预测分别存储两个运动矢量信息,如果两个子区域运动矢量所在参考列表相同,则取第二个预测分区的运动矢量作为单项预测存储。

  1. AMVP

二、高级运动矢量预测技术(AMVP)

AMVP模式是H.265/HEVC中提出的新的MV预测技术,H.266/VVC仍采用了该技术,并在HEVC的基础上进行了改进。AMVP通过高效表达运动矢量插值MVDF进行运动信息编码。主要是利用空域和时域的运动矢量的相关性,为当前PU建立了候选预测MV列表,编码端从其中选出最优的预测MVP,并对MVP进行差分编码(即通过运动搜索获得真正的MV,将运动矢量差进行编码(MV-MVP));解码端通过建立相同的列表,仅需要预测MVP在列表中的索引运动向量残差MVD即可计算出当前PU的MV。

Merge与AMVP模式的区别:

  • MVP和MVD差异
    Merge模式是直接将通过空域和时域MV的相关性获得的预测MVP作为最终的MV,而不存在MVD;
    AMVP模式是以相关性得到预测MVP作为搜索起点,通过运动搜索获得更准确的MVP,然后再将预测的MVP和搜索得到的MV之间的差值MVD进行编码
  • 编码传输内容差异
    Merge模式仅需要传最佳预测MV在候选列表中的索引;
    AMVP模式除了需要传最佳MVP在候选列表的索引,还需要对运动矢量差MVD进行编码
  • Merge模式和AMVP模式的候选列表长度不同
    (Merge模式的候选列表长度为6,AMVP模式的候选列表长度为2)
  • 构建MVP候选列表的方式也不同
    AMVP的候选列表长度为2,候选MV总共有4种类型:

– 基于相邻块的空域MVP
– 基于同位块的时域MVP
– 基于历史信息构建的HMVP
– 零MV

2.1 常规AMVP模式

AMVP是针对运动矢量信息的差分编码技术。利用空域、时域上运动矢量的相关性,为当前CU建立MVP候选列表,和Merge不同,AMVP在构建候选MVP候选列表的时候,针对不同参考图像得到的MVP列表可能不同,需要编码参考帧索引,还需要编码选中MVD列表缩影和MVD(MV-MVP)。AMVP列表针对每个参考图像建立,每个列表各项只包含单项运动信息,且列表长度仅为2。

2.1.1 空域候选MVP

VVC的AMVP模式规定,只能从当前PU的左侧和上方各产生一个候选MV,最多允许2个空域候选MV,左侧的检查顺序是A1->A0,上侧的检查顺序是B1->B0->B2。
在这里插入图片描述

2.1.2 时域候选MVP

与merge类似

2.1.3 基于历史的候选MVP

从HMVP中最近4项进行选择

2.1.4 零值MVP

2.2 对称运动矢量差分编码技术(SMVD)

对于包含双向运动信息的AMVP模式,可以使用对称MVD编码模式。具体的,在编码时,仅需要编码前向MVD0,后向的MVD1可由MVD1=(-MVD0)推导得到。换句话说,对于双向预测,并且参考位于当前帧两侧的情形,前后向运动矢量具有对称一致性,可以采用对称运动矢量差分编码。对称MVD模式(symmetric MVD mode)是VVC中新提出的一种双向预测时MVD语法单元传输模式。在使用对称MVD模式时,在传输双向预测的运动信息时不需要传list0和list1中参考图像的索引和list1的MVD。这些信息可以在解码端生成。
在这里插入图片描述
使用条件

  • AMVP候选包含双向运动信息
  • 当前CU的前向参考帧列表List0中距离最近的参考图像和后向参考帧列表List1中距离最近的参考图像正好处于当前图像的两侧

**解码过程 **
对称MVD模式的解码过程如下:

1、在slice层,变量BiDirPredFlag, RefIdxSymL0和RefIdxSymL1按如下方式生成:

  • 若mvd_l1_zero_flag=1,则BiDirPredFlag=0
  • 否则,如果在list0中离当前图像最近的参考图像和在list1中离当前图像最近的参考图像分别是前向参考图像和后向参考图像对或分别是后向参考图像和前向参考图像对,且list0和list1的参考图片都是短期参考图片,则BiDirPredFlag=1。否则,BiDirPredFlag=0

2、在CTU层,如果CU是双向预测且BiDirPredFlag=1,则需要在码流中显示传输一个对称模式标识符来表明是否使用对称模式。

当对称模式标识符为真时,在码流中只需要传mvp_l0_flag, mvp_l1_flag和MVD0。list-0和list-1的参考索引分别被设置为等于该对参考图片。MVD1=(-MVD0)最终运动向量可由下式生成:
在这里插入图片描述
在解码端MVD1由MVD0的相反数生成,如下图所示。
在这里插入图片描述
在编码端进行对称MVD模式的运动估计时需要一个初始MV。这个初始MV是从单向运动搜索MV、双向运动搜索MV和AMVP list中选择率失真代价最小的MV得到。

采用SMVD的时候,前后向参考图像直接选择两个参考图像列表中距离当前图像最近的,并且时间上处于当前图像两侧的短期参考图像。这样可以有效节省前向MV的参考图像索引、后向图像索引、后向MVD编码消耗的比特数。只需要编码双向AMVP候选列表索引、前向运动矢量差MVD。过程如下:

分别获取两个参考图像列表中距离当前帧最近的短期参考图像,得到当前CU的前后向参考图像
根据前后向参考图像索引RefIdxSymL0和RefIdxSymL1分别建立前向AMVP候选列表和后向AMVP候选列表
根据MVP列表缩影,得到双向MVP
根据码流得到前向MVD,推断得到后向MVD
计算得到MV

2.3 自适应运动矢量精度(AMVR)

允许每个CU自适应选择一种精度表示MVD,最高精度为1/16,最低精度为4亮度像素。适用于传输非0MVD的情况吗,如常规AMVP、SMVD、仿射AMVP。使用率失真优化的方法噶判断是否使用AMVR以及采用何种精度。VTM使用快速算法跳过部分MVD精度。
由于实际运动通常是连续的,因此整像素精度通常不能很好地表示物体的运动。在HEVC中,亮度分量的运动矢量使用1/4像素精度,色度分量的运动矢量使用1/8像素精度;在VVC中,进一步提高了像素精度,亮度分量的运动矢量使用1/16像素精度,色度分量的运动矢量使用1/32像素精度。随着像素精度的增长,预测精度增长,但随之需要大量的编码比特。

综合考虑预测精度和编码比特消耗,VVC提出了自适应运动矢量精度AMVR技术,在CU级对亮度分量MVD采用不同的像素精度进行编码。根据当前CU帧间预测模式的不同,亮度分量MVD编码精度有不同的选取策略:

常规AMVP模式:1/4亮度像素精度,1/2亮度像素精度,整数亮度像素精度或四倍亮度像素精度。
仿射AMVP模式:1/4亮度像素精度,整数亮度像素精度或1/16亮度像素精度。
在编码端,需要比较各个精度下的率失真代价并选出代价最小的编码精度作为当前CU的最优精度。为了降低编码端复杂度,避免对每个MVD精度进行四次(Affine AMVP为三次)CU级的率失真代价的比较,在VVC中使用一些快速算法来跳过除了1/4精度以外某些MVD精度的率失真代价检查。

在HEVC中,当Slice header中的use_integer_mv_flag等于0时,编码快的MVD(CU的运动矢量和预测运动矢量之间的差值)以四分之一亮度像素精度进行编码。 在VVC中,引入了CU级自适应运动矢量精度(AMVR)方案。 AMVR允许以不同的精度对CU的MVD进行编码。

根据当前CU的模式(常规AMVP模式或仿射AVMP模式),可以如下自适应地选择当前CU的MVD:

常规AMVP模式:1/4亮度像素精度,1/2亮度像素精度,整数亮度像素精度或四倍亮度像素精度。
仿射AMVP模式:1/4亮度像素精度,整数亮度像素精度或1/16亮度像素精度。
只有当前CU具有至少一个非零MVD分量时,才会传输CU级像素精度。 如果所有MVD分量(即参考列表L0和参考列表L1的水平和垂直MVD)均为零,则MVD默认使用1/4亮度像素精度。

对于至少有一个非零MVD分量的CU,第一个标志位表示CU是否使用1/4亮度像素精度。如果第一个标志为0,则不需要传输其余的标志,当前CU的MVD使用1/4亮度像素精度。否则,

对于常规AMVP模式,需要第二个标志位以表示CU使用1/2亮度像素精度或其他MVD精度(整数或四倍亮度像素精度)。在1/2亮度像素精度下,使用6抽头插值滤波器而不是默认的8抽头插值滤波器。如果不是1/2亮度像素精度,则需要第三个标志用来指示当前CU使用整数亮度像素精度还是四倍亮度像素精度。
对于仿射AMVP模式,第二个标志用于指示是使用整数亮度像素精度还是1/16 亮度像素精度。
为了确保重建的MV具有同样的精度,在与MVD相加之前,CU的MVP(运动矢量预测)会四舍五入到与MVD相同的精度。MVP向零进行四舍五入(即负MVP向正无穷大舍入,正MVP向负无穷大舍入)。

编码器使用RD检查确定当前CU的MV精度。为了避免总是对每个CU的MVD执行四次精度检查,在VTM11中,除了1/4亮度像素精度外,其余MVD精度的RD检查只能在某些条件下执行。

对于普通AVMP模式,首先计算1/4亮度像素精度和整数亮度像素精度的RD Cost。然后将整数亮度像素精度的RD Cost与1/4亮度像素精度的RD Cost进行比较,以确定是否有必要进一步检验四倍亮度像素精度的RD Cost。如果1/4亮度像素精度的RD Cost远小于整数亮度像素精度,则跳过四倍亮度像素精度的RD检查。如果整数亮度像素精度的RD Cost显著大于先前测试的MVD精度的最佳RD Cost,则跳过1/2亮度像素精度的检查。
对于仿射AMVP模式,如果在检查仿射Merge/Skip模式、Merge/Skip模式、常规AMVP模式的1/4亮度像素精度和仿射AMVP模式1/4亮度像素精度的率失真代价后未选择仿射帧间模式,则不检查1/16亮度像素精度和整数亮度像素精度的仿射帧间模式。
此外,在1/16亮度像素级精度仿射帧间模式中,以1/4亮度像素精度仿射帧间模式得到的仿射参数作为搜索起点。

运动场存储
在VVC中,显式发出信号的运动矢量的最高精度为1/4像素精度。 在某些仿射帧间预测模式中,以1/16亮度像素精度导出运动矢量,以1/16精度执行运动补偿预测。 在内部运动场存储方面,所有运动矢量都以1 / 16亮度精度存储。

对于TMVP和SbTVMP使用的临时运动场存储,与HEVC中的16x16大小粒度相比,VVC以8x8大小粒度执行运动场压缩。

VTM中,MV的精度的定义:

1
2
3
4
5
6
7
8
9
enum MvPrecision
{
MV_PRECISION_4PEL = 0, // 4-pel
MV_PRECISION_INT = 2, // 1-pel, shift 2 bits from 4-pel
MV_PRECISION_HALF = 3, // 1/2-pel
MV_PRECISION_QUARTER = 4, // 1/4-pel (the precision of regular MV difference signaling), shift 4 bits from 4-pel
MV_PRECISION_SIXTEENTH = 6, // 1/16-pel (the precision of internal MV), shift 6 bits from 4-pel
MV_PRECISION_INTERNAL = 2 + MV_FRACTIONAL_BITS_INTERNAL,
};

几种模式的像素精度定义:

1
2
3
const MvPrecision Mv::m_amvrPrecision[4] = { MV_PRECISION_QUARTER, MV_PRECISION_INT, MV_PRECISION_4PEL, MV_PRECISION_HALF }; // for cu.imv=0, 1, 2 and 3
const MvPrecision Mv::m_amvrPrecAffine[3] = { MV_PRECISION_QUARTER, MV_PRECISION_SIXTEENTH, MV_PRECISION_INT }; // for cu.imv=0, 1 and 2
const MvPrecision Mv::m_amvrPrecIbc[3] = { MV_PRECISION_INT, MV_PRECISION_INT, MV_PRECISION_4PEL }; // for cu.imv=0, 1 and 2

精度转换的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void changePrecision(const MvPrecision& src, const MvPrecision& dst)
{
const int shift = (int)dst - (int)src;
if (shift >= 0)
{
*this <<= shift;
}
else
{
const int rightShift = -shift;
const int nOffset = 1 << (rightShift - 1);
hor = hor >= 0 ? (hor + nOffset - 1) >> rightShift : (hor + nOffset) >> rightShift;
ver = ver >= 0 ? (ver + nOffset - 1) >> rightShift : (ver + nOffset) >> rightShift;
}
}
  1. 基于子块的帧间预测技术

三、基于子块的帧间预测技术

视频内容包括平移、旋转、缩放、拉伸运动,利用基于子块的帧间预测模式将CU划分为多个子块,利用CU的运动信息按一定规则得到每个子块不同的运动信息,可以节省此类运动信息所包含的比特数。之前的帧间预测的运动估计仅考虑了简单的平移运动,但在自然界中还存在缩放、旋转、透视等运动,如果仅使用之前的平移运动模型,无法有效表示缩放等运动。

研究发现,仿射(Affine)运动模型能够很好地描述自然界中的非平移运动,VVC中引入了基于块的放射运动补偿技术,包括四参数模型和六参数模型。具体地,利用块中的两个控制点(四参数模型)或者三个控制点(六参数模型)的MV,来推导出整个块中每个4x4子块的MV,之后分别根据每个子块的MV通过运动补偿得到每个子块的预测值。
在这里插入图片描述
和传统的帧间预测模式类似,Affine模式也分为Affine Merge模式和Affine AMVP模式,Affine Merge模式是通过空间相邻的CU运动信息得到控制点的MV;Affine AMVP模式是根据空间相邻的CU运动信息预测控制点的MV,再以预测的CPMV作为搜索起点进行运动估计,得到最佳的CPMV,并将二者的差值和预测CPMV在候选列表中的索引传给解码端。

3.1 基于子块的时域MV预测技术(SbTMVP)

使用同位图像ColPic中参考块的运动信息直接得到当前CU子块的MV信息,在当前片是P片时为每个子块构造一个单项预测TMVP,在当前片为B片时为每个子块构造一个双向预测TMVP。当前CU各子块的运动信息由相应同位子块推导得到,当同位子块的运动信息不同时,子块运动信息也不同。

– 获取同位运动偏移
– 获取中心运动信息
– 获取子块运动信息:针对内部子块推导得到对应的TMVP
– 基于子块的仿射运动补偿
运动具有一定的规律性,可以统一通过仿射变换的思想。即块内任意像素的运动矢量可以由已知固定像素的运动矢量确定。

有4、6参数仿射模型,只需要编码CU控制点的运动矢量,根据模型公式就可以推导出CU内所有秀昂宿的运动矢量。VVC为每个4x4亮度子块推导一个共用的与i你懂矢量,以中心位置推导得到的运动矢量。并给出了内存访问带宽限制机制和回退模式。

与HEVC中的时间运动矢量预测(temporal motion vector prediction, TMVP)类似,SbTMVP使用同位图片中的运动场来改善当前图片中CU的运动矢量预测和Merge模式。不同的是,sbTMVP预测的是子CU级的运动。

SbTMVP中使用的子CU大小固定为8x8,SbTMVP模式仅适用于宽度和高度都大于或等于8的CU。

在VVC中,SbTMVP和Affine Merge的候选是共存的,将SbTVMP得到的候选和仿射Merge候选共同组成基于子块的Merge候选列表。 通过序列参数集(SPS)标志启用/禁用SbTVMP模式。 如果启用了SbTMVP模式,则将SbTMVP预测变量添加为基于子块的Merge候选列表的第一项,然后是仿射Merge候选项。

将sbTMVP和Affine Merge共同的候选列表称为基于子块的Merge列表,其大小在SPS中用信号通知,并且基于子块的Merge列表的最大允许大小为5。

仿射Merge模式

3.2、仿射Merge模式

在基于子块的仿射运动补偿的基础上,各控制点的运动矢量(CPMV)由SubBlkMergeMVP列表直接得到,是merge的扩展模式。列表中每项候选项包含多个运动矢量,由空间、时间相邻CU的运动信息生成。

SubBlkMergeMVP列表

空间相邻仿射模式CU继承候选:最多包含2个,左侧相邻块扫描顺序A0-A1,上册相邻块为B0-B2,每侧采用一个使用仿射模式编码的CU来推导CPMVP候选。

空域和时域相邻CU的平移MV构造:设置4个控制点备选。查表组合得到候选CPMVP
零值MV填充

对于宽度和高度大于等于8的CU, 可以使用Affine Merge模式。Affine Merge模式中,控制点的运动矢量CPMV是根据空域相邻CU的运动信息生成的,最多有五个候选项,有以下五种方式生成CPMV的候选:

继承Affine Merge候选项:直接继承其相邻CU的CPMV候选项(最多有两个候选项,上相邻CU最多一个,左相邻CU最多一个)
构造Affine Merge候选项:使用相邻CU的平移运动的MVs构造CPMVPs候选项(不限个数,直至填充够5个)
零MV(若不足5,则填充)

3.3、仿射AMVP模式

当仿射merge无法得到有效的控制点运动矢量的时候使用。针对指定的参考图像,利用仿射AMVP列表得到CPMV的预测值CPMVP,结合对应控制点MVD(MvdCp)表示CPMV。需要将单双向运动信息,包括参考索引、CPMVP列表索引、对应的Mvdcp,和预测残差一起送入码流。
Affine AMVP模式可应用于宽度和高度均大于或等于16的CU。在Affine AMVP模式下,需要传输其预测CPMV在候选列表中的索引以及它和运动搜索得到的实际CPMV的残差。Affine AVMP候选列表大小为2,它是通过依次使用以下5种CPMV候选类型生成的:

  1. 继承AMVP候选项:继承其相邻CU的CPMV候选项
  2. 构造Affine AMVP候选项:使用相邻CU的平移运动的MVs构造CPMVPs候选项
  3. 直接使用相邻CU的平移MV
  4. 同位块的时域MV
  5. 零MV

仿射AMVP模式列表
1. 空域相邻仿射模式CU继承:根据采用仿射模式的空域相邻CU的CPMV推导得到CPMVP候选。
2. 空域相邻CU的平移MVP构造:根据MV平移得到
3. 空域相邻CU的平移MV填充
4. 时域平移MV填充
5. 零值MV填充

帧间后处理技术

四、帧间后处理技术

为了进一步提高帧间预测的准确性,VVC采用了多种帧间预测后处理技术值,以修正帧间预测的预测像素,提高预测性能。
主要包含三个技术:

  • DMVR技术:用于修正常规Merge模式双向预测MV,以Merge列表中的双向MV为初始MV,在一定范围内镜像搜索,以获得更精确的MV。
  • BDOF技术:使用双向光流技术来修正双向预测的预测值。
  • PROF技术:使用光流技术来细化基于子块的Affine运动补偿预测。

4.1 解码端运动矢量细化(DMVR)

merge模式可以高效表示运动矢量信息,但可能会导致其运动矢量并非使参考块与编码块最匹配。所以VVC采用了一种双边匹配的解码端运动矢量细化技术。

正对采用双向预测merge模式的CU,可以直接得到前后向运动矢量MVL0\MVL1。以对称的方式,给这两个运动矢量加一个小的偏移量MV_diff。基本过程:

参考块获取,根据前后向运动矢量,对每个子块实施运动补偿得到前后向参考块,并对前后向参考子块进行扩充。
整像素搜索:通过计算前后向参考子块的SAD衡量其匹配程度,找到最小的对应MV_diff作为整像素搜索的最优MV整像素精度偏移量。
亚像素计算:亚像素精度MV_diff并不搜索MV_diff周围的亚像素位置,而是基于二次误差曲面函数计算得到。
在这里插入图片描述

4.2 基于光流场的预测值修正

光流指视频帧内容在时域上的瞬时运动速度,即像素在时间域上的运动速度。时间较短的两帧,光流也表现为统一目标点在两帧之间位移,即像素的运动矢量。所以可以利用图像帧中像素亮度在时间域上的变化及像素空域相关性,计算视频帧之间的像素运动信息。因此可以根据参考像素值、光流值、空域梯度得到。当运动补偿中运动矢量存在较小误差时,可以计算运动矢量误差,利用预测值进行修正。

4.3 双向光流预测值修正(BDOF)

采用参考帧对称的帧间双向预测的编码块,前、后向预测参考块和当前编码块间的光流对称。可以根据光流公式推到i计算解决一个优化问题:对齐后前后向预测参考块应一致。可以求出双向预测得到的预测值,和双向光流补偿值,得到当前块的补偿光流。为4x4的子块估计一个光流。

子块获取:包括padding预处理
子块亮度空域梯度和时域梯度的计算:根据公式计算
光流计算:VVC采用了简化的光流计算方式
补偿值计算:进一步得到每个像素的光流修正值

4.4 基于光流的仿射预测修正

针对采用仿射运动补偿的编码块,光流预测细化技术为4x4子块的每个像素计算光流补偿值,即像素运动矢量和子块运动矢量的插值,然后计算每个像素的亮度补偿值。

4.5 子块光流计算

子块亮度空域梯度计算
亮度补偿值计算:利用空间梯度和光流补偿值,计算每个像素的亮度补偿值

五、帧间加权预测

Slice级加权预测
由于光强造成全局或者局部的亮度变化,但是这类相邻图像内容依旧相似,但在像素值上无法反应,所以对于亮度整体渐变的场景,slice级加权预测可以有效应对,对参考图像的重建像素值做一个线性变换得到预测值。

可以用于单项预测也可以用于双向预测,slice内所有CU采用其中一组或者两组加权参数。

5.1、CU级双向加权预测(BCW)

仅对双向预测CU开启,使用少量预定义的权值,且不同配置下权集不同,编码索引。
在VVC中,双向预测模式可以对两个预测信号进行加权平均。
在这里插入图片描述
其中w为权重,总共包含5个权重,w∈{-2,3,4,5,10}。权重由预测模式确定。
BCW仅适用于具有256个或更多亮度像素的CU(即CU宽度乘以CU高度大于或等于256)。

5.2、加权预测(WP)

加权预测用于修正P Slice或B Slice中的运动补偿预测像素,加权预测表示预测像素可以用一个(适用于P Slice情况)或者两个(适用于B Slice情况)参考图像中的像素通过与加权系数相乘得出,如下:
在这里插入图片描述
加权预测适用于两图像之间像素值整体变化且有相同变化规律的情形,如淡入、淡出等效果。
加权预测(WP)用于帧级加权,双向加权预测(BCW)用于CU级。加权预测WP既可用在双向预测,也可用在单向预测;双向加权预测BCW仅可用在双向预测。

六、帧间预测模式组织结构

VVC中使用了大量的帧间编码技术,但是编码选取帧间预测模式也需要消耗很多比特。所以设计了树形结构来编码该模式的使用。
在这里插入图片描述

运动估计

七、运动估计

由于视频中通常存在很多运动物体,因此简单地将相邻帧的同位像素作为预测值的预测精度并不高,因此,通常使用运动估计来掌握运动物体的运动情况。通过运动估计,可以在参考帧中找到一个最佳匹配块,最佳匹配块到当前块的位移即为运动矢量,得到运动矢量之后,就可以通过运动补偿得到当前块的预测值。
在这里插入图片描述
运动估计要求当前块在参考帧中某一范围内,找出最佳的匹配块,目前主流标准中常用的匹配准则是SAD准则和SATD准则,即
在这里插入图片描述
SAD(s,p)表示的是原始块s和匹配块p的绝对误差和,λmotion
表示编码运动信息(如MV、参考图像索引)等所需的比特数,λmotion
为运动估计过程中的拉格朗日因子

由于运动搜索算法复杂度非常高,所以编码器里常用的是快速运动搜索算法。在自然界中,物体的运动具有一定的连续性,所以相邻的两幅图像之间的物体运动可能并不是以整数像素为单位的,而有可能是1/2 像素,1/4 像素等等分像素单位。此时若依然使用整数像素进行搜索,则会出现匹配不准确的问题,导致最终的预测值和实际值之间的残差过大,影响编码性能。因此,近年来视频标准中常采用分像素运动估计,即首先对参考帧的行和列方向进行插值,对插值后的图像中进行搜索。HEVC采用1/4像素精度进行运动估计,VVC中采用1/16像素精度运动估计。

H.266/VVC的参考软件平台VTM中使用TZSearch搜索算法进行运动估计。
TZSearch算法中的菱形搜索模板
TZSearch算法中的正方形搜索模板
TZSearch算法的步骤:

①确定搜索起始点:VVC中采用AMVP技术来确定起始搜索点,AMVP会给出若干个候选预测MV,编码器从中选择率失真代价最小的作为预测MV,并用其所指向的位置作为起始搜索点。

②以步长1开始,按照上图所示的菱形模板(或正方形模板)在搜索范围内进行搜索,其中步长以2的整数次幂的形式进行递增,选出率失真代价最小的点作为该步骤搜索的结果。

③若步骤②中得到的最优点对应的步长为1,则需要在该点的周围进行两点搜索,其主要目的是补充搜索最优点周围尚未搜索的点。如下图所示,若步骤②使用的是菱形模板,则最优点可能是2、4、5、7;若步骤二使用的是正方形模板,则最优点可能为1~8。两点搜索将会搜索图中与当前最优点距离最近的两个点。例如,若最优点为2,则会搜索a,b两个点;若最优点为6,则会搜素e,g两个点。

④若步骤②中得到的最优点对应的步长大于某个阈值,则以该最优点为中心,在一定范围内做全搜索,即搜索该范围内的所有的点,选择率失真代价最小的作为该步骤的最优点。

⑤以步骤四得到的最优点为新的起始的搜索点,重复步骤二到步骤四,细化搜索,当相邻两次细化搜索得到的最优点一致时停止细化搜索,此时得到的MV即为最终的MV。

运动补偿

八、运动补偿

几何划分帧间预测技术GPM模式需要对划分边缘进行加权渐变处理与多种帧间预测模式同时应用的光流场修正、片级运动补偿(WP)、CU级权重的加权预测(BCW)等预测块处理技术

帧间编码信息的存储
需要存储除了参考帧、MV水平分量、MV垂直分量,还有半像素插值滤波器索引、CU级权重双向加权预测的权重索引、用于放射模式的局部多控制点MV信息、参考图像的MV信息。

亚像素插值
VVC采用双线性插值方法,插值滤波器为一维滤波器,按照先水平方向后垂直方向的顺序完成二维亚像素插值。
用于提升运动矢量精度也用来防止当前帧与参考帧分辨率不同的情况。

插值滤波器选择
(1)亮度分量:最高支持1/16像素精度插值,统一为8抽头滤波器。
(2)色度分量:最高支持1/32像素精度插值,统一使用4抽头滤波器。

水平、垂直滤波器的系数分别由水平、垂直方向的缩放因子决定,最后通过查表的方式获得插值滤波器的系数。