SLAM实战项目(1) — ORB-SLAM2稠密地图重建

2023-11-04

目录

1 整体思路

2 功能实现

3 结果运行

(1) TUM数据集下载

(2) associate.py用于RGB和Depth匹配

(3) 运行数据集

4 CMakeLists.txt文件修改

5 完整PointCloudMapping.h和PointCloudMapping.cc

6 报错分析

7 思考扩展


文章参考部分开源代码和报错文章

1 整体思路

  • 利用RGB图和Depth图创建稠密点云,在深度值有效的条件下生成点云。
  • 利用跟踪线程的关键帧,利用关键帧生成的点云变换到世界坐标系保存为全局点云地图。
  • 闭环线程中全局BA后用更新的位姿调整全局点云地图。
  • 可视化线程显示全局点云地图 。
  • 主线程中进行全局点云地图的关闭和保存。

2 功能实现

(1) 在PointCloudMapping.cc中创建构造函数,在构造函数中,定义存储稠密点云的mpGlobalCloud,设置滤波参数,创建可视化线程mptViewerThread

    PointCloudMapping::PointCloudMapping()
    {
        std::cout << "Point cloud mapping has structed. " << std::endl;
        mpGlobalCloud = boost::make_shared< PointCloud >( );

        //体素滤波
        //voxel.setLeafSize(resolution, resolution, resolution);

        //RadiusOutlierRemoval滤波器     
        voxel.setMeanK (60);        //设置在进行统计时考虑查询点临近点数
        voxel.setStddevMulThresh (0.6);    

        mptViewerThread = make_shared<thread>(bind(&PointCloudMapping::Viewer, this));
    }

 在PointCloudMapping.h中进行声明

typedef pcl::PointXYZRGBA PointT;
typedef pcl::PointCloud<PointT> PointCloud;

//构造函数
PointCloudMapping();
//创建RadiusOutlierRemoval滤波器对象  
pcl::StatisticalOutlierRemoval<PointT> voxel;
//声明稠密点云
PointCloud::Ptr mpGlobalCloud;

(2)  在PointCloudMapping.cc中创建InsertKeyFrame函数


    void PointCloudMapping::InsertKeyFrame(KeyFrame* kf, cv::Mat &color_img, cv::Mat &depth_img)
    {
        cout << "recieve a keyframe, id = " << kf->mnId << endl;
        unique_lock<mutex> lck(mmKeyFrameMutex);
        mvKeyFrames.push_back(kf);
        GeneratePointCloud(kf, color_img, depth_img);
    }

 在PointCloudMapping.h中进行声明

//对mutex进行声明
mutex mmKeyFrameMutex;
//声明mvKeyFrames用于存储关键帧
vector<KeyFrame*> mvKeyFrames;

思考:在什么地方mpPointCloudMapping->InsertKeyFrame?

在Tracking中插入mpPointCloudMapping->InsertKeyFrame

void Tracking::CreateNewKeyFrame()
{
    ...
    mpLocalMapper->InsertKeyFrame(pKF);

    // 点云地图插入关键帧
    mpPointCloudMapping->InsertKeyFrame(pKF, mImRGB, mImDepth); 
    
    mpLocalMapper->SetNotStop(false);
    mnLastKeyFrameId = mCurrentFrame.mnId;
    mpLastKeyFrame = pKF;
}

思考:如何见PointCloudMapping传入到Tracking和LoopClosing?

在Tracking.cc和LoopClosing.cc构造函数中传入

Tracking::Tracking(System *pSys, ORBVocabulary* pVoc, FrameDrawer *pFrameDrawer, MapDrawer *pMapDrawer, Map *pMap,shared_ptr<PointCloudMapping> pPointCloud,
    KeyFrameDatabase* pKFDB, const string &strSettingPath, const int sensor): mState(NO_IMAGES_YET), mSensor(sensor), mbOnlyTracking(false), mbVO(false), mpORBVocabulary(pVoc),mpPointCloudMapping(pPointCloud),
    mpKeyFrameDB(pKFDB), mpInitializer(static_cast<Initializer*>(NULL)), mpSystem(pSys), mpViewer(NULL), mpFrameDrawer(pFrameDrawer), mpMapDrawer(pMapDrawer), mpMap(pMap), mnLastRelocFrameId(0)
{}

在Tracking.h和LoopClosing.h中声明

shared_ptr<PointCloudMapping> mpPointCloudMapping;

思考:在什么地方传入RGB和Depth?

Tracking线程中有传入imRGB和imD,直接利用RGB和Depth比较方便

cv::Mat Tracking::GrabImageRGBD(const cv::Mat &imRGB,const cv::Mat &imD, const double &timestamp)
{
    mImGray = imRGB;
    mImRGB = imRGB; 
    mImDepth = imD;
    ...
}

