運動估計

理論知識:

兩幀之間的物體運動是平移運動,位移量不是很很大,所以會以塊作爲單位分配運動矢量,在運動估計中採用了大量的參考幀預測來提高精度,當前的待編碼塊可以在緩存內的所有重建幀中尋找最優的匹配塊進行運動補償,以便很好的去除時間域的冗餘度。爲每一個塊尋求一個運動矢量MV,並進行運動補償預測編碼。在每個分割區域中都有其對應的運動矢量,並對運動矢量以及塊的選擇方式進行編碼和傳輸。
運動估計ME所表達的運動矢量MV,其研究的內容就是如何加速,有效的獲得足夠精確的mv,並且把前一幀所得的運動信息通過運動補償MC來進行變換,量化編碼,最後輸出。
縮寫含義:me得到的是mV
預測得到的是mvp
差值是mvd
MV:運動向量,參考幀中相對於當前幀的偏移
MVp:參考運動向量
MVD:兩個向量間的差別


提高運動估計算法的效率的主要技術有:初始搜索點的選擇,匹配準則,和運動搜索策略。
1.運動估計初始點的搜索:
1)直接選擇參考幀對應塊的中心位置,這種方法簡單,但容易陷入局部最優點,如果初始的步長太大,而原點(指待搜索塊的中心點在參考幀中的相同位置的對應點)不是最優點時候,可能使快速搜索跳出原點周圍的區域,而去搜索較遠的點,導致搜索方向的不確定性,陷入局部最優。
2)選擇預測的起點,以預測點作爲搜索的起點,
x264採用的將運動估計矢量和參考幀的左邊,上邊和右上邊的MB的中值MV作爲起點進行ME。
2. 匹配準則,
x264中所採用的匹配準則是SAD,SATD. SAD 即絕對誤差和,僅反映殘差時域差異,影響PSNR值,不能有效反映碼流的大小。SATD即將殘差經哈德曼變換的4×4塊的預測殘差絕對值總和,可以將其看作簡單的時頻變換,其值在一定程度上可以反映生成碼流的大小。因此,不用率失真最優化時,可將其作爲模式選擇的依據。 
一般幀內要對所有的模式進行檢測,幀內預測選用SATD.在做運動估計時,一般而言,離最優匹配點越遠,匹配誤差值SAD越大,這就是有名的單一平面假設,現有的運動估計快速算法大都利用該特性。但是,轉換後 SATD值並不滿足該條件,如果在整象素中運用SATD搜索,容易陷入局部最優點。而在亞象素中,待搜索點不多,各點處的SAD差異相對不大,可以用 SATD選擇碼流較少的匹配位置。
3.運動搜索策略
x264所採用的運動搜索策略(對應的最後面的程序中有描述):
#define X264_ME_DIA                  0
#define X264_ME_HEX                  1
#define X264_ME_UMH                  2
#define X264_ME_ESA                  3
#define X264_ME_TESA                 4

下面就在x264中的運動估計所涉及的函數進行跟蹤:
ME的分析在函數x264_slice_write( x264_t *h )中的x264_macroblock_analyse( h );中:進入這個函數:由於對於I幀類型採用的幀內編碼,這部分沒有采用ME,所以對於I幀的分析略。
進入幀間類型(P/B)的分析中:以P幀的16*16MB爲例進行跟蹤:進入函數:
x264_mb_analyse_inter_p16x16( x264_t *h, x264_mb_analysis_t *a )
{   //對參考幀中的所有16*16塊進行分析
for( i_ref = 0; i_ref < h->mb.pic.i_fref[0]; i_ref++ )
{
.......
/* search with ref */
LOAD_HPELS( &m, h->mb.pic.p_fref[0][i_ref], 0, i_ref, 0, 0 );
x264_mb_predict_mv_16x16( h, 0, i_ref, m.mvp );//下面的有詳細的註釋1
x264_mb_predict_mv_ref16x16( h, 0, i_ref, mvc, &i_mvc );//釋2
x264_me_search_ref( h, &m, mvc, i_mvc, p_halfpel_thresh );//釋3
.......
}

}

