手写rgbd-slam 并用TUM数据集进行测试以及用evo评价结果

这篇博客主要介绍自己写的一个rgbd的前端里程计,并用TUM的数据集(包括ROS和非ROS的接口)进行测试。代码在这里

原理

前端里程计主要借助opencv的pnp算法实现,具体的思路是对每一帧提取特征,然后根据深度图计算出这些特征对应的三维点。对新来的图像做tracking,找到对应的特征,然后求解pnp就可以计算出位姿。对所有相邻的帧之间都进行这样的计算,就得到了视觉里程计。。果然还是很简单粗暴的实现啊,没有建图,没有优化,作为一个初始框架,以后有机会再往里添东西。

代码部分

除去主文件还剩视觉里程计和数据接口两个代码,先看接口部分的代码。

数据接口

接口部分完成了对TUM数据(ROS格式和非ROS格式)的读取。需要注意两种格式的深度数据不一样,ROS是直接的深度值,32位的float,无效点是nan
float d = depth_.ptr<float>(m)[n];
而非ROS的是16位的ushort,无效点是0
ushort d = depth_.ptr<ushort>(m)[n];

非ROS

在使用TUM非ROS数据前,首先要进行时间戳对齐,使用TUM提供的associate.py文件,这个可以直接在官网下载到,然后在数据集的路径下依次执行:
python associate.py rgb.txt depth.txt > associate.txt
python associate.py associate.txt groundtruth.txt > allData.txt
最后得到时间对齐后的所有数据,包括groundtruth。groundtruth主要是用来读取第一帧的世界坐标,因为最后用evo与groundtruth比较的时候,需要结果同样是groundtruth的世界坐标系,而不是我们计算的相对第一帧的世界坐标系
读allData.txt的代码如下,比较简单,就不做解释了。。。

void readAssocTextfile(std::string filename,
                       std::vector<std::string>& inputRGBPaths,
                       std::vector<std::string>& inputDepthPaths,
                       std::vector<std::string>& inputTimeStamps,
                       std::vector<float>& pose) {
    std::string line;
    std::ifstream in_stream(filename.c_str());
    
    bool readPose(false);
    while (!in_stream.eof()) {
        std::getline(in_stream, line);
        std::stringstream ss(line);
        std::string buf;
        int count = 0;
        while (ss >> buf) {
            count++;
            if (count == 2) {
                inputRGBPaths.push_back(buf);
            } else if (count == 4) {
                inputDepthPaths.push_back(buf);
            } else if (count == 1) {
                inputTimeStamps.push_back(buf);
            } 
            if(count == 4){
                //get the first frame pose in the world
                if(readPose)
                    break;
                else{
                    ss >> buf;
                    for(int i=0; i<7; i++){
                        ss >> buf;
                        pose.push_back(atof(buf.c_str()));
                        std::cout<<pose[i]<<" ";
                    }
                    std::cout<<std::endl;
                    readPose = true;
                }
            }
        }
    }
    in_stream.close();
}
ROS

ROS版本的数据读取就是topic的方式了,因为topic就是按照时间戳记录的,所以不需要做对齐,在回调函数里将图像传给VO,这部分在调试的过程中,比较担心因为计算VO比较费时而导致丢数据,但是因为这个实现简单粗暴,并不很费时间,所以可以实际测试并没有丢。