(3)  在PointCloudMapping.cc中创建:GeneratePointCloud函数 

    void PointCloudMapping::GeneratePointCloud(KeyFrame* kf, cv::Mat &color_img, cv::Mat &depth_img)
    {
        // cv::imshow("color img", color_img);
        // cv::imshow("depth img", depth_img);
        PointCloud::Ptr tmp (new PointCloud());
        for ( int m=0; m<depth_img.rows; m+=3 )
        {
            for ( int n=0; n<depth_img.cols; n+=3 )
            {
                float d = depth_img.ptr<float>(m)[n];
                if (d < 0.01 || d>5)
                    continue;
                PointT p;
                p.z = d;
                p.x = ( n - kf->cx) * p.z / kf->fx;
                p.y = ( m - kf->cy) * p.z / kf->fy;
                
                p.b = color_img.ptr<uchar>(m)[n*3];
                p.g = color_img.ptr<uchar>(m)[n*3+1];
                p.r = color_img.ptr<uchar>(m)[n*3+2];

                // cout << p.x << " " << p.y << " " << p.z << endl;
                    
                tmp->points.push_back(p);
            }
        }
        
        cout << "The keyframe has point clouds: " << tmp->points.size() << endl;
        kf->mptrPointCloud = tmp;
    }

 (4)  在PointCloudMapping.cc中创建UpdateCloud函数 

    void PointCloudMapping::UpdateCloud()
    {
        unique_lock<mutex> lck(mmCloudeUpdateMutex);
        mbLoopBusy = true;
        cout << endl << endl << "start loop map point." << endl << endl;
        PointCloud::Ptr tmp(new PointCloud);

        for(int i = 0; i < mvKeyFrames.size(); i++)
        {
            if(!mvKeyFrames[i]->isBad())
            {
                PointCloud::Ptr cloud(new PointCloud);
                pcl::transformPointCloud( *(mvKeyFrames[i]->mptrPointCloud), *cloud, Converter::toMatrix4d(mvKeyFrames[i]->GetPoseInverse()));
                *tmp += *cloud;
            }
        }

        cout << "finsh loop map" << endl;
        //voxel.setInputCloud(tmp);
        //voxel.filter(*mpGlobalCloud);
        mbLoopBusy = false;
    }

  在PointCloudMapping.h中进行声明

//全局BA后更新关键帧的位姿来调整地图点
void UpdateCloud();   
//对mutex和bool进行声明
mutex mmCloudeUpdateMutex;
mutex mmShutDownMutex;
bool mbLoopBusy = false;

思考:在什么地方UpdateCloud()?

在全局BA后更新点云,全局BA后的点云经过优化,位姿最优

void LoopClosing::RunGlobalBundleAdjustment(unsigned long nLoopKF)
{
    ...
    mpMap->InformNewBigChange();
    mpLocalMapper->Release();
    //在全局BA后更新点云
    mpPointCloudMapping->UpdateCloud();
}

(5)  在PointCloudMapping.cc中创建Viewer函数 

   void PointCloudMapping::Viewer()
    {
        pcl::visualization::CloudViewer viewer("Dense map viewer");
        while(1)
        {
            size_t N = 0;
            {
                unique_lock<mutex> lck(mmKeyFrameMutex);
                if(mbLoopBusy)
                {
                    continue;
                }
                {
                    unique_lock<mutex> lck_shut_down(mmShutDownMutex);
                    if(mbShutDownFlag)
                    {
                        cout << "Viewer has break " << endl;
                        break;
                    }
                }

                N = mvKeyFrames.size();
                if(N == mnLastKeyFrameId)
                {
                    continue;
                }
                else
                {
                    unique_lock<mutex> lck_(mmCloudeUpdateMutex);
                    for(size_t i = mnLastKeyFrameId; i < N; i++)
                    {
                        PointCloud::Ptr p (new PointCloud);
                        pcl::transformPointCloud( *(mvKeyFrames[i]->mptrPointCloud), *p, Converter::toMatrix4d(mvKeyFrames[i]->GetPoseInverse()));
                        //cout<<"处理好第i个点云"<<i<<endl;
                        *mpGlobalCloud += *p;
                    }
                    PointCloud::Ptr tmp(new PointCloud);
                    voxel.setInputCloud(mpGlobalCloud);
                    voxel.filter(*tmp);
                    mpGlobalCloud->swap(*tmp);

                    mnLastKeyFrameId = N;
                    cout << "Total has point clouds: " << mpGlobalCloud->points.size() << endl;
                }
            }
            viewer.showCloud(mpGlobalCloud);
        }
    }

在PointCloudMapping.h中进行声明

//可视化
void Viewer();

//声明mutex和其他变量
mutex mmShutDownMutex;
bool mbShutDownFlag = false;
size_t mnLastKeyFrameId = 0;
mutex mmCloudeUpdateMutex;

(6)  在PointCloudMapping.cc中创建SaveCloud函数 

    void PointCloudMapping::SaveCloud()
    {
        pcl::io::savePCDFile("result.pcd",*mpGlobalCloud);
        cout << "global cloud save finished !" << endl;
    }

思考:在什么地方SaveCloud?

在rgbd_tum.cc文件是整个系统的主函数,在主函数运行结束时SaveCloud

int main(int argc, char **argv)
{
    //保存点云
    SLAM.save();
    SLAM.Shutdown(); 

    // Stop all threads
    return 0;
}

 (7)  在PointCloudMapping.cc中创建InsertKeyFrame函数 


    void PointCloudMapping::ShutDown()
    {
        {
            unique_lock<mutex> lck(mmShutDownMutex);
            mbShutDownFlag = true;
        }
        mptViewerThread->join();
        cout << "Point cloud mapping has shut down! " << endl;
    }

