Codec
AV1

AV1 specification を読む (ループフィルタ)

More than 1 year has passed since last update.

AV1 specification 日本語訳

デブロッキングフィルタ処理です。

だいたいH.264, H.265 と似たような処理です。
図示などはまた後日。


ループフィルタ処理

この処理の入力は、再構成した画素の配列 CurrFrame です。

この処理の出力は、更新した配列 CurrFrame で、デブロックした画素が含まれます。

ループフィルタの目的は、視覚的なアーティファクトを除去(少なくとも低減)することです。
スーパーブロックやサブブロック間の符号化の独立性に関連します。

最初に、8.8.1節で規定されるループフィルタフレーム初期化処理を呼びます。

そして、以下のようなスーパーブロックのラスタスキャン順にループフィルタを適用します。

for (row=0; row<MiRows; row+=8)
    for (col=0; col<MiCols; col +=8)
        for (plane=0; plane<3; plane++)
            for (pass=0; pass<2; pass++)

8.8.2節で規定されるスーパーブロックループフィルタ処理を、plane, pass, row, col を入力として呼びます。

注意:
ループフィルタはデコード処理の必須部分です。
ループフィルタの結果は、後続のフレームの予測に使われるためです。

注意:
多くの画素が1回以上フィルタされる可能性があるので、上記で規定しているどのエッジを処理するかの順番は、どの実装でも同じである必要がります。
1つのエッジ内では、画素は並列にフィルタできます。

注意:
ループフィルタは、マクロブロックが「再構成」(つまり、予測値と残差が加算)された後に適用されます。
correct decoding is predicated on the fact
that already-constructed portions of the current frame referenced via intra prediction are not yet filtered.

ループフィルタフレーム初期化処理

この処理の出力は、テーブル LvlLookup です。

この処理は、フィルタ強度LUTをフレームに1回準備するために呼ばれます。

nShift = loop_filter_level >> 5

segment_id = 0.. MAX_SEGMENTS-1 について、以下の手順を適用します。

  1. lvlSeg = loop_fliter_level
  2. seg_feature_active(SEG_LVL_ALT_L)==1ならば、以下の手順を適用します。 a. segmentation_abs_or_delta_update==1ならば、lvlSeg=FeatureData[segmant_id][SEG_LVL_ALT_L] b. segmentation_abs_or_deta_update==0ならば、lvlSeg=FeatureData[segmant_id][SEG_LVL_ALT_L]+loop_filterlevel c. vlSeg=Clip3(0, MAX_LOOP_FILTER, lvlSeg)
  3. loop_filter_delta_update==0ならば、LvlLookup[segment_id][ref][mode]=lelSeg, ref=INTRA_FRAME..MAX_REF_FRAMES-1, mode=0..MAX_MODE_LF_DELTAS-1
  4. loop_filter_delta_enabled=1として、以下を適用します。
intraLvl = ...

スーパーブロックループフィルタ処理

この処理の入力は以下の通りです。

  • Y, U, V 画素のそれをフィルタするかを規定する変数 plane
  • エッジの方向を指定する変数 pass。pass==0は垂直ブロック境界、pass==1は水平ブロック境界です。
  • 8x8ブロック単位のスーパーブロック座標 row, col

この処理の出力は、配列 CurrFrame の更新された値です。

subX, subY をカレントプレーンのサブサンプリング変数とします。

  • plane==0ならば、subX=0, subY=0。
  • そうではなkれば、subX=subsampllng_x, subY=subsampling_y1.

dx, dy, sub, edgeLen を以下のように求めます。

  • pass==0ならば、dx=1, dy=0, sub=subX, edgeLen=64>>subY
  • pass==1ならば、dx=0, dy=1, sub=subY, edgeLen=64>>subX

dx, dyはフィルタ売る画素間のオフセットです。

subはフィルタの方向(つまり、フィルタする境界に垂直方向)のサブサンプリング因子です。

edgeLenは画素単位の境界の長さです(lumaで64、サブサンプリングされたchromaはもっと少ない)。

