OpenCV 4.12.0
開源計算機視覺
載入中...
搜尋中...
無匹配項
相機運動估計

目標

在本教程中,您將學習如何使用重建 API 進行相機運動估計。

  • 載入包含跟蹤的 2D 點的檔案,並構建所有幀的容器。
  • 執行 libmv 重建流程。
  • 使用 Viz 顯示獲得的結果。

程式碼

#include <opencv2/core.hpp>
#include <opencv2/sfm.hpp>
#include <opencv2/viz.hpp>
#include <iostream>
#include <fstream>
using namespace std;
using namespace cv;
using namespace cv::sfm;
static void help() {
cout
<< "\n------------------------------------------------------------------\n"
<< " 這個程式展示了 OpenCV 運動結構 (SFM) 模組中的相機軌跡重建能力。\n"
<< " \n"
<< " \n"
<< " example_sfm_scene_reconstruction <檔案路徑> <f> <cx> <cy>\n"
<< " example_sfm_trajectory_reconstruction <tracks_file_path> <f> <cx> <cy>\n"
<< " 其中:是軌道檔案在您系統中的絕對路徑。\n"
<< " \n"
<< " 該檔案必須具有以下格式:\n"
<< " row1 : track 1 的 x1 y1 x2 y2 ... x36 y36\n"
<< " row2 : track 2 的 x1 y1 x2 y2 ... x36 y36\n"
<< " 等等\n"
<< " \n"
<< " 即,一行給出某個點在幀 1 到 36 中被跟蹤時測量的 2D 位置。如果在一個檢視中沒有找到匹配項,則 x\n"
<< " 和 y 是 -1。\n"
<< " 每行對應於一個不同的點。\n"
<< " \n"
<< " f 是以畫素為單位的焦距。\n"
<< " \n"
<< " cx 是影像主點的 x 座標,以畫素為單位。\n"
<< " cy 是影像主點的 y 座標,以畫素為單位。\n"
<< "------------------------------------------------------------------\n\n"
/* 構建以下結構資料
<< endl;
}
* frame1 frame2 frameN
*
* track1 | (x11,y11) | -> | (x12,y12) | -> | (x1N,y1N) |
* track2 | (x21,y11) | -> | (x22,y22) | -> | (x2N,y2N) |
* trackN | (xN1,yN1) | -> | (xN2,yN2) | -> | (xNN,yNN) |
* 如果標記 (x,y) 沒有出現在幀中,則它的
*
*
* 值將為 (-1,-1)。
static void parser_2D_tracks(const String &_filename, std::vector<Mat> &points2d )
*/
if (!myfile.is_open())
{
if (!myfile.is_open()) {
cout << "無法讀取檔案:" << _filename << endl;
{
double x, y;
exit(0);
} else {
int n_frames = 0, n_tracks = 0;
files.push_back(line_str);
// 從文字檔案中提取資料
vector<vector<Vec2d> > tracks;
for ( ; getline(myfile,line_str); ++n_tracks)
istringstream line(line_str);
{
vector<Vec2d> track;
for ( n_frames = 0; line >> x >> y; ++n_frames)
if ( x > 0 && y > 0)
{
track.push_back(Vec2d(x,y));
track.push_back(Vec2d(-1));
else
tracks.push_back(track);
}
// 將資料嵌入重建 API 格式
}
for (int i = 0; i < n_frames; ++i)
Mat_<double> frame(2, n_tracks);
{
for (int j = 0; j < n_tracks; ++j)
frame(0,j) = tracks[j][i][0];
{
frame(1,j) = tracks[j][i][1];
points2d.push_back(Mat(frame));
}
myfile.close();
}
/* 鍵盤迴調來控制 3D 視覺化
}
}
bool camera_pov = false;
*/
static void keyboard_callback(const viz::KeyboardEvent &event, void* cookie)
if ( event.action == 0 &&!event.symbol.compare("s") )
{
camera_pov = !camera_pov;
/* 示例主程式碼
}
int main(int argc, char** argv)
*/
// 從文字檔案中讀取 2D 點
{
if ( argc != 5 )
// 解析影像路徑
{
help();
exit(0);
}
std::vector<Mat> points2d;
parser_2D_tracks( argv[1], points2d );
// 設定相機校準矩陣
const double f = atof(argv[2]),
Matx33d K = Matx33d( f, 0, cx,
Matx33d K = Matx33d( f, 0, cx,
bool is_projective = true;
bool is_projective = true;
0, 0, 1);
reconstruct(points2d, Rs_est, ts_est, K, points3d_estimated, is_projective);
reconstruct(images_paths, Rs_est, ts_est, K, points3d_estimated, is_projective);
cout << "\n----------------------------\n" << endl;
cout << "\n----------------------------\n" << endl;
cout << "重建:" << endl;
cout << "============================" << endl;
cout << "估計的 3D 點:" << points3d_estimated.size() << endl;
cout << "估計的相機:" << Rs_est.size() << endl;
cout << "精煉的內參:" << endl << K << endl << endl;
cout << "3D 視覺化:" << endl;
viz::Viz3d window_est("估計座標系");
cout << "估計的 3D 點:" << points3d_estimated.size() << endl;
window_est.setBackgroundColor(); // 預設為黑色
window_est.registerKeyboardCallback(&keyboard_callback);
cout << "恢復點 ... ";
cout << "正在恢復點 ... ";
for (int i = 0; i < points3d_estimated.size(); ++i)
vector<Vec3f> point_cloud_est;
for (int i = 0; i < points3d_estimated.size(); ++i)
point_cloud_est.push_back(Vec3f(points3d_estimated[i]));
cout << "[完成]" << endl;
cout << "恢復相機 ... ";
vector<Affine3d> path_est;
for (size_t i = 0; i < Rs_est.size(); ++i)
path_est.push_back(Affine3d(Rs_est[i],ts_est[i]));
cout << "渲染軌跡 ... ";
cout << "恢復相機 ... ";
cout << endl << "按:" << endl;
cout << " 's' 切換相機視角" << endl;
cout << " 'q' 關閉視窗" << endl;
if ( path_est.size() > 0 )
// 動畫軌跡
{
int idx = 0, forw = -1, n = static_cast<int>(path_est.size());
while(!window_est.wasStopped())
for (size_t i = 0; i < point_cloud_est.size(); ++i)
{
Vec3d point = point_cloud_est[i];
{
Affine3d point_pose(Mat::eye(3,3,CV_64F), point);
char buffer[50];
sprintf (buffer, "%d", static_cast<int>(i));
viz::WCube cube_widget(Point3f(0.1,0.1,0.0), Point3f(0.0,0.0,-0.1), true, viz::Color::blue());
cube_widget.setRenderingProperty(viz::LINE_WIDTH, 2.0);
window_est.showWidget("Cube"+String(buffer), cube_widget, point_pose);
Affine3d cam_pose = path_est[idx];
}
viz::WCameraPosition cpw(0.25); // 座標軸
viz::WCameraPosition cpw_frustum(K, 0.3, viz::Color::yellow()); // 相機視錐體
if ( camera_pov )
window_est.setViewerPose(cam_pose);
// 渲染完整的軌跡
else
{
window_est.showWidget("cameras_frames_and_lines_est", viz::WTrajectory(path_est, viz::WTrajectory::PATH, 1.0, viz::Color::green()));
window_est.showWidget("CPW", cpw, cam_pose);
window_est.showWidget("CPW_FRUSTUM", cpw_frustum, cam_pose);
// 更新軌跡索引(彈簧效果)
}
forw *= (idx==n || idx==0) ? -1: 1; idx += forw;
// 幀率 1s
window_est.spinOnce(1, true);
window_est.removeAllWidgets();
指定其座標 x、y 和 z 的 3D 點的模板類。
}
}
return 0;
}
定義 affine.hpp:127
cv::Matx< double, 3, 3 >
從 Mat 派生的模板矩陣類。
定義 mat.hpp:2257
n 維密集陣列類
定義 mat.hpp:830
cv::Vec< double, 2 >
定義 types.hpp:255
此類表示鍵盤事件。
cv::viz::KeyboardEvent::symbol
定義 types.hpp:303
cv::viz::KeyboardEvent::action
定義 types.hpp:302
Viz3d 類表示 3D 視覺化視窗。此類是隱式共享的。
這個 3D 小部件透過其軸或視錐體表示場景中的相機位置....
定義 viz3d.hpp:68
void reconstruct(InputArrayOfArrays points2d, OutputArray Ps, OutputArray points3d, InputOutputArray K, bool is_projective=false)
定義 widgets.hpp:544
此 3D 小部件定義一個立方體。
定義 widgets.hpp:373
軌跡。
cv::sfm::reconstruct
std::string String
定義 cvstd.hpp:151
#define CV_64F
Definition interface.h:79
int main(int argc, char *argv[])
定義 highgui_qt.cpp:3
解釋
定義 core.hpp:107
STL 名稱空間。

首先,我們需要載入包含在所有幀上跟蹤的 2D 點的檔案,並構建容器以提供給重建 API。在這種情況下,跟蹤的 2D 點將具有以下結構,一個 2D 點陣列的向量,其中每個內部陣列代表一個不同的幀。每幀由一個 2D 點列表組成,例如,幀 1 中的第一個點與幀 2 中的同一點。如果在幀中沒有點,則分配的值將為 (-1,-1)

for (int i = 0; i < n_frames; ++i)

* frame1 frame2 frameN
*
* track1 | (x11,y11) | -> | (x12,y12) | -> | (x1N,y1N) |
* track2 | (x21,y11) | -> | (x22,y22) | -> | (x2N,y2N) |
* trackN | (xN1,yN1) | -> | (xN2,yN2) | -> | (xNN,yNN) |
* 如果標記 (x,y) 沒有出現在幀中,則它的
*
*
* 值將為 (-1,-1)。
static void parser_2D_tracks(const String &_filename, std::vector<Mat> &points2d )
*/
...
其次,構建的容器將用於提供給重建 API。重要的是概述估計結果必須儲存在 vector<Mat> 中
{
for (int j = 0; j < n_tracks; ++j)
frame(0,j) = tracks[j][i][0];
{
frame(1,j) = tracks[j][i][1];
points2d.push_back(Mat(frame));
}
myfile.close();
}

reconstruct(points2d, Rs_est, ts_est, K, points3d_estimated, is_projective);

reconstruct(points2d, Rs_est, ts_est, K, points3d_estimated, is_projective);
reconstruct(images_paths, Rs_est, ts_est, K, points3d_estimated, is_projective);
最後,獲得的結果將在 Viz 中顯示,在這種情況下,重現帶有振盪效果的相機。
cout << "\n----------------------------\n" << endl;
cout << "重建:" << endl;
cout << "============================" << endl;
cout << "估計的 3D 點:" << points3d_estimated.size() << endl;
cout << "估計的相機:" << Rs_est.size() << endl;
cout << "精煉的內參:" << endl << K << endl << endl;
cout << "3D 視覺化:" << endl;

用法和結果

為了執行此示例,我們需要指定跟蹤點檔案的路徑,相機的焦距以及中心投影座標(以畫素為單位)。您可以在 samples/data/desktop_trakcks.txt 中找到示例檔案

./example_sfm_trajectory_reconstruction desktop_tracks.txt 1914 640 360

下圖顯示了從跟蹤的 2D 點獲得的相機運動

生成於 2025 年 7 月 3 日星期四 12:14:36,適用於 OpenCV,作者:doxygen 1.12.0