H.264 White Paper学习笔记(二)帧内预测
为什么要有帧内预测?因为一般来说,对于一幅图像,相邻的两个像素的亮度和色度值之间经常是比较接近的,也就是颜色是逐渐变化的,不会一下子突变成完全不一样的颜色。而进行视频编码,目的就是利用这个相关性,来进行压缩。
很好理解,存储一个像素的亮度值可能需要8个bit,但是如果相邻的两个像素变化不大,我存储一个像素的原始值,以及第二个像素相对第一个像素的变化值,那么第二个值我可能用2个bit就够了,这就节约了很多的空间。而节约存储消耗的bit数,也就是节约码率,贯穿了H.264编码器的所有过程,不管是帧内预测、帧间预测、变换、量化、熵编码,一切的一切都是为这个目的服务的,明白了这一点,我们就能轻易的理解H.264编码器,所有看上去复杂难懂的地方,都是为了一个目的——节约码率。
说回到帧内预测,帧内预测的流程见下图
首先,是上图中蓝色的部分,假设现在我需要对一个像素X进行编码,在编码这个像素之前,先假设我已经有一个参考像素X’了,这个参考像素与同一帧的临近像素有关,根据参考像素X’的值,我得到了一个预测值Xp。
然后,是上图中红色的部分,我用编码的像素X减去预测值Xp,得到了残差d,这个残差d代替原始值X被编码进最终的图像,起到了节省码率的作用
最后,是上图中黑色的部分,残差d和预测值Xp相加,得到了X’,用于下一个像素的预测。
总结起来就是三步:
1、以同一帧图像内的临近像素作为参考,计算预测值Xp
2、原始值X和预测值Xp的差值d,被传递到解码端
3、解码端接收到差值d,将其与预测值Xp相加,就得到了“原始值”X’,X’=Xp+d
步骤很简单,但是里面有几个问题要明确,首先要知道的是,我们固然可以按像素来进行预测,但是这样太费事了,要计算很多次,而且由于帧内预测的特性,你要预测当前的像素,能参考的像素只能是它的临近像素,也就是前面已经编码完成的,它后面的像素还没有编到,所以自然是不能用来编码当前像素的,因此我们只能一个像素一个像素进行编码,编完了一个才能编下一个,这样显然会很慢。于是H.264标准中提出按块进行计算,一个宏块是16x16像素,然后它可以分成子块,最小是4x4的(这个大小是对于亮度编码而言,至于色度编码,4:2:0格式的色度宏块的长和宽都是亮度宏块的一半),这样也能大大提高计算速度。因此下面提到的“值”可以代表“像素”也可以代表“块”,从原理上来说是一样的,而实际采用的是“块”,因此我也就统一用块这个词了。
我们从上面三个步骤从头开始一点点找问题
1、这个参考值X’是怎么来的?答案是在第三步里,使用Xp和传递过来的残差d相加得到的,这个X’用于后面的块编码时的参考。
2、预测值Xp是怎么从X’获得的?答案是根据X’,通过某个公式计算得到的。而这个X’并不是只有一个块,而是有左侧、左上、正上、右上一共四个块作为参考。
3、上面提到的这个某个公式是什么?根据白皮书,Intra(帧内预测)有两种,一种是4x4大小的亮度块,一种是16x16大小的亮度块。
对于4x4大小的亮度块,我们有9种预测模式,如下图所示
对于预测模式0(vertical),当前块的十六个像素值,完全由其上方块最后一行的那四个像素值决定,第一列所有的Xp值都等于A,第二排都等于B,以此类推。预测模式1(horizontal)也是一样,完全由图中IJKL四个像素值决定。
对于预测模式2(DC),则十六个像素值完全相等,等于ABCDIJKL这八个像素的平均值。
对于预测模式3-8,倾斜方向的,各个像素是由A到L像素通过权重不等的公式加权计算的
比如对于模式3(diagnal down-left)来说,a=(A+2B+C+2)/4,这里+2代表四舍五入,b和e=(B+2C+D)/4,cfi=(C+2D+E+2)/4,dgjm=(D+2E+F+2)/4,hkn=(E+2F+G+2)/4,lo=(F+2G+H+2)/4,p=(G+2H+H+2)/4=(G+3H+2)/4
对于模式4(diag down-right),加权系数也是(1,2,1)/4,afkp用IMA三个像素计算,以此类推
对于模式5(vertical right),aj=(M+A+1)/2,同理bk是AB均值,cl是BC均值,d是CD均值(因为这几个像素延长线都不在预测用的13个像素里面)。en在M的延长线上,所以等于(I+2M+A+2)/4,同理fo\gp\h\i\m都可以计算出来
同理模式6、7、8也基本跟5一样,注意对于模式8来说,klmnop四个像素都等于L,因为其延长线在L的下面和前面,没有可以平均的像素,于是只好用L一个值代替了。
还有一点要注意的是,这里面A到L的像素有的没有怎么办?对于模式2(DC),有什么用什么,反正是求均值就对了。其余的模式就必须用到的那几个像素都存在才行(EFGH四个像素可以不存在,此时认为都等于D)
最终,我们要选择哪种模式进行预测?为了在后面节约码率考虑,当然是预测的越准越好,也就是选择Xp和X’差距最小为好。而评判这个差距其实也有好几种算法,比如SAD、SATD等,等到了变换那里再详细说。总之我们用一个公式把上面9种模式的预测都评价了一番,选出里面最好的一种,作为4x4帧内预测的选择。
我们注意到这里有9种模式,之后要进行编码的话,我们除了把残差编进去,总得知道我预测的时候用了哪种模式吧,9这个数就尴尬了,因为刚好三个比特可以表示8种,四个比特可以表示16种,所以3bit不够4bit又浪费了。怎么办呢?有个方法就比较巧妙,我有1bit用来表示我当前用的模式和前面的是不是一样的,因为经常有这样的情况,我前面块用的预测方向和现在这个块用的预测方向一样(比如物体边缘是一条直线,那么对应的那几个块用的预测方向很可能都是一样的),如果一样,我只用1bit就足够存储了,如果不一样,我再用用4个bit存储,也就达到了节约bit的目的。
说完了4x4的亮度块,我们看看16x16大小的亮度块。16x16亮度块有四种模式,如下图
前面三种很好理解了,重点是第四种plane的计算方式,从图上看大概能理解,不过具体算法我还不是很理解。
我们假设左上角起,上方那一行是17个像素是a1 b2 c3 d4 e5 f6 g7 h8 i9 j8 k7 l6 m5 n4 o3 p2 q1,用这17个像素计算一个H值。
我在上面标了191的数字,有数字相同的8对像素,后面计算的时候,都是一对对的计算的。
i9;j8 - h8;k7 - g7;l6 - f6;m5 - e5;n4 - d4;o3 - c3;p2 - b2;q1 - a1,这九对分别乘以权重0到8(也就是i9这个像素没有用到),而最左边和最右边两个像素权重最大。
同理V值,也是一样的算法,从上到下像素记作带’的(当然a1’ = a1)。
然后计算一个A值,它等于右上角(q1)和左下角(V值计算时候对应的那个q1’)的和乘以16
计算一个B值,它等于(5H+32)/64,计算一个C值,C=(5V+32)/64,这种后面加了32又除以64的,其实都是用来四舍五入的。
然后就能计算我们的预测值了。
i00 = A - 7B -7C + 16
pix[0][0] = i00 / 32(超出0到255范围的要截断成0或者255)
然后计算第一行十六个像素分别是i00 + B到i00 + 15*B,然后除以32(然后截断到0~255)
第二行是在第一行基础上加了一个C,一直到第16行,加了15个C,于是这256个像素都算出来了。
这就是plane方式的算法。算出来结果就跟下图一样。
对于色度块是亮度块的四分之一,也就是8x8的,那就只有一种了,预测模式也跟亮度16x16块的类似,有四种,只不过具体的序号不一样而已。是0代表DC,1代表horizontal,2代表vertical,3代表plane。
plane的算法和上面16x16的类似,只不过系数变了,而且两个色度块用的方式一定都是一样的。
好了,我们回到前面的帧内预测的步骤,还有几个问题没有解决
4、重建值X’和原始值是不是一样的?答案是不一样,因为d在传到解码端的过程中经过了量化、变换、反变换和反量化,有了精度的损失,因此Xp+d得到的X’跟原始的X是不一样的。这也是为什么要用重建值X’来得到Xp而不是用原始值X。因为解码器那边在解码图像的时候,用到的是有损的X’获得Xp,如果编码器用无损的X来得到Xp的话,那么得到的残差d在编码侧和解码侧就不一致了,这个误差扩散到了下一个块里。
好了,这样上面的步骤就组成了一个循环,可以一直编码下去了,当然最开始没有参考的那个块,其预测值Xp又是怎么来的呢?可以有一种方式叫做DC128,也就是认为这个块的每个像素都是0x80,然后依据此来计算。
实际上对于一个16x16的宏块,上面这个16x16的四种模式和它16个子块(大小4x4)的9种模式都会算一遍,然后用16个子块的SATD最小值和16x16的四种模式的SATD最小值比较,选择更小的那个,所以这里面计算量还是很大的。x264源代码里有很多节约计算量的设计,不过这些就得看代码了,白皮书里面是没有的,以后再做研究。
另外x264里面还有Intra8x8亮度块的模式,这种我认为应该也是为了找到一个预测最准的模式而加进去的,而且也不常用,所以也就不多介绍了。
回过头来看最开始的那个步骤图,跟白皮书里给的编码器流程图,是不是正是编码器的一部分呢?