问题现象:
解码的图像在墙壁部分有很明显的方块,其它图像变化大的地方(如人脸部分)情况还好。
问题分析:
以前没有这种情况的呀。改变了的只有编码速率。原来为了兼顾网络状况,我们将码率降得比较低。码率小,量化的精度就低,宏块与宏块之间的量化误差变大。当颜色变化很平缓时,这种很小的误差就变得明显了,图像上的体现就是宏块边界出现明显的颜色跳变,也就是方块。
问题解决:
还好mpeg4标准考虑到了这个现象,他们提供了一种叫deblock的解码后处理方法来弱化这种现象。实际将deblock添加后图像确实平滑许多,方块也没了。
deblock原理:
大概做法就是将每个点与它上下10个点和左右10个点的颜色平均。具体做法是将图像按8*8划分成块,按块的边界做平均。 如下图所示。
| |
| |
块1 | 块2 | v0
v0 v1 v2 v3 v4|v5 v6 v7 v8 v9 | v1
| | v2
| | v3
| | v4
----------------------------------------------------------------
| | v5
| | v6
| | v7
块3 | 块4 | v8
| | v9
| |
| |
-----------------------------------------------------------------
每次计算取10个点,首先判断10个点的差别,如果差别很小就用默认模式将v4 v5做个细小的修正,如果差别很大则用DC offset模式修正v1-v8共8个点的值。判别的算法是:
eq_cnt = y(v0-v1) + y(v1-v2) + y(v2-v3) + y(v4-v5) + y(v5-v6)
+ y(v6-v7) + y(v7-v8)
其中if(x<= THR1) y(x) =0 else y(x) = 1; //THR1 是一个经验值 1
if(eq_cnt < THR2) //THR1 是一个经验值 6
default mode
else
DC offset mode
DC offset模式的规则:
max = MAX(v[1],v[2],v[3],v[4],v[5],v[6],v[7],v[8]);
min = MIN(v[1],v[2],v[3],v[4],v[5],v[6],v[7],v[8]);
if((max - min) < 2* quant)
颜色平均
else
什么也不做
按 1,1,2,2,4,2,2,1,1 的权重平均颜色。比如
s[1] = (uint8_t)((6*p0 +(v[1]<<2)+(v[2]<<1)+(v[3]<<1)+v[4]+v[5]+8)>>4);
s[2] = (uint8_t)(((p0<<2)+(v[1]<<1)+(v[2]<<2)+(v[3]<<1)+(v[4]<<1)+v[5]+v[6]+8)>>4);
s[3] = (uint8_t)(((p0<<1)+(v[1]<<1)+(v[2]<<1)+(v[3]<<2)+(v[4]<<1)+(v[5]<<1)+v[6]+v[7]+8)>>4);
类似的计算出s[1]-s[8],然后回写到v[1]-v[8]的位置。
计算量分析:
统计可以知道绝大部分使用了DC offset模式。如果图像尺寸是352*288,只分析亮度分量的计算量。
8*8块竖向分界线有352/8-1 = 43 条 ,每条分界线上有288个点,也就是竖向deblock要做43*288=12384个平均操作。
横向向分界线有288/8-1 = 35 条 ,每条分界线上有352个点,也就是竖向deblock要做35*352=12320个平均操作。
每个平均操作需要执行:
(1)从内存中读取10个byte, 竖向时10个byte是连续的,横向时每个byte相隔一行的宽度
(2)计算eq_cnt
(3)从v[1]-v[8]中找出最大的最小的值
(4)计算s[1]-s[8]
(5)将s[1]-s[8]存到v[1]-v[8]
另外,由于这些操作会修改图像的值,而如果图像是I或P帧,它还要作为后继帧的参考帧,图像数据不能变,所以需要将它拷贝到临时区中处理,这个拷贝也极耗时间。
天啦,每帧deblock将会大约读两遍帧数据和写一遍帧数据,将比yuv->rgb更恐怖的耗时间。实际的情况真是
令人沮丧,原本优化到32fbs了,加上deblock后直接降到16fbs。
优化分析:
(1)读取和写入内存会占大部分时间,不过这部分没什么好优化的。
(2)计算eq_cnt,这部分计算量很小,也不必在意
(3)从v[1]-v[8]中找出最大的最小的值
max = MAX(s[1],MAX(s[2],MAX(s[3],MAX(s[4],MAX(s[5],MAX(s[6],MAX(s[7],s[8])))))));
min = MIN(s[1],MIN(s[2],MIN(s[3],MIN(s[4],MIN(s[5],MIN(s[6],MIN(s[7],s[8])))))));
xvid给了两种方法计算MAX和MIN
#define MIN(X, Y) ((X)<(Y)?(X):(Y))
#define MAX(X, Y) ((X)>(Y)?(X):(Y))
#define FAST_MAX(x,y) ((x) - ((((x) - (y))>>(32 - 1)) & ((x) - (y))))
#define FAST_MIN(x,y) ((x) + ((((y) - (x))>>(32 - 1)) & ((y) - (x))))
第二种方法耗4条指令。
如果用汇编则只需3条指令
mov r1,r2 // r1 = max(r2,r3)
cmp r3,r1
movgt r1,r3
(4)计算s[1]-s[8],公式中有一些重复的计算,可以先计算重复部分后整体代换。
这些优化的都是耗时不太大的部分,真正耗时的是内存的存取部分。可以根据图像质量的状况适当减小计算的点数。标准中每次平均计算都取10点写8点,实际可以读8点写6点。