视觉里程计

1.直接法的引出

尽管特征点法在视觉里程计中占
据主流地位,研究者们认识到它至少有以下几个缺点:

  1. 关键点的提取与描述子的计算非常耗时。实践当中,SIFT 目前在 CPU 上是无法实时计算的,而 ORB 也需要近 20 毫秒的计算。如果整个 SLAM 以 30 毫秒/帧的速度运行,那么一大半时间都花在计算特征点上。
  2. 使用特征点时,忽略了除特征点以外的所有信息。一张图像有几十万个像素,而特征点只有几百个。只使用特征点丢弃了大部分可能有用的图像信息。
  3. 相机有时会运动到特征缺失的地方,往往这些地方没有明显的纹理信息。例如,有时我们会面对一堵白墙,或者一个空荡荡的走廓。这些场景下特征点数量会明显减少,我们可能找不到足够的匹配点来计算相机运动。

使用特征点法估计相机运动时,我们把特征点看作固定在三维空间的不动点。根据它们在相机中的投影位置,通过最小化重投影误差 (Reprojection error)来优化相机运动。在这个过程中,我们需要精确地知道空间点在两个相机中投影后的像素位置——这也就是我们为何要对特征进行匹配或跟踪的理由。同时,我们也知道,计算、匹配特征需要付出大量的计算量。相对的,在直接法中,我们并不需要知道点与点之间之间的对应关系,而是通过最小化光度误差(Photometric error)来求得它们。

2. 光流(Optical Flow)

光流是一种描述像素随着时间,在图像之间运动的方法,如图 8-1 所示。随着时间的经过,同一个像素会在图像中运动,而我们希望追踪它的运动过程。计算部分像素运动的称为稀疏光流,计算所有像素的称为稠密光流。稀疏光流以 Lucas-Kanade 光流为代表,并可以在 SLAM 中用于跟踪特征点位置。

2.1 Lucas-Kanade 光流


在 LK 光流中,我们假设某一个窗口内的像素具有相同的运动。

3. 实践:LK光流

THU数据集处理如果遇到报错,参考:
使用TUM的associate.py脚本报错AttributeError: ‘dict_keys‘ object has no attribute ‘remove‘

useLK.cpp

#include <iostream>
#include <fstream>
#include <list>
#include <vector>
#include <chrono>
using namespace std; #include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/video/tracking.hpp>int main( int argc, char** argv )
{if ( argc != 2 ){cout<<"usage: useLK path_to_dataset"<<endl;return 1;}string path_to_dataset = argv[1];string associate_file = path_to_dataset + "/associate.txt";ifstream fin( associate_file );if ( !fin ) {cerr<<"I cann't find associate.txt!"<<endl;return 1;}string rgb_file, depth_file, time_rgb, time_depth;list< cv::Point2f > keypoints;      // 因为要删除跟踪失败的点,使用listcv::Mat color, depth, last_color;for ( int index=0; index<100; index++ ){fin>>time_rgb>>rgb_file>>time_depth>>depth_file;color = cv::imread( path_to_dataset+"/"+rgb_file );depth = cv::imread( path_to_dataset+"/"+depth_file, -1 );if (index ==0 ){// 对第一帧提取FAST特征点vector<cv::KeyPoint> kps;cv::Ptr<cv::FastFeatureDetector> detector = cv::FastFeatureDetector::create();detector->detect( color, kps );for ( auto kp:kps )keypoints.push_back( kp.pt );last_color = color;continue;}if ( color.data==nullptr || depth.data==nullptr )continue;// 对其他帧用LK跟踪特征点vector<cv::Point2f> next_keypoints; vector<cv::Point2f> prev_keypoints;for ( auto kp:keypoints )prev_keypoints.push_back(kp);vector<unsigned char> status;vector<float> error; chrono::steady_clock::time_point t1 = chrono::steady_clock::now();cv::calcOpticalFlowPyrLK( last_color, color, prev_keypoints, next_keypoints, status, error );chrono::steady_clock::time_point t2 = chrono::steady_clock::now();chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>( t2-t1 );cout<<"LK Flow use time:"<<time_used.count()<<" seconds."<<endl;// 把跟丢的点删掉int i=0; for ( auto iter=keypoints.begin(); iter!=keypoints.end(); i++){if ( status[i] == 0 ){iter = keypoints.erase(iter);continue;}*iter = next_keypoints[i];iter++;}cout<<"tracked keypoints: "<<keypoints.size()<<endl;if (keypoints.size() == 0){cout<<"all keypoints are lost."<<endl;break; }// 画出 keypointscv::Mat img_show = color.clone();for ( auto kp:keypoints )cv::circle(img_show, kp, 10, cv::Scalar(0, 240, 0), 1);cv::imshow("corners", img_show);cv::waitKey(0);last_color = color;}return 0;
}

