H.264的错误恢复(error resilience)【待整理】

引子

[h264 @ 0x10a076a00] concealing 1403 DC, 1403 AC, 1403 MV errors in P frame

一. 错误隐藏

错误恢复(error resilience)
错误隐藏(error concealment)
错误隐藏是一种在解码端进行处理的技术。错误隐藏就是解码器在不需要从编码器得到额外信息的前提下,利用当前帧已接收宏块或先前已接收视频序列的相关性对丢失宏块进行恢复,以主观可接受的近似原来质量的视频数据来隐藏错误受损的数据。错误也分为帧间隐藏(intra frame)和帧内隐藏(inter frame)。

二. ffmpeg错误隐藏框架分析

libavcode\error_resilience.c
以264解码为例,error resilience的三个步骤都在decode_nal_units中调用:
h264_decode_frame -> decode_nal_units

  1. ff_er_frame_start(decode slice header) decode_nal_units ->
    decode_slice_header -> ff_h264_frame_start -> ff_er_frame_start
    ff_er_frame_start中对error_status_table,error_count进行了初始化
  2. ff_er_add_slice (decode slice)
    decode_nal_units -> ff_h264_execute_decode_slices -> 分单线程和多线程(slice level并行)
    以单线程为例:ff_h264_execute_decode_slices -> decode_slice -> ff_er_add_slice,decode_slice分为四部分:熵编码、宏块解码、环路滤波和错误隐藏。完成解码、滤波等标准解码流程后,ff_er_add_slice根据解码情况设置error_status_table,用于最终的错误隐藏。
  3. ff_er_frame_end(frame level)
    decode_nal_units -> ff_er_frame_end -> B slice , 单向帧间:guess_mv,帧内:guess_dc。
    完成slice的解码后,会调用ff_er_frame_end,具体进行错误隐藏的地方。

三. guess_dc函数实现:(帧内错误隐藏)

以8*8块为单位,基于DCT的错误隐藏技术
以8×8的DCT变换为例,左上角u=0,v=0的为DC系数,其余63个系数为AC系数。DC系数为低频,高能量部分,相邻块的DC系数存在一定的相关性。
DCT中,系数计算公式:

F(u,v)=2MNC(u)C(v)x=0M1y=0N1f(x,y)cos[(2x+1)uπ/2M]cos[(2y+1)vπ/2N]

其中 u=0,1,2,...,Mv=0,1,2,...,N
C(u)C(v)=121u=0,v=0other

DC系数:
F(0,0)=1MNx=0M1y=0N1f(x,y)

第一步:宏块扫描计算每个宏块的DC值;
依次从左->右,上->下,右->左,下->上四个方向扫描,计算出图像中每个8*8块的DC值。将每个丢失宏块上下左右四个方向最邻近的正确解码块的DC值存放在col[b_x + b_y*stride][j](j=0,1,2,3)中;这四个正确解码块到丢失块的距离保存在dist[b_x + b_y*stride][j](j=0,1,2,3)中,当某个方向上的正确解码宏块不存在时,distance记为9999。
这里写图片描述
第二步:丢失宏块DC值恢复
公式如下:
这里写图片描述
第三步:将得到的DC值填充到丢失的8*8的宏块中

四. guess_mv函数实现的过程:(帧间错误隐藏)

(相邻块运动矢量估计方法)

