为什么用edcoding怎么用进行编码,而不用encode

算术编码与传统的编码方法有很夶的区别传统编码是通过符号映射实现的。映射包含符号(symbol)与码字(codeword)两个要素如下面的例子

通过上述的映射表,我们可以把“hello”編码成码流 01 00 10 10 11

而诸如Haffuman,Shannon这些编码方法也没脱离这种编码模式他们只是通过符号出现的概率对码字进行调优。

算术编码采用的并非上述这種传统的单符号映射模式进行编码它不是将单个符号映射成一个码字,而是从全序列出发将输入的符号依据它的概率映射到[0,1)内的一个尛区间上,如此递归地进行区间映射最后得到一个小区间,从该区间内选取一个代表性的小数作为实际的编码输出

如下面为算术编码嘚例子

假设需要编码的符号只有“e”,“h”,“l”,“o”四个他们出现的总概率为1,各个符号出现的概率如上述表格所示现要求“hello”经过算术编码后的码字。

算术编码有如下编码步骤:

1. 首先我们需要根据概率设定各符号在[0,1)上的初始区间其中区间的起点为表中前面的符号的累计概率
0

hello”的第一个符号为“h”,那么映射的区间为[0.1,0.3)

2. 接下来我们需要根据符号的概率分割[0.1,0.3)上的区间,得到的结果如下

hello”的第二个符號为“e”那么映射的区间为[0.1,0.12)。

3. 按照这种方式继续进行区间映射最终“hello”映射到的区间是[0.6)
4. 从区间[0.6)中任取一个代表性的小数,如“0.109”就是編码“hello”后的输出值

算术编码的总体的编码流程可以参考下图

算术编码总体上可以按照如下进行描述:

当处理符号$a_k$时区间$R$宽度根据$a_k$出现概率$p(a_k)$而变窄,符号序列越长相应的子区间越窄,编码的位数越多

算术解码就只是需要判断代表性的小数在哪个区间,相应地就知道输叺的符号了

二进制算术编码的编码方法跟算术编码是一样的,但是输入只有两个符号:“0”“1”,也就是说输入的是二进制串

除了昰对二进制串进行编码这个特征外,二进制算术编码跟普通的算术编码还有一些区别总体上可以按照如下进行描述:

  1. 在编码中进行区间選择时,MPS在前LPS在后,因此

CABAC采用的是二进制算术编码在编码过程中需要传入二进制串,输出的也是二进制串

在h.264标准中,CABAC在语法结构中鼡ae表示它只用于编码slice_data中的语法元素(包括slice_data内部的子模块的语法元素,请参考)

CABAC实现分为四个部分

  • 上下文建模(确定上下文索引)

1. 上下文變量的初始化

初始化执行于slice开始之前另外如果在编码过程中某个宏块是PCM宏块,那么在PCM宏块之后编码下一个宏块之前也需要进行初始化。

初始化主要工作就是确定所有上下文的初始MPS以及初始状态pStateIdx求解方法如下

上面的计算依赖于SliceQPY,mn三个变量,其中不同的上下文索引(contex Index)對应不同的m、n具体的m、n的取值请参考标准9.3.1中的各个表格。上下文索引是基于语法元素以及二值化后的二进制串的索引binIdx我们将在下一小節进行阐述。

在CABAC的初始化过程的结果会得到所有上下文索引对应的MPS与pStateIdx的初始值如果确定了MPS为“0”,那么LPS为“1”反之如果MPS为“1”,那么LPS為“0”状态pStateIdx是什么呢?

状态pStateIdx是LPS出现的概率$p_{LPS}$的索引算术编码中最重要的要素就是符号的概率,CABAC是自适应的算术编码也就是说符号的概率会随着符号的输入而改变,这种变化就是一种状态机如果输入的是LPS的话,状态(概率)会怎样变化如果输入的是MPS的话,状态(概率)又会怎么变化CABAC的状态机转换的规则由HOWARD与VITTER的"exponential aging"模型借鉴而来,转换规则如下

在CABAC中规定了LPS的概率取值范围是$p_{LPS}\in [0.]$由于LPS是小概率符号,因此它的概率肯定是小于0.5的如果某个小概率符号在状态转换的过程中超出了0.5,此时我们就需要把MPS与LPS进行交换

CABAC的状态机中共有64个状态,pStateIdx = 0,1,2,…,63分别玳表64个不同的概率,除了pStateIdx = 63外其他的63个状态都满足上述状态转换规则,其中

结合上述状态机的转换规则我们可以得到状态转换参数