edge=0..(16>>sub)-1 昇順、i=0..edgeLen-1 について、以下の手順を適用します。

  1. x, yを以下のように求めます。
    • pass==0ならば、x=col*8+edge*(4<<subX), y=row*8+(i<<subY)
    • pass==1ならば、x=col*8+(i<subX), y=row*8+edge*(4<<subY)
  2. loopCol = ((x>>3) >> subX) << subX
  3. loopRow = ((y>>3) >> subY) << subY (loopRow, loopColは8x8ブロック単位の輝度座標です)
  4. MiSize = MiSizes[loopRow][loopCol]
  5. tx_size = TxSizes[loopRow][loopCol]
  6. txSz = (plane>0) ? get_ux_tx_size() : tx_size
  7. sbSize を以下のように求めます。
    • sub==0ならば、sbSize = MiSize
    • sub==1ならば、sbSize = Max(BLOCK_16X16, MiSize)
  8. skip = Skips[loopRow][loopCol]
  9. isIntra = RefFrames[loopRow][loopCol][0] <= INTRA_FRAME
  10. isBlockEdgeを以下のように求めます(1のとき画素が予測ブロック境界をまたぐ)。
    • pass==0 && xが8*num8x8BlocksWideLookup[sbSoze]の倍数ならば、blockEdge=1
    • pass==1 && yが8*num8x8BlocksHighLookup[sbSoze]の倍数ならば、blockEdge=1
    • そうではなければ、blockEdge=0。
  11. isTxEdgeを以下のように求めます(1のとき画素が変換ブロック境界をまたぐ)。
    • pass==1 && subX==1 && MiColsが奇数 && edgeが奇数 && (x+8)>=MiCols*8ならば、txEdge=0。(色差ブロックの水平境界が、画像の右エッジをまたぐ場合に対応します)
    • edgeが(1<<txSz)の倍数ならば、isTxedge=1
    • そうではなければ、isTxEdge=0
  12. is32Edgeを以下のように求めます(1のとき画素が32画素境界をまたぐ)。
    • edgeが8の倍数の時、is32Edge=1
    • そうではなければ、is32Edge=0
  13. onScreenを以下のように求めます(1のとき境界の両側が可視領域内である)。
    • x>=8*MiColsならば、onScreen=0
    • y>=8*MiRowsならば、onScreen=0
    • pass==0 && x==0 ならば、onScreen=0
    • pass==1 && y==0 ならば、onScreen=0
    • そうではなければ、onScreen=1
  14. applyFilterを以下のように求めます(1のときフィルタする)。
    • onScreen==0 ならば、applyFilter=0
    • isBlockEdge==1 ならば、applyFilter=1
    • isTxEdge==1 && isIntra==1 ならば、applyFilter=1
    • isTxEdge==1 && skip==0 ならば、applyFilter=1
    • そうではなければ、applyFilter=0
  15. 8.8.3節で規定するフィルタサイズ処理を、txSz, is32Edge, pass, x, y, subX, subY を入力として呼び出し、出力を filterSize(使われる最大フィルタサイズ)とします。
  16. 8.8.4節で規定される適応フィルタ強度処理を、loopRow, loopColを入力として呼び出し、出力を lvl, limit, blimit, thresh とします。
  17. applyFilter==1 && lvl>0 ならば、8.8.5節で規定される画素フィルタ処理を、x=x>>subX, y=y>>subY, plane, limit, blimit, thresh, plane, dx, dy, filterSize を入力として呼び出します。

###フィルタサイズ処理

この処理の入力は以下の通りです。

  • 変換ブロックサイズ txSz
  • エッジが32画素境界であるかどうか is32Edge
  • エッジの方向 pass
  • 輝度座標 x,y
  • カレントプレーンのサブサンプリング sub_x, sub_y

この処理の出力は、使用することができる最大フィルタサイズ filterSize です。