思考:在什么地方ShutDown?

void System::Shutdown()
{
    mpLocalMapper->RequestFinish();
    mpLoopCloser->RequestFinish();
    mpPointCloudMapping->ShutDown();
    ...
}

3 结果运行

(1) TUM数据集下载

TUM数据集下载地址:Computer Vision Group - Dataset Download

(2) associate.py用于RGB和Depth匹配

#!/usr/bin/python
# Software License Agreement (BSD License)
#
# Copyright (c) 2013, Juergen Sturm, TUM
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
#  * Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above
#    copyright notice, this list of conditions and the following
#    disclaimer in the documentation and/or other materials provided
#    with the distribution.
#  * Neither the name of TUM nor the names of its
#    contributors may be used to endorse or promote products derived
#    from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# Requirements: 
# sudo apt-get install python-argparse

"""
The Kinect provides the color and depth images in an un-synchronized way. This means that the set of time stamps from the color images do not intersect with those of the depth images. Therefore, we need some way of associating color images to depth images.

For this purpose, you can use the ''associate.py'' script. It reads the time stamps from the rgb.txt file and the depth.txt file, and joins them by finding the best matches.
"""

import argparse
import sys
import os
import numpy


def read_file_list(filename):
    """
    Reads a trajectory from a text file. 
    
    File format:
    The file format is "stamp d1 d2 d3 ...", where stamp denotes the time stamp (to be matched)
    and "d1 d2 d3.." is arbitary data (e.g., a 3D position and 3D orientation) associated to this timestamp. 
    
    Input:
    filename -- File name
    
    Output:
    dict -- dictionary of (stamp,data) tuples
    
    """
    file = open(filename)
    data = file.read()
    lines = data.replace(","," ").replace("\t"," ").split("\n") 
    list = [[v.strip() for v in line.split(" ") if v.strip()!=""] for line in lines if len(line)>0 and line[0]!="#"]
    list = [(float(l[0]),l[1:]) for l in list if len(l)>1]
    return dict(list)

def associate(first_list, second_list,offset,max_difference):
    """
    Associate two dictionaries of (stamp,data). As the time stamps never match exactly, we aim 
    to find the closest match for every input tuple.
    
    Input:
    first_list -- first dictionary of (stamp,data) tuples
    second_list -- second dictionary of (stamp,data) tuples
    offset -- time offset between both dictionaries (e.g., to model the delay between the sensors)
    max_difference -- search radius for candidate generation

    Output:
    matches -- list of matched tuples ((stamp1,data1),(stamp2,data2))
    
    """
    //python2
    first_keys = first_list.keys()
    second_keys = second_list.keys()
    
    //python3
    //first_keys = list(first_list.keys())
    //second_keys = list(second_list.keys())
    potential_matches = [(abs(a - (b + offset)), a, b) 
                         for a in first_keys 
                         for b in second_keys 
                         if abs(a - (b + offset)) < max_difference]
    potential_matches.sort()
    matches = []
    for diff, a, b in potential_matches:
        if a in first_keys and b in second_keys:
            first_keys.remove(a)
            second_keys.remove(b)
            matches.append((a, b))
    
    matches.sort()
    return matches

if __name__ == '__main__':
    
    # parse command line
    parser = argparse.ArgumentParser(description='''
    This script takes two data files with timestamps and associates them   
    ''')
    parser.add_argument('first_file', help='first text file (format: timestamp data)')
    parser.add_argument('second_file', help='second text file (format: timestamp data)')
    parser.add_argument('--first_only', help='only output associated lines from first file', action='store_true')
    parser.add_argument('--offset', help='time offset added to the timestamps of the second file (default: 0.0)',default=0.0)
    parser.add_argument('--max_difference', help='maximally allowed time difference for matching entries (default: 0.02)',default=0.02)
    args = parser.parse_args()

    first_list = read_file_list(args.first_file)
    second_list = read_file_list(args.second_file)

    matches = associate(first_list, second_list,float(args.offset),float(args.max_difference))    

    if args.first_only:
        for a,b in matches:
            print("%f %s"%(a," ".join(first_list[a])))
    else:
        for a,b in matches:
            print("%f %s %f %s"%(a," ".join(first_list[a]),b-float(args.offset)," ".join(second_list[b])))
            
        

由于Python2和python3语法的差别,需要将associate.py中第86行87行的

first_keys = first_list.keys()
second_keys = second_list.keys()

改为

first_keys = list(first_list.keys())
second_keys = list(second_list.keys())

(3) 运行数据集

rgbd_dataset_freiburg3_long_office_household替换为自己的数据集名称,rgbd_dataset_freiburg3_long_office_household放在orb-slam2下,放在其他文件及路径要对应更改。

./Examples/RGB-D/rgbd_tum Vocabulary/ORBvoc.txt ./Examples/RGB-D/TUM1.yaml rgbd_dataset_freiburg3_long_office_household rgbd_dataset_freiburg3_long_office_household/associations.txt

4 CMakeLists.txt文件修改

cmake_minimum_required(VERSION 2.8)	#设定cmake最小版本号
project(ORB_SLAM2)			#指定项目工程
set(CMAKE_CXX_STANDARD 14)		#C++版本可能会报错