//釋1:進行16*16的塊的mv預測,得到運動估計的起始方向,並將獲得的MV賦值給MVP,在下一步中使用
x264_mb_predict_mv_16x16( h, 0, i_ref, m.mvp );
void x264_mb_predict_mv_16x16( x264_t *h, int i_list, int i_ref, int16_t mvp[2] )
{
int     i_refa = h->mb.cache.ref[i_list][X264_SCAN8_0 - 1];//亮度左邊塊
int16_t *mv_a  = h->mb.cache.mv[i_list][X264_SCAN8_0 - 1];
int     i_refb = h->mb.cache.ref[i_list][X264_SCAN8_0 - 8];//亮度上邊塊
int16_t *mv_b  = h->mb.cache.mv[i_list][X264_SCAN8_0 - 8];
int     i_refc = h->mb.cache.ref[i_list][X264_SCAN8_0 - 8 + 4];//亮度的右上邊塊
int16_t *mv_c  = h->mb.cache.mv[i_list][X264_SCAN8_0 - 8 + 4];
//當i_refc不存在時,就將i_refc賦值爲左上邊的塊
if( i_refc == -2 )
{
i_refc = h->mb.cache.ref[i_list][X264_SCAN8_0 - 8 - 1];
mv_c   = h->mb.cache.mv[i_list][X264_SCAN8_0 - 8 - 1];
}
//看i_efa/b/c是否是在參考幀所對應中的那一塊,若是i_count++,i_count是用來進行Mvp預測選擇何種方式的一種標誌
if( i_refa == i_ref ) i_count++;
if( i_refb == i_ref ) i_count++;
if( i_refc == i_ref ) i_count++;

if( i_count > 1 )
{
median:
x264_median_mv( mvp, mv_a, mv_b, mv_c );
}
else if( i_count == 1 )
{
if( i_refa == i_ref )
*(uint32_t*)mvp = *(uint32_t*)mv_a;
else if( i_refb == i_ref )
*(uint32_t*)mvp = *(uint32_t*)mv_b;
else
*(uint32_t*)mvp = *(uint32_t*)mv_c;
}
else if( i_refb == -2 && i_refc == -2 && i_refa != -2 )
*(uint32_t*)mvp = *(uint32_t*)mv_a;
else
goto median;
}
}


//釋2:細化16*16塊mv預測
/* This just improves encoder performance, it's not part of the spec */
x264_mb_predict_mv_ref16x16( h, 0, i_ref, mvc, &i_mvc );

void x264_mb_predict_mv_ref16x16( x264_t *h, int i_list, int i_ref, int16_t mvc[9][2], int *i_mvc )
{
//設運動補償
#define SET_MVP(mvp) { /
*(uint32_t*)mvc[i] = *(uint32_t*)mvp; /
i++; /
}
......
//空間預測:獲取左邊,上邊和左上的mb的mvc[i],得到不同的類型的MVC,獲得i個mvc
if( h->mb.i_neighbour & MB_LEFT )
{
int i_mb_l = h->mb.i_mb_xy - 1;
/* skip MBs didn't go through the whole search process, so mvr is undefined */
if( !IS_SKIP( h->mb.type[i_mb_l] ) )
SET_MVP( mvr[i_mb_l] );
}
if( h->mb.i_neighbour & MB_TOP )
{
int i_mb_t = h->mb.i_mb_top_xy;
if( !IS_SKIP( h->mb.type[i_mb_t] ) )
SET_MVP( mvr[i_mb_t] );

if( h->mb.i_neighbour & MB_TOPLEFT && !IS_SKIP( h->mb.type[i_mb_t - 1] ) )
SET_MVP( mvr[i_mb_t-1] );
if( h->mb.i_mb_x < h->mb.i_mb_stride - 1 && !IS_SKIP( h->mb.type[i_mb_t + 1] ) )
SET_MVP( mvr[i_mb_t+1] );
}
//時間預測
//dx,dy表示在時間差上的參考幀上對應點的座標差
#define SET_TMVP(dx, dy) { /
int i_b4 = h->mb.i_b4_xy + dx*4 + dy*4*h->mb.i_b4_stride; /
int i_b8 = h->mb.i_b8_xy + dx*2 + dy*2*h->mb.i_b8_stride; /
int ref_col = l0->ref[0][i_b8]; /
if( ref_col >= 0 ) /
{ /
int scale = (h->fdec->i_poc - h->fdec->ref_poc[0][i_ref]) * l0->inv_ref_poc[ref_col];/
mvc[i][0] = (l0->mv[0][i_b4][0]*scale + 128) >> 8;/
mvc[i][1] = (l0->mv[0][i_b4][1]*scale + 128) >> 8;/
i++; /
} /
}
}