CMakeLIsts.txt

cmake_minimum_required( VERSION 2.8 )
project( useLK )set( CMAKE_BUILD_TYPE Release )find_package( OpenCV )
include_directories( ${OpenCV_INCLUDE_DIRS} )add_executable( useLK useLK.cpp )
target_link_libraries( useLK ${OpenCV_LIBS} )

输出结果

4. 直接法(Direct Methods)

4.1 直接法的推导




4.2 直接法的讨论

在我们上面的推导中,P 是一个已知位置的空间点,它是怎么来的呢?在 RGB-D 相机下,我们可以把任意像素反投影到三维空间,然后投影到下一个图像中。如果在单目相机中,这件事情要更为困难,因为我们还需考虑由 P 的深度带来的不确定性。详细的深度估计放到 13 讲中讨论。现在我们先来考虑简单的情况,即 P 深度已知的情况。
根据 P 的来源,我们可以把直接法进行分类:

  1. P 来自于稀疏关键点,我们称之为稀疏直接法。通常我们使用数百个至上千个关键点,并且像 L-K 光流那样,假设它周围像素也是不变的。这种稀疏直接法不必计算描述子,并且只使用数百个像素,因此速度最快,但只能计算稀疏的重构。
  2. P 来自部分像素。我们看到式(8.16)中,如果像素梯度为零,整一项雅可比就为零,不会对计算运动增量有任何贡献。因此,可以考虑只使用带有梯度的像素点,舍弃像素梯度不明显的地方。这称之为半稠密(Semi-Dense)的直接法,可以重构一个半稠密结构。
  3. P 为所有像素,称为稠密直接法。稠密重构需要计算所有像素(一般几十万至几百万个),因此多数不能在现有的 CPU 上实时计算,需要 GPU 的加速。但是,如前面所讨论的,梯度不明显的点,在运动估计中不会有太大贡献,在重构时也会难以估计位置。

5. 实践:RGB-D 的直接法

5.1 稀疏直接法

直接法是由以下顶点和边组成的:

  1. 优化变量为一个相机位姿,因此需要一个位姿顶点。由于我们在推导中使用了李代数,故程序中使用李代数表达的 SE(3) 位姿顶点。与上一章一样,我们将使用“Ver-texSE3Expmap”作为相机位姿。
  2. 误差项为单个像素的光度误差。由于整个优化过程中 I 1 (p 1 ) 保持不变,我们可以把它当成一个固定的预设值,然后调整相机位姿,使 I 2 (p 2 ) 接近这个值。于是,这种边只连接一个顶点,为一元边。由于 g2o 中本身没有计算光度误差的边,我们需要自己定义一种新的边。

direct_sparse.cpp