#编译的类型(debug;release)
IF(NOT CMAKE_BUILD_TYPE)
  SET(CMAKE_BUILD_TYPE Release)
ENDIF()

#输出消息:"Build type: Release"(打印调试信息)
MESSAGE("Build type: " ${CMAKE_BUILD_TYPE})	

#cmake_c_flags用来设置编译选项 如 -g -wall(不展示警告);-march=native,GCC会自动检测你的CPU支持的指令集
#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}  -Wall  -O3 -march=native ")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall   -O3 -march=native")

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}  -Wall  -O3 ")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall   -O3 ")

#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}  -Wall  -O3 ")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall   -O3 ")

#include:从文件或模块加载并运行CMake代码 ;CheckCXXCompilerFlag: 检查CXX编译器是否支持给定标志
include(CheckCXXCompilerFlag)		

# Check C++11 or C++0x support
#以下代码都用于自动判断系统编译器是否支持c++11标准;	
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
if(COMPILER_SUPPORTS_CXX11)
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
   add_definitions(-DCOMPILEDWITHC11)
   message(STATUS "Using flag -std=c++11.")
elseif(COMPILER_SUPPORTS_CXX0X)
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
   add_definitions(-DCOMPILEDWITHC0X)
   message(STATUS "Using flag -std=c++0x.")
else()
   message(FATAL_ERROR "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
endif()


#list(APPEND <list><element> [<element> ...])  添加新element到list中
LIST(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules)

#设置OpenCV的目录
find_package(OpenCV 3.0 QUIET)
if(NOT OpenCV_FOUND)
   find_package(OpenCV 2.4.3 QUIET)
   if(NOT OpenCV_FOUND)
      message(FATAL_ERROR "OpenCV > 2.4.3 not found.")
   endif()
endif()


#找到各种头文件以及源码
find_package(Eigen3 REQUIRED NO_MODULE)
find_package(Pangolin REQUIRED)
find_package(PCL 1.10 REQUIRED)

include_directories(
${PROJECT_SOURCE_DIR}
${PROJECT_SOURCE_DIR}/include
${EIGEN3_INCLUDE_DIR}
${Pangolin_INCLUDE_DIRS}
${PCL_INCLUDE_DIRS}
)

#设置变量到工程lib下,构建时将所有LIBRARY目标放置的位置
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)

#建立共享库
add_library(${PROJECT_NAME} SHARED
src/System.cc
src/Tracking.cc
src/LocalMapping.cc
src/LoopClosing.cc
src/ORBextractor.cc
src/ORBmatcher.cc
src/FrameDrawer.cc
src/Converter.cc
src/MapPoint.cc
src/KeyFrame.cc
src/Map.cc
src/MapDrawer.cc
src/Optimizer.cc
src/PnPsolver.cc
src/Frame.cc
src/KeyFrameDatabase.cc
src/Sim3Solver.cc
src/Initializer.cc
src/Viewer.cc
src/PointCloudMapping.cc
)


#该指令的作用为将目标文件与库文件进行链接
target_link_libraries(${PROJECT_NAME}
${OpenCV_LIBS}
${EIGEN3_LIBS}
${Pangolin_LIBRARIES}
${PCL_LIBRARIES} 
${PROJECT_SOURCE_DIR}/Thirdparty/DBoW2/lib/libDBoW2.so
${PROJECT_SOURCE_DIR}/Thirdparty/g2o/lib/libg2o.so
)

# Build examples

#指定可执行文件的输出位置,CMAKE_RUNTIME_OUTPUT_DIRECTORY: 构建RUNTIME目标文件的输出目
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/Examples/RGB-D)

#使用指定的源文件将可执行文件添加到项目中
add_executable(rgbd_tum
Examples/RGB-D/rgbd_tum.cc)
target_link_libraries(rgbd_tum ${PROJECT_NAME})

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/Examples/Stereo)

add_executable(stereo_kitti
Examples/Stereo/stereo_kitti.cc)
target_link_libraries(stereo_kitti ${PROJECT_NAME})

add_executable(stereo_euroc
Examples/Stereo/stereo_euroc.cc)
target_link_libraries(stereo_euroc ${PROJECT_NAME})


set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/Examples/Monocular)

add_executable(mono_tum
Examples/Monocular/mono_tum.cc)
target_link_libraries(mono_tum ${PROJECT_NAME})

add_executable(mono_kitti
Examples/Monocular/mono_kitti.cc)
target_link_libraries(mono_kitti ${PROJECT_NAME})

add_executable(mono_euroc
Examples/Monocular/mono_euroc.cc)
target_link_libraries(mono_euroc ${PROJECT_NAME})

5 完整PointCloudMapping.h和PointCloudMapping.cc

#ifndef POINTCLOUDMAPPING_H
#define POINTCLOUDMAPPING_H

#include "System.h"
#include <pcl/visualization/cloud_viewer.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/filters/statistical_outlier_removal.h>
#include <pcl/common/transforms.h>
#include <pcl/io/pcd_io.h>
#include <condition_variable>

