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;
//4*4块的序号,水平方向:block_x。因为采用的宏块或亚宏块的尺寸不超过16×16,所以最大值为4
if ((*mv = (int******)calloc(4,sizeof(int*****))) == NULL)
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采取不同的预测方式
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;
}
。。。
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、相关知识回顾(详细内容可参考本人8.22的报告“H.264中高精度的运动估计”)
(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]没有用到,它的作用也无法解释。
7、 遗留问题
no
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#
Smart_zz 发表于2004-11-09 02:50:00 IP: 138.23.212.*
看不到贴的流程图啊。版主能不能再贴一次?
#
Laoha 发表于2004-12-10 21:41:00 IP: 60.63.197.*
很关键的流程图, 没了它相关的代码看不明白, 恳请版主再贴一次图。
#
sunshin1314 发表于2004-12-12 18:18:00 IP: 218.0.4.*
已经贴过了,我自己是可以看到到。我找找原因
#
Andersen 发表于2004-12-16 09:54:00 IP: 61.149.33.*
还是看不到图!:(
#
多啦A梦 发表于2004-12-17 18:05:00 IP: 202.113.12.*
图片的超链接地址有问题
#
张大伟 发表于2005-07-28 20:30:00 IP: 61.186.252.*
李先生,您好
我是一名学生,对视频处理比较 感兴趣
这段时间在研究264的JM86和V3 H。264/AVC协议
现在对基本的一些技术有一定了解,但还很不够。
目前主要觉得INTER MODE DESISION比较难
我今天上网搜索了一下网页,看到您的BLOG,觉得您写的文章非常不错,让我清楚了一些没有清楚的地方,谢谢您^_^
我现在对ENCODE—ONE——MACROBLOCK函数还不是太清楚,您能不能帮我注释一下这段程序?我不太清楚它怎么选择出来什么样的模式是最好的,谢
谢!
关于INTERMODE DESISION您有什么比较好的文章说明这个问题的吗?我看的都是一些泛泛的东西,实际的过程还不是很了解
对有些关键的变量还没有搞懂什么意思,比如RUNS,RERUN,bestpdir,程序决策的过程是怎么样?等等
* Mode Decision for a macroblock
*************************************************************************************
*/
void encode_one_macroblock ()
{
static const int b8_mode_table[6] = {0, 4, 5, 6, 7}; // DO NOT CHANGE ORDER !!!
static const int mb_mode_table[7] = {0, 1, 2, 3, P8x8, I16MB, I4MB}; // DO NOT CHANGE ORDER !!!
int valid[MAXMODE];
int rerun, block, index, mode, i0, i1, j0, j1, pdir, ref, i, j, k, ctr16x16, dummy;
double qp, lambda_mode, lambda_motion, min_rdcost, rdcost = 0, max_rdcost=1e30;
int lambda_motion_factor;
int fw_mcost, bw_mcost, bid_mcost, mcost, max_mcost=(1<<30);
int curr_cbp_blk, cnt_nonz = 0, best_cnt_nonz = 0, best_fw_ref = 0, best_pdir;//
int cost=0;
int min_cost = max_mcost, min_cost8x8, cost8x8, cost_direct=0, have_direct=0, i16mode;
int intra1 = 0;
int intra = (((img->type==P_SLICE||img->type==SP_SLICE) &&
#
flyonthesky 发表于2005-07-28 20:52:00 IP: 61.186.252.*
李先生,您好
我是一名学生,对视频处理比较 感兴趣
这段时间在研究264的JM86和V3 H。264/AVC协议
现在对基本的一些技术有一定了解,但还很不够。
目前主要觉得INTER MODE DESISION比较难
我今天上网搜索了一下网页,看到您的BLOG,觉得您写的文章非常不错,让我清楚了一些没有清楚的地方,谢谢您^_^
我现在对ENCODE—ONE——MACROBLOCK函数还不是太清楚,您能不能帮我注释一下这段程序?我不太清楚它怎么选择出来什么样的模式是最好的,谢
谢!
关于INTERMODE DESISION您有什么比较好的文章说明这个问题的吗?我看的都是一些泛泛的东西,实际的过程还不是很了解
对有些关键的变量还没有搞懂什么意思,比如RUNS,RERUN,bestpdir,程序决策的过程是怎么样?等等
#
李世平 发表于2005-07-29 12:12:00 IP: 61.186.252.*
张大伟,你好,
encode_one_macroblock()的程序结构大致如下:
{
for(rerun=0;,,,)//rerun和rdo loss有关,不用理会
{
//宏块级的运动估计
for()
{}
//亚宏块级的运动估计和模式选择
if(valid[P8x8])
{}
//宏块级的模式选择,一共有7钟模式,包括帧内预测模式
其中,RDCost_for_macroblocks()是重点!!
}
}
#
张大伟 发表于2005-07-29 18:50:00 IP: 61.186.252.*
李先生,您好
明明知道您已经回了帖子,但是看不到,可能是浏览器有问题,您能发到我的邮箱吗
[email protected]
谢谢
#
trunk 发表于2005-08-08 09:52:00 IP: 61.186.252.*
peter all-mv的初始化中有这么个语句
if (((*mv)[i][j][k][l] = (int**)calloc(9,sizeof(int*))) == NULL)
这是给blocktype分配空间 怎么来的9呢?
all_mv初始化中9怎么来的?blocktype 不就是只有七种吗? 16*16....4*4
请您回答以下可以吗?
#
李世平 发表于2005-08-08 10:08:00 IP: 61.186.252.*
trunk 你好,
估计是7种宏块模式加上skip mode 和 direct mode
#
sophie 发表于2005-11-21 22:35:00 IP: 211.83.101.*
李先生,您好
上面看到您提到了skipped mode 和direct mode,我不是很清楚这两种模式的具体区别,尤其是skipped Mode ,您能给我解释一下吗?
谢谢您
#
李世平 发表于2005-11-22 16:16:00 IP: 218.0.4.*
to sophie,你好,
skip mode和direct mode的共同特点就是不需要传输运动矢量和残差数据。
skip mode是针对P帧的
direct mode是针对B帧的,又分为空间的和时间的两种。
具体内容这里也讲不清楚,我准备近期写一篇关于这方面的文档,敬请关注。
#
sophie 发表于2005-11-23 14:55:00 IP: 211.83.101.*
李先生,
您好,谢谢您的回复。
我现在还有一个问题不明白,向您请教:
在H.264的mv_search.c中有MotionBlockSearch()和
您在上面讲的SubPelBlockSearch()两个函数,这两者里面都包含sub-pel search,他们2者有什么不同呢?分别有何作用呢?
谢谢
#
sophie 发表于2005-11-23 16:42:00 IP: 211.83.101.*
李先生,
您好,上面这个问题我搞明白了,耽误您时间了,不好意思!
谢谢
#
李世平 发表于2005-11-23 16:45:00 IP: 218.0.4.*
to sophie,你好,
应该是BlockMotionSearch吧
两者是调用和被调用的关系。
BlockMotionSearch()分别调用FullPelBlockSearch()和SubPelBlockSearch()来实现整象素点和半象素,1/4象素点的运动估计
#
jiepig 发表于2005-12-22 20:12:00 IP: 166.111.65.*
李先生,您好!
看了您写的这篇文章给我很大启发,非常感谢,您说有些内容您在以前的报告中写过,可是我现在看不到了,您能不能发给我一份?非常感谢,我的邮箱是
[email protected]
#
sonic 发表于2006-03-05 11:26:00 IP: 59.64.193.*
李先生好
我是个本科生 毕业设计的题目是4*4亮度块的贞内预测,不知道您有没有好的文章能帮我理解整个9种模式选择的过程,重要的函数,参数的作用,真是不胜感激
我的邮箱
[email protected]
#
李世平 发表于2006-03-08 19:08:00 IP: 218.0.4.*
TO sonic:
panfen的一篇文章不错,是JVT WORKSHOP里的文档。
JVT-G013
Fast Mode Decision for Intra Prediction
#
李世平 发表于2006-03-08 21:40:00 IP: 218.0.4.*
to 冰凌儿:
../../common/frame.h(27) : fatal error C1083: Cannot open include file: 'inttypes.h': No such file or directory
是因为找不到'inttypes.h'这个文件, window c中找不到该文件,谁有这个文件的麻烦发到我邮箱:
[email protected]
谢谢了
===========
你可以把这句代码注释掉,或者换成:
#ifdef HAVE_STDINT_H
#include <stdint.h>
#else
#include <inttypes.h>
#endif
#
fantacy 发表于2006-04-08 20:37:00 IP: 222.18.41.*
李先生您好:
请问结构体InputParameters里面的rdopt是什么,有什么作用?
#
李世平 发表于2006-04-15 10:18:00 IP: 218.0.4.*
对应config文件种rdo选项,
=0:无rdo
=1: rdo
=2: 允许失真的rdo
#
骆驼 发表于2006-05-23 19:33:00 IP: 125.96.51.*
李sir:你好!
今天发现你的专栏,做的真好!对我帮助很大。
请教问题:我已经下载了MPEG4标准测试序列(QCIF格式),用MATLAB做运动估计时,如何读取序列图像的每一帧数据?
我初入视频领域,不懂的问题多多啊,谢谢!
骆驼
#
xkager 发表于2006-06-22 14:32:00 IP: 202.120.36.*
李先生:
您好!
我是一个初学者,264标准里的MbaffFrameFlag 变量,我不是很理解他的意思(我个人觉得MbaffFrameFlag 为1就表示帧场自适应,但是又感觉MbaffFrameFlag 为1代表帧场自适应并且当前为宏块为帧宏块。),还请您指教!谢谢!
#
qwe 发表于2006-10-30 22:31:00 IP: 218.185.222.*
请教peter: 在encoder_one_macroblock() 中对P8X8模式预测时使用了,但对16*16、8*16和16*8模式进行预测时,却并没有用。是不是对P8*8使用RDO多余了,因为后面的RDCost_for_macroblocks()函数适用于所有的模式。
当我看到你对张大伟的解释,在P8*8中的RDO又好像是应给在对“亚宏块级的模式选择”,但代码确实是对整个P8*8的宏块作的RDO呀!这到底是怎么回事,是不是我对某部分理解错了,请指点!
如果看到,请一定要回帖呀!谢谢!
#
chang9495 发表于2007-01-26 10:18:54 IP:
李先生您好:
因目前正在研究H.264的內插(interpolation),但在看源碼部分有一些不瞭解,您在本篇有提到(详细内容可参考本人8.22的报告“H.264中高精度的运动估计”),但是沒看到存檔,不知可否將这个文件发到我邮箱:
[email protected],謝謝!!
#
mminrong 发表于2007-01-29 17:47:12 IP: 222.90.141.*
李老师您好:
我下载了一的JM H264解码程序,我想知道文件从什么地方输入,根据decoder.cfg吗,如果是,输入输出的文件的路径需要重新指定吗?还是放在当前工程的 .dsw目录下?
一同打开的工程,除了decode 工程外,还有encode,rtpdump工程,rtpdump工程的功能是什么?
谢谢
#
mminrong 发表于2007-01-31 09:01:26 IP:
Hi 李老师,
以上问题已经基本解决。
但是还有一个问题,我下载了JM12。0 decoder 能在VC6下运行无误,但是encoder也能编译通过,但是总是提示encoder configer 文件某个参数不能识别。不知道是为什么?
谢谢
#
sunshine1314 发表于2007-02-01 16:15:22 IP: 219.133.40.*
to chang9495: 那个文档比较久远,找不到了
to mminrong :
decoder.cfg中输入绝对路径即可
to mminrong:
cfg文件的版本不对。
#
jogh264 发表于2007-04-09 13:25:05 IP: 218.104.71.*
在BlockMotionSearch ()函数中最后几行:
//===============================================
//===== SET MV'S AND RETURN MOTION COST =====
//===============================================
if(input->FMEnable)
{
int h4x4blkno = (img->pix_x>>2)+block_x;
int v4x4blkno = (img->pix_y>>2)+block_y;
for (i=0; i < (bsx>>2); i++)
{
for (j=0; j < (bsy>>2); j++)
{
all_mv[block_x+i][block_y+j][list][ref][blocktype][0] = mv_x;
all_mv[block_x+i][block_y+j][list][ref][blocktype][1] = mv_y;
mv_array[h4x4blkno+i][v4x4blkno+j][0] = mv_x;
mv_array[h4x4blkno+i][v4x4blkno+j][1] = mv_y;
}
}
}
else
{
for (i=0; i < (bsx>>2); i++)
{
for (j=0; j < (bsy>>2); j++)
{
all_mv[block_x+i][block_y+j][list][ref][blocktype][0] = mv_x;
all_mv[block_x+i][block_y+j][list][ref][blocktype][1] = mv_y;
}
}
}
return min_mcost;
请问李老师all_mv和mv_array不都是BlockMotionSearch ()中定义的局部变量吗,这样设置mv变量有什么用?
#
sunshine1314 发表于2007-04-12 21:25:22 IP: 219.134.1.*
好久没看JM的代码了。通过你贴的这段代码本身的分析是:
input->FMEnable的情况,及快速运动估计的情况下,各种块尺寸的运动矢量是有关联性的,可以做个假设,16x16块的运动矢量可以作为16x8等块的初始运动矢量,所以mv_array就是充当暂时存储的作用。
no