#include <iostream>
#include <fstream>
#include <list>
#include <vector>
#include <chrono>
#include <ctime>
#include <climits>#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>#include <g2o/core/base_unary_edge.h>
#include <g2o/core/block_solver.h>
#include <g2o/core/optimization_algorithm_levenberg.h>
#include <g2o/solvers/dense/linear_solver_dense.h>
#include <g2o/core/robust_kernel.h>
#include <g2o/types/sba/types_six_dof_expmap.h>using namespace std;
using namespace g2o;/********************************************* 本节演示了RGBD上的稀疏直接法 ********************************************/// 一次测量的值,包括一个世界坐标系下三维点与一个灰度值
struct Measurement
{Measurement ( Eigen::Vector3d p, float g ) : pos_world ( p ), grayscale ( g ) {}Eigen::Vector3d pos_world;float grayscale;
};inline Eigen::Vector3d project2Dto3D ( int x, int y, int d, float fx, float fy, float cx, float cy, float scale )
{float zz = float ( d ) /scale;float xx = zz* ( x-cx ) /fx;float yy = zz* ( y-cy ) /fy;return Eigen::Vector3d ( xx, yy, zz );
}inline Eigen::Vector2d project3Dto2D ( float x, float y, float z, float fx, float fy, float cx, float cy )
{float u = fx*x/z+cx;float v = fy*y/z+cy;return Eigen::Vector2d ( u,v );
}// 直接法估计位姿
// 输入:测量值(空间点的灰度),新的灰度图,相机内参; 输出:相机位姿
// 返回:true为成功,false失败
bool poseEstimationDirect ( const vector<Measurement>& measurements, cv::Mat* gray, Eigen::Matrix3f& intrinsics, Eigen::Isometry3d& Tcw );// project a 3d point into an image plane, the error is photometric error
// an unary edge with one vertex SE3Expmap (the pose of camera)
class EdgeSE3ProjectDirect: public BaseUnaryEdge< 1, double, VertexSE3Expmap>
{public:EIGEN_MAKE_ALIGNED_OPERATOR_NEWEdgeSE3ProjectDirect() {}EdgeSE3ProjectDirect ( Eigen::Vector3d point, float fx, float fy, float cx, float cy, cv::Mat* image ): x_world_ ( point ), fx_ ( fx ), fy_ ( fy ), cx_ ( cx ), cy_ ( cy ), image_ ( image ){}virtual void computeError(){const VertexSE3Expmap* v  =static_cast<const VertexSE3Expmap*> ( _vertices[0] );Eigen::Vector3d x_local = v->estimate().map ( x_world_ );float x = x_local[0]*fx_/x_local[2] + cx_;float y = x_local[1]*fy_/x_local[2] + cy_;// check x,y is in the imageif ( x-4<0 || ( x+4 ) >image_->cols || ( y-4 ) <0 || ( y+4 ) >image_->rows ){_error ( 0,0 ) = 0.0;this->setLevel ( 1 );}else{_error ( 0,0 ) = getPixelValue ( x,y ) - _measurement;}}// plus in manifoldvirtual void linearizeOplus( ){if ( level() == 1 ){_jacobianOplusXi = Eigen::Matrix<double, 1, 6>::Zero();return;}VertexSE3Expmap* vtx = static_cast<VertexSE3Expmap*> ( _vertices[0] );Eigen::Vector3d xyz_trans = vtx->estimate().map ( x_world_ );   // q in bookdouble x = xyz_trans[0];double y = xyz_trans[1];double invz = 1.0/xyz_trans[2];double invz_2 = invz*invz;float u = x*fx_*invz + cx_;float v = y*fy_*invz + cy_;// jacobian from se3 to u,v// NOTE that in g2o the Lie algebra is (\omega, \epsilon), where \omega is so(3) and \epsilon the translationEigen::Matrix<double, 2, 6> jacobian_uv_ksai;jacobian_uv_ksai ( 0,0 ) = - x*y*invz_2 *fx_;jacobian_uv_ksai ( 0,1 ) = ( 1+ ( x*x*invz_2 ) ) *fx_;jacobian_uv_ksai ( 0,2 ) = - y*invz *fx_;jacobian_uv_ksai ( 0,3 ) = invz *fx_;jacobian_uv_ksai ( 0,4 ) = 0;jacobian_uv_ksai ( 0,5 ) = -x*invz_2 *fx_;jacobian_uv_ksai ( 1,0 ) = - ( 1+y*y*invz_2 ) *fy_;jacobian_uv_ksai ( 1,1 ) = x*y*invz_2 *fy_;jacobian_uv_ksai ( 1,2 ) = x*invz *fy_;jacobian_uv_ksai ( 1,3 ) = 0;jacobian_uv_ksai ( 1,4 ) = invz *fy_;jacobian_uv_ksai ( 1,5 ) = -y*invz_2 *fy_;Eigen::Matrix<double, 1, 2> jacobian_pixel_uv;jacobian_pixel_uv ( 0,0 ) = ( getPixelValue ( u+1,v )-getPixelValue ( u-1,v ) ) /2;jacobian_pixel_uv ( 0,1 ) = ( getPixelValue ( u,v+1 )-getPixelValue ( u,v-1 ) ) /2;_jacobianOplusXi = jacobian_pixel_uv*jacobian_uv_ksai;}// dummy read and write functions because we don't care...virtual bool read ( std::istream& in ) {}virtual bool write ( std::ostream& out ) const {}protected:// get a gray scale value from reference image (bilinear interpolated)inline float getPixelValue ( float x, float y ){uchar* data = & image_->data[ int ( y ) * image_->step + int ( x ) ];float xx = x - floor ( x );float yy = y - floor ( y );return float (( 1-xx ) * ( 1-yy ) * data[0] +xx* ( 1-yy ) * data[1] +( 1-xx ) *yy*data[ image_->step ] +xx*yy*data[image_->step+1]);}
public:Eigen::Vector3d x_world_;   // 3D point in world framefloat cx_=0, cy_=0, fx_=0, fy_=0; // Camera intrinsicscv::Mat* image_=nullptr;    // reference image
};int main ( int argc, char** argv )
{if ( argc != 2 ){cout<<"usage: useLK path_to_dataset"<<endl;return 1;}srand ( ( unsigned int ) time ( 0 ) );string path_to_dataset = argv[1];string associate_file = path_to_dataset + "/associate.txt";ifstream fin ( associate_file );string rgb_file, depth_file, time_rgb, time_depth;cv::Mat color, depth, gray;vector<Measurement> measurements;// 相机内参float cx = 325.5;float cy = 253.5;float fx = 518.0;float fy = 519.0;float depth_scale = 1000.0;Eigen::Matrix3f K;K<<fx,0.f,cx,0.f,fy,cy,0.f,0.f,1.0f;Eigen::Isometry3d Tcw = Eigen::Isometry3d::Identity();cv::Mat prev_color;// 我们以第一个图像为参考,对后续图像和参考图像做直接法for ( int index=0; index<10; index++ ){cout<<"*********** loop "<<index<<" ************"<<endl;fin>>time_rgb>>rgb_file>>time_depth>>depth_file;color = cv::imread ( path_to_dataset+"/"+rgb_file );depth = cv::imread ( path_to_dataset+"/"+depth_file, -1 );if ( color.data==nullptr || depth.data==nullptr )continue; cv::cvtColor ( color, gray, cv::COLOR_BGR2GRAY );if ( index ==0 ){// 对第一帧提取FAST特征点vector<cv::KeyPoint> keypoints;cv::Ptr<cv::FastFeatureDetector> detector = cv::FastFeatureDetector::create();detector->detect ( color, keypoints );for ( auto kp:keypoints ){// 去掉邻近边缘处的点if ( kp.pt.x < 20 || kp.pt.y < 20 || ( kp.pt.x+20 ) >color.cols || ( kp.pt.y+20 ) >color.rows )continue;ushort d = depth.ptr<ushort> ( cvRound ( kp.pt.y ) ) [ cvRound ( kp.pt.x ) ];if ( d==0 )continue;Eigen::Vector3d p3d = project2Dto3D ( kp.pt.x, kp.pt.y, d, fx, fy, cx, cy, depth_scale );float grayscale = float ( gray.ptr<uchar> ( cvRound ( kp.pt.y ) ) [ cvRound ( kp.pt.x ) ] );measurements.push_back ( Measurement ( p3d, grayscale ) );}prev_color = color.clone();continue;}// 使用直接法计算相机运动chrono::steady_clock::time_point t1 = chrono::steady_clock::now();poseEstimationDirect ( measurements, &gray, K, Tcw );chrono::steady_clock::time_point t2 = chrono::steady_clock::now();chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>> ( t2-t1 );cout<<"direct method costs time: "<<time_used.count() <<" seconds."<<endl;cout<<"Tcw="<<Tcw.matrix() <<endl;// plot the feature pointscv::Mat img_show ( color.rows*2, color.cols, CV_8UC3 );prev_color.copyTo ( img_show ( cv::Rect ( 0,0,color.cols, color.rows ) ) );color.copyTo ( img_show ( cv::Rect ( 0,color.rows,color.cols, color.rows ) ) );for ( Measurement m:measurements ){if ( rand() > RAND_MAX/5 )continue;Eigen::Vector3d p = m.pos_world;Eigen::Vector2d pixel_prev = project3Dto2D ( p ( 0,0 ), p ( 1,0 ), p ( 2,0 ), fx, fy, cx, cy );Eigen::Vector3d p2 = Tcw*m.pos_world;Eigen::Vector2d pixel_now = project3Dto2D ( p2 ( 0,0 ), p2 ( 1,0 ), p2 ( 2,0 ), fx, fy, cx, cy );if ( pixel_now(0,0)<0 || pixel_now(0,0)>=color.cols || pixel_now(1,0)<0 || pixel_now(1,0)>=color.rows )continue;float b = 255*float ( rand() ) /RAND_MAX;float g = 255*float ( rand() ) /RAND_MAX;float r = 255*float ( rand() ) /RAND_MAX;cv::circle ( img_show, cv::Point2d ( pixel_prev ( 0,0 ), pixel_prev ( 1,0 ) ), 8, cv::Scalar ( b,g,r ), 2 );cv::circle ( img_show, cv::Point2d ( pixel_now ( 0,0 ), pixel_now ( 1,0 ) +color.rows ), 8, cv::Scalar ( b,g,r ), 2 );cv::line ( img_show, cv::Point2d ( pixel_prev ( 0,0 ), pixel_prev ( 1,0 ) ), cv::Point2d ( pixel_now ( 0,0 ), pixel_now ( 1,0 ) +color.rows ), cv::Scalar ( b,g,r ), 1 );}cv::imshow ( "result", img_show );cv::waitKey ( 0 );}return 0;
}bool poseEstimationDirect ( const vector< Measurement >& measurements, cv::Mat* gray, Eigen::Matrix3f& K, Eigen::Isometry3d& Tcw )
{/*// 初始化g2otypedef g2o::BlockSolver<g2o::BlockSolverTraits<6,1>> DirectBlock;  // 求解的向量是6*1的DirectBlock::LinearSolverType* linearSolver = new g2o::LinearSolverDense< DirectBlock::PoseMatrixType > ();DirectBlock* solver_ptr = new DirectBlock ( linearSolver );// g2o::OptimizationAlgorithmGaussNewton* solver = new g2o::OptimizationAlgorithmGaussNewton( solver_ptr ); // G-Ng2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg ( solver_ptr ); // L-Mg2o::SparseOptimizer optimizer;optimizer.setAlgorithm ( solver );optimizer.setVerbose( true );*/// 初始化g2o(新版本用法)typedef g2o::BlockSolver<g2o::BlockSolverTraits<6,1>> DirectBlock;  // 求解的向量是6*1的std::unique_ptr<DirectBlock::LinearSolverType> linearSolver ( new g2o::LinearSolverDense<DirectBlock::PoseMatrixType>());std::unique_ptr<DirectBlock> solver_ptr ( new DirectBlock ( std::move(linearSolver)));     // 矩阵块求解器g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg ( std::move(solver_ptr));g2o::SparseOptimizer optimizer;optimizer.setAlgorithm ( solver );optimizer.setVerbose( true );g2o::VertexSE3Expmap* pose = new g2o::VertexSE3Expmap();pose->setEstimate ( g2o::SE3Quat ( Tcw.rotation(), Tcw.translation() ) );pose->setId ( 0 );optimizer.addVertex ( pose );// 添加边int id=1;for ( Measurement m: measurements ){EdgeSE3ProjectDirect* edge = new EdgeSE3ProjectDirect (m.pos_world,K ( 0,0 ), K ( 1,1 ), K ( 0,2 ), K ( 1,2 ), gray);edge->setVertex ( 0, pose );edge->setMeasurement ( m.grayscale );edge->setInformation ( Eigen::Matrix<double,1,1>::Identity() );edge->setId ( id++ );optimizer.addEdge ( edge );}cout<<"edges in graph: "<<optimizer.edges().size() <<endl;optimizer.initializeOptimization();optimizer.optimize ( 30 );Tcw = pose->estimate();
}

CMakeLists.txt

cmake_minimum_required( VERSION 2.8 )
project( directMethod )set( CMAKE_BUILD_TYPE Release )# 添加cmake模块路径
list( APPEND CMAKE_MODULE_PATH /home/ql/slamebook/lib/g2o/cmake_modules)find_package( OpenCV )
include_directories( ${OpenCV_INCLUDE_DIRS} )find_package( G2O )
include_directories( ${G2O_INCLUDE_DIRS} ) include_directories( "/usr/include/eigen3" )set( G2O_LIBS g2o_core g2o_types_sba g2o_solver_csparse g2o_stuff g2o_csparse_extension
)add_executable( direct_sparse direct_sparse.cpp )
target_link_libraries( direct_sparse ${OpenCV_LIBS} ${G2O_LIBS} )# add_executable( direct_semidense direct_semidense.cpp )
# target_link_libraries( direct_semidense ${OpenCV_LIBS} ${G2O_LIBS} )

5.6 直接法优缺点总结

最后,我们总结一下直接法的优缺点。大体来说,它的优点如下:
• 可以省去计算特征点、描述子的时间。
• 只要求有像素梯度即可,无须特征点。因此,直接法可以在特征缺失的场合下使用。比较极端的例子是只有渐变的一张图像。它可能无法提取角点类特征,但可以用直接法估计它的运动。
• 可以构建半稠密乃至稠密的地图,这是特征点法无法做到的。
另一方面,它的缺点也很明显:
非凸性——直接法完全依靠梯度搜索,降低目标函数来计算相机位姿。其目标函数中需要取像素点的灰度值,而图像是强烈非凸的函数。这使得优化算法容易进入极小,只在运动很小时直接法才能成功。
单个像素没有区分度。找一个和他像的实在太多了!——于是我们要么计算图像块,要么计算复杂的相关性。由于每个像素对改变相机运动的“意见”不一致。只能少数服从多数,以数量代替质量。
灰度值不变是很强的假设。如果相机是自动曝光的,当它调整曝光参数时,会使得图像整体变亮或变暗。光照变化时亦会出现这种情况。特征点法对光照具有一定的容忍性,而直接法由于计算灰度间的差异,整体灰度变化会破坏灰度不变假设,使算法失败。针对这一点,目前的直接法开始使用更细致的光度模型标定相机,以便在曝光时间变化时也能让直接法工作。

视觉SLAM十四讲-高翔 第8讲 视觉里程计2相关推荐

