視覺十四講 第八講 光流法 特徵點追蹤

2022-06-08 05:48:10 字數 4527 閱讀 6235

特徵點估計相機運動的方法,主要是在關鍵點和描述子的計算非常耗時;而且在紋理資訊比較少的情況下,特徵點的數量會明顯減少。

解決方案:

1.保留特徵點,只計算關鍵點,不計算描述子,然後使用光流法跟蹤特徵點的運動,從而實現特徵點的匹配。

2.只計算關鍵點,不計算描述子。使用直接法計算下一時刻特徵點的位置,從而實現特徵點的匹配。

第一種方法,是把特徵點匹配換成光流法,估計相機運動時仍然採用對極幾何、pnp或icp演算法。仍然需要計算角點。

第二種方法,是通過畫素的灰度資訊,同時估計相機運動和點的投影,不要求提取到的點必須為角點,甚至可以是隨機的選點。

由於i(x,y,t) = i(x+dx,y+dy,t+dt),所以:\(\fracdx + \fracdy + \fracdt = 0\)

兩邊除dt,得:

\(\frac \frac + \frac \frac =- \frac\)

其中dx/dt為畫素在x軸上的運動速度,dy/dt為y軸上的速度,記為u,v。同時\(\frac\)為該點在x方向的梯度,另一項為y方向的梯度,記為\(i_\),\(i_\),寫成矩陣形式為:

我們計算的是u、v,所以乙個點無法計算,故假設該點附近的乙個視窗內的畫素都具有相同的運動。

考慮乙個大小為w*w的視窗,共有\(w^\)數量的畫素,有\(w^\)個方程:

這是乙個關於u,v的超定方程,傳統解法是求最小二乘解

這樣就得到畫素在影象間的運動速度u,v。由於畫素梯度僅在區域性有效,如果一次迭代不夠好,可以多迭代幾次這個方程。

影象梯度:

影象梯度一般也可以用中值差分:

dx(i,j) = [i(i+1,j) - i(i-1,j)]/2;

dy(i,j) = [i(i,j+1) - i(i,j-1)]/2;

lk光流程式:

vectorkp1;

//通過gftt來獲取角點

ptrdetector = gfttdetector::create(500, 0.01, 20); // maximum 500 keypoints

detector->detect(img1, kp1);

vectorpt1, pt2;

for (auto &kp: kp1) pt1.push_back(kp.pt);

vectorstatus;

vectorerror;

//直接呼叫lk光流函式

cv::calcopticalflowpyrlk(img1, img2, pt1, pt2, status, error);

該問題可以看成乙個優化問題:通過最小化灰度誤差來估計最優的畫素偏差。

相關**:

getpixelvalue:採用雙線性內插法,來估計乙個點的畫素:

公式為:

inline float getpixelvalue(const cv::mat &img, float x, float y) 

class opticalflowtracker

void calculateopticalflow(const range &range);

private:

const mat &img1;

const mat &img2;

const vector&kp1;

vector&kp2;

vector&success;

bool inverse = true;

bool has_initial = false;

};void opticalflowsinglelevel(

const mat &img1,

const mat &img2,

const vector&kp1,

vector&kp2,

vector&success,

bool inverse, bool has_initial)

void opticalflowtracker::calculateopticalflow(const range &range)

double cost = 0, lastcost = 0;

bool succ = true; // indicate if this point succeeded

// gauss-newton iterations

eigen::matrix2d h = eigen::matrix2d::zero(); // hessian

eigen::vector2d b = eigen::vector2d::zero(); // bias

eigen::vector2d j; // jacobian

for (int iter = 0; iter < iterations; iter++) else

cost = 0;

// compute cost and jacobian

//對視窗的所有畫素進行計算

for (int x = -half_patch_size; x < half_patch_size; x++)

for (int y = -half_patch_size; y < half_patch_size; y++) else if (iter == 0)

// compute h, b and set cost;

b += -error * j;

cost += error * error;

if (inverse == false || iter == 0)

}// compute update

eigen::vector2d update = h.ldlt().solve(b); //求解方程u,v

if (std::isnan(update[0]))

if (iter > 0 && cost > lastcost)

// 更新 dx, dy

dx += update[0];

dy += update[1];

lastcost = cost;

succ = true;

if (update.norm() < 1e-2)

}success[i] = succ;

// kp2,找到了kp2和kp1的匹配點座標

kp2[i].pt = kp.pt + point2f(dx, dy);}}

上面光流只計算了單層光流,當相機運動較快的時候,單層光流容易達到乙個區域性極小值,這時可以引入影象金字塔來進行優化。

影象金字塔就是對同乙個影象進行縮放,得到不同解析度下的影象,以原始影象作為金字塔的底層,沒向上一層,就進行一定倍率的縮放。

由粗至精的好處是,當原始影象運動較大的時候,在上幾層的影象優化值裡,畫素運動會比較小,就避免了陷入區域性極小值。

void opticalflowmultilevel(

const mat &img1,

const mat &img2,

const vector&kp1,

vector&kp2,

vector&success,

bool inverse) ;

// 建立4層金字塔,圖1,圖2,每層縮小0.5

chrono::steady_clock::time_point t1 = chrono::steady_clock::now();

vectorpyr1, pyr2; // image pyramids

for (int i = 0; i < pyramids; i++) else

}chrono::steady_clock::time_point t2 = chrono::steady_clock::now();

auto time_used = chrono::duration_cast>(t2 - t1);

cout << "build pyramid time: " << time_used.count() << endl;

// coarse-to-fine lk tracking in pyramids

vectorkp1_pyr, kp2_pyr;

//最上層kp1的特徵點處理 縮小 0.5*0.5*0.5

for (auto &kp:kp1)

for (int level = pyramids - 1; level >= 0; level--)

}for (auto &kp: kp2_pyr)

kp2.push_back(kp);

}

完整**:

視覺SLAM十四講第四講

主要目標 理解李群與李代數的概念,掌握 so 3 se 3 與對應李代數的表示方式。理解 bch 近似的意義。學會在李代數上的擾動模型。使用 sophus 對李代數進行運算。旋轉矩陣自身是帶有約束的 正交且行列式為 1 它們作為優化變數時,會引入額外的約束,使優化變得困難。通過李群 李代數間的轉換關...

《視覺SLAM十四講》第2講

目錄本講主要內容 1 視覺slam中的感測器 2 經典視覺slam框架 3 slam問題的數學表述 想象乙個在室內的移動機械人在自由地探索室內的環境,那麼定位與建圖可以直觀地理解成 1 我在什麼地方?定位 2 周圍環境是怎樣的?建圖 而要完成定位和建圖則需要各種感測器的支援。感測器一般可以分為兩類,...

視覺SLAM十四講 第十二講筆記

講完了前端後端,這一講講的是回環檢測。比起之前的內容要容易理解多了 其思路是 回環檢測的作用 消除累積誤差。累積誤差是怎麼來的呢?前端根據區域性 相鄰或相近 幀給出位姿估計,後端也是根據前端已有的結果進行優化 比如p x k xk 1 p x k x p xk xk 1 這樣,當前端每一幀都存在誤差...