H264解码器实现-帧间预测之像素值预测

前言

本文所说的像素值预测是指根据当前预测块的MV向量和预测所需的参考块的像素值数据,计算出当前预测块的预测像素值的过程。该过程得到的预测像素值经过运动补偿后(与反变换量化之后得到的残差像素值相加)可以得到滤波前的重建像素值。

原理说明

一般来说,只要知道MV向量,就可以将MV向量所指向参考块的像素值作为当前块的预测值,但是H264采用1/4亚像素级运动估计方法,MV向量并不是以整数像素点为单位的,而是以1/4像素为单位的。这也就意味着我们需要将MV指向的参考块进行像素内插操作,进而找到MV向量具体指向的位置,同时得到预测像素值。
假设当前4x4块的参考关系如下图所示:
图1:4x4块参考关系示意图
从图中可以看出当前块的左上角P0的坐标为(128,128)MV向量为(-19,-5)参考块左上角G的坐标为(123,126)。由于MV向量以1/4像素为单位,我们可以先将该MV向量做整像素单位对齐,找到该整数像素点的坐标,对齐后的MV向量我们暂时称为MVz,然后计算实际参考的分像素点相对与该整数像素点的向量MVf,可以看出MVf = MV - MVz。如下图所示,MVz为(-20,-8),刚好指向参考块的G像素,MVf为(1,3)指向参考块做了像素内插后的p像素点。同理可得P4的参考的整像素点为M点,实际参考点同样也是像素内插后相对与M点的(1,3)位置,其他像素点依次类推。
4x4块像素内插
图中的a,b,c,d,e,f,g,h,i,j,k,m,n,p,q,r,s等小写字母表示的都是通过像素内插得到的分像素点。那么像素内插过程是如何的呢,H264标准协议第8.4.2.2.1章节有详细描述,这里简单说明一下原理,请看下图:
像素内插示意图
图中灰色方块代表整数像素点,其余为分数像素点。像素内插遵循以下规则:
(1)首先生成参考图像亮度成分半像素像素。
半像素点(如b,h,m,s)通过对相应整像素点进行6 抽头滤波得出,权重为(1/32 ,-5/32 ,5/8, 5/8, -5/32, 1/32)。例如b 像素点计算如下:

b = round((E - 5F + 20G + 20H - 5I + J) / 32)

类似的,h 由A、C、G、M、R、T 滤波得出。

h = round((A − 5C + 20G + 20M − 5R + T) / 32) 

一旦邻近(垂直或水平方向)整像素点的所有像素都计算出,剩余的半像素点便可以通过对6 个垂直或水平方向的半像素点滤波而得。例如,j 由cc, dd, h,m,ee,ff 滤波或者由aa,bb,b,s,gg,hh得出,两者计算出的结果应该是相同的。

j = round((cc − 5dd + 20h + 20m − 5ee + ff) / 1024)
//或者
j = round((aa − 5bb + 20b + 20s − 5gg + hh) / 1024)

(2)半像素点计算出来以后,1/4 像素点就可通过线性内插得出.。
1/4像素点内插

1/4 像素点(如a,c, i, k, d, f, n, q)由邻近像素内插而得,如a像素计算如下:

a = round((G + b) / 2)

注意有4个特殊的1/4像素点e,g,p,r,他们是使用对角线上的两个半像素点得到,如:

e = round((h + b) / 2)

至此像素内插的过程就全部介绍完了。

代码实现

帧间预测需要使用三个输入数据:
(1)当前预测块的MV向量。
(2)当前预测块的参考块的像素重建数据。
(3)当前预测块左上角P0位置坐标。
输出:
(1)当前预测块的预测值。

下列代码展示了当前4x4块左上角P0坐标为(4,4),运动向量MV为(-7,-5)的帧间预测过程。
根据原理可知:
MVz = (-8,-8),MVf = (1,3),因此P0参考的像素点坐标G为(2,2)根据这些信息我们可以计算出整个当前4x4块的预测像素值。