namespace ORB_SLAM2
{
class PointCloudMapping
{
public:
    //构造函数
    PointCloudMapping();
    //将Tracking线程得到的关键帧传入到稠密建图中,并开始稠密建图
    void InsertKeyFrame(KeyFrame* kf, cv::Mat& color_img, cv::Mat& depth_img);
    //通过RGB图、深度图构建该关键帧在相机坐标下的稠密点云
    void GeneratePointCloud(KeyFrame* kf, cv::Mat& color_img, cv::Mat& depth_img);
    //全局BA后更新关键帧的位姿来调整地图点
    void UpdateCloud();
    //可视化
    void Viewer();
    void SaveCloud();
    void ShutDown();

    typedef pcl::PointXYZRGBA PointT;
    typedef pcl::PointCloud<PointT> PointCloud;

    PointCloud::Ptr mpGlobalCloud;
    shared_ptr<thread> mptViewerThread;
    vector<KeyFrame*> mvKeyFrames;
    size_t mnLastKeyFrameId = 0;
    mutex mmKeyFrameMutex;
    mutex mmCloudeUpdateMutex;
    mutex mmShutDownMutex;

    bool mbLoopBusy = false;
    bool mbShutDownFlag = false;

    double resolution = 0.04;
    //pcl::VoxelGrid<PointT> voxel;
    pcl::StatisticalOutlierRemoval<PointT> voxel;
};

} 

#endif // POINTCLOUDMAPPING_H

#include "PointCloudMapping.h"
#include "Converter.h"


namespace ORB_SLAM2
{
    PointCloudMapping::PointCloudMapping()
    {
        std::cout << "Point cloud mapping has structed. " << std::endl;
        mpGlobalCloud = boost::make_shared< PointCloud >( );

        //体素滤波
        //voxel.setLeafSize(resolution, resolution, resolution);

        //RadiusOutlierRemoval滤波器     
        //voxel.setMeanK (60);        //设置在进行统计时考虑查询点临近点数
        //voxel.setStddevMulThresh (0.6);    

        mptViewerThread = make_shared<thread>(bind(&PointCloudMapping::Viewer, this));
    }

    void PointCloudMapping::InsertKeyFrame(KeyFrame* kf, cv::Mat &color_img, cv::Mat &depth_img)
    {
        cout << "recieve a keyframe, id = " << kf->mnId << endl;
        unique_lock<mutex> lck(mmKeyFrameMutex);
        mvKeyFrames.push_back(kf);
        GeneratePointCloud(kf, color_img, depth_img);

    }

    void PointCloudMapping::GeneratePointCloud(KeyFrame* kf, cv::Mat &color_img, cv::Mat &depth_img)
    {

        // cv::imshow("color img", color_img);
        // cv::imshow("depth img", depth_img);
        PointCloud::Ptr tmp (new PointCloud());
        for ( int m=0; m<depth_img.rows; m+=3 )
        {
            for ( int n=0; n<depth_img.cols; n+=3 )
            {
                float d = depth_img.ptr<float>(m)[n];
                if (d < 0.01 || d>5)
                    continue;
                PointT p;
                p.z = d;
                p.x = ( n - kf->cx) * p.z / kf->fx;
                p.y = ( m - kf->cy) * p.z / kf->fy;
                
                p.b = color_img.ptr<uchar>(m)[n*3];
                p.g = color_img.ptr<uchar>(m)[n*3+1];
                p.r = color_img.ptr<uchar>(m)[n*3+2];

                // cout << p.x << " " << p.y << " " << p.z << endl;
                    
                tmp->points.push_back(p);
            }
        }
        
        cout << "The keyframe has point clouds: " << tmp->points.size() << endl;
        kf->mptrPointCloud = tmp;
    }

    void PointCloudMapping::UpdateCloud()
    {
        unique_lock<mutex> lck(mmCloudeUpdateMutex);
        mbLoopBusy = true;
        cout << endl << endl << "start loop map point." << endl << endl;
        PointCloud::Ptr tmp(new PointCloud);

        for(int i = 0; i < mvKeyFrames.size(); i++)
        {
            if(!mvKeyFrames[i]->isBad())
            {
                PointCloud::Ptr cloud(new PointCloud);
                pcl::transformPointCloud( *(mvKeyFrames[i]->mptrPointCloud), *cloud, Converter::toMatrix4d(mvKeyFrames[i]->GetPoseInverse()));
                *tmp += *cloud;
            }
        }

        cout << "finsh loop map" << endl;
        //voxel.setInputCloud(tmp);
        //voxel.filter(*mpGlobalCloud);
        mbLoopBusy = false;
    }