  1. 高翔视觉SLAM十四讲(第二版)各种软件、库安装的以及报错解决方法

    目录 前言 系统版本 下载高翔视觉SLAM十四讲第二版的源代码 一.安装 Vim 二.安装 g++ 三.安装 KDevelop 以及汉化 1.安装 2.汉化 四.安装 Eigen 库 五.安装 Pan ...

  2. 视觉SLAM十四讲(高翔第二版)

    视觉SLAM十四讲高翔第二版调试记录 前言 一.3.7可视化演示程序运行全过程Pangolin plotTrajectory.cpp 1. **在3rdparty中下载Pangolin** 2. ** ...

  3. 【读书笔记】《视觉SLAM十四讲(高翔著)》 第13讲

    文章目录 工程文件一:dense_monocular(单目稠密地图) 工程文件二:dense_RGBD(点云地图 & 八叉树地图) 本博客的内容是本章程序编译运行方法,记录调通本章程序的过程. ...

  4. 视觉SLAM十四讲(3):三维空间刚体运动

    本章需要掌握的知识点有:旋转矩阵,变换矩阵,四元数,欧拉角定义和数学表达:同时也要掌握Eigen库关于矩阵.几何模块的使用方法. 文章目录 3.1 旋转矩阵 3.1.1 点,向量和矩阵的关系 3.1. ...