//釋3
x264_me_search_ref( h, &m, mvc, i_mvc, p_halfpel_thresh );
void x264_me_search_ref( x264_t *h, x264_me_t *m, int16_t (*mvc)[2], int i_mvc, int *p_halfpel_thresh )
{
//初始化
.......

bmx = x264_clip3( m->mvp[0], mv_x_min*4, mv_x_max*4 );
bmy = x264_clip3( m->mvp[1], mv_y_min*4, mv_y_max*4 );
//這些變量*4,或者左移2位,是因爲要得到分數像素(1/4像素)
pmx = ( bmx + 2 ) >> 2;
pmy = ( bmy + 2 ) >> 2;
bcost = COST_MAX;

/* try extra predictors if provided */
if( h->mb.i_subpel_refine >= 3 )
{
uint32_t bmv = pack16to32_mask(bmx,bmy);
COST_MV_HPEL( bmx, bmy );     //對COST_MV_HPEL目的:獲得最佳cost的座標
for( i = 0; i < i_mvc; i++ )
{
if( *(uint32_t*)mvc[i] && (bmv - *(uint32_t*)mvc[i]) )
{
int mx = x264_clip3( mvc[i][0], mv_x_min*4, mv_x_max*4 );
int my = x264_clip3( mvc[i][1], mv_y_min*4, mv_y_max*4 );
COST_MV_HPEL( mx, my );
}
}
bmx = ( bpred_mx + 2 ) >> 2;
bmy = ( bpred_my + 2 ) >> 2;
COST_MV( bmx, bmy );
}
else
{
/* check the MVP */
COST_MV( pmx, pmy );
bcost -= BITS_MVD( pmx, pmy );
for( i = 0; i < i_mvc; i++ )
{
int mx = (mvc[i][0] + 2) >> 2;
int my = (mvc[i][1] + 2) >> 2;
if( (mx | my) && ((mx-bmx) | (my-bmy)) )
{
mx = x264_clip3( mx, mv_x_min, mv_x_max );
my = x264_clip3( my, mv_y_min, mv_y_max );
COST_MV( mx, my );
}
}
}
COST_MV( 0, 0 );

//下面是對me方式的選擇switch語句:#define X264_ME_DIA                  0
#define X264_ME_HEX                  1
#define X264_ME_UMH                  2
#define X264_ME_ESA                  3
#define X264_ME_TESA                 4
//switch( h->mb.i_me_method )中的參數 h->mb.i_me_method = h->param.analyse.i_me_method;
//根據用戶的命令輸入決定運動矢量的精度程度,根據空間相關性,用求出的左,上,左上的編碼的宏塊的//MV得到當前mb的mv的預測值mvp,以預測向量mvp的爲初始原點,進行整數像素的搜索

case X264_ME_DIA:
//鑽石形搜索:在do_while循環中,總是以一個菱形的形式進行搜索,只是原點發生變化,這個變化時有//bcost帶來的,而座標
//原點是有bmx,bmy的變化來獲得:
//bmx,bmy的定義:bmx = x264_clip3( m->mvp[0], mv_x_min*4, mv_x_max*4 );
bmy = x264_clip3( m->mvp[1], mv_y_min*4, mv_y_max*4 );

bcost <<= 4;//這裏的左移是爲了和(costs[0]<<4)+N對應
do
{
//以bmx,bmy爲基點在周圍進行其四點的mv cost計算
COST_MV_X4_DIR( 0,-1, 0,1, -1,0, 1,0, costs );
COPY1_IF_LT( bcost, (costs[0]<<4)+1 );//cost左移了,還要再加N了,加N時爲了區別是哪個點
COPY1_IF_LT( bcost, (costs[1]<<4)+3 );
COPY1_IF_LT( bcost, (costs[2]<<4)+4 );
COPY1_IF_LT( bcost, (costs[3]<<4)+12 );
if( !(bcost&15) )//後4位進行檢測,如果後4位是0,就是證明所進行比較的4點都是比原點要大,所以不需要繼續搜索了
break;
bmx -= (bcost<<28)>>30;//爲什麼要這麼麻煩的同時左移和右移了,何不直接除以4
bmy -= (bcost<<30)>>30;
bcost &= ~15;
if( !CHECK_MVRANGE(bmx, bmy) )
break;
} while( ++i < i_me_range );
........
case X264_ME_HEX:六邊形搜索+正方形細化,先進行六邊形搜索,計算六個方向的矢量的cost,以最小者爲起點,再進行正方形細化,
搜索當前的最佳的mv的頭的8個連結點的向量的cost,比較大小得到mv,過程和鑽石形類似


case X264_ME_UMH:非對稱十字多六邊形網格搜索,
具體的搜索步驟引用(http://bbs.chinavideo.org/viewthread.php?tid=7204&highlight=%D4%CB%B6%AF%B9%C0%BC%C6)
JM中快速整像素運動估計算法 (Unsymmetrical-cross Muti-Hexagon- grid Search)即UMHexagonS,該算法高效的起始點預測和搜索策略,
該算法用四個步驟完成。
第一步:用多種預測模式進行初始搜索點預測。主要對以下運動矢量所指向的點進行搜索,獲得當前最優預測起點。
A,中值預測;
B,原點預測;
C,上層塊預測;
D,前幀同位置塊預測;
E,相鄰(多)參考幀預測。
第二步:進行混合搜索,包括如下:
A,非對稱十字搜索。
B,5×5 全搜索。
C,擴展的多層次六邊形(六角形)格點搜索。
第三步:以當前最優點爲中心,用六邊形(六角形)進行搜索,直至最優點在六邊型的中點爲止。
第四步:以當前最優點爲中心,用小菱形進行搜索,直至最優點在小菱形的中點爲止。

在x264中,對於初始索引點的位置是在x264_mb_predict_mv_16x16中已經獲得,在case X264_ME_UMH中主要是進行後面的三步。部分函數解釋如下:
...................
DIA1_ITER( pmx, pmy );//在1/4像素出進行小菱形的搜索,並獲得最小值
.........
// 若爲i_piexl爲4*4時,直接進行六邊形細化,因爲其預測矢量的精度較高,可以跳過十字形搜索和多級六邊形搜索,
if(i_pixel == PIXEL_4x4)
goto me_hex2;
............  
//  將獲得的1/4像素的cost(ucost2)和整像素的cost進行比較,若果相等就賦值cross_start=3,此時的Bcost//爲整像素的cost,ucost1爲初始的cost
if( bcost == ucost2 )
cross_start = 3;
................
//cross 函數主要是在進行十字搜索,在垂直和水平方向進行搜索最小的cost
CROSS( 3, range, range );
.............................





case X264_ME_ESA:
窮盡搜索法,x264已經取消了這種古老的全搜索法,而是採用下面改進的搜索法
  case X264_ME_TESA:hadamard 全搜索法,這個算法和ESA相比主要是在搜索範圍上的變化

//在完成了上面的整像素搜索後,由參數設置來進行1/2,1/4像素的搜索
if( bpred_cost < bcost )
{
m->mv[0] = bpred_mx;
m->mv[1] = bpred_my;
m->cost = bpred_cost;
}
else
{
m->mv[0] = bmx << 2;
m->mv[1] = bmy << 2;
m->cost = bcost;
}

/* compute the real cost */
m->cost_mv = p_cost_mvx[ m->mv[0] ] + p_cost_mvy[ m->mv[1] ];
if( bmx == pmx && bmy == pmy && h->mb.i_subpel_refine < 3 )
m->cost += m->cost_mv;

/* subpel refine */
if( h->mb.i_subpel_refine >= 2 )
{
int hpel = subpel_iterations[h->mb.i_subpel_refine][2];
int qpel = subpel_iterations[h->mb.i_subpel_refine][3];
refine_subpel( h, m, hpel, qpel, p_halfpel_thresh, 0 );
}
}

以上只是針對16*16幀間的MB的運動估計的跟蹤,其他MB類型的ME類似。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章