Outline:
1、 运动估计相关的数据结构和变量
2、 相关重要变量的初始化
3、 运动估计函数(BlockMotionSearch())的流程
4、 运动矢量预测
5、 整象素点运动估计
6、 亚象素点运动估计(即高精度象素点运动估计)
7、 亚象素点的插值预测
8、 遗留问题
1、运动估计相关的数据结构和变量
a、六重指针all_mv的解释
(1)声明的样式如下:
int****** all_mv;
(2)出现之处:
全局变量img的结构元素all_mv和pred_mv(其数据结构在global.h中定义)
mv_search.c的BlockMotiongSearch()函数(对一块进行运动估计的函数,后面会重点讲述)中声明的局部变量int****** all_mv;
(3)六重指针的含义:
对应的是一个六维数组如下:
all_mv[block_x][block_y][list][ref][blocktype][direction]
其中block_x, block_y分别表示4*4块在整个宏块16*16内的水平和垂直位置,同时也说明所保存的运动矢量都是以4*4为单位的,假如有一个8*4的块,其运动矢量会保存成相同的两份。
List表示的是哪个参考帧列表
Ref表示的是参考帧序号
Blocktype表示的是宏块的类型,有16×16,16×8。。。4×4
Direction表示水平或垂直方向,其值分别是0和1
2、相关重要变量的初始化
a、img->all_mv的初始化
其初始化的整个过程如下:
首先,在lencod.c的main()函数调用了init_img()(在同一文件中)函数
然后init_img()又调用了get_mem_mv(img->all_mv)(在同一文件中)对all_mv进行初始化。Pred_mv的初始化同上。下面重点分析一下get_mem_mv()函数
int get_mem_mv (int******* mv)
{
int i, j, k, l, m;
if ((*mv = (int******)calloc(4,sizeof(int*****))) == NULL) //水平方向:block_x。因为采用的宏块或亚宏块的尺寸不超过16×16,所以最大值为4
no_mem_exit ("get_mem_mv: mv");
for (i=0; i<4; i++)
{
if (((*mv)[i] = (int*****)calloc(4,sizeof(int****))) == NULL)//block_y
no_mem_exit ("get_mem_mv: mv");
for (j=0; j<4; j++)
{
if (((*mv)[i][j] = (int****)calloc(2,sizeof(int***))) == NULL)//list。6?
no_mem_exit ("get_mem_mv: mv");
for (k=0; k<2; k++)
{
if (((*mv)[i][j][k] = (int***)calloc(img->max_num_references,sizeof(int**))) ==NULL)//ref
no_mem_exit ("get_mem_mv: mv");
for (l=0; l<img->max_num_references; l++)
{
if (((*mv)[i][j][k][l] = (int**)calloc(9,sizeof(int*))) ==NULL)//blocktype:16*16,16*8...
no_mem_exit ("get_mem_mv: mv");
for (m=0; m<9; m++)
if (((*mv)[i][j][k][l][m] = (int*)calloc(2,sizeof(int))) == NULL)//x or y direction:0 or 1
no_mem_exit ("get_mem_mv: mv");
}
}
}
}
return 4*4*img->max_num_references*9*2*sizeof(int);
}
3、 运动估计函数(BlockMotionSearch())的流程
对一个块(各种尺寸)进行运动估计的最核心的函数就是mv_search.c中的BlockMotionSearch(),该函数的具体流程如下:
a、 获取当前块的数据
b、 运动矢量预测
c、 整象素点搜索
d、 亚象素点搜索
e、 保存MV并返回SAD值
下面对b,c,d三块内容进行重点分析
4、运动矢量预测
相关的函数是:SetMotionVectorPredictor(),mv_search.c
a、相应宏定义的解释:
#define MVPRED_MEDIAN 0 //中值预测方式
#define MVPRED_L 1 //取左相邻块的运动矢量
#define MVPRED_U 2 //取上相邻块的运动矢量
#define MVPRED_UR 3 //取右上相邻块的运动矢量
b、该函数的具体流程
(1)先检查各相邻块是否有效
(2)结合参考帧序号等(和分块模式)情况来确定预测方式mvPredType
(3)根据mvPredType采取不同的预测方式
(4)获取相邻块的MV
(5)根据预测方式计算预测MV
c、重要的程序段
。。。
//判断相邻4*4块的有效性
getLuma4x4Neighbour(mb_nr, block_x, block_y, -1, 0, &block_a);//left
getLuma4x4Neighbour(mb_nr, block_x, block_y, 0, -1, &block_b);//up
getLuma4x4Neighbour(mb_nr, block_x, block_y, blockshape_x, -1, &block_c);//right-up
getLuma4x4Neighbour(mb_nr, block_x, block_y, -1, -1, &block_d);//left-up
。。。
/* Prediction if only one of the neighbors uses the reference frame
* we are checking
*///只有一个相邻块的参考帧序号和当前参卡帧序号相同的情况下,采用的预测方式相对较简单
if(rFrameL == ref_frame && rFrameU != ref_frame && rFrameUR != ref_frame) mvPredType = MVPRED_L;
else if(rFrameL != ref_frame && rFrameU == ref_frame && rFrameUR != ref_frame) mvPredType = MVPRED_U;
else if(rFrameL != ref_frame && rFrameU != ref_frame && rFrameUR == ref_frame) mvPredType = MVPRED_UR;
。。。
//根据mvPredType采取不同的预测方式
switch (mvPredType)
{
case MVPRED_MEDIAN:
。。。
{
//取中值
pred_vec = mv_a+mv_b+mv_c-min(mv_a,min(mv_b,mv_c))-max(mv_a,max(mv_b,mv_c));
}
break;
case MVPRED_L:
pred_vec = mv_a;
if(input->FMEnable) temp_pred_SAD[hv] = SAD_a;
break;
case MVPRED_U:
pred_vec = mv_b;
if(input->FMEnable) temp_pred_SAD[hv] = SAD_b;
break;
case MVPRED_UR:
pred_vec = mv_c;
if(input->FMEnable) temp_pred_SAD[hv] = SAD_c;
break;
default:
break;
}
。。。
//非MBAFF比较简单
//MBAFF下,若当前块和相邻块属于同一帧块或畅快,则直接比较两者的参考帧索引号,反之,场块的参考帧号数以二和帧块索引号比较,或者把帧块索引号乘以二
for (hv=0; hv < 2; hv++)
{
if (!img->MbaffFrameFlag || hv==0)
{
mv_a = block_a.available ? tmp_mv[list][block_a.pos_x][block_a.pos_y][hv] : 0;
mv_b = block_b.available ? tmp_mv[list][block_b.pos_x][block_b.pos_y][hv] : 0;
mv_c = block_c.available ? tmp_mv[list][block_c.pos_x][block_c.pos_y][hv] : 0;
}
else
{
if (img->mb_data[img->current_mb_nr].mb_field)
{
mv_a = block_a.available ? img->mb_data[block_a.mb_addr].mb_field?
tmp_mv[list][block_a.pos_x][block_a.pos_y][hv]:
tmp_mv[list][block_a.pos_x][block_a.pos_y][hv] / 2:
0;
mv_b = block_b.available ? img->mb_data[block_b.mb_addr].mb_field?
tmp_mv[list][block_b.pos_x][block_b.pos_y][hv]:
tmp_mv[list][block_b.pos_x][block_b.pos_y][hv] / 2:
0;
mv_c = block_c.available ? img->mb_data[block_c.mb_addr].mb_field?
tmp_mv[list][block_c.pos_x][block_c.pos_y][hv]:
tmp_mv[list][block_c.pos_x][block_c.pos_y][hv] / 2:
0;
}
else
{
mv_a = block_a.available ? img->mb_data[block_a.mb_addr].mb_field?
tmp_mv[list][block_a.pos_x][block_a.pos_y][hv] * 2:
tmp_mv[list][block_a.pos_x][block_a.pos_y][hv]:
0;
mv_b = block_b.available ? img->mb_data[block_b.mb_addr].mb_field?
tmp_mv[list][block_b.pos_x][block_b.pos_y][hv] * 2:
tmp_mv[list][block_b.pos_x][block_b.pos_y][hv]:
0;
mv_c = block_c.available ? img->mb_data[block_c.mb_addr].mb_field?
tmp_mv[list][block_c.pos_x][block_c.pos_y][hv] * 2:
tmp_mv[list][block_c.pos_x][block_c.pos_y][hv]:
0;
}
}
。。。。。
5、 整象素点运动估计
在此只分析采用全搜索的情况,不分析采用快速搜索方法的情况。相关的函数是:FullPelBlockMotionSearch(),mv_search.c
其中重要的程序段如下:
……
//===== loop over all search positions =====
//对搜索区的每个象素点位置进行循环搜索(全搜索)
for (pos=0; pos<max_pos; pos++)
……
NOTE:该函数比较简单,所以在此不具体分析。另外值得一提的是,对于搜索中的匹配准则,JM8.5中采用了RDO的策略,该部分在9.19的报告中已经讲过,大家可以参考那个报告里面的相关内容。
6、亚象素点运动估计(即高精度象素点运动估计)
相关函数是:SubPelBlockMotionSearch(), image.c
a、函数思想
先通过已找到的整象素点为搜索中心,进行半象素点运动估计;然后再通过找到的半象素匹配点,再进行1/4象素点的运动估计
b、重要程序段
。。。
//找到对应的参考帧
ref_picture = listX[list+list_offset][ref];//!!
if (apply_weights)
{
ref_pic = listX[list+list_offset][ref]->imgY_ups_w;
}
else
{//ref_pic指向的是已经上采样到1/4象素点的亮度数据
ref_pic = listX[list+list_offset][ref]->imgY_ups;
}
。。。
/*********************************
***** *****
***** HALF-PEL REFINEMENT *****
***** *****
*********************************/
//===== convert search center to quarter-pel units =====
*mv_x <<= 2;
*mv_y <<= 2;
//===== set function for getting pixel values =====
if ((pic4_pix_x + *mv_x > 1) && (pic4_pix_x + *mv_x < max_pos_x4 - 2) &&
(pic4_pix_y + *mv_y > 1) && (pic4_pix_y + *mv_y < max_pos_y4 - 2) )
{
PelY_14 = FastPelY_14;//指向指针的函数
}
else
{
PelY_14 = UMVPelY_14;//无限制运动矢量模式
}
//===== loop over search positions =====
for (best_pos = 0, pos = min_pos2; pos < max_pos2; pos++)
{
。。。
*d++ = orig_line[x0 ] - PelY_14 (ref_pic, ry, rx0 , img_height,img_width);
。。。
/************************************
***** *****
***** QUARTER-PEL REFINEMENT *****
***** *****
************************************/
。。。//略,和半象素点运动估计类似
7、亚象素点插值预测
a、函数调用的流程
亚象素点插值预测是在将重建图像插入到参考帧列表时才会被调用到。所以整个函数调用的流程如下:(NOTE:箭头表示调用关系)
b、相关知识回顾
(1)对于半象素点的预测插值
采用的是6-tap filter,具体公式如下:
(2)对于1/4象素点的预测插值
采用的是求均值的方法,具体公式如下:
c、UnifiedOneForthPix()函数的重点程序段分析
(1)该函数位于image.c文件中
。。。
//开辟缓冲区,IMG_PAD_SIZE是为了满足UMV模式
get_mem2D (&(s->imgY_ups), (2*IMG_PAD_SIZE + s->size_y)*4, (2*IMG_PAD_SIZE + s->size_x)*4);
。。。
//所求1/2象素点在水平整象素点的位置上 以及整象素点数据的直接赋值
for (j = -IMG_PAD_SIZE; j < s->size_y + IMG_PAD_SIZE; j++)
{
for (i = -IMG_PAD_SIZE; i < s->size_x + IMG_PAD_SIZE; i++)
{
jj = max (0, min (s->size_y - 1, j));
//6-tap filter
is =
(ONE_FOURTH_TAP[0][0] *
(imgY[jj][max (0, min (s->size_x - 1, i))] +
imgY[jj][max (0, min (s->size_x - 1, i + 1))]) +
ONE_FOURTH_TAP[1][0] *
(imgY[jj][max (0, min (s->size_x - 1, i - 1))] +
imgY[jj][max (0, min (s->size_x - 1, i + 2))]) +
ONE_FOURTH_TAP[2][0] *
(imgY[jj][max (0, min (s->size_x - 1, i - 2))] +
imgY[jj][max (0, min (s->size_x - 1, i + 3))]));
img4Y_tmp[j + IMG_PAD_SIZE][(i + IMG_PAD_SIZE) * 2] = imgY[jj][max (0, min (s->size_x - 1, i))] * 1024; // 1/1 pix pos
img4Y_tmp[j + IMG_PAD_SIZE][(i + IMG_PAD_SIZE) * 2 + 1] = is * 32; // 1/2 pix pos
}
}
//所求1/2象素点在垂直整象素点的位置上以及在1/2象素点的位置上
。。。//略
/* 1/4 pix */
。。。//略
。。。
(2)ONE_FOURTH_TAP的解释:
const int ONE_FOURTH_TAP[3][2] =
{
{20,20},
{-5,-4},
{ 1, 0},
};
NOTE:在JM8.5中,ONE_FOURTH_TAP[?][1]没有用到,它的作用也无法解释。
(3)UnifiedOneForthPix函数流程图
开始---半像素水平内插---半像素垂直内插---1/4像素水平内插---1/4像素垂直内插---1/4像素45°内插---1/4像素反45°内插---色度直接复制整像素点的值-结束
注:部分红色字为转载后补充。