天天看點

px4flow 中flow.c解讀

一、塊比對光流法基本原理:

        将視訊序列圖像的每一幀分成很多互不重疊的塊,并認為塊内所有像素的位移矢量是一緻的。然後,對于目前幀中的某塊,根據一定的比對準則,在參考幀的給定搜尋範圍内找出與此塊最為相似的塊,由相似度最高的塊與目前塊的相對位置計算出運動位移,所得到的運動位移即為目前塊中每個像素點的運動矢量,目前幀中所有小塊的運動矢量的集合為目前幀的光流場。

        如圖所示,假設t時刻對應目前幀G0,在t`時刻對應參考幀G1,将圖像G0分割成許多互不重疊且大小為M*N的小塊,在給定搜尋步長(um,vm)後,對所有小塊進行比對運算,對于其中中心點坐标為(x,y)的小塊,在G1中對應的小塊的起始搜尋坐标範圍為從(x-um,y-vm)到(x+um,y+vm)的區域,将搜尋區域内的所有小塊與G0中的小塊進行比對運算,得到的誤差最小塊,即為最佳比對塊。最佳比對塊的起始點坐标與G0中小塊的起始點坐标(x,y)之差即為G0中此小塊内所有像素點的運動矢量。

px4flow 中flow.c解讀
px4flow 中flow.c解讀

  在實際應用中,為了計算友善,往往取小塊的M,N值為相等且值為2的

倍數,步長um,vm也取值相等。塊比對光流算法的精度收到塊大小M,N值的影響,當M,N值過大時,塊内所有像素的位移矢量一緻的假設條件容易被破壞,當M,N值過小時,塊内資訊量的減少會降低比對塊的準确度。

二、代碼分析

一、确定比對塊及對應光流:

px4flow 中flow.c解讀

for (j = pixLo; j < pixHi; j += pixStep)

{

for (i = pixLo; i < pixHi; i += pixStep)

{

這裡的for循環是周遊目前幀的塊。

而在每一塊K中,我們要做如下操作:

(1)尋找角點,看該塊是否有必要進行比對,若沒有角點,跳出此層循環,移動到下一個塊K+1;若有角點,進入下面的for循環

(2)尋找最佳比對塊

for (jj = winmin; jj <= winmax; jj++)

{

for (ii = winmin; ii <= winmax; ii++)

{

這一步,是在參考幀中,(i,j)點的鄰域(winmin,winmax)範圍内,尋找與塊K比對的塊K’。

根據如下公式,選擇連續兩幀圖檔的最佳比對塊

px4flow 中flow.c解讀

其中f(I,j)是灰階值,(i,j)是像素點的坐标,(m,n)是位移。

具體代碼如下:

uint32_t temp_dist = ABSDIFF(base1, base2 + ii);

其中:*base1 = image1 + j * (uint16_t) global_data.param[PARAM_IMAGE_WIDTH] + i;

*base2 = image2 + (j+jj) * (uint16_t) global_data.param[PARAM_IMAGE_WIDTH] + i;

周遊該鄰域範圍内的每一個像素,直到找到最相似的那個塊。

記錄其位移(ii,jj),相似度dist。

這裡比較的是以(i,j)(i+ii,j+jj)為基點的左下方8*8的像素塊。

接下來,當上述相似度dist滿足一定的門檻值範圍時,我們才會進行如下操作:

if (dist < global_data.param[PARAM_BOTTOM_FLOW_VALUE_THRESHOLD])

{

meanflowx += (float) sumx;

meanflowy += (float) sumy;

compute_subpixel(image1, image2, i, j, i + sumx, j + sumy, acc, (uint16_t) global_data.param[PARAM_IMAGE_WIDTH]);

uint32_t mindist = dist; // best SAD until now

uint8_t mindir = 8; // direction 8 for no direction

for(uint8_t k = 0; k < 8; k++)

{

if (acc[k] < mindist)

{

// SAD becomes better in direction k

mindist = acc[k];

mindir = k;

}

}

這裡解釋一下compute_subpixel() 這個函數:

對于image2中的(i+ii,j+jj)這個位置的像素X,我們利用其周圍的8個像素0~7,計算出S0,S1,S2……S7,它們的位置如下

px4flow 中flow.c解讀
px4flow 中flow.c解讀
px4flow 中flow.c解讀

然後再利用S0,S1……S7,計算出T1,T3,T5,T7。S0,T1,S2,T3,S4,T5,S6,T7這8個值得位置如下

px4flow 中flow.c解讀
px4flow 中flow.c解讀

得到X周圍8個方向的子像素之後,我們再用這8個子像素分别與image1的(i,j)位置的像素做差比較,并存入acc[]。

對image1的第1列和第5列的每一個像素都做上述操作。如此便積累了這8個方向的dist和。

對于下面的for循環,目的是找到最小的acc[]。

為什麼要找這個最小值呢?

光流不是回到原位,而是抑制其偏移運動。

我已經找到最相似的塊了(ii,jj),也就是知道物體移動到哪個位置了,我想找到一個與原圖像塊最比對的方向,以便後續光流的彌補運動。

最後的一小段代碼,是直方圖統計,統計x,y方向的光流總值。

uint8_t hist_index_x = 2*sumx + (winmax-winmin+1);

if (subdirs[i] == 0 || subdirs[i] == 1 || subdirs[i] == 7) hist_index_x += 1;

if (subdirs[i] == 3 || subdirs[i] == 4 || subdirs[i] == 5) hist_index_x += -1;

uint8_t hist_index_y = 2*sumy + (winmax-winmin+1);

if (subdirs[i] == 5 || subdirs[i] == 6 || subdirs[i] == 7) hist_index_y += -1;

if (subdirs[i] == 1 || subdirs[i] == 2 || subdirs[i] == 3) hist_index_y += 1;

histx[hist_index_x]++;

histy[hist_index_y]++;

X方向

px4flow 中flow.c解讀

Y方向

px4flow 中flow.c解讀

二、求整體平均光流

所有塊的光流(位移)都找到之後,我們取平均,得到整體的運動情況,以便做補償。

這裡分兩種情況:

如果滿足如下條件,我們用直方圖均衡法來求光流的平均值,

否則,我們直接用位移累加值來求平均光流。

if (FLOAT_AS_BOOL(global_data.param[PARAM_BOTTOM_FLOW_HIST_FILTER]))

{

先看直方圖法:

首先找到直方圖的峰值:

for (j = 0; j < hist_size; j++)

{

if (histx[j] > maxvaluex)

{

maxvaluex = histx[j];

maxpositionx = j;

}

if (histy[j] > maxvaluey)

{

maxvaluey = histy[j];

maxpositiony = j;

}

}

确定直方圖峰值的一個小鄰域,并在該鄰域内取權重平均值作為平均光流。

if (FLOAT_AS_BOOL(global_data.param[PARAM_BOTTOM_FLOW_HIST_FILTER]))

{

uint16_t hist_x_min = maxpositionx;

uint16_t hist_x_max = maxpositionx;

uint16_t hist_y_min = maxpositiony;

uint16_t hist_y_max = maxpositiony;

if (maxpositionx > 1 && maxpositionx < hist_size-2)

{

hist_x_min = maxpositionx - 2;

hist_x_max = maxpositionx + 2;

}

else if (maxpositionx == 0)

{

hist_x_min = maxpositionx;

hist_x_max = maxpositionx + 2;

}

else if (maxpositionx == hist_size-1)

{

hist_x_min = maxpositionx - 2;

hist_x_max = maxpositionx;

}

else if (maxpositionx == 1)

{

hist_x_min = maxpositionx - 1;

hist_x_max = maxpositionx + 2;

}

else if (maxpositionx == hist_size-2)

{

hist_x_min = maxpositionx - 2;

hist_x_max = maxpositionx + 1;

}

if (maxpositiony > 1 && maxpositiony < hist_size-2)

{

hist_y_min = maxpositiony - 2;

hist_y_max = maxpositiony + 2;

}

else if (maxpositiony == 0)

{

hist_y_min = maxpositiony;

hist_y_max = maxpositiony + 2;

}

else if (maxpositiony == hist_size-1)

{

hist_y_min = maxpositiony - 2;

hist_y_max = maxpositiony;

}

else if (maxpositiony == 1)

{

hist_y_min = maxpositiony - 1;

hist_y_max = maxpositiony + 2;

}

else if (maxpositiony == hist_size-2)

{

hist_y_min = maxpositiony - 2;

hist_y_max = maxpositiony + 1;

}

float hist_x_value = 0.0f;

float hist_x_weight = 0.0f;

float hist_y_value = 0.0f;

float hist_y_weight = 0.0f;

for (uint8_t h = hist_x_min; h < hist_x_max+1; h++)

{

hist_x_value += (float) (h*histx[h]);

hist_x_weight += (float) histx[h];

}

for (uint8_t h = hist_y_min; h<hist_y_max+1; h++)

{

hist_y_value += (float) (h*histy[h]);

hist_y_weight += (float) histy[h];

}

histflowx = (hist_x_value/hist_x_weight - (winmax-winmin+1)) / 2.0f ;

histflowy = (hist_y_value/hist_y_weight - (winmax-winmin+1)) / 2.0f;

}

第二種情況,直接用位移累加值求平均。

else

{

uint32_t meancount_x = 0;

uint32_t meancount_y = 0;

for (uint8_t h = 0; h < meancount; h++)

{

float subdirx = 0.0f;

if (subdirs[h] == 0 || subdirs[h] == 1 || subdirs[h] == 7) subdirx = 0.5f;

if (subdirs[h] == 3 || subdirs[h] == 4 || subdirs[h] == 5) subdirx = -0.5f;

histflowx += (float)dirsx[h] + subdirx;

meancount_x++;

float subdiry = 0.0f;

if (subdirs[h] == 5 || subdirs[h] == 6 || subdirs[h] == 7) subdiry = -0.5f;

if (subdirs[h] == 1 || subdirs[h] == 2 || subdirs[h] == 3) subdiry = 0.5f;

histflowy += (float)dirsy[h] + subdiry;

meancount_y++;

}

histflowx /= meancount_x;

histflowy /= meancount_y;

}

最終輸出結果histflowx,histflowy給旋轉補償。

三、旋轉補償

為了減去由于攝像頭自身旋轉産生的光流,我們需要對運動場的旋轉部件進行旋轉補償,分别得到圖像平面x,y方向的光流分量vx,vy:

px4flow 中flow.c解讀

焦距f,和攝像頭角速度w是已知的。

px4flow 中flow.c解讀

這邊,飛行器的坐标軸和光流的坐标軸有如圖所示的關系,是以在補償運算時有如下公式:

* -y_rate gives x flow

* x_rates gives y_flow

當陀螺儀的轉動較大,大于門檻值時,我們才做補償,如果微小偏移,可以忽略,不做補償。

并且,the compensated value is clamped to the maximum measurable flow value (param BFLOW_MAX_PIX) +0.5 (sub pixel flow can add half pixel to the value)

if(fabsf(y_rate) > global_data.param[PARAM_GYRO_COMPENSATION_THRESHOLD])

{

float y_rate_pixel = y_rate * (get_time_between_images() / 1000000.0f) * focal_length_px;

float comp_x = histflowx + y_rate_pixel;

if (comp_x < (-SEARCH_SIZE - 0.5f))

*pixel_flow_x = (-SEARCH_SIZE - 0.5f);

else if (comp_x > (SEARCH_SIZE + 0.5f))

*pixel_flow_x = (SEARCH_SIZE + 0.5f);

else

*pixel_flow_x = comp_x;

}

else

{

*pixel_flow_x = histflowx;

}

if(fabsf(x_rate) > global_data.param[PARAM_GYRO_COMPENSATION_THRESHOLD])

{

float x_rate_pixel = x_rate * (get_time_between_images() / 1000000.0f) * focal_length_px;

float comp_y = histflowy - x_rate_pixel;

if (comp_y < (-SEARCH_SIZE - 0.5f))

*pixel_flow_y = (-SEARCH_SIZE - 0.5f);

else if (comp_y > (SEARCH_SIZE + 0.5f))

*pixel_flow_y = (SEARCH_SIZE + 0.5f);

else

*pixel_flow_y = comp_y;

}

else

{

*pixel_flow_y = histflowy;

}

四、與超音波結合

最後和超音波結合得到最終地面的速度:

float flow_compx = pixel_flow_x / focal_length_px / (get_time_between_images() / 1000000.0f);

float flow_compy = pixel_flow_y / focal_length_px / (get_time_between_images() / 1000000.0f);

if (distance_valid)

{

float new_velocity_x = - flow_compx * sonar_distance_filtered;

float new_velocity_y = - flow_compy * sonar_distance_filtered;

px4flow 中flow.c解讀

1.2個for循環是尋找角點

2.後2個for是在角點的鄰域尋找比對塊,鄰域是以角點為中心的九宮格,尋找方法是以角點為左上角畫8*8的塊 和 鄰域的每個像素為左上角畫8*8的塊 來比對的

3.統計方向

4.旋轉的補償

5.輸出光流。