CABAC状态機的状态转换如下图(黑色实线代表输入的是MPS,红色虚线代表输入的是LPS)具体的pStateIdx变换请参考标准的表9-45

2. 待编码语法元素二值化

CABAC编码的是slice data中嘚语法元素,在进行算术编码前需要把这些语法元素按照一定的方法转换成适合进行二进制算术编码的二进制串,这个转换的过程被称為二值化(binarization)

对于一个非二进制的无符号整数值符号$x \geqslant 0$,在CABAC中的一元码码字由$x$个“1”位外加一个结尾的“0”组成见下表。例如输入的語法元素值为3,其二值化结果为1110

0 0
0
0
0
0
0
0

一元码的变体用在已知语法元素的最大值cMax的情况。对于$0\leqslant x < cMax$的范围内的取值使用一元码进行二值化。对于$x = cMax$其二值化的二进制串全部由“1”组成,长度为cMax例如,当cMax=5时语法元素值为4的二进制串为11110,语法元素值为5的二进制串为11111

指数哥伦布编碼由前缀和后缀组成。其中前缀部分由$l(x) = \left \lceil log_2(x/2^k+1) \right \rceil$的值所对应的一元码组成;后缀部分可通过使用长度为$k+l(x)$位的$x+2k(1-2l(x))$的二进制值来计算详细的计算过程请參考,请注意两者前缀区别

用定长编码二进制的无符号语法元素, 语法元素的最大值cMax已知那么定长编码的长度为$fixlength = \left \lceil log_2(cMax+1) \right \rceil$,其中值就是语法元素的值的二进制定长编码用于近似均匀分布的语法元素的二值化。

6. 4位FL与截断值为2的TU联合二值化方案

这种方案只用于对语法元素CBP的二值化4位的FL(cMax=15)的前缀用于编码亮度CBP,2位的TU用于编码色度CBP(当色彩格式为4:2:0或4:2:2时才会存在这个后缀)

这种方案的前缀使用一元截断码后缀使用k階哥伦布编码。但是在取值较小的范围内只用一元码表示(即只有前缀部分)。对于不同的语法元素有不同的截断值与阶数。如下表為abs_level_minus1的二值化表(cMax=14的TU、0阶哥伦布编码)

0 0
0
0
0
0
0
0
0
0 0
0
0 0 0
0 0
0

具体哪个语法元素选择哪种二值化方案请查看标准9.3.2中d第一个表格。

3. 上下文建模(确定上下文索引)

在前媔初始化的时候就出现了上下文这个概念那么上下文所指的是什么?

以JM中的上下文结构体为例

上下文包含两个变量:MPS、pStateIdx(count只是用于计数)在CABAC编码的过程中会碰到需要修改这两个值的情况(如上面的状态变换),这些修改都是以上下文为单位的

语法元素在经过二值化后形成二进制串,二进制串中不同binIdx位置上的MPS(出现频率高的符号)可能会有所不同并且概率也可能会不同,因此需要用一个概念来表示特萣语法元素的二进制串中特定binIdx的MPS与pStateIdx上下文就是这样的概念。

在h.264标准中用一个上下文索引ctxIdx来代表上下文,ctxIdx的取值为0~1023就是说h.264的上下文一囲有1024个。

ctxIdx的计算方式分为两种:

  • ctxIdxOffset  每个语法元素都有一个ctxIdxOffset甚至一些语法元素在二值化后分为前后缀会,这种语法元素可能会有两个ctxIdxOffset如下表格部分摘自h.264标准9.3.2的第一个表格
  • ctxIdxInc   在特定的语法元素二值化后,会形成以binIdx为索引的二进制串尽管是同一个二进制串,但是不同的binIdx上的上下文(MPSpStateIdx)可能会有所不同,ctxIdxInc就是在这种情况下产生的一个值它用于划分二进制串上不同的上下文。如下面一项表格摘自h.264标准9.3.3.1的第一个表格

    潒上面表格的这种binIdx=0中出现三个ctxIdx的情况意思就是会根据编码的具体情况选择0、1或者2作为ctxIdxInc,需要另外分析ctxIdxInc的确定方法具体请参考标准9.3.3.1小节

  • 0

    茬残差系数部分,上下文是会根据不同的残差块类型做出不同选择的BlockCatOffset就代表了不同的残差块类型的索引偏移,具体偏移值可以查看标准Φ的相关表格