  5. 视觉SLAM十四讲(2):初识SLAM

    这一讲主要介绍视觉SLAM的结构,并完成第一个SLAM程序:HelloSLAM. 目录 2.1 小萝卜的例子 单目相机 双目相机 深度相机 2.2 经典视觉SLAM框架 2.3 SLAM问题的数学表述 ...

  6. 视觉SLAM十四讲(1):预备知识

    最近在学习高翔博士的<视觉SLAM十四讲>(第二版),算是初学本书,配套资源还算蛮丰富的,有代码(第一版和第二版都有),B站上也有高翔博士对第一版录制的讲解视频,真的是很贴心. 来吧,让我 ...

  7. 视觉slam十四讲 pdf_视觉SLAM十四讲|第12讲 回环检测

    1. 什么是回环检测 前面有说过累积误差的问题,前一时刻的误差会积累到后面,导致画不成圈圈,如图12-1所示,而画圈圈(全局一致性)很重要,所以需要有一个步骤来纠正当前的计算偏差. 回环检测通过判断相 ...

  8. 视觉SLAM十四讲学习笔记专栏汇总

    专栏汇总 视觉SLAM十四讲学习笔记-第一讲_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习笔记-第二讲-初识SLAM_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习笔记-第二 ...

  9. 【《视觉SLAM十四讲》前ch2-ch6实践全过程和遇到的问题及解决办法】

