一 位于图像边界的超级块划分

在实际的视频编码过程中给你,图像的宽度和高度可能不是128或64的整数倍,这会导致图像的右侧和底部的超级块可能会超出图像的实际边界。为了解决这个问题,当超级块的任何部分超出图像的底部或右侧边界时,这些超级块被强制进行划分,直到所有的编码像素块完全包含在图像的边界之内。在图3-2的示例中,该图片的分辨率是832x480,因此,当超级块大小是128x128时,右侧和底部的超级块都超出了图像边界,而当超级块大小是64x64时,仅仅底部超级块超出了图像边界。

这些超出图像边界的超级块,AV1标准会使用四叉树划分模式PARTITION_SPLIT 或者长宽比为1:2的水平划分模式PARTITION_HORZ,或者长宽比时2:1的垂直划分模式PARTITION_VERT,强制对这些超级块进行划分。在超级块强制划分过程中,如果生成的四叉树节点仍存在一部分区域位于图像边界之外,那么该四叉树节点将继续被强制划分,直到每个编码块的所有像素都位于图像边界内部。AV1使用语法元素split_or_horz和split_or_vert表示每个四叉树节点的划分模式。

1 如果当前四叉树节点的左上角四叉树子节点超出了图像的底部边界,那么AV1使用语法元素split_or_horz 表示其划分模式,此时,split_or_horz 等于0表示使用划分模式 PARTITION_HORZ划分当前节点,否则,表示使用划分模式PARTITION_SPLIT划分当前节点。

2 如果当前四叉树节点的左上角四叉树子节点超出了图像的右侧边界,那么AV1使用于法元素split_or_vert 表示其划分模式。split_or_vert等于0表示使用PARTITION_VERT划分当前节点,否则,使用PARTITION_SPLIT划分当前节点。

3 如果当前四叉树节点的左上角四叉树子节点既超出了图像的右侧边界,又超出了图像的底部边界,那么AV1使用划分模式PARTITION_SPLIT对该四叉树节点进行划分,此时,不需要编码任何语法元素。

假设变量r和c分别表示当前四叉树节点的行坐标和列坐标,r和c均以4亮度像素为基本单位,变量bSize表示四叉树节点的大小,函数parse symbol 是解析语法元素symbol的函数,那么AV1标准文档定义的编码块划分流程decode_partition如下所示:

decode_partition(r, c, bSize) {
//如果当前四叉树节点的起始行坐标r或列坐标c超过图片边界,则退出。
    if (r >= MiRows || c >= MiCols)
        return 0;
    AvailU = is_inside(r - 1, c)
    AvailL = is_inside(r, c - 1)
    
    //根据当前四叉树节点的尺寸bSize获取节点的宽度
    num4x4 = Num_4x4_Blocks_Wide[bSize]
    //halfBlock4x4是当前四叉树节点宽度的一半,quarterBlock4x4 是宽度的1/4
    halfBlock4x4 = num4x4 >> 1
    quarterBLock4x4 = halBlock4x4>>1
    //hasRows和hasCols分别表示四叉树节点的左上角四叉树子节点是否超过底部边界
    //和右侧边界,hasRows和hasCols取值false表示超过了图像边界
    hasRows = (r + halfBlock4x4) < MiRows
    hasCols = (c + halfBlock4x4) < MiCols
    if (bSize < BLOCK_8x8) {
    //尺寸bSize,小于8x8时,不需要继续分割
            partition = PARTITION_NONE
    } else if (hasRows && hasCols) {
    //左上角四叉树子节点位于图像内部,则解析语法元素parition
        parse(partition)
    } else if (hasCols) {
    //左上角四叉树子节点超过图像底部边界,则解析语法元素split_or_horz
    //并据此对partition进行赋值
        parse(split_or_horz)
        partition = split_or_horz ? PARTITION_SPLIT: PARTITION_HORZ
    } else if (hasRows) {
    //左上角四叉树子节点超过图像右侧边界,则解析语法元素split_or_horz
    //并据此对partition进行赋值
        parse(split_or_vert)
        partition = split_or_vert ? PARTITION_SPLIT: PARTITION_VERT
    } else {
    //左上角四叉树子节点同时超过图像底部和右侧边界,则把partition,
    //赋值为PARTITION_SPLIT
        partition = PARTITION_SPLIT
    }
    subSize = Partition_SUbSize[partition][bSize]
    splitSize = Partition_Subsize[PARTITION_SPLIT][bSize]
    
    if (partition == PARTITION_NONE) {
    //当节点不再继续划分,则解码重构当前节点
        decode_block(r, c, subSize)
    } else if (partition == PARTITION_HORZ) {
    //解码重构上方的编码块
        decode_block(r, c, subSize)
        //当下侧的编码块位于图像内部时,则解码重构下方编码块
        if (hasRows) {
            decode_block(r + halfBlock4x4, c, subSize)
        }
    } else if (partition == PARTITION_VERT) {
    //解码重构左侧编码块
        decode_block(r, c, subSize)
        //当右侧编码块位于图像内部时,则解码重构右侧编码块
        if (hasCols)
            decode_block(r, c + halfBlock4x4, subSize)
    } else if (partition == PARTITION_SPLIT) {
    //按照四叉树划分模式,递归解码子节点
        decode_partition(r, c, subSize)
        decode_partition(r, c + halfBlock4x4, subSize)
        decode_partition(r + halfBlock4x4, c, subSize)
        decode_partition(r + halfBLock4x4, c + halfBlock4x4, subSize)
    } else if (partition == PARTITION_HORZ_A) {
    //解码重构上方两个方形编码块
        decode_block(r, c, splitSize)
        decode_block(r, c + halfBlock4x4, splitSize)
        //解码重构下方一个矩形编码块
        decode_block(r + halfBlock4x4, c, subSize)
    } else if (partition == PARTITION_HORZ_B) {
    //解码重构上方一个矩形编码块
        decode_block(r, c, subSize)
        //解码下方两个方向编码块
        decode_block(r + halfBLock4x4, c, splitSize)
        decode_block(r + halfBLock4x4, c + halfBlock4x4, splitSize)
    } else if (partition == PARTITION_VERT_A) {
    //解码左侧两个方形编码块
        decode_block(r, c, splitSize)
        decode_block(r + halfBLock4x4, c, splitSize)
        //解码右侧一个矩形编码块
        decode_block(r, c + halfBlock4x4, subSize)
    } else if (partition == PARTITION_VERT_B) {
    //解码左侧一个方形编码块
        decode_block(r, c, subSize)
        //解码右侧两个方形编码块
        decode_block(r, c + halfBlock4x4, splitSize)
        decode_block(r + halfBlock4x4, c + halfBlock4x4, splitSIze)
    } else if (partition == PARTITION_HORZ_4) {
        decode_block(r + quarterBlock4x4 * 0, c, subSize)
        decode_block(r + quarterBlock4x4 * 1, c, subSize)
        decode_block(r + quarterBlock4x4 * 2, c, subSize)
        //当最后一个编码块位于图像内部时,则解码重构最后一个编码块
        if (r + quarterBlock4x4 * 3 < MiRows)
            decode_block(r + quarterBlock4x4 * 3, c, subSize)
    } else {
        decode_block(r + quarterBlock4x4 * 0, c, subSize)
        decode_block(r + quarterBlock4x4 * 1, c, subSize)
        decode_block(r + quarterBlock4x4 * 2, c, subSize)
        //当最后一个编码块位于内部时,则解码重构最后一个编码块
        if (c + quarterBlock4x4 * 3 < MiCols)
            decode_block(r, c + quarterBlock4x4 * 3, subSize)
    }
}

二 根据AV1标准文档定义的编码块划分流程decode_partition, 如果当前四叉树节点仅仅超出了图像的底部边界但是没有超出图像的右侧边界,那么当前节点的split_or_vert似乎在任何条件下都能被赋值为PARTITION_VERT,而与当前所有节点在图像内部区域的大小无关。只有当四叉树节点所在图片内部正好可以通过划分模式

PARTITION_VERT产生时,该节点的split_or_vert才能被赋值为PARTITION_VERT;否则,该节点不能被正确解码,对于超出图像的右侧边界但是没有超出图像底部边界的超级块,只有当四叉树节点所在图片内部区域正好可以通过划分模式PARTITION_HORZ产生时,其split_or_horz才能被赋值为PARTITION_HORZ 图3-7位图像边界处的超级块划分示意图。

Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