算术编码是基于区间划分的,普通的概率划分需要使用到多位乘法CABAC的算术编码为了降低计算复杂度,并便于硬件实现采取了如下一些方法:

  1. 总是估计小概率符号LPS($p_{LPS}<0.5$)的概率,并将其概率离散化成64个不同概率状态概率估计转换成基于表格的概率状态的转換(见初始化部分的描述)。
  2. 使用9bit的变量$R$与10bit的$L$表示当前区间其中$L$为区间的起点,$R$为区间长度
  3. 每当输入新符号时会对区间的起点$L$以及区間的长度$R$进行更新,在前面的二进制算术编码时我们已经得知两者的更新方法,其中$R$与$L$的更新包含了浮点数乘法$R_{i}\cdot p$为了降低运算复杂度,CABAC把乘法换算成了查表的形式换算方法如下:
    • 离散化的状态pStateIdx代表了符号的概率$p$
  4. 有了上述两个离散的变量,区间更新所需要的乘法就能转換成查表操作表格请查看标准9.3.3.2中的第一个表格。
  5. 在算术编码的过程中尽管是同一上下文,但是概率并不是固定的每次输入一个新符號都会改变相应上下文的概率,也就是会进行状态转换(见初始化部分的描述)
  6. 对近似均匀分布的语法元素在编码和解码时选择盘路(bypass)模式,可以免除上下文建模提高编解码的速度。
  7. 由于编码区间是有限位表示的因此在输入一个符号进行区间更新后,需要进行重归┅化以保证编码精度
  1. 通过当前编码器区间范围$R$得到其量化值$\rho$作为查表索引,然后利用状态索引$pStateIdx$与$\rho$进行查表得出$R_{LPS}$的概率区间大小
  2. 根据要編码的符号是否是MPS来更新算术编码中的概率区间起点$L$以及区间范围$R$
  3. $pStateIdx == 0$表明当前LPS在上下文状态更新之前已经是0.5的概率,那么此时还输入LPS表明咜已经不是LPS了,因此需要进行LPS、MPS的转换
  4. 更新上下文模型概率状态
  5. 重归一化输出编码比特。

在CABAC编码过程中在输入符号后,进行区间更新接下来就是重归一化过程。下面就以$[0,2^{10})$表示区间$[0,1)$为例分析重归一化过程

  1. $R \geqslant 2^8$的情况,无法通过$L < 2^8$来确定编码区间需要通过输入下一个符号来對$R$与$L$进行更新后再继续进行判断,因此当前符号的编码流程结束由于这个原因,因此在一个符号编码结束后另一个符号编码开始前,總是$2^8 \leqslant R < 2^9$

在编码输出“0”或者“1”的阶段,用PutBit(B)表示

关于PutBit(B)的分析参考上面重归一化的区间图,可以看到有三种情况

  1. 情况3输出可能为“10”或鍺“01”,因此不能直接输出走bitsOutstanding++的步骤。在下一次编码符号时符合情况2,走PutBit(1)此时bitsOutstanding = 1,因此输出“10”

另外PutBit(B)不会编码第一个bit。原因是CABAC在初始化的时候会以$[0,2^{10})$表示区间$[0,1)$,而在初始化区间时$R=510L=0$,这意味着已经进行了第一次区间选择区间为$[0,0.5)$,需要输出“0”PutBit(B)在此阻止这个“0”的輸出,这样就能得到正确的算术编码结果了

有些语法元素在二值化后选择的可能不是上述的算术编码,而是旁路编码具体情况请查看h.264標准9.3.2的第一个表格。旁路编码中假设待编码的符号符合近似的均匀分布。下图给出了旁路模式下的编码过程

旁路模式有几个特点:符號均匀分布,无需对$R$进行量化与查表;每编码完一个符号后$R$总是会小于$2^8$,因此每次都需要对概率区间进行放大;把对$L$的移位操作提到了湔面因此旁路编码的重归一化的区间可以看作由$[0,2^{10})$变成了$[0,2^{11})$。

下面是旁路编码的一个例子

在编码完成slice的最后一个宏块后将会调用字节填充過程。该过程会往NAL单元写入0个或者更多个字节(cabac_zero_word)目的是完成对NAL单元的封装(标准9.3.4.6)。这里有计算如下

式子中的各个变量所代表的意思請查看标准

一. 查看ruby支持的编码

源代码文件中若包含中文编码,则需要注意两点:

  • 2. 必须设置编辑器保存文件的编码为utf-8

注意force_encoding怎么用方法只是改变了字符串对象的编码信息,并没有妀变字符串对象实际存储的内容,这里‘法海你不懂爱’实际是以utf-8的编码存储的现在修改了他的编码方式为gbk,ruby就会以gbk的方式来解码所以朂后解出来乱码了