    void PointCloudMapping::Viewer()
    {
        pcl::visualization::CloudViewer viewer("Dense map viewer");
        while(1)
        {
            size_t N = 0;
            {
                unique_lock<mutex> lck(mmKeyFrameMutex);
                if(mbLoopBusy)
                {
                    continue;
                }
                {
                    unique_lock<mutex> lck_shut_down(mmShutDownMutex);
                    if(mbShutDownFlag)
                    {
                        cout << "Viewer has break " << endl;
                        break;
                    }
                }

                N = mvKeyFrames.size();
                if(N == mnLastKeyFrameId)
                {
                    continue;
                }
                else
                {
                    unique_lock<mutex> lck_(mmCloudeUpdateMutex);
                    for(size_t i = mnLastKeyFrameId; i < N; i++)
                    {
                        PointCloud::Ptr p (new PointCloud);
                        pcl::transformPointCloud( *(mvKeyFrames[i]->mptrPointCloud), *p, Converter::toMatrix4d(mvKeyFrames[i]->GetPoseInverse()));
                        //cout<<"处理好第i个点云"<<i<<endl;
                        *mpGlobalCloud += *p;
                    }
                    //PointCloud::Ptr tmp(new PointCloud);
                    //voxel.setInputCloud(mpGlobalCloud);
                    //voxel.filter(*tmp);
                    //mpGlobalCloud->swap(*tmp);

                    mnLastKeyFrameId = N;
                    cout << "Total has point clouds: " << mpGlobalCloud->points.size() << endl;
                }
            }
            viewer.showCloud(mpGlobalCloud);
        }
    }

    void PointCloudMapping::SaveCloud()
    {
        pcl::io::savePCDFile("result.pcd",*mpGlobalCloud);
        cout << "global cloud save finished !" << endl;
    }

    void PointCloudMapping::ShutDown()
    {
        {
            unique_lock<mutex> lck(mmShutDownMutex);
            mbShutDownFlag = true;
        }
        mptViewerThread->join();
        cout << "Point cloud mapping has shut down! " << endl;
    }
}

6 报错分析

(1) 编译出现参数未声明问题,参考代码中有些未在头文件中声明,要完整声明才能正确编译。

(2) 运行数据集时出现段错误,出现可视化界面后闪退

ORB Extractor Parameters: 
- Number of Features: 1000
- Scale Levels: 8
- Scale Factor: 1.2
- Initial Fast Threshold: 20
- Minimum Fast Threshold: 7

Depth Threshold (Close/Far Points): 2.98842

-------
Start processing sequence ...
Images in the sequence: 2488

New map created with 891 points
recieve a keyframe, id =1
段错误 (核心已转储)

后面搜寻各种解决办法:

解决办法1:主文件和Thirdparty/g2o文件中的CMakeList.txt,将-march=native删除,仍未解决。

#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}  -Wall  -O3 -march=native ")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall   -O3 -march=native")

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}  -Wall  -O3 ")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall   -O3 ")

解决办法2:尝试将Eigen版本降低为Eigen-3.1.0,同样也没有解决

解决办法3:后面尝试运行提供的参考代码成功运行,定位错误的原因为代码问题,System.cc文件没有声明:

mpPointCloudMapping = make_shared<PointCloudMapping>();

(3) 数据运行结果,自己运行和参考代码运行存在明显不同,存在重复错乱的问题

解决办法:对比运行数据发现,点云数明显不同,思考显示和运行数据判断,可能是RGB图和深度信息不匹配的情况,没有有效提取特征点。

定位问题在mImDepth名称在其他地方没有同步更改,更改后稠密建图正常。

cv::Mat Tracking::GrabImageRGBD(const cv::Mat &imRGB,const cv::Mat &imD, const double &timestamp)
{
    mImGray = imRGB;
    mImRGB = imRGB; 
    mImDepth = imD;
    
    // mImGray、mImRGB、mImDepth在后面出现的地方同步更改
}

 (4) 编译时修改代码后好像会延续上次的错误,删除编译文件后解决。

7 思考扩展

(1) 跟踪线程和局部建图线程中插入 mpPointCloudMapping->InsertKeyFrame 函数的优缺点

跟踪线程插入

  • 优点:跟踪线程中InsertKeyFrame进行稠密建图,可以很好的利用RGB和Depth
  • 缺点:会占用个跟踪线程的时间