#include <stdio.h>
#include <stdlib.h>

#define PIC_WIDTH 16 //图像宽度
#define PIC_HIGHT 16 //图像高度

#define BLK_WIDTH 4 //当前块宽度
#define BLK_HIGHT 4 //当前块高度

#define REF_POS_X 2 //参考块左上角像素点G的横坐标
#define REF_POS_Y 2 //参考块左上角像素点G的纵坐标

#define MAX_PIX_VALUE 32 //像素点最大像素值

typedef char imgpel;

static inline int imin(int a, int b)
{
  return ((a) < (b)) ? (a) : (b);
}

static inline int imax(int a, int b)
{
  return ((a) > (b)) ? (a) : (b);
}

static inline int iClip1(int high, int x)
{
  x = imax(x, 0);
  x = imin(x, high);

  return x;
}

/* 该函数以MVf的值命名,因为所有MVf相同的帧内差值过程都是一样的,只是使用的整数像素点不一样而已 */
/* 参数说明:                                                                            */
/* (1) block: 当前块左上角P0像素地址                                                      */
/* (2) cur_imgY: 参考块左上角G像素相对与整幅图像所在行地址                                  */
/* (3) block_size_y: 当前块高度                                                          */
/* (4) block_size_y: 当前块宽度                                                          */
/* (5) x_pos: 参考块左上角G像素的横坐标x                                                  */
/* (6) shift_x: 整个图像Buffer的宽度,注意这里不一定和图像宽度一致                          */
/* (7) max_imgpel_value: 像素值最大值                                                    */
static void get_luma_13(imgpel **block, imgpel **cur_imgY, int block_size_y, int block_size_x, int x_pos, int shift_x, int max_imgpel_value)
{
  /* Diagonal interpolation */
  int i, j;
  imgpel *p0, *p1, *p2, *p3, *p4, *p5;
  imgpel *orig_line;
  int result;

  int jj = 1;

  /* 由于MVf(1,3)表示分像素p位置,根据原理可知它是由对角线上两个半像素计算而来 */
  /* 因此这里先横向内插 */
  for (j = 0; j < block_size_y; j++)
  {
    p0 = &cur_imgY[jj++][x_pos - 2];
    p1 = p0 + 1;
    p2 = p1 + 1;
    p3 = p2 + 1;
    p4 = p3 + 1;
    p5 = p4 + 1;

    //printf("ver %02d %02d %02d %02d %02d %02d\n", *p0, *p1, *p2, *p3, *p4, *p5);
    orig_line = block[j];

    for (i = 0; i < block_size_x; i++)
    {
      //计算s分像素点,公式见原理说明
      result  = (*(p0++) + *(p5++)) - 5 * (*(p1++) + *(p4++)) + 20 * (*(p2++) + *(p3++));
      *(orig_line++) = (imgpel) iClip1(max_imgpel_value, ((result + 16)>>5));
    }
  }

  /* 这里开始进行垂直方向内插 */
  p0 = &(cur_imgY[-2][x_pos]);
  for (j = 0; j < block_size_y; j++)
  {
    p1 = p0 + shift_x;
    p2 = p1 + shift_x;
    p3 = p2 + shift_x;
    p4 = p3 + shift_x;
    p5 = p4 + shift_x;
    orig_line = block[j];
    //printf("hor %02d %02d %02d %02d %02d %02d\n", *p0, *p1, *p2, *p3, *p4, *p5);

    for (i = 0; i < block_size_x; i++)
    {
      //计算h分像素点,公式见原理说明
      result  = (*(p0++) + *(p5++)) - 5 * (*(p1++) + *(p4++)) + 20 * (*(p2++) + *(p3++));
      //计算p分像素点,注意这里将h的取平均操作放在这里了
      *orig_line = (imgpel) ((*orig_line + iClip1(max_imgpel_value, ((result + 16) >> 5)) + 1) >> 1);
      orig_line++;
    }
    p0 = p1 - block_size_x ;
  }
}