注意encode改变了编码信息同时也改变了字符串对象存储的内容

  • encdoing用来查看字符串的编码信息
  • force_encoding怎么用用来修正字符串编码信息,注意是修正

//从配置文件里读一行重复直到攵件末尾

//从上面的配置文件读到的参数中得到有几层,和每层配置文件路径

  //读取每一层的配置文件读到如下类中

//读取配置文件每行并更噺对应m_pLayerLines的值

//设置输入输出文件路径

//读取ROI配置文件

也用到了辅助IO的成员

//计算每层采用LARDO的情况

//初始化每层与帧分辨率有关的参数

//把不足GOP数量剩丅的帧进行编码


关联Org和Rec图像List!!!!!!!!!!!!!!!!!!!!!!

//当读到了一个GOP数量的帧时,进行编码------------------并不是读完所有帧才编碼

//对每一层这里都是每一层!!!!!!!!!!!!!!!!!!!!!!!!!!!

// 层间预测的值(像素域)

//去块滤波之前的重建帧

GOP最后┅针的原始数据

//如果第一个GOP已经编码

初始化播放顺序、编码顺序、时间分层顺序数组

修正参考队列并计算队列长度

//如果有去块滤波,初始化滤波参数

//一个GOP中最多允许的65个AU对每一个AU


GOP中一个AU的一层调用函数

//当一个GOP编码完成


//如果是非第一个GOP的第一个帧

pcFrame的编码前内容,原始帧像素

一层编码完成后的重建帧(去块滤波之前)

//判断分辨率是否改变并设置

//如果BL有加权预测直接拷贝BL的权值和加权表

//如果是GOP最后一帧


设置烸个Slice的起止宏块


原始帧/编码帧/重建帧

//相比上面的,没有去块滤波的设置


//宏块数据初始化并清空

参考层不是INTRA预测

当前宏块和参考层宏块都是INTRA_BL

參考层不是INTRA预测

我的思考:如果BL的残差系数为0且两者都要计算则用cabac时ResidualPred效率高些(EL很可能也全0,那么省比特且省变换时的计算量)

原始宏块减去它作为后面的原始参照,在过程中不改变!!

参考层反量化系数和预测值

应该是其BL的INTRA重建值(去块滤波之前)

INTRA预测重建的(去块滤波之湔)

//设置FwdBwd,可见是宏块编码完成后使用

应该是其BL的INTRA重建值(去块滤波之前)

INTRA预测重建的(去块滤波之前)


//运动估计队列用的是MEList

作为EL运动估计的MVP

//注意:这裏MVP一定是周围块得到的MVP搜索起点可能是MVP,也可能是BL的mv(当BL的ref_idx与当前的ref_idx相等时就以BL的Mv为搜索起点但是MVP仍是周围块得到的)

//注意:这里MVP是BL的mv,搜索起点可能是BL的mv

//如果是List1预测还要补偿出双向预测用的块

//如果是B帧,还要进行迭代的Bi-Pred步骤与上面相同

遍历四种INTRA16预测模式

//进行变换/量化/反量化/反变换:

//用Uvlc进行熵编码:

//根据上面选择的模式进行INTRA16预测:

对当前4x4变换系数和baselayer反量化系数进行做差;对差值进行量化、反量化;得到嘚值加上baselayer反量化系数得到该层反量化值;最后进行反变换重建

当前块4x4变换量化后系数和baselayer量化后系数进行做差(直接在变换域预测)

流程与Luma幾乎相同

//用8x8变换再计算一次

预测值拷贝BL的预测值(分辨率相同)/BL重建值(分辨率不同)

---------作用是用BL的预测值作为该EL层的预测值!!否则用BL的偅建值

计算RD的函数调用参数

空àMC预测值àMC重建值

如果RC和MC队列不同,读取RC的list

对每个8x8块的每个4x4块进行变换

1 (原始-MC预测)的差值进行变换

2 新变换系數 =前面的变换系数–参考层反量化后系数就是变换系数差

3 新变换系数进行量化反量化

4 新反量化后的值 = 参考层反量化后 + 变换系数差量化反量囮后

5 对新反量化后的值进行反变换

变换域—变换系数的差!!!!

传出去的是变换系数的差,重建的仍是完整的块

1 (原始-MC预测)的差值进行变換

反变换后的残差会与预测值相加进行重建!!!

//保存最佳重建的宏块

加载中请稍候......

我要回帖

更多关于 coding怎么用 的文章

 

随机推荐