第一步:
设定宏块状态标志位fixed[mb_xy],用以标记当前宏块运动矢量 MV 的恢复状态,MV_FROZEN 表示宏块 MV 锁定,MV_CHANGED 表示遍历一轮过后MV 发生改变,MV_UNCHANGED 表示遍历一轮过后 MV 没有发生改变。遍历整幅图像中的宏块,将没有发生 MV 错误的宏块,fixed[mb_xy]赋值为 MV_FROZEN。
第二步:
倘若没开启 MV 错误隐藏功能,或者正常解码宏块数很少(小于宏块宽的一半),则认为没有进行运动矢量 MV 寻找的必要,所有错误宏块 MV 置 0后调用宏块解码函数进行宏块解码。
第三步:
若开启了 MV 错误隐藏功能,且正常解码宏块数较多,则通过以下步骤进行 MV 恢复。该过程主要分三个大循环:
(i)遍历整个图像宏块 MV 的状态,只对上下左右MV正确宏块的宏块进行MV 恢复,错误宏块通过其上下左右四个正常解码或经过恢复的宏块的 MV建立一张候选向量表(候选运动矢量表,依次存放以下 9个运动矢量:初始值、0、上一帧中对应位置宏块、上方宏块、下方宏块、左方宏块、右方宏块、上下左右宏块矢量平均值、上下左右宏块矢量中间值的平均值),最后用候补向量 MV 对应的参考宏块替代当前宏块,得出的宏块数据和四周四个宏块(如果存在)进行边界匹配,选取差值最小的 MV作为该宏块该轮筛选得到的 MV,若该 MV 与原始 MV 相同,则fixed 标记为UNCHANGED,否则 FIXED 标记为 CHANGED;
(ii)重复上一次遍历,直到与 MV 正确宏块所有相邻宏块 MV 状态都为 UNCHANGED 且至少经过两次遍历;
(iii)将上述两个步骤中经过恢复后fixed 值标记为MV_FROZEN,直到所有宏块 FIXED都标记为MV_FROZEN,跳出循环。

图例

黄:MV_FROZEN 3
蓝:MV_CHANGED 2
红:MV_UNCHANGED 1

1、错误遮蔽开始之前,宏块初始状态图,黄色为正确解码宏块,白色为没有正确解码的宏块。
这里写图片描述

2、第一轮错误遮蔽后的宏块状态图。新增蓝色宏块是由白色宏块经过运动矢量恢复后被赋予了新的运动矢量值,状态标识位 MV_FROZEN 赋值为 MV_CHANGED。
这里写图片描述

3、第二轮错误遮蔽后的宏块状态图。新增橙色宏块代表在这一轮运动矢量恢复过程中其运动矢量并没有发生改变,故其标志位 MV_CHANGED 变为 MV_UNCHANGE,而之前蓝色块在这轮错误遮蔽后运动矢量依旧发生改变,标志位不变。
这里写图片描述

4、经过多轮错误遮蔽后的宏块状态图。恢复宏块的运动矢量不发生变化,其表示为置为 MV_FROZEN。
这里写图片描述

5、一次最外层循环结束,重复上述步骤,直到所有宏块FIXED变为MV_FROZEN。
列表内容

第四步:
最后将获取MV对应的宏块值填充到当前丢失宏块
问:为什么要选取候选运动矢量表?
目的是为了简化参考宏块的选取,因为直接遍历参考帧内所有宏块进行边界匹配算法择优虽然可以取得最优解,但是因此带来的算法复杂度很大,不能满足视频播放实时性的需求。

五. 错误隐藏相关技术

