ICP 3D3D

SVD方法

非线性方法

实践

使用两幅图的RGB-D图像,通过特征匹配获取两组3D点,最后利用ICP计算他们的位姿变换。

pose_estimation_3d3d

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <Eigen/SVD>
#include <g2o/core/base_vertex.h>
#include <g2o/core/base_unary_edge.h>
#include <g2o/core/block_solver.h>
#include <g2o/core/optimization_algorithm_gauss_newton.h>
#include <g2o/solvers/eigen/linear_solver_eigen.h>
#include <g2o/types/sba/types_six_dof_expmap.h>
#include <chrono>using namespace std;
using namespace cv;void find_feature_matches (const Mat& img_1, const Mat& img_2,std::vector<KeyPoint>& keypoints_1,std::vector<KeyPoint>& keypoints_2,std::vector< DMatch >& matches );// 像素坐标转相机归一化坐标
Point2d pixel2cam ( const Point2d& p, const Mat& K );void pose_estimation_3d3d (const vector<Point3f>& pts1,const vector<Point3f>& pts2,Mat& R, Mat& t
);void bundleAdjustment(const vector<Point3f>& points_3d,const vector<Point3f>& points_2d,Mat& R, Mat& t
);// g2o edge
class EdgeProjectXYZRGBDPoseOnly : public g2o::BaseUnaryEdge<3, Eigen::Vector3d, g2o::VertexSE3Expmap>
{
public:EIGEN_MAKE_ALIGNED_OPERATOR_NEW;EdgeProjectXYZRGBDPoseOnly( const Eigen::Vector3d& point ) : _point(point) {}virtual void computeError(){const g2o::VertexSE3Expmap* pose = static_cast<const g2o::VertexSE3Expmap*> ( _vertices[0] );// measurement is p, point is p'_error = _measurement - pose->estimate().map( _point );}virtual void linearizeOplus(){g2o::VertexSE3Expmap* pose = static_cast<g2o::VertexSE3Expmap *>(_vertices[0]);g2o::SE3Quat T(pose->estimate());Eigen::Vector3d xyz_trans = T.map(_point);double x = xyz_trans[0];double y = xyz_trans[1];double z = xyz_trans[2];_jacobianOplusXi(0,0) = 0;_jacobianOplusXi(0,1) = -z;_jacobianOplusXi(0,2) = y;_jacobianOplusXi(0,3) = -1;_jacobianOplusXi(0,4) = 0;_jacobianOplusXi(0,5) = 0;_jacobianOplusXi(1,0) = z;_jacobianOplusXi(1,1) = 0;_jacobianOplusXi(1,2) = -x;_jacobianOplusXi(1,3) = 0;_jacobianOplusXi(1,4) = -1;_jacobianOplusXi(1,5) = 0;_jacobianOplusXi(2,0) = -y;_jacobianOplusXi(2,1) = x;_jacobianOplusXi(2,2) = 0;_jacobianOplusXi(2,3) = 0;_jacobianOplusXi(2,4) = 0;_jacobianOplusXi(2,5) = -1;}bool read ( istream& in ) {}bool write ( ostream& out ) const {}
protected:Eigen::Vector3d _point;
};int main ( int argc, char** argv )
{if ( argc != 5 ){cout<<"usage: pose_estimation_3d3d img1 img2 depth1 depth2"<<endl;return 1;}//-- 读取图像Mat img_1 = imread ( argv[1], CV_LOAD_IMAGE_COLOR );Mat img_2 = imread ( argv[2], CV_LOAD_IMAGE_COLOR );vector<KeyPoint> keypoints_1, keypoints_2;vector<DMatch> matches;find_feature_matches ( img_1, img_2, keypoints_1, keypoints_2, matches );cout<<"一共找到了"<<matches.size() <<"组匹配点"<<endl;// 建立3D点Mat depth1 = imread ( argv[3], CV_LOAD_IMAGE_UNCHANGED );       // 深度图为16位无符号数,单通道图像Mat depth2 = imread ( argv[4], CV_LOAD_IMAGE_UNCHANGED );       // 深度图为16位无符号数,单通道图像Mat K = ( Mat_<double> ( 3,3 ) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1 );vector<Point3f> pts1, pts2;for ( DMatch m:matches ){ushort d1 = depth1.ptr<unsigned short> ( int ( keypoints_1[m.queryIdx].pt.y ) ) [ int ( keypoints_1[m.queryIdx].pt.x ) ];ushort d2 = depth2.ptr<unsigned short> ( int ( keypoints_2[m.trainIdx].pt.y ) ) [ int ( keypoints_2[m.trainIdx].pt.x ) ];if ( d1==0 || d2==0 )   // bad depthcontinue;Point2d p1 = pixel2cam ( keypoints_1[m.queryIdx].pt, K );Point2d p2 = pixel2cam ( keypoints_2[m.trainIdx].pt, K );float dd1 = float ( d1 ) /5000.0;float dd2 = float ( d2 ) /5000.0;pts1.push_back ( Point3f ( p1.x*dd1, p1.y*dd1, dd1 ) );pts2.push_back ( Point3f ( p2.x*dd2, p2.y*dd2, dd2 ) );}cout<<"3d-3d pairs: "<<pts1.size() <<endl;Mat R, t;pose_estimation_3d3d ( pts1, pts2, R, t );cout<<"ICP via SVD results: "<<endl;cout<<"R = "<<R<<endl;cout<<"t = "<<t<<endl;cout<<"R_inv = "<<R.t() <<endl;cout<<"t_inv = "<<-R.t() *t<<endl;cout<<"calling bundle adjustment"<<endl;bundleAdjustment( pts1, pts2, R, t );// verify p1 = R*p2 + tfor ( int i=0; i<5; i++ ){cout<<"p1 = "<<pts1[i]<<endl;cout<<"p2 = "<<pts2[i]<<endl;cout<<"(R*p2+t) = "<<R * (Mat_<double>(3,1)<<pts2[i].x, pts2[i].y, pts2[i].z) + t<<endl;cout<<endl;}
}void find_feature_matches ( const Mat& img_1, const Mat& img_2,std::vector<KeyPoint>& keypoints_1,std::vector<KeyPoint>& keypoints_2,std::vector< DMatch >& matches )
{//-- 初始化Mat descriptors_1, descriptors_2;// used in OpenCV3Ptr<FeatureDetector> detector = ORB::create();Ptr<DescriptorExtractor> descriptor = ORB::create();// use this if you are in OpenCV2// Ptr<FeatureDetector> detector = FeatureDetector::create ( "ORB" );// Ptr<DescriptorExtractor> descriptor = DescriptorExtractor::create ( "ORB" );Ptr<DescriptorMatcher> matcher  = DescriptorMatcher::create("BruteForce-Hamming");//-- 第一步:检测 Oriented FAST 角点位置detector->detect ( img_1,keypoints_1 );detector->detect ( img_2,keypoints_2 );//-- 第二步:根据角点位置计算 BRIEF 描述子descriptor->compute ( img_1, keypoints_1, descriptors_1 );descriptor->compute ( img_2, keypoints_2, descriptors_2 );//-- 第三步:对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离vector<DMatch> match;// BFMatcher matcher ( NORM_HAMMING );matcher->match ( descriptors_1, descriptors_2, match );//-- 第四步:匹配点对筛选double min_dist=10000, max_dist=0;//找出所有匹配之间的最小距离和最大距离, 即是最相似的和最不相似的两组点之间的距离for ( int i = 0; i < descriptors_1.rows; i++ ){double dist = match[i].distance;if ( dist < min_dist ) min_dist = dist;if ( dist > max_dist ) max_dist = dist;}printf ( "-- Max dist : %f \n", max_dist );printf ( "-- Min dist : %f \n", min_dist );//当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.for ( int i = 0; i < descriptors_1.rows; i++ ){if ( match[i].distance <= max ( 2*min_dist, 30.0 ) ){matches.push_back ( match[i] );}}
}Point2d pixel2cam ( const Point2d& p, const Mat& K )
{return Point2d(( p.x - K.at<double> ( 0,2 ) ) / K.at<double> ( 0,0 ),( p.y - K.at<double> ( 1,2 ) ) / K.at<double> ( 1,1 ));
}void pose_estimation_3d3d (const vector<Point3f>& pts1,const vector<Point3f>& pts2,Mat& R, Mat& t
)
{Point3f p1, p2;     // center of massint N = pts1.size();for ( int i=0; i<N; i++ ){p1 += pts1[i];p2 += pts2[i];}p1 = Point3f( Vec3f(p1) /  N);p2 = Point3f( Vec3f(p2) / N);vector<Point3f>     q1 ( N ), q2 ( N ); // remove the centerfor ( int i=0; i<N; i++ ){q1[i] = pts1[i] - p1;q2[i] = pts2[i] - p2;}// compute q1*q2^TEigen::Matrix3d W = Eigen::Matrix3d::Zero();for ( int i=0; i<N; i++ ){W += Eigen::Vector3d ( q1[i].x, q1[i].y, q1[i].z ) * Eigen::Vector3d ( q2[i].x, q2[i].y, q2[i].z ).transpose();}cout<<"W="<<W<<endl;// SVD on WEigen::JacobiSVD<Eigen::Matrix3d> svd ( W, Eigen::ComputeFullU|Eigen::ComputeFullV );Eigen::Matrix3d U = svd.matrixU();Eigen::Matrix3d V = svd.matrixV();if (U.determinant() * V.determinant() < 0){for (int x = 0; x < 3; ++x){U(x, 2) *= -1;}}cout<<"U="<<U<<endl;cout<<"V="<<V<<endl;Eigen::Matrix3d R_ = U* ( V.transpose() );Eigen::Vector3d t_ = Eigen::Vector3d ( p1.x, p1.y, p1.z ) - R_ * Eigen::Vector3d ( p2.x, p2.y, p2.z );// convert to cv::MatR = ( Mat_<double> ( 3,3 ) <<R_ ( 0,0 ), R_ ( 0,1 ), R_ ( 0,2 ),R_ ( 1,0 ), R_ ( 1,1 ), R_ ( 1,2 ),R_ ( 2,0 ), R_ ( 2,1 ), R_ ( 2,2 ));t = ( Mat_<double> ( 3,1 ) << t_ ( 0,0 ), t_ ( 1,0 ), t_ ( 2,0 ) );
}void bundleAdjustment (const vector< Point3f >& pts1,const vector< Point3f >& pts2,Mat& R, Mat& t )
{// 初始化g2otypedef g2o::BlockSolver< g2o::BlockSolverTraits<6,3> > Block;  // pose维度为 6, landmark 维度为 3Block::LinearSolverType* linearSolver = new g2o::LinearSolverEigen<Block::PoseMatrixType>(); // 线性方程求解器Block* solver_ptr = new Block( linearSolver );      // 矩阵块求解器g2o::OptimizationAlgorithmGaussNewton* solver = new g2o::OptimizationAlgorithmGaussNewton( solver_ptr );g2o::SparseOptimizer optimizer;optimizer.setAlgorithm( solver );// vertexg2o::VertexSE3Expmap* pose = new g2o::VertexSE3Expmap(); // camera posepose->setId(0);pose->setEstimate( g2o::SE3Quat(Eigen::Matrix3d::Identity(),Eigen::Vector3d( 0,0,0 )) );optimizer.addVertex( pose );// edgesint index = 1;vector<EdgeProjectXYZRGBDPoseOnly*> edges;for ( size_t i=0; i<pts1.size(); i++ ){EdgeProjectXYZRGBDPoseOnly* edge = new EdgeProjectXYZRGBDPoseOnly(Eigen::Vector3d(pts2[i].x, pts2[i].y, pts2[i].z) );edge->setId( index );edge->setVertex( 0, dynamic_cast<g2o::VertexSE3Expmap*> (pose) );edge->setMeasurement( Eigen::Vector3d(pts1[i].x, pts1[i].y, pts1[i].z) );edge->setInformation( Eigen::Matrix3d::Identity()*1e4 );optimizer.addEdge(edge);index++;edges.push_back(edge);}chrono::steady_clock::time_point t1 = chrono::steady_clock::now();optimizer.setVerbose( true );optimizer.initializeOptimization();optimizer.optimize(10);chrono::steady_clock::time_point t2 = chrono::steady_clock::now();chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>(t2-t1);cout<<"optimization costs time: "<<time_used.count()<<" seconds."<<endl;cout<<endl<<"after optimization:"<<endl;cout<<"T="<<endl<<Eigen::Isometry3d( pose->estimate() ).matrix()<<endl;}

./build/pose_estimation_3d3d  1.png 2.png 1_depth.png 2_depth.png

视觉里程计4(SLAM十四讲ch7)-ICP相关推荐

  1. 视觉SLAM十四讲——ch7

    视觉SLAM十四讲--ch7 ch7视觉里程计 本章目标: 1.理解图像特征点的意义,并掌握在单副图像中提取出特征点及多副图像中匹配特征点的方法 2.理解对极几何的原理,利用对极几何的约束,恢复出图像 ...

  2. SLAM十四讲ch7代码调整(undefined reference to symbol)

    SLAM十四讲ch7代码调整--2021.6.14 1.首先大部分的代码需要在Cmakelists中更新至c++14,否则会出现如下报错 /usr/local/include/g2o/core/bas ...

  3. SLAM十四讲 ch7 orb_self.cpp中402行代码:cv::DMatch m{i1, 0, 256}报错

    orb_self.cpp中402行代码:cv::DMatch m{i, 0, 256}; 报错:warning: narrowing conversion of 'i' from 'size_t {a ...

  4. 视觉SLAM十四讲学习笔记-第七讲-视觉里程计-三角测量和实践

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

  5. 视觉SLAM十四讲学习笔记-第七讲-视觉里程计-对极几何和对极约束、本质矩阵、基础矩阵

    专栏系列文章如下:  专栏汇总 视觉SLAM十四讲学习笔记-第一讲_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习笔记-第二讲-初识SLAM_goldqiu的博客-CSDN博客 视觉SLA ...

  6. 视觉SLAM十四讲学习笔记-第七讲-视觉里程计-特征点法和特征提取和匹配实践

    专栏系列文章如下: 视觉SLAM十四讲学习笔记-第一讲_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习笔记-第二讲-初识SLAM_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习 ...

  7. 【slam十四讲第二版】【课本例题代码向】【第七讲~视觉里程计Ⅱ】【使用LK光流(cv)】【高斯牛顿法实现单层光流和多层光流】【实现单层直接法和多层直接法】

    [slam十四讲第二版][课本例题代码向][第七讲~视觉里程计Ⅱ][使用LK光流(cv)][高斯牛顿法实现单层光流和多层光流][实现单层直接法和多层直接法] 0 前言 1 使用LK光流(cv) 1.1 ...

  8. 视觉SLAM十四讲学习笔记-第七讲-视觉里程计-PnP和实践

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

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

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

最新文章

  1. Stream Processing: Apache Kafka的Exactly-once的定义 原理和实现
  2. markdown python整段话_(7)python少儿编程之基础语法(二)
  3. 【Python】电商用户复购数据实战:图解Pandas的移动函数shift
  4. C++运算符重载(10)
  5. linux 嵌入式 快照_Linux 系统之Systemd
  6. 11.1-全栈Java笔记:多线程技术的基本概念
  7. Python类的成员
  8. 网络工程师Day10 以太网接口和链路配置
  9. WiFi Explorer Mac版WiFi管理器常见问题解答
  10. Win10画图实用小功能------反色
  11. 手机APP测试类型与方法
  12. python查询水果价格_C语言查询水果价格
  13. 房屋租赁合同无效条件包括哪些
  14. 记录遇到的小问题:Google浏览器在搜索时自动出现搜索记录的问题
  15. python提交表单发邮件_通过Mailgun和Python发送带有表单数据的电子邮件
  16. Large scale GAN training for high fidelity natural image synthesis解读
  17. PIC16F15323单片机 (中断与定时器Timer0)
  18. Access安全吗?Access安全性之QA详解
  19. 网站历史博物馆来过反爬
  20. 使用Dronekit控制无人机,DroneKit配置

热门文章

  1. 什么是办公自动化(OA)?
  2. BIOS功能调用表格
  3. Java 提供给第三方使用接口方法
  4. word文档加密、只读
  5. 【更新】MindFusion.WinForms Pack v2019.R1发布,改进Visio2013Exporter
  6. saas商城跟源码商城对比优势在哪里
  7. win7 桌面计算机不显示器,教你解决win7检测不到第二个显示器的方法
  8. linux查看进程grep工作组,Linux下查看一个进程打开了哪...-linux 如何找到进程的工作目录...-使用 grep 恢复误删的文本文件_169IT.COM...
  9. 牛客网数字化招聘解决方案,支持10万人同时在线笔试
  10. python 添加半透明水印_怎么利用python给图片添加半透明水印