2022.10.20 bionic
目錄
-
- 1 單目稠密圖重建實踐
-
- 1.1 修改CMakeLists.txt
- 1.2 實作
- 2 RGB_D稠密建圖
-
- 2.1 c_cpp_properties.json
- 2.2 launch.json
- 2.3 修改pointcloud_mapping.cpp
- 2.4 修改 CMakeLists.txt
- 2.5 編譯執行
- 3 從RGB_D稠密建圖點雲重建網格圖
-
- 3.1 修改surfel_mapping.cpp
- 3.2 修改CMakeLists.txt
- 4 八叉樹地圖
-
- 4.1 安裝octomap
- 4.2 修改octomap_mapping.cpp
- 4.3 修改Cmake
1 單目稠密圖重建實踐
本工程采用的資料集是使用REMODE的測試資料集。它提供了一架無人集采集的單目俯視圖像,共有200張,同時提供了每張圖像的真實位姿資料集自取
remode_test_data.zip
資料集下載下傳
連結: link
提取碼:lrjl
1.1 修改CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(dense_monocular)
set(CMAKE_BUILD_TYPE "Release")
set(CMAKE_CXX_STANDARD 14)
############### dependencies ######################
# Eigen
include_directories("/usr/include/eigen3")
# OpenCV
find_package(OpenCV 3.1 REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
# Sophus
find_package(Sophus REQUIRED)
include_directories(${Sophus_INCLUDE_DIRS})
set(THIRD_PARTY_LIBS
${OpenCV_LIBS}
${Sophus_LIBRARIES} fmt)
add_executable(dense_mapping dense_mapping.cpp)
target_link_libraries(dense_mapping ${THIRD_PARTY_LIBS})
1.2 實作
解決Failed to load module canberra-gtk-module錯誤
sudo apt-get install libcanberra-gtk-module
再次執行上述代碼
Average squared error = 0.297042, average error: -0.0441052
*** loop 21 ***
Average squared error = 0.296148, average error: -0.0433047
*** loop 22 ***
Average squared error = 0.295283, average error: -0.0425199
*** loop 23 ***
Average squared error = 0.294497, average error: -0.0418232
*** loop 24 ***
Average squared error = 0.293742, average error: -0.041222
*** loop 25 ***
Average squared error = 0.293078, average error: -0.0406909
*** loop 26 ***
Average squared error = 0.292565, average error: -0.0402602
*** loop 27 ***
Average squared error = 0.292156, average error: -0.039893
*** loop 28 ***
Average squared error = 0.29175, average error: -0.0395667
*** loop 29 ***
Average squared error = 0.291299, average error: -0.039213
*** loop 30 ***
Average squared error = 0.290664, average error: -0.0387603
*** loop 31 ***
Average squared error = 0.290207, average error: -0.0384232
*** loop 32 ***
Average squared error = 0.289564, average error: -0.0379961
*** loop 33 ***
Average squared error = 0.289188, average error: -0.0377423
*** loop 34 ***
Average squared error = 0.288831, average error: -0.0373817
*** loop 35 ***
Average squared error = 0.28817, average error: -0.0369678
*** loop 36 ***
Average squared error = 0.28776, average error: -0.0366581
*** loop 37 ***
Average squared error = 0.287351, average error: -0.0363875
*** loop 38 ***
Average squared error = 0.286935, average error: -0.0361054
*** loop 39 ***
Average squared error = 0.286412, average error: -0.0357438
*** loop 40 ***
2 RGB_D稠密建圖
這裡的話,我是用了vscode來做
如果不知道怎麼用vscode可以補充下這方面操作
2.1 c_cpp_properties.json
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"compilerPath": "/usr/bin/clang",
"cStandard": "c17",
"cppStandard": "c++14",
"intelliSenseMode": "linux-clang-x64"
}
],
"version": 4
}
2.2 launch.json
{
// 使用 IntelliSense 了解相關屬性。
// 懸停以檢視現有屬性的描述。
// 欲了解更多資訊,請通路: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) 啟動",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/debug_test",
"args": ["debug_test"],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "為 gdb 啟用整齊列印",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "将反彙編風格設定為 Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
}
]
}
把cpp檔案全部放進src目錄下
2.3 修改pointcloud_mapping.cpp
#include <iostream>
#include <fstream>
using namespace std;
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <Eigen/Geometry>
#include <boost/format.hpp> // for formating strings
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/filters/statistical_outlier_removal.h>
int main(int argc, char **argv) {
vector<cv::Mat> colorImgs, depthImgs; // 彩色圖和深度圖
vector<Eigen::Isometry3d> poses; // 相機位姿
ifstream fin("../data/pose.txt");
if (!fin) {
cerr << "cannot find pose file" << endl;
return 1;
}
for (int i = 0; i < 5; i++) {
boost::format fmt("../data/%s/%d.%s"); //圖像檔案格式
colorImgs.push_back(cv::imread((fmt % "color" % (i + 1) % "png").str()));
depthImgs.push_back(cv::imread((fmt % "depth" % (i + 1) % "png").str(), -1)); // 使用-1讀取原始圖像
double data[7] = {0};
for (int i = 0; i < 7; i++) {
fin >> data[i];
}
Eigen::Quaterniond q(data[6], data[3], data[4], data[5]);
Eigen::Isometry3d T(q);
T.pretranslate(Eigen::Vector3d(data[0], data[1], data[2]));
poses.push_back(T);
}
// 計算點雲并拼接
// 相機内參
double cx = 319.5;
double cy = 239.5;
double fx = 481.2;
double fy = -480.0;
double depthScale = 5000.0;
cout << "正在将圖像轉換為點雲..." << endl;
// 定義點雲使用的格式:這裡用的是XYZRGB
typedef pcl::PointXYZRGB PointT;
typedef pcl::PointCloud<PointT> PointCloud;
// 建立一個點雲
PointCloud::Ptr pointCloud(new PointCloud);
for (int i = 0; i < 5; i++) {
PointCloud::Ptr current(new PointCloud);
cout << "轉換圖像中: " << i + 1 << endl;
cv::Mat color = colorImgs[i];
cv::Mat depth = depthImgs[i];
Eigen::Isometry3d T = poses[i];
for (int v = 0; v < color.rows; v++)
for (int u = 0; u < color.cols; u++) {
unsigned int d = depth.ptr<unsigned short>(v)[u]; // 深度值
if (d == 0) continue; // 為0表示沒有測量到
Eigen::Vector3d point;
point[2] = double(d) / depthScale;
point[0] = (u - cx) * point[2] / fx;
point[1] = (v - cy) * point[2] / fy;
Eigen::Vector3d pointWorld = T * point;
PointT p;
p.x = pointWorld[0];
p.y = pointWorld[1];
p.z = pointWorld[2];
p.b = color.data[v * color.step + u * color.channels()];
p.g = color.data[v * color.step + u * color.channels() + 1];
p.r = color.data[v * color.step + u * color.channels() + 2];
current->points.push_back(p);
}
// depth filter and statistical removal
PointCloud::Ptr tmp(new PointCloud);
pcl::StatisticalOutlierRemoval<PointT> statistical_filter;
statistical_filter.setMeanK(50);
statistical_filter.setStddevMulThresh(1.0);
statistical_filter.setInputCloud(current);
statistical_filter.filter(*tmp);
(*pointCloud) += *tmp;
}
pointCloud->is_dense = false;
cout << "點雲共有" << pointCloud->size() << "個點." << endl;
// voxel filter
pcl::VoxelGrid<PointT> voxel_filter;
double resolution = 0.03;
voxel_filter.setLeafSize(resolution, resolution, resolution); // resolution
PointCloud::Ptr tmp(new PointCloud);
voxel_filter.setInputCloud(pointCloud);
voxel_filter.filter(*tmp);
tmp->swap(*pointCloud);
cout << "濾波之後,點雲共有" << pointCloud->size() << "個點." << endl;
pcl::io::savePCDFileBinary("map.pcd", *pointCloud);
return 0;
}
2.4 修改 CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(dense_RGBD)
#set(CMAKE_BUILD_TYPE Release) 注意這裡需要注釋掉,不然沒法在vscode中運作
set(CMAKE_CXX_STANDARD 14)
# opencv
find_package(OpenCV 3.1 REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
# eigen
include_directories("/usr/include/eigen3/")
# pcl
find_package(PCL REQUIRED)
include_directories(${PCL_INCLUDE_DIRS})
add_definitions(${PCL_DEFINITIONS})
# octomap
#find_package(octomap REQUIRED)
#include_directories(${OCTOMAP_INCLUDE_DIRS})
add_executable(pointcloud_mapping src/pointcloud_mapping.cpp)
target_link_libraries(pointcloud_mapping ${OpenCV_LIBS} ${PCL_LIBRARIES})
2.5 編譯執行
然後點最下面一行小齒輪執行編譯,三角符号執行
這裡在build會生成一個map.pcd的檔案,使用pcl_viewer來觀察它
pcl_viewer /home/zhangyuanbo/Slam14_2/ch12/dense_RGBD/build/map.pcd
3 從RGB_D稠密建圖點雲重建網格圖
3.1 修改surfel_mapping.cpp
//
// Created by gaoxiang on 19-4-25.
//
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/kdtree/kdtree_flann.h>
#include <pcl/surface/surfel_smoothing.h>
#include <pcl/surface/mls.h>
#include <pcl/surface/gp3.h>
#include <pcl/surface/impl/mls.hpp>
// typedefs
typedef pcl::PointXYZRGB PointT;
typedef pcl::PointCloud<PointT> PointCloud;
typedef pcl::PointCloud<PointT>::Ptr PointCloudPtr;
typedef pcl::PointXYZRGBNormal SurfelT;
typedef pcl::PointCloud<SurfelT> SurfelCloud;
typedef pcl::PointCloud<SurfelT>::Ptr SurfelCloudPtr;
SurfelCloudPtr reconstructSurface(
const PointCloudPtr &input, float radius, int polynomial_order) {
pcl::MovingLeastSquares<PointT, SurfelT> mls;
pcl::search::KdTree<PointT>::Ptr tree(new pcl::search::KdTree<PointT>);
mls.setSearchMethod(tree);
mls.setSearchRadius(radius);
mls.setComputeNormals(true);
mls.setSqrGaussParam(radius * radius);
mls.setPolynomialFit(polynomial_order > 1);
mls.setPolynomialOrder(polynomial_order);
mls.setInputCloud(input);
SurfelCloudPtr output(new SurfelCloud);
mls.process(*output);
return (output);
}
pcl::PolygonMeshPtr triangulateMesh(const SurfelCloudPtr &surfels) {
// Create search tree*
pcl::search::KdTree<SurfelT>::Ptr tree(new pcl::search::KdTree<SurfelT>);
tree->setInputCloud(surfels);
// Initialize objects
pcl::GreedyProjectionTriangulation<SurfelT> gp3;
pcl::PolygonMeshPtr triangles(new pcl::PolygonMesh);
// Set the maximum distance between connected points (maximum edge length)
gp3.setSearchRadius(0.05);
// Set typical values for the parameters
gp3.setMu(2.5);
gp3.setMaximumNearestNeighbors(100);
gp3.setMaximumSurfaceAngle(M_PI / 4); // 45 degrees
gp3.setMinimumAngle(M_PI / 18); // 10 degrees
gp3.setMaximumAngle(2 * M_PI / 3); // 120 degrees
gp3.setNormalConsistency(true);
// Get result
gp3.setInputCloud(surfels);
gp3.setSearchMethod(tree);
gp3.reconstruct(*triangles);
return triangles;
}
int main(int argc, char **argv) {
// Load the points
PointCloudPtr cloud(new PointCloud);
if (argc == 0 || pcl::io::loadPCDFile(argv[1], *cloud)) {
cout << "failed to load point cloud!";
return 1;
}
cout << "point cloud loaded, points: " << cloud->points.size() << endl;
// Compute surface elements
cout << "computing normals ... " << endl;
double mls_radius = 0.05, polynomial_order = 2;
auto surfels = reconstructSurface(cloud, mls_radius, polynomial_order);
// Compute a greedy surface triangulation
cout << "computing mesh ... " << endl;
pcl::PolygonMeshPtr mesh = triangulateMesh(surfels);
cout << "display mesh ... " << endl;
pcl::visualization::PCLVisualizer vis;
vis.addPolylineFromPolygonMesh(*mesh, "mesh frame");
vis.addPolygonMesh(*mesh, "mesh");
vis.resetCamera();
vis.spin();
}
3.2 修改CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
#set(CMAKE_BUILD_TYPE Release)
set(CMAKE_CXX_FLAGS "-std=c++11 -O2")
# opencv
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
# eigen
include_directories("/usr/include/eigen3/")
# pcl
find_package(PCL REQUIRED)
include_directories(${PCL_INCLUDE_DIRS})
add_definitions(${PCL_DEFINITIONS})
add_executable(surfel_mapping src/surfel_mapping.cpp)
target_link_libraries(surfel_mapping ${OpenCV_LIBS} ${PCL_LIBRARIES})
編譯後這裡直接在終端 build内
./surfel_mapping map.pcd
4 八叉樹地圖
4.1 安裝octomap
sudo apt-get install liboctomap-dev octovis
4.2 修改octomap_mapping.cpp
ifstream fin("../data/pose.txt");
boost::format fmt("../data/%s/%d.%s"); //圖像檔案格式
#include <iostream>
#include <fstream>
using namespace std;
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <octomap/octomap.h> // for octomap
#include <eigen3/Eigen/Geometry>
#include <boost/format.hpp> // for formating strings
int main(int argc, char **argv) {
vector<cv::Mat> colorImgs, depthImgs; // 彩色圖和深度圖
vector<Eigen::Isometry3d> poses; // 相機位姿
ifstream fin("../data/pose.txt");
if (!fin) {
cerr << "cannot find pose file" << endl;
return 1;
}
for (int i = 0; i < 5; i++) {
boost::format fmt("../data/%s/%d.%s");
colorImgs.push_back(cv::imread((fmt % "color" % (i + 1) % "png").str()));
depthImgs.push_back(cv::imread((fmt % "depth" % (i + 1) % "png").str(), -1)); // 使用-1讀取原始圖像
double data[7] = {0};
for (int i = 0; i < 7; i++) {
fin >> data[i];
}
Eigen::Quaterniond q(data[6], data[3], data[4], data[5]);
Eigen::Isometry3d T(q);
T.pretranslate(Eigen::Vector3d(data[0], data[1], data[2]));
poses.push_back(T);
}
// 計算點雲并拼接
// 相機内參
double cx = 319.5;
double cy = 239.5;
double fx = 481.2;
double fy = -480.0;
double depthScale = 5000.0;
cout << "正在将圖像轉換為 Octomap ..." << endl;
// octomap tree
octomap::OcTree tree(0.01); // 參數為分辨率
for (int i = 0; i < 5; i++) {
cout << "轉換圖像中: " << i + 1 << endl;
cv::Mat color = colorImgs[i];
cv::Mat depth = depthImgs[i];
Eigen::Isometry3d T = poses[i];
octomap::Pointcloud cloud; // the point cloud in octomap
for (int v = 0; v < color.rows; v++)
for (int u = 0; u < color.cols; u++) {
unsigned int d = depth.ptr<unsigned short>(v)[u]; // 深度值
if (d == 0) continue; // 為0表示沒有測量到
Eigen::Vector3d point;
point[2] = double(d) / depthScale;
point[0] = (u - cx) * point[2] / fx;
point[1] = (v - cy) * point[2] / fy;
Eigen::Vector3d pointWorld = T * point;
// 将世界坐标系的點放入點雲
cloud.push_back(pointWorld[0], pointWorld[1], pointWorld[2]);
}
// 将點雲存入八叉樹地圖,給定原點,這樣可以計算投射線
tree.insertPointCloud(cloud, octomap::point3d(T(0, 3), T(1, 3), T(2, 3)));
}
// 更新中間節點的占據資訊并寫入磁盤
tree.updateInnerOccupancy();
cout << "saving octomap ... " << endl;
tree.writeBinary("octomap.bt");
return 0;
}
4.3 修改Cmake
cmake_minimum_required(VERSION 2.8)
project(octomap_mapping)
#set(CMAKE_BUILD_TYPE Release)
set(CMAKE_CXX_STANDARD 14)
# opencv
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
# eigen
include_directories("/usr/include/eigen3/")
# pcl
find_package(PCL REQUIRED)
include_directories(${PCL_INCLUDE_DIRS})
add_definitions(${PCL_DEFINITIONS})
# octomap
find_package(octomap REQUIRED)
include_directories(${OCTOMAP_INCLUDE_DIRS})
add_executable(octomap_mapping src/octomap_mapping.cpp)
target_link_libraries(octomap_mapping ${OpenCV_LIBS} ${PCL_LIBRARIES} ${OCTOMAP_LIBRARIES})
執行後會在工程主路徑下生成一個octomap.bt檔案
然後可以使用octovis打開
可視化界面簡單,按下1鍵,可根據高度資訊着色
在右側有八叉樹地深度限制條,這裡可以調節地圖的分辨率
由于我們構造時使用的預設深度是 16 層,是以這裡顯示 16 層的話即最高分辨率,也就是每個小塊的邊長為 0.05米。
當我們将深度減少一層時,八叉樹的葉子節點往上提了一層,每個小塊的邊長就增加兩倍,變成 0.1 米。可以看到,我們能夠很容易地調節地圖分辨率以适應不同的場合。
Octomap還有一些可以探索的地方,例如,我們可以友善地查詢任意點的占據機率,以此設計在地圖中進行導航的方法。