局部建图线程中插入

  • 优点:不会占用跟踪线程的时间,更好的满足实时性的要求,在局部建图后的关键帧是经过优化后的位姿,同时可以在局部建图后建立稠密点云地图,效果会更好。
  • 缺点:利用RGB和Depth没有跟踪线程方便

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SLAM实战项目(1) — ORB-SLAM2稠密地图重建 的相关文章

  • 尚未注册类型“IServiceProviderFactory[Autofac.ContainerBuilder]”的服务

    当运行以下命令添加数据库迁移脚本时 出现以下错误 dotnet ef migrations add InitialCreate v o Migrations context MyContext 访问 Microsoft Extensions
  • 通过 SocketCAN 进行 boost::asio

    我正在考虑利用升压阿西奥 http www boost org doc libs 1 49 0 doc html boost asio html从a读取数据套接字CAN http en wikipedia org wiki SocketCA
  • QCombobox 向下箭头图像

    如何更改Qcombobox向下箭头图像 现在我正在使用这个 QSS 代码 但这不起作用 我无法删除向下箭头边框 QComboBox border 0px QComboBox down arrow border 0px background
  • 使用 Enumerable.OfType() 或 LINQ 查找特定类型的所有子控件

    Existed MyControl1 Controls OfType
  • 如何在 C# / .NET 中创建内存泄漏[重复]

    这个问题在这里已经有答案了 可能的重复 托管代码中是否可能存在内存泄漏 特别是 C 3 0 https stackoverflow com questions 6436620 is it possible to have a memory
  • 平滑滚动.net 表单

    您好 我正在 net 中使用表单 并且在运行时动态添加大量链接标签 我将这些链接标签添加到面板并将该面板添加到 winform 当链接标签的数量增加时 表单会显示一个自动滚动条 垂直 现在 当我使用自动滚动向下滚动时 表单在滚动时不会更新其
  • 在 Xamarin 中隐藏软键盘

    如何隐藏软键盘以便在聚焦时显示Entry在 Xamarin forms 便携式表单项目中 我假设我们必须为此编写特定于平台的渲染器 但以下内容不起作用 我创建自己的条目子类 public class MyExtendedEntry Entr
  • 读取 C# 中的默认应用程序设置

    我的自定义网格控件有许多应用程序设置 在用户范围内 其中大部分是颜色设置 我有一个表单 用户可以在其中自定义这些颜色 并且我想添加一个用于恢复默认颜色设置的按钮 如何读取默认设置 例如 我有一个名为的用户设置CellBackgroundCo
  • 类的成员复制

    在学习 复制成员 概念时 书中给出了如下说法 此外 如果非静态成员是引用 const 或没有复制赋值的用户定义类型 则无法生成默认赋值 我不太明白这个声明到底想传达什么 或者说这个说法指的是哪一种场景 谢谢 该语句与编译器自动为您编写的类
  • Visual Studio Code:如何配置 includePath 以获得更好的 IntelliSense 结果

    我是使用 Visual Studio Code 的完全初学者 我不知道我在做什么 我已经四处搜索 也许还不够 但我找不到像我这样的人如何配置的简单解释c cpp properties json每当我单击带有绿色波浪线下划线的行旁边的黄色灯泡
  • C# 构建一个 webservice 方法,它接受 POST 方法,如 HttpWebRequest 方法

    我需要一个接受 POST 方法的 Web 服务 访问我的服务器正在使用 POST 方法 它向我发送了一个 xml 我应该用一些 xml 进行响应 另一方面 当我访问他时 我已经使用 HttpWebRequest 类进行了管理 并且工作正常
  • 如何从文本文件读取整数到数组

    这就是我想做的 我对此有些不满 但我希望你能容忍我 这对我来说是一个非常新的概念 1 在我的程序中 我希望创建一个包含 50 个整数的数组来保存来自文件的数据 我的程序必须获取用户的文档文件夹的路径 2 文件的名称为 grades txt
  • C++ php 和静态库

    我创建了一个library a 其中包含 cpp 和 h 文件 其中包含很多类 嵌套类和方法 我想在 php 示例中包含这个静态库并尝试使用它 我想提一下 我是 php 新手 我已经在 test cpp 文件中测试了我的 libray a
  • IEnumerable.Except 不起作用,那么我该怎么办?

    我有一个 linq to sql 数据库 非常简单 我们有 3 个表 项目和用户 有一个名为 User Projects 的连接表将它们连接在一起 我已经有了一个获得的工作方法IEnumberable
  • 跨多个域的 ASP.NET 会话

    是否有合适的 NET 解决方案来在多个域上提供持久服务器会话 即 如果该网站的用户在 www site1 com 下登录 他们也将在 www site2 com 下登录 安全是我们正在开发的程序的一个问题 Thanks 它是否需要在会话中
  • 更改 Windows Phone 系统托盘颜色

    有没有办法将 Windows Phone 上的系统托盘颜色从黑色更改为白色 我的应用程序有白色背景 所以我希望系统托盘也是白色的 您可以在页面 XAML 中执行此操作
  • C++ Streambuf 方法可以抛出异常吗?

    我正在尝试找到一种方法来获取读取或写入流的字符数 即使存在错误并且读 写结束时间较短 该方法也是可靠的 我正在做这样的事情 return stream rdbuf gt sputn buffer buffer size 但如果streamb
  • 矩阵到数组 C#

    这将是转换方阵的最有效方法 例如 1 2 3 4 5 6 7 8 9 into 1 2 3 4 5 6 7 8 9 in c 我在做 int array2D new int 1 2 3 4 5 6 7 8 9 int array1D new
  • C++0x中disable_if在哪里?

    Boost 两者都有enable if and disable if 但 C 0x 似乎缺少后者 为什么它被排除在外 C 0x 中是否有元编程工具允许我构建disable if按照enable if 哦 我刚刚注意到std enable i
  • 使我的 COM 程序集调用异步

    我刚刚 赢得 了在当前工作中维护用 C 编码的遗留库的特权 这个dll 公开使用 Uniface 构建的大型遗留系统的方法 除了调用 COM 对象之外别无选择 充当此遗留系统与另一个系统的 API 之间的链接 在某些情况下 使用 WinFo