1,空域错误隐藏算法
空域错误隐藏技术就是利用帧内宏块的相关性利用周边正确解码宏块来恢复当前错误宏块的方法。由于I帧的采用帧内预测的编码模式,所以对于I帧的错误遮蔽通常采用空域错误遮蔽方法进行恢复。常用的空域错误遮蔽方法有线性插值、方向性插值方法实现丢失宏块的重建,碰到丢失宏块相邻宏块边缘较为复杂的情况,需对周边宏块进行方向求导选取导数最大点的所对应的方向做线性插值。另外,块匹配方法也是一种高效的帧内恢复策略,通过搜索寻找与相邻宏块周边像素相似度最高的宏块来重建当前丢失宏块。
(1) 线性插值方法
双线性插值法:
这里写图片描述
上述算法在视频图像信息比较平缓的情况下,可以取得不错的遮蔽效果,而且遮蔽过程比较简单,但是如果有边缘通过,就无法很好地恢复边缘信息,经过插值后的重建图像显得较为模糊。
(2)方向性插值方法
这里写图片描述
一般插值时选择的参考像素点在丢失宏块周围的上下左右块。倘若这些位置上的块信息也丢失且也没有恢复,就利用对角线方向上的宏块进行求导计算。如果延伸的参考点坐标不在整数位置上,可以采用 6 抽头的维纳滤波器得到该点的值。该方法的缺点是算法复杂度较高,因为插值时还要用到了维纳滤波器,要算出周围块每个点的梯度并且做方向上的判断。
(3)块匹配方法
BNM(Best neighborhood matching)算法,将丢失宏块相邻宏块边界像素加至当前丢失宏块,利用搜索算法在当前帧内进行边界差值匹配,边界差值最小的边界所包含的宏块被认为是最佳匹配宏块,将最佳匹配块中的像素值填充至丢失宏块对应的像素位置完成丢失宏块的重建。
这里写图片描述
(4) 选择性方向内插方法
这里写图片描述
2,时域错误隐藏算法
P帧编码预测采用帧间预测方式取得了较好的压缩效果,但是帧间预测方式存在的缺陷是一旦参考帧发生错误,后续帧就无法无损恢复。时域错误遮蔽算法就是基于前后帧的帧间相关性对丢失帧进行错误恢复,由于相邻帧时间间隔很短,所以帧间相关性很强,通过估计当前帧内丢失宏块的运动矢量,直接运动矢量对应宏块作为当前帧的重建宏块,可以取得比帧内错误遮蔽更好的遮蔽效果。
(1)零运动矢量方法
最简单的帧间错误遮蔽的方法就是直接用当前帧丢失宏块在前一帧相同位置的宏块的像素值来重建当前宏块,即将当前丢失宏块的运动矢量(Motion
Vector,MV)置零,被称为时域替换法(Tempral Replacement),又叫零运
动矢量法(Zero Motion Vector,ZMV),如下图 。这种方法的有点是运算简便,对运动平缓的视频图像遮蔽效果较好,但是随着视频图像内容运动变得剧烈,前后帧间相同位置相关性减弱,效果明显变差。
这里写图片描述
(2)相邻块运动矢量估计方法
为了解决 TR 方法对于运动比较剧烈的图像序列错误遮蔽效果不理想的缺点,需要引入运动补偿估计来恢复丢失宏块的运动矢量。运动矢量主要是利用相邻帧在时间上对应宏块像素值的相关性以及空间上相邻宏块运动矢量的相关性,根据丢失宏块的周围正确解码宏块的运动矢量,估算出当前错误宏块的运动矢量,根据此运动矢量在参考帧中找到相应宏块复制其像素值至当前宏块完成当前丢失宏块的重建。 根据空间运动的相关性,相邻宏块的运动情况较好地反映当前宏块的运动状况。运动矢量恢复方法,当中较为典型的方法是利用相邻宏块运动矢量的均值对当前宏块运动矢量进行恢复;也有利用丢失宏块周边宏块的运动矢量的中值进行恢复。
(3)边界匹配方法(BMA)
BMA 算法中运动矢量的选择是根据参考宏块与丢失宏块边界像素插值最小的策略,选择与相邻边界衔接最平滑的参考宏块作为匹配宏块,零运动矢量位于候选运动矢量之列。利用边界像素差值最小的匹配方法获得重建宏块,由于边界上的像素值改变较小,所以边界比较平缓,让人感觉块效应并不太明显,可以较好地表明边缘运动的方向 。
这里写图片描述
(4)拉格朗日插值方法
拉格朗日插值算法求取最佳运动矢量,利用丢失宏块周围正确解码宏块的运动矢量插值得到当前宏块的运动矢量。考虑以每个 4×4 子块作为一个单元块,创建原点位于丢失宏块的对称中心的二维坐标系。每个单元作为一个元素,根据每个子块中心与原点距离为其设定横坐标纵坐标。若左右宏块不存在,就利用丢失宏块上下宏块的运动矢量来做插值。如图 2-10 所示,该算法将丢失宏块划分为 16 个 4×4 的子块,对每个子块根据周边宏块对应子块的运动矢量分别进行插值。图中的灰色块 Fi,j是一个丢失的宏块,Fi-1,j,Fi+1,j,Fi,j-1,Fi,j+1为相邻正常接收的宏块。Mv0,Mv1,Mv2,Mv3表示相邻 4×4 子块的运动矢量,V0,V1,V2,V3为待遮蔽的运动矢量。
这里写图片描述
以水平方向插值为例,相邻宏块对应 4×4 子块的运动矢量分别是Mv0,Mv1,Mv2
,Mv3,根据其对应坐标利用拉格朗日插值公可以求出 V0,V1,V2,V3。以此类推,利用 Fi-1,j和 Fi+1,j的所有 4×4 子块,可以计算出 Fi,j中其它 4×4 子块的 运动矢量,从而得到 Fi,j宏块包含的共计 16 个子块的运动矢量。

参考

guess_dc和guess_mv
guess_dc函数
guess_mv函数
ffmpeg错误隐藏框架


  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值