    文章目录 前言 一.运行环境配置 1.在虚拟机上安装Ubuntu14.04 2.方便Ubuntu使用 二.<十四讲>的实践部分过程与问题 1.Ubuntu下安装包的两种方法 2.编译高翔的 ...

  10. tensorflow 语义slam_研究《视觉SLAM十四讲从理论到实践第2版》PDF代码+《OpenCV+TensorFlow深度学习与计算机视觉实战》PDF代码笔记...

    我们知道随着人工神经网络和深度学习的发展,通过模拟视觉所构建的卷积神经网络模型在图像识别和分类上取得了非常好的效果,借助于深度学习技术的发展,使用人工智能去处理常规劳动,理解语音语义,帮助医学诊断和支 ...

最新文章

  1. 不学51直接学stm32可以吗?学stm32需要哪些基础?
  2. python学习第一模块练习
  3. matlab 罗盘图与羽毛图
  4. 数据结构利器之私房STL(上)
  5. 开发之痛:稳定的测试环境,怎么就那么难
  6. js 跳转到 百度指定地址定位点
  7. python写spark_python开发sparkSQL应用
  8. 网吧母盘网上精华+个人总结=超详细
  9. arm-linux-gcc stdio.h,arm-linux-gcc stdio.h no such file or directory错误
  10. vivo平台sdk php说明书,vivo
  11. 程序员赚的辛苦钱及好朋友借钱[借钱时你是爷爷,借出去后丢一个朋友不说还多出一个爷爷]
  12. 跑跑飞弹室外跑步AR游戏代码方案设计
  13. 图像处理:实现图片镜像(基于python)
  14. 6.数据仓库搭建之数据仓库设计
  15. 我想死你们了,大家好!
  16. 上海市“专精特新”中小企业认定条件及奖励政策解读
  17. python读取文件夹下所有图片
  18. win2003遭受udp攻击导致带宽占用很大
  19. 【220】【3】滑动窗口(双指针)的应用,另有序的容器应用
  20. ffmpeg学习五:avformat_open_input函数源码分析(以mp4文件为例)

热门文章

  1. PHP画a梦,使用canvas画“哆啦A梦”时钟的代码
  2. Motion Sensors
  3. 基于AD7606八通道高速数据采集模块
  4. Win2003系统优化与管理
  5. 想要开亚马逊需要多少费用?以下是具体明细
  6. AutoCAD VBA单行文字转换为多行文字
  7. 关注DSP:最新应用前景及中国市场发展情况分析
  8. Python正则表达式(附正则表达式教程、调试工具、可视化工具)
  9. Centos6安装和使用ClamAV杀毒软件
  10. php判断ip是否是内网/外网ip