随机推荐

  • IPv6 PMTUD 路径发现机制 工作原理

    Technorati 标签 IPv6 PMTUD PMTUD IPv6 PMTUD是IPv6的一个工作机制 其主要的目的就是 当网络源发送数据报文到目的的时候 避免分段 也可以称为分片 源节点可以使用发现整个路径上面最大的MTU与目的节点通
  • Android opengles2.0 背景透明

    在Android上开发OpenGL ES应用时 默认的背景不透明的 即使使用了glClearColor来设置了不透明度为0 且纹理图片中有透明的部分也可能被GLView的背景填充 那么首先解决GLView的透明背景问题吧 要设置透明的第一步
  • python-gitlab

    一 安装 pip install python gitlab 官方文档 http python gitlab readthedocs io en stable API https docs gitlab com ce api project
  • springboot项目层次结构_SpringBoot 项目目录结构(工程结构)

    一 代码层结构 根目录 com jianbao 启动类JianbaoApplication java推荐放在根目录 com jianbao 包下 数据实体类domain jpa项目 com jianbao domain mybatis项目
  • 江西理工大学计算机网络基础试卷,无线网络技术作业(江西理工大学期末复习)...

    无线网络技术 1 1 跳频扩频和直接序列扩频各有什么特点 我的答案 跳频扩频 1 一定扩频码序列进行选择的多频率频移键控调制 载波频率不断跳变 2 发送方看似随机的无线电频率序列广播消息 并以固定间隔从一频率跳到另一频率 3 接收方接收时也
  • java对象和类的定义 属性 方法

    类 class 对象 Object instance 实例 1 类可以看成一类对象的模板 对象可以看成该类的一个具体实例 2 类是用于描述同一类型的对象的一个抽象概念 类中定义了这一类对象所应具有的共同属性 方法 类的定义方式 每一个源文件
  • JSP数据交互(一)---内置对象》response

    JSP内置对象之response response对象用于响应客户请求并向客户端输出信息 设置响应参数等 页面重定向 void sendRedirect String location 客户端将重新发送请求到指定的URL 实现登陆验证 并验
  • vector、list、queue

    引用 windows程序员面试指南 vector vector 类似于C语言中的数组 vector 支持随机访问 访问某个元素的时间复杂度 O 1 vector 插入和删除元素效率较低 时间复杂度O n vector 是连续存储 没有内存碎
  • 重构——写在后面

    重构方法有很多 但是只要满足以下条件 怎么重构都是合理的 原则一 SRP Single responsibility principle 单一职责原则又称单一功能原则 核心 解耦和增强内聚性 高内聚 低耦合 描述 类被修改的几率很大 因此应
  • MyBatis实现Mysql数据库分库分表操作和总结(推荐)

    阅读目录 前言 MyBatis实现分表最简单步骤 分离的方式 分离的策略 分离的问题 分离的原则 实现分离的方式 总结 前言 作为一个数据库 作为数据库中的一张表 随着用户的增多随着时间的推移 总有一天 数据量会大到一个难以处理的地步 这时
  • 对于git功能的探索与研究

    读前提示 注意 本文只是面向初学者或者之前并未接触过git而想学习如何初步使用git的读者 如果您很擅长使用git 并善于维护远程仓库 那么不建议您看此篇文章 这会浪费您的时间 当然 这篇文章还是能很好地告诉初学者如何简单的运用git的 比
  • 【C++】类的隐式转换和explicit抑制类的隐式转换

    2023年8月5日 周六下午 今天在网上找了很久都没找到有精确定义了类的隐式转换条件的资料 最后是在权威书籍 C Primer 第5版 里面找到的 说真的 虽然我认为 C Primer 第5版 不适合作为新手学习C 的教材 因为内容太多了
  • [[概率论与数理统计-2]:随机函数、概率、概率函数、概率分布函数

    作者主页 文火冰糖的硅基工坊 文火冰糖 王文兵 的博客 文火冰糖的硅基工坊 CSDN博客 本文网址 https blog csdn net HiWangWenBing article details 123608954 目录 第1章 随机与
  • ZonedDateTime 转为字符串

    Java8新特性ZonedDateTime 这个类有很多好用的方法 但是也有很多坑 它转为字符串时间不对 一般会少几个小时 这个因为地区时间不对 我们只需要转为字符串的时间添加几小时就好 代码如下 public static String
  • c++ vector内存释放踩坑,内存泄漏

    目录 vector删除元素 智能指针 vector移动元素位置 vector条件删除
  • 编译原理之first集,follow集,select集解析

    为了方便自顶向下语法分析 需要求文法对应的first集 follow集 以及select集 本文主要分为两部分 一个是求法解析 还有一个例子详解 第一部分是求法解析 将对first集 follow集 select集分为三种讲解方法 定义介绍
  • pyspark对字段加前缀,拼接字符串

    代码逻辑 在df中 当字段main task id为 0 时 则对字段sub task id加前缀 check 否则取其本身的值 正确代码如下 df2 df withColumn sub task id when col main task
  • SQL窗口函数的使用

    定义 窗口函数 又叫OLAP Online Anallytical Processing 函数 可对数据库数据进行实时分析处理 功能 同时分组和排序 不减少原表的行数 区别于聚合函数 每行数据都生成一个结果 使用场景 排名问题 topN问题
  • USE_STDPERIPH_DRIVER, STM32F10X_HD说明

    如果在STM32工程编译时没有添加USE STDPERIPH DRIVER STM32F10X HD时会出现如下错误 library cortex m3 stm32f10x h 96 error 35 error directive Ple
  • SLAM实战项目(1) — ORB-SLAM2稠密地图重建

    目录 1 整体思路 2 功能实现 3 结果运行 1 TUM数据集下载 2 associate py用于RGB和Depth匹配 3 运行数据集 4 CMakeLists txt文件修改 5 完整PointCloudMapping h和Poin