void dataPrepare::colorCallback(const sensor_msgs::ImageConstPtr& msg)
{
    //ROS_INFO("color  callback open");
    //cout<<msg->header.stamp<<endl;
    double h_time = msg->header.stamp.toSec();
    try
    {
        color_ = cv_bridge::toCvShare(msg, "bgr8")->image;
        vo_.setColor(color_);
        // color msg comes later 
        if(!vo_.isInitialized()){
            vo_.init();
        }
        else{
            cv::Mat pose = vo_.estimatePose(h_time);   
    }
    catch (cv_bridge::Exception& e)
    {
        ROS_ERROR("Could not convert from '%s' to 'bgr8'.", msg->encoding.c_str());
    }
    //ROS_INFO("color    callback end");
}
void dataPrepare::depthCallback(const sensor_msgs::ImageConstPtr& msg)
{
    //ROS_INFO("depth  callback open");
    //cout<<msg->header.stamp<<endl;
    try
    {
        depth_ = cv_bridge::toCvShare(msg, sensor_msgs::image_encodings::TYPE_32FC1)->image;
        vo_.setDepth(depth_);
    }
    catch (cv_bridge::Exception& e)
    {
        ROS_ERROR("Could not convert from '%s' to 'mono16'.", msg->encoding.c_str());
    }
    //ROS_INFO("callback end");
}

还有一部分是完成了发布odom以及发布点云,可以通过RVIZ来显示结果。

VO

VO的实现也是针对ROS与非ROS编写会有所不同,但内容相差不大,这里就只放一种

void VO::playSequence(const std::vector<std::string>& inputRGBPaths,
                      const std::vector<std::string>& inputDepthPaths,
                      const std::vector<std::string>& inputTimeStamps) {

    cv::Mat color = cv::imread(inputRGBPaths[0]);
    cv::Mat depth = cv::imread(inputDepthPaths[0], -1);
    
	cv::Mat& ref_img  = color;

	vector<cv::Point3f> mapPoints;
	vector<cv::Point2f> featurePoints;

	extractKeyPoints(color, depth, mapPoints, featurePoints);

	cv::Mat curImg;
	cv::Mat traj = cv::Mat::zeros(600, 600, CV_8UC3);

    std::string fname = "voPoses.txt";
    std::ofstream f;
    f.open(fname.c_str(), std::fstream::out);
	for(int i=1; i<inputRGBPaths.size(); i++){

		curImg = cv::imread(inputRGBPaths[i]);
		cv::imshow( "image", curImg);
		
		std::vector<cv::Point3f> mapPointsRef;
		std::vector<cv::Point2f> featurePointsRef;

		tracking(ref_img, curImg, featurePoints, mapPoints, mapPointsRef, featurePointsRef);

		if(mapPointsRef.size() == 0) 
		    continue;

		cv::Mat dist_coeffs = cv::Mat::zeros(4, 1, CV_64F);
		cv::Mat rvec, tvec;
		
		vector<int> inliers;
		//3d frame to 2d frame
 		cv::solvePnPRansac(mapPointsRef, featurePointsRef, K_, dist_coeffs,rvec, tvec, false, 100, 8.0, 0.99, inliers);

		if(inliers.size() < 5) 
		    continue;

		float inliers_ratio = inliers.size()/float(mapPointsRef.size());

		//cout << "inliers ratio: " << inliers_ratio << endl;

		cv::Mat R_matrix;
		cv::Rodrigues(rvec,R_matrix); 
		R_matrix = R_matrix.t();
		cv::Mat t_vec = -R_matrix*tvec;

		cv::Mat inv_transform = cv::Mat::eye(4,4,CV_64F);
		R_matrix.copyTo(inv_transform.rowRange(0,3).colRange(0,3));
		t_vec.copyTo(inv_transform.rowRange(0,3).col(3));

        cv::Mat depth = cv::imread(inputDepthPaths[i], -1);
	    createNewPoints(curImg, depth, inv_transform, featurePoints, mapPoints);

        //save pose.txt
        std::stringstream strs;
        strs << std::setprecision(6) << std::fixed << inputTimeStamps[i] << " ";
        
        cv::Mat Twc = Two_ * inv_transform;
        
        Eigen::Vector3d trans = toVector3d(Twc.rowRange(0,3).col(3));
        Eigen::Matrix3d rot = toMatrix3d(Twc.rowRange(0,3).colRange(0,3));
        
        f << strs.str() << trans(0) << " " << trans(1) << " " << trans(2) << " ";
        
        Eigen::Quaterniond currentCameraRotation(rot);
        f << currentCameraRotation.x() << " " << currentCameraRotation.y() << " " << currentCameraRotation.z() << " " << currentCameraRotation.w() << "\n";
    
    
		ref_img = curImg;

		// plot the information
		string text  = "Red color: estimated trajectory";

		t_vec.convertTo(t_vec, CV_32F);
		//cout << t_vec.t() << endl;
        //cout<<t_vec.at<float>(0)<<" "<<t_vec.at<float>(1)<<" "<<t_vec.at<float>(2)<<endl;

		cv::Point2f center = cv::Point2f(int(60*t_vec.at<float>(0)) + 300, int(60*t_vec.at<float>(2)) + 100);

		cv::circle(traj, center ,1, cv::Scalar(0,0,255), 2);
		cv::rectangle(traj, cv::Point2f(10, 30), cv::Point2f(550, 50),  cv::Scalar(0,0,0), cv::FILLED);
		putText(traj, text, cv::Point2f(10,50), cv::FONT_HERSHEY_PLAIN, 1, cv::Scalar(0, 0,255), 1, 5);
		cv::imshow( "Trajectory", traj);
		cv::waitKey(1);

	}
	f.close();

}

里面有几个关键函数:

  • extractKeyPoints 提取当前帧的二维特征以及计算对应的三维点;
  • tracking 计算相邻两帧之间的三维点与关键点的匹配;
  • solvePnPRansac 计算pnp求解位姿;
  • createNewPoints 计算下一帧的二维特征以及计算对应的三维点;

整个实现逻辑也比较简单,后面那部分矩阵的计算是转到groundtruth对应的坐标系下,然后就可以进行对比了。

测试

用TUM最最开始的数据集rgbd_dataset_freiburg1_xyz进行测试,结果emmmm当然很感人了。。。希望后续加优化之后,结果能有很大的提升吧
用evo工具得到的结果:
voPoses是自家的算法,rgbdslam是别人家的优秀算法
轨迹对比
xyz对比
rpy对比

分析

当然不能满足于现在这个简单粗暴的版本,在此基础上还有很多改进的地方,最紧要的是先把后端优化以及建图加进去,以及考虑和其他传感器进行融合,这里就先埋下很多坑吧。。。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值