int main(int argc, char*argv[])
{
        int i;
        int j;
        char **img_y;
        char *img_y_vir;
        char *cur_block_y_vir;
        char **cur_block_y;

		// 分配参考图像空间,这样分配可以使用二维数组访问方式来访问数据,如img_y[0][0]
        img_y = (char **)malloc(sizeof(char*) * PIC_HIGHT);
        img_y_vir = (char*)malloc(PIC_WIDTH * PIC_HIGHT);
        for(i = 0; i < PIC_HIGHT; i++) {
                img_y[i] = img_y_vir + i * PIC_WIDTH;
        }

		// 分配当前块空间,这样分配可以使用二维数组访问方式来访问数据,如cur_block_y[0][0]
        cur_block_y = (char **)malloc(sizeof(char*) * BLK_HIGHT);
        cur_block_y_vir = (char*)malloc(BLK_WIDTH * BLK_HIGHT);
        for(i = 0; i < BLK_HIGHT; i++) {
                cur_block_y[i] = cur_block_y_vir + i * BLK_WIDTH;
        }

		// 这里使用随机函数随机生成参考图像数据
		printf("ref pic data:\n");
        for(i = 0; i < PIC_HIGHT; i++) {
                for(j = 0; j < PIC_WIDTH; j++) {
                        img_y[i][j] =  rand() % 32;
                        printf("%02d ", img_y[i][j]);
                }
                printf("\n");
        }

		//开始针对MVf为(1,3)的情况进行帧间预测,注意如果MV计算出来的MVf是其他值,这里暂不支持。
		printf("ref left top point G is (%d, %d)\n", REF_POS_X, REF_POS_Y);
        get_luma_13(cur_block_y, &img_y[REF_POS_X], BLK_HIGHT, BLK_WIDTH, REF_POS_Y, PIC_WIDTH, MAX_PIX_VALUE);

		printf("cur block data:\n");
        for(i = 0; i < BLK_HIGHT; i++) {
                for(j = 0; j < BLK_WIDTH; j++) {
                        printf("%02d ", cur_block_y[i][j]);
                }
                printf("\n");
        }

        free(img_y);
        free(img_y_vir);
        free(cur_block_y);
        free(cur_block_y_vir);

        return 0;
}

运行结果:

ref pic data:
07 06 09 19 17 31 10 12 09 13 26 11 18 27 03 06
28 02 20 24 27 08 07 13 22 26 14 03 19 31 09 26
06 18 13 23 17 24 03 26 05 29 05 23 24 09 30 20
11 18 13 06 27 20 20 17 14 02 20 01 01 29 28 07
16 09 30 01 01 01 28 07 30 01 30 23 10 28 11 22
15 24 28 10 12 16 27 27 18 15 28 20 12 24 27 28
02 26 30 03 27 26 10 26 27 09 17 06 05 28 28 20
21 24 30 01 09 25 28 27 08 25 15 21 17 11 17 19
05 15 23 00 09 01 26 05 10 11 11 16 08 07 04 29
31 03 30 08 28 27 04 05 20 19 26 05 30 11 25 03
27 16 04 04 17 30 09 28 10 20 12 18 27 16 15 27
19 13 03 16 08 07 21 28 27 15 02 25 26 27 29 21
11 01 26 28 31 03 24 09 24 04 27 19 21 10 14 08
24 18 24 00 25 13 29 20 28 31 14 23 26 11 12 05
12 06 01 11 10 26 21 02 30 16 21 19 27 04 28 19
22 20 19 15 02 16 04 30 15 18 21 09 29 02 14 09
ref left top point G is (2, 2)
cur block data:
08 16 25 24
20 00 08 13
25 07 07 14
21 10 28 19
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值