この処理の目的は、フィルタがフレーム境界の時のクロマフィルタの幅を削減するためです。
さらに、32画素の整数倍の境界では、フィルタサイズの最大は TX_8X8にクリップされます。

baseSizeを以下のように求めます。

  • txSz==TX_4x4 && is32Edge==1 ならば、baseSize=TX_8x8
  • そうではなければ、baseSize=Min(TX_16x16, txSz)

出力 filterSize は以下のように求めます。

  • 以下のすべての条件が真であれば、fiterSize=TX_8x8
    1. pass==0(垂直境界をフィルタしている)
    2. subX==1
    3. baseSize==TX_16X16
    4. x>>3 == MiCols-1
  • そうではなく、以下のすべての条件が真であれば、fiterSize=TX_8x8
    1. pass==1(水平境界をフィ ルタしている)
    2. subY==1
    3. baseSize==TX_16X16
    4. x>>3 == MiRow-1
  • そうではなければ、filterSize=baseSize

適応フィルタ強度処理

この処理の入力は、8x8ブロック単位の輝度座標 loopRow, loopColです。

この処理の出力は、lvl, limit, blimit, tresh です。

lvl を以下のように求めます

  • segment = SegmentIds[loopRow][loopCol]
  • ref = RefFrames[loopRow][loopCol][0]
  • mode = YModes[loopRow][loopCol]
  • modeType を以下のように求めます。
    1. mode==NEARESTMV || NEARMV || NEWMV ならば、modeType=1
    2. そうではない(mode==ZEROMV)ならば、modeType=0
  • lvl = LvlLookup[segment_id][ref][modeType]

shift を以下のように求めます。

  • loop_filter_sharpness>4 ならば、shift=2
  • そうではなく、loop_fileter_shapness>0 ならば、shift=1
  • そうではなければ、shift=0

limit を以下のように求めます。

  • loopfilter_sharpness>0 ならば、limmit=Cip3(1, 9-loop_filter_shapness, lvl>>shift)
  • そうではなければ、limit=Max(1, lvl>>shift)

blimit = 2*(lvl+2) + limmit

thresh = lvl >> 4

画素フィルタ処理

この処理の入力は以下の通りです。

  • CurrFrame[plane] 内座標 x,y
  • Y, U, Vプレーンを指定する plane
  • フィルタ処理の強度 limit, blimit, thresh
  • フィルタするエッジの垂直方向を示す dx, dy
  • フィルタの最大サイズ filterSize

この処理の出力は、CurrFrame の更新された値です。

最初に、8.8.5.1節で規定されるフィルタマスク処理を、x, y, plane, limit, blimit, thresh, dx, dy, ilterSize を入力として呼び、出力を hevMask, filterMask, flatMask, flatMask2 とします。

そして、適切なフィルタ処理を、x, y, plane, dx, dy を入力として、以下のように呼びます。

  • filterMask==0ならば、フィルタは呼ばれません
  • そうではなく、filterSize==TX_4xX4 || flatMask==0 ならば、8.8.5.2節で規定される狭フィルタ処理が呼ばれます(入力 hevMask)。
  • そうではなく、filterSize==TX_8X8 || flatMask2==0 ならば、8.8.5.3節で規定される広フィルタ処理を呼びます(入力 log2Size=3)。
  • そうではなければ、8.8.5.3節で規定される広フィルタ処理を呼びます(入力 log2Size=4)。

フィルタマスク処理

この処理の入力は以下の通りです。

  • CurrFrame[plane] 内座標 x,y
  • Y, U, Vプレーンを指定する plane
  • フィルタ処理の強度 limit, blimit, thresh
  • フィルタするエッジの垂直方向を示す dx, dy
  • フィルタの最大サイズ filterSize

この処理の出力は以下の通りです。

  • hevMask
  • fileterMask
  • flatMask (filterSize>=TX_8X8 のときのみ使用)
  • flatMask2 (filterSize>=TX_16X16 のときのみ使用)

これらの出力マスクは、指定される境界の両端の画素の差分に依存します。
これらの画素は以下のように規定します。

q0 = CurrFrame[plane][y       ][x       ]
q1 = CurrFrame[plane][y + dy  ][x + dx  ]
q2 = CurrFrame[plane][y + dy*2][x + dx*2]
q3 = CurrFrame[plane][y + dy*3][x + dx*3]
q4 = CurrFrame[plane][y + dy*4][x + dx*4]
q5 = CurrFrame[plane][y + dy*5][x + dx*5]
q6 = CurrFrame[plane][y + dy*6][x + dx*6]
q7 = CurrFrame[plane][y + dy*7][x + dx*7]

p0 = CurrFrame[plane][y       ][x       ]
p1 = CurrFrame[plane][y - dy  ][x - dx  ]
p2 = CurrFrame[plane][y - dy*2][x - dx*2]
p3 = CurrFrame[plane][y - dy*3][x - dx*3]
p4 = CurrFrame[plane][y - dy*4][x - dx*4]
p5 = CurrFrame[plane][y - dy*5][x - dx*5]
p6 = CurrFrame[plane][y - dy*6][x - dx*6]
p7 = CurrFrame[plane][y - dy*7][x - dx*7]

注意:
q4..q7, p4..p7 は、filterSize==TX_16X16 のときのみ使います。

hevMask は、画素が高いエッジ分散を持つかどうかを示します。
以下のように計算します。

hevMask  = 0
threshBd = thresh << (BitDepth-8)
hevMask |= (Abs(p1-p0) > threshBd)
hevMask |= (Abs(q1-q0) > threshBd)

filerMask は、
indicates whether adjacent samples close to the edge
(within four samples either side of the specified boundary)
vary by less than the limits given by limit and blimit.
これは、任意のフィルタリングをすべきかどうかを決定し、以下のように求めます。

limitBd  = limit << (BitDepth-8)
blimitBd = limit << (BitDepth-8)

mask = 0
mask |= (Abs(p3-p2) > limitBd)
mask |= (Abs(p2-p1) > limitBd)
mask |= (Abs(p1-p0) > limitBd)
mask |= (Abs(p1-p0) > limitBd)
mask |= (Abs(p2-p1) > limitBd)
mask |= (Abs(p3-p2) > limitBd)
mask |= (Abs(p0-p0) > limitBd)
mask |= (Abs(p3-p2) * 2 + Abs(p1-q1)/2> limitBd)

filterMask = (mask==0)

flatMask は、filterSIze >= TX_8X8 のときだけ使いm必要です
指定された境界から4画素以内が平坦な領域かを示します。
これは、境界と画素との間が 高々(1<<(BitDepth-8)) の差分であるかどうかです。
以下のように計算します。

thresholdBd = 1 << (BitDepth-8)
if (filterSize >= TX_8X8) {
    mask = 0;
    mask |= (Abs(p1-p0) > thresholdBd)
    mask |= (Abs(q1-q0) > thresholdBd)
    mask |= (Abs(p2-p0) > thresholdBd)
    mask |= (Abs(q2-q0) > thresholdBd)
    mask |= (Abs(p3-p0) > thresholdBd)
    mask |= (Abs(q3-q0) > thresholdBd)
    flatMask = (mask == 0)
}

flatMask2 は、filterSIze >= TX_16X16 のときだけ使いm必要です
指定さた境界から4..8画素以内が平坦な領域かを示します。
これは、境界と画素との間が 高々(1<<(BitDepth-8)) の差分であるかどうかです。
(このため、flatMask & flatMask2 == 0 ならば、全部の領域が平坦です)
以下のように計算します。

thresholdBd = 1 << (BitDepth-8)
if (filterSize >= TX_8X8) {
    mask = 0;
    mask |= (Abs(p7-p0) > thresholdBd)
    mask |= (Abs(q7-q0) > thresholdBd)
    mask |= (Abs(p6-p0) > thresholdBd)
    mask |= (Abs(q6-q0) > thresholdBd)
    mask |= (Abs(p5-p0) > thresholdBd)
    mask |= (Abs(q5-q0) > thresholdBd)
    mask |= (Abs(p4-p0) > thresholdBd)
    mask |= (Abs(q4-q0) > thresholdBd)
    flatMask2 = (mask == 0)
}

狭フィルタ処理

このフィルタの入力は以下の通りです。

  • このエッジが高分散かどうかを指定する hevMask
  • CurrFrame[plane] 内の座標 x, y
  • Y, U, V プレーンを指定する plane
  • フィルタ処理の強度を指定する limit, blimit, thresh
  • フィルタするエッジの垂直方向を指定する dx, dy

この処理では、指定された境界の両端最大2画素を変更し、それはhevMaskに依存します。

  • hevMask==0 (画素が高分散ではない)ならば、この処理では内側の2画素(境界の両端)を使って、両側の2画素ずつを更新します。
  • そうではない(画素が高分散)ならば、入力4画素(境界の両端2画素ずつ)を使って両側の1画素ずつを更新します。

この処理では、地域が -(1<<(BitDepth-1)) .. (1<<(BitDpeth-1)) になるように、入力の画素値から 0x80<<(BitDepth-8) を差し引きます。
以下の関数によって、中間変数のこの範囲に収めます。

filter4_clamp(value)
{
    return Clip3(-(1<<(BitDepth-1)), (1<<(BitDepth-1))-1, value)
}

フィルタ処理は以下の通りです。

q0 = CurrFrame[plane][y     ][x]
q1 = CurrFrame[plane][y+dy  ][x+dx]
p0 = CurrFrame[plane][y-dy  ][x-dx]
p1 = CurrFrame[plane][y-dy*2][x-dx*2]

ps1 = p1 - (0x80 << (BitDepth-8))
ps0 = p0 - (0x80 << (BitDepth-8))
qs0 = q0 - (0x80 << (BitDepth-8))
qs1 = q1 - (0x80 << (BitDepth-8))

filter = hevMask ? filter4_clamp(ps1-qs1) : 0
filter = filter4_clamp(filter + 3*(qs0-ps0))
filter1 = filter4_clamp(filter + 4) >> 3
filter2 = filter4_clamp(filter + 3) >> 3

oq0 = filter4_clamp(qs0 - filter1) + (0x80 << (BitDepth-8))
op0 = filter4_clamp(ps0 + filter2) + (0x80 << (BitDepth-8))

CurrFrame[plane][y   ][x   ] = oq0
CurrFrame[plane][y-dy][x-dx] = op0

if (!hevMask)
{
    filter = Round2(filter1, 1)
    oq1 = filter4_clamp(qs1 - filter) + (0x80 << (BitDepth-8))
    op1 = filter4_clamp(ps1 + filter) + (0x80 << (BitDepth-8))
    CurrFrame[plane][y+dy  ][x+dx  ] = oq1
    CurrFrame[plane][y-dy*2][x-dx*2] = op1
}

広フィルタ処理

この処理の入力は以下の通りです。

  • CurrFrame[plane] 内の座標 x, y
  • Y, U, V プレーンを指定する plane
  • フィルタするエッジの垂直方向を指定する dx, dy
  • tap 数の log2 を指定する log2Size

この処理は、境界の両端の画素が平坦な領域として検出されたときのみ適用されます。

n = (1 << (log2Size-1)) -1 (中心画素の両端のフィルタタップ数)

この処理では、指定された教会の両端の画素を以下のローパスフィルタを適用して更新します。

for (i=-n; i<n; i++)
{
    t = CurrFrame[plane][y+i*dy][x+i*dx]
    for (j=-n, j<=n; j++)
    {
        p = Clip3(-(n+1), n, i+j)
        t += CurrFrame[plane][y+p*dy][x+p*dx]
    }
    F[i] = Round2(t, log2Size)
}
for (i=-n; i<n; i++)
{
    CurrFrame[plane][y+i*dy][x+i*dx] = F[i]
}