From dd9b45df1b2ae86f8636364386434a74ae053d1a Mon Sep 17 00:00:00 2001 From: yinglongfeng <33148859+yinglongfeng@users.noreply.github.com> Date: Sun, 1 Jul 2018 19:04:02 +0200 Subject: [PATCH 01/10] VOwithoutBA --- ResultsPictures/withoutBA_9.png | Bin 0 -> 68331 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 ResultsPictures/withoutBA_9.png diff --git a/ResultsPictures/withoutBA_9.png b/ResultsPictures/withoutBA_9.png new file mode 100644 index 0000000000000000000000000000000000000000..c157ac138693b2d1a216303b659b9268592c23c8 GIT binary patch literal 68331 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 Date: Sun, 1 Jul 2018 19:05:35 +0200 Subject: [PATCH 02/10] Add files via upload --- testVSLAM.cpp | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 testVSLAM.cpp diff --git a/testVSLAM.cpp b/testVSLAM.cpp new file mode 100644 index 0000000..e46d35f --- /dev/null +++ b/testVSLAM.cpp @@ -0,0 +1,98 @@ +#include +#include + + +int main(int argc, char const *argv[]){ + +// std::cout<<"hello my slam "< groundTruthPoses; + + if (argc >= 6){ + std::string ground_truth_path = argv[5]; + slam.readGroundTruthData(ground_truth_path, num_images, groundTruthPoses); + } + Eigen::Vector3d translGTAccumulated, translEstimAccumulated(1,1,1); + cv::Mat window = cv::Mat::zeros(1000, 1000, CV_8UC3); + cv::Mat prevImageLeft; + std::vector pointsCurrentFrame, pointsPrevFrame; + std::vector keypoints; + cv::Mat descriptors; + Eigen::Matrix3d cumR = Eigen::Matrix3d::Identity(); + + std::string image_name_template = "00000"; + //TO DO read camera_instrinsics_path + std::string camera_instrinsics_path = argv[4]; + Eigen::Matrix3d K = slam.getCameraIntrinsics(camera_instrinsics_path); + double fx = K(0,0); + + int k(1); + std::vector previousFrame2DPoints, currFrame2DPoints; + cv::Mat previousImage; + for(int i=0;i Date: Sun, 1 Jul 2018 19:05:51 +0200 Subject: [PATCH 03/10] Delete testVisualSLAM.cpp --- testVisualSLAM.cpp | 50 ---------------------------------------------- 1 file changed, 50 deletions(-) delete mode 100644 testVisualSLAM.cpp diff --git a/testVisualSLAM.cpp b/testVisualSLAM.cpp deleted file mode 100644 index ebd3bde..0000000 --- a/testVisualSLAM.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "VisualSLAM.h" - -int main(int argc, char** argv){ - - if (argc < 5){ - std::cout << "Usage: ./slam " << std::endl; - exit(1); - } - - std::string input_left_images_path = argv[1]; - std::string input_right_images_path = argv[2]; - std::string camera_intrinsics_path = argv[3]; - int num_images = std::stoi(argv[4]); - std::string image_name_template = "00000"; - - if (num_images <= 0) - { - throw std::runtime_error("The number of image pairs is invalid"); - } - - VisualSLAM slam; - int k = 1; - slam.readCameraIntrisics(camera_intrinsics_path); - for (int i = 0; i < num_images; i++){ - if (i == std::pow(10, k)){ - image_name_template = image_name_template.substr(0, image_name_template.length() - 1); - k++; - } - - std::string image_left_name = input_left_images_path + image_name_template + std::to_string(i) + ".png"; - std::string image_right_name = input_right_images_path + image_name_template + std::to_string(i) + ".png"; - cv::Mat image_left = cv::imread(image_left_name, 0); - cv::Mat image_right = cv::imread(image_right_name, 0); - - if (image_left.cols == 0 || image_left.rows == 0){ - throw std::runtime_error("Cannot read the image with the path: " + image_left_name); - } - - if (image_right.cols == 0 || image_right.rows == 0){ - throw std::runtime_error("Cannot read the image with the path: " + image_right_name); - } - //cv::imshow("Image_left", image_left); - //cv::imshow("Image_right", image_right); - //cv::waitKey(0); - slam.performFrontEndStep(image_left, image_right); - } - - slam.visualizeAllPoses(); - return 0; -} From 820a6e895f54c7a00686b5ef725fcfd22f9a75ab Mon Sep 17 00:00:00 2001 From: yinglongfeng <33148859+yinglongfeng@users.noreply.github.com> Date: Sun, 1 Jul 2018 19:06:13 +0200 Subject: [PATCH 04/10] Add files via upload --- README.md | 39 ++++++++++++--------------------------- 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 763bd2f..f7cc92a 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,12 @@ -# VisualSLAM -Visual-based Navigation project - -## Notes: -1. Please create a feature branch for your work and use master branch only for working solutions. -2. **No build flies, images or executables are allowed** to be commited to the repository. -3. CMake is used to build the project - -## How to build and run the code: -1. Download and install VTK for opencv Viz module: https://www.vtk.org/download/ -2. If you have already installed OpenCV, you need to reinstall it such that OpenCV can find freshly-installed VTK library (credits to https://stackoverflow.com/questions/23559925/how-do-i-use-install-viz-in-opencv?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa) -3. -```bash -mkdir build -cmake .. -make -./slam ../data/left ../data/right 10 - -## only 22 for visualization otherwise we can only see pose data -./slam ../data/left/ ../data/right/ ../data/calib.txt 22 -``` - -## something yinglong has changed: -1. add bundle ajusterment of ceres version -2. performFrontEndStep(), estimatePose3D2D(), so that we optimize the P3d[inlier_index] and P2d[inlier_index] instead of p3d[all] and p2d[all] -3. please take care, I am using old version of Sophus library, I try to change all SE3-->SE3d and #inlclude -->#include - if you complie with the error of sophus, pleas change it as I said or contact me in messenger +# VSLAM_project + +./testVSLAM ../data/left_image/ ../data/right_image/ 10 ../data/calib.txt + + + +#### +./testVSLAM /media/fyl/冯颖龙/vision_based_navigation_linux_disk/kitti_data_set/dataset/sequences/00/image_0/ /media/fyl/冯颖龙/vision_based_navigation_linux_disk/kitti_data_set/dataset/sequences/00/image_1/ 100 ../data/calib.txt ../data/00.txt + + + + From f164b4f38d2b5d7d526673b0427891b2c0a50276 Mon Sep 17 00:00:00 2001 From: yinglongfeng <33148859+yinglongfeng@users.noreply.github.com> Date: Sun, 1 Jul 2018 19:06:36 +0200 Subject: [PATCH 05/10] Add files via upload --- include/BundleAdjuster.h | 79 +++-------------------------------- include/Map.h | 24 +++++++---- include/VisualOdometry.h | 90 +++++++++++++++++----------------------- include/VisualSLAM.h | 55 ++++++++++++++++-------- 4 files changed, 95 insertions(+), 153 deletions(-) diff --git a/include/BundleAdjuster.h b/include/BundleAdjuster.h index 221dc1f..3d59f1c 100644 --- a/include/BundleAdjuster.h +++ b/include/BundleAdjuster.h @@ -1,82 +1,13 @@ -// * Class BundleAdjuster is responsible for 3D structure and camera pose optimizations -#include -#include -#include -using namespace Eigen; -#include -#include -#include -#include -#include -#include -#include -#include -using namespace std; -#include -#include -#include - -using ceres::AutoDiffCostFunction; -using ceres::CostFunction; -using ceres::Problem; -using ceres::Solver; -using ceres::Solve; - -//#include "VisualSLAM.h" - - - -struct ReprojectionError3D -{ - ReprojectionError3D(double observed_u, double observed_v) - :observed_u(observed_u), observed_v(observed_v) - {} - - template - bool operator()(const T* const camera_R, const T* const camera_T, const T* point, T* residuals) const - { - T p[3]; - /***** code from hk technology */ - ceres::QuaternionRotatePoint(camera_R, point, p); - p[0] += camera_T[0]; - p[1] += camera_T[1]; - p[2] += camera_T[2]; - - double fx=718.856; - double fy=718.856; - double cx=607.193; - double cy=185.216; - T xp = p[0]*fx*1./ p[2] +cx; - T yp = p[1]*fy*1./ p[2] +cy; - ///////////////////////////////////////////////////////////////////// - residuals[0] = xp - T(observed_u); - residuals[1] = yp - T(observed_v); - return true; - } - - static ceres::CostFunction* Create(const double observed_x, - const double observed_y) - { - return (new ceres::AutoDiffCostFunction< - ReprojectionError3D, 2, 4, 3, 3>( - new ReprojectionError3D(observed_x,observed_y))); - } - - double observed_u; - double observed_v; -}; - -class BundleAdjuster { +class BundleAdjuster{ +private: public: - BundleAdjuster(); -// int get_camera_parameter(); - Sophus::SE3d optimizeLocalPoseBA_ceres(std::vector p3d,std::vector p2d,Eigen::Matrix3d K,Sophus::SE3d pose,int iteration_times); - Sophus::SE3d optimizeLocalPoseBA_g2o(std::vector p3d,std::vector p2d,Eigen::Matrix3d K,Sophus::SE3d pose,int iteration_times); + BundleAdjuster() {}; -}; + //TO DO Bundle Adjustment +}; \ No newline at end of file diff --git a/include/Map.h b/include/Map.h index 1d87e03..e39e647 100644 --- a/include/Map.h +++ b/include/Map.h @@ -1,14 +1,20 @@ -#include +#include #include -/** - * Class Map is responsible for storing the 3D structure - */ -class Map { + + +class Map{ private: - std::vector structure3D; + int test_a; + std::vector cumPose; + int poseIndex; public: - Map(); - void updateStructure3d(); // update 3D point locations -}; + Map(); + int getValue(); + + void updateCumPose(Sophus::SE3 newPose); + void updatePoseIndex(); + + std::vector getCumPose(); +}; \ No newline at end of file diff --git a/include/VisualOdometry.h b/include/VisualOdometry.h index 1679cc1..5f16cb3 100644 --- a/include/VisualOdometry.h +++ b/include/VisualOdometry.h @@ -1,54 +1,40 @@ -#include -#include -#include //2 -#include -#include - -#include -#include -#include -#include +#include +#include +// opencv +#include +#include #include +#include +//#include +#include #include - -#include -#include -#include - -struct KeyFrame { - cv::Mat image; - cv::Mat disparity_map; - std::vector keypoints; - cv::Mat descriptor; -}; - -class VisualOdometry { - private: - KeyFrame refFrame; - Sophus::SE3d pose; - - public: - VisualOdometry(); - void setReferenceFrame(const cv::Mat image, const cv::Mat disparity, const std::vector keypoints, const cv::Mat descriptor); - KeyFrame getReferenceFrame() const; - - void setPose(const Sophus::SE3d pose); - Sophus::SE3d getPose() const; - - cv::Mat getDisparityMap(const cv::Mat image_left, const cv::Mat image_right); - cv::Rect computeROIDisparityMap(cv::Size2i src_sz, cv::Ptr matcher_instance); - - void extractORBFeatures(cv::Mat frame_new, std::vector& keypoints_new, cv::Mat& descriptors_new); - std::vector findGoodORBFeatureMatches(std::vector keypoints_new, cv::Mat descriptors_new); - - void get3D2DCorrespondences(std::vector keypoints_new, std::vector matches, std::vector& p3d, std::vector& p2d, - cv::Mat disparity_map, Eigen::Matrix3d K); - void get2D2DCorrespondences(std::vector keypoints_new, std::vector matches, std::vector& p2d_1, std::vector& p2d_2); - - std::vector estimatePose3D2D(std::vector p3d, std::vector p2d, Eigen::Matrix3d K); - void estimatePose2D2D(std::vector p2d_1, std::vector p2d_2, Eigen::Matrix3d K); - - void trackFeatures(); - void computeAndShowPointCloud(const cv::Mat image_left, const cv::Mat disparity, const float baseline, Eigen::Matrix3d K); - -}; +// eigen +#include +#include +//sophus +#include + +class VisualOdometry{ +private: +// std::vector status; + float baseline = 0.53716; + cv::Mat disparityMap; +// std::vector historyPose; +public: + VisualOdometry() {}; + + //TO DO harrisDection + //TO DO featureTracking + std::vector corr2DPointsFromPreFrame2DPoints(cv::Mat previousImage, cv::Mat currImage, + std::vector& previousFrame2DPoints, + std::vector& currFrame2DPoints); + //TO DO getDisparityMap + cv::Rect computeROIDisparityMap(cv::Size2i src_sz, cv::Ptr matcher_instance); + void generateDisparityMap(const cv::Mat image_left, const cv::Mat image_right); + //TO DO getDepth currFrame2DPoints + std::vector getDepth3DPointsFromCurrImage(std::vector& currFrame2DPoints,Eigen::Matrix3d K); + + //TO DO poseEstimate2D3DPnp + Sophus::SE3 poseEstimate2D3DPNP(std::vector& p3d, std::vector& p2d,Eigen::Matrix3d K); + +}; \ No newline at end of file diff --git a/include/VisualSLAM.h b/include/VisualSLAM.h index ef26d38..994af47 100644 --- a/include/VisualSLAM.h +++ b/include/VisualSLAM.h @@ -1,28 +1,47 @@ -#include "Map.h" +#include +#include +#include +// project file #include "BundleAdjuster.h" +#include "Map.h" #include "VisualOdometry.h" +// eigen file +#include +#include +// opencv file +#include +#include +#include +#include +#include "opencv2/imgproc/imgproc.hpp" +// Sophus +#include -#include // std::cout -#include // std::ifstream -class VisualSLAM { +class VisualSLAM{ private: -// Map map; BundleAdjuster BA; + Map map; VisualOdometry VO; Eigen::Matrix3d K; + std::vector groundTruthData; + - std::vector historyPoses; public: - VisualSLAM(); - Sophus::SE3d getPose(int index); - int getNumberPoses() const; - Eigen::Matrix3d getCameraMatrix() const; - double getFocalLength() const; - - void readCameraIntrisics(std::string camera_intrinsics_file); - void performFrontEndStep(cv::Mat left_image, cv::Mat right_image); // feature detection / tracking and matching - void runBackEndRoutine(); // optimization over parameters.numImagesBundleAdjustment images - void update(); // update map and poses - void visualizeAllPoses(); -}; + VisualSLAM(); + int getTestValueFromMap(); + //TO DO get camera intrinsics + Eigen::Matrix3d getCameraIntrinsics(std::string camera_intrinsics_path); + Sophus::SE3 getPose(int k ); + + + //TO DO estimate3D2DFrontEndWithOpicalFlow() + Sophus::SE3 estimate3D2DFrontEndWithOpicalFlow(cv::Mat leftImage_, cv::Mat rightImage, std::vector + &previousFrame2DPoints, std::vector&currFrame2DPoints,cv::Mat& previousImage); + + void readGroundTruthData(std::string fileName, int numberFrames, std::vector& groundTruthData); + + void plotTrajectoryNextStep(cv::Mat& window, int index, Eigen::Vector3d& translGTAccumulated, Eigen::Vector3d& translEstimAccumulated, + Sophus::SE3 groundTruthPose, Sophus::SE3 groundTruthPrevPose, Eigen::Matrix3d& cumR, Sophus::SE3 pose, + Sophus::SE3 prevPose = Sophus::SE3(Eigen::Matrix3d::Identity(), Eigen::Vector3d(0,0,0))); +}; \ No newline at end of file From bfed61e8fdcea9f58b0eec02485975dea211fee9 Mon Sep 17 00:00:00 2001 From: yinglongfeng <33148859+yinglongfeng@users.noreply.github.com> Date: Sun, 1 Jul 2018 19:07:10 +0200 Subject: [PATCH 06/10] VOwithoutBA --- src/BundleAdjuster.cpp | 175 ------------------- src/Map.cpp | 30 +++- src/VisualOdometry.cpp | 333 ++++++++++++++---------------------- src/VisualSLAM.cpp | 375 +++++++++++++++++++---------------------- 4 files changed, 327 insertions(+), 586 deletions(-) diff --git a/src/BundleAdjuster.cpp b/src/BundleAdjuster.cpp index c0a0e89..60319c1 100644 --- a/src/BundleAdjuster.cpp +++ b/src/BundleAdjuster.cpp @@ -1,177 +1,2 @@ #include "BundleAdjuster.h" -typedef Eigen::Matrix Vector6d; -typedef Eigen::Matrix Matrix66d; - -BundleAdjuster::BundleAdjuster() {} - -Sophus::SE3d BundleAdjuster::optimizeLocalPoseBA_ceres(std::vector p3d,std::vector p2d, Eigen::Matrix3d K,Sophus::SE3d pose,int numberIterations) -{ - assert(p3d.size() == p2d.size()); - - Sophus::SE3d T_esti(pose); // estimated pose - Eigen::Matrix3d R = T_esti.so3().matrix(); - Eigen::Vector3d t = T_esti.translation(); - - Eigen::Quaterniond q_rotation(R) ; -// cout<<"Quaterniond: "< p3d,std::vector p2d, Eigen::Matrix3d K,Sophus::SE3 pose,int numberIterations){ -// assert(p3d.size() == p2d.size()); -// double cost = 0, lastCost = 0; -// int nPoints = p3d.size(); - -// double fx = K(0,0); -// double fy = K(1,1); - -// Sophus::SE3 T_esti(pose); // estimated pose - -// for (int iter = 0; iter < numberIterations; iter++) { -// Matrix66d H = Matrix66d::Zero(); -// Vector6d b = Vector6d::Zero(); -// cost = 0; -// // compute cost -// for (int i = 0; i < nPoints; i++) { -// // compute cost for p3d[I] and p2d[I] -// Eigen::Vector3d point3DEigen(p3d[i].x, p3d[i].y, p3d[i].z); - -// Eigen::Vector2d point2DEigen(p2d[i].x, p2d[i].y); - -// Eigen::Vector3d pointTransformed = T_esti*point3DEigen; - -// Eigen::Vector3d pointReprojected = K * pointTransformed; -// pointReprojected /= pointTransformed[2]; - -// Eigen::Vector2d e(0,0); // error -// e[0]=point2DEigen[0] - pointReprojected[0]; -// e[1]=point2DEigen[1] - pointReprojected[1]; - -// // compute jacobian -// Eigen::Matrix J; - -// double z = pointTransformed[2]; -// double y = pointTransformed[1]; -// double x = pointTransformed[0]; - -// J(0,0) = fx/z; -// J(0,1) = 0.0; -// J(0,2) = -(fx*x)/(z*z); -// J(0,3) = -(fx*x*y)/(z*z); -// J(0,4) = fx + ((fx*x*x)/(z*z)); -// J(0,5) = -(fx*y)/z; -// J(1,0) = 0.0; -// J(1,1) = fy/z; -// J(1,2) = -(fy*y)/(z*z); -// J(1,3) = -fy - ((fy*y*y)/(z*z)); -// J(1,4) = (fy*x*y)/(z*z); -// J(1,5) = (fy*x)/z; - -// J=-J; - -// H += J.transpose() * J; -// b += -J.transpose() * e; - -// cost += 0.5 *(e[0]*e[0] + e[1]*e[1]); -// // cout<<"cost on line is : "< 0 && cost >= lastCost) { -// // cost increase, update is not good -// std::cout << "cost: " << cost << ", last cost: " << lastCost << std::endl; -// break; -// } - -// // update your estimation -// T_esti = Sophus::SE3::exp(dx)*T_esti; - -// lastCost = cost; - -// std::cout << "iteration " << iter << " cost=" << std::cout.precision(12) << cost << std::endl; -// //std::cout << "estimated pose: \n" << T_esti.matrix() << std::endl; - -// } -// return T_esti; -//} diff --git a/src/Map.cpp b/src/Map.cpp index bfa7253..1fb9516 100644 --- a/src/Map.cpp +++ b/src/Map.cpp @@ -1,7 +1,31 @@ #include "Map.h" -Map::Map() {} -void Map::updateStructure3d() { - // TODO + +Map::Map() { + test_a =666; + poseIndex = 0 ; + +} + +int Map::getValue() { + return test_a; +} + +void Map::updateCumPose(Sophus::SE3 newPose) { + + if (cumPose.empty()){ + cumPose.push_back(newPose); + } + assert(poseIndex == cumPose.size()); + cumPose.push_back(newPose.inverse() * cumPose[poseIndex - 1]); +} + +void Map::updatePoseIndex() { + + poseIndex = poseIndex + 1 ; +} + +std::vector Map::getCumPose() { + return cumPose; } \ No newline at end of file diff --git a/src/VisualOdometry.cpp b/src/VisualOdometry.cpp index 2f5a213..3e89d29 100644 --- a/src/VisualOdometry.cpp +++ b/src/VisualOdometry.cpp @@ -1,33 +1,29 @@ #include "VisualOdometry.h" -#include "BundleAdjuster.h" -//using namespace cv::xfeatures2d; -//using namespace cv; - -VisualOdometry::VisualOdometry(){} - -void VisualOdometry::setReferenceFrame(const cv::Mat image, const cv::Mat disparity, const std::vector keypoints, const cv::Mat descriptor){ - image.copyTo(refFrame.image); - disparity.copyTo(refFrame.disparity_map); - refFrame.keypoints = keypoints; - descriptor.copyTo(refFrame.descriptor); -} - -KeyFrame VisualOdometry::getReferenceFrame() const { - return refFrame; -} - -void VisualOdometry::setPose(const Sophus::SE3d pose){ - this->pose = pose; -} - -Sophus::SE3d VisualOdometry::getPose() const{ - return pose; +//TO DO harrisDection +//TO DO featureTracking +std::vector VisualOdometry::corr2DPointsFromPreFrame2DPoints(cv::Mat previousImage, cv::Mat currImage, + std::vector &previousFrame2DPoints_, + std::vector &currFrame2DPoints) { + // Parameters for lucas kanade optical flow +// std::vector currFrame2DPoints= previousFrame2DPoints_; + std::vector status; + std::vector err; + cv::Size winSize = cv::Size(31, 31); + int maxLevel = 3; + cv::TermCriteria termcrit(cv::TermCriteria::COUNT|cv::TermCriteria::EPS,20,0.03); + + cv::calcOpticalFlowPyrLK(previousImage, currImage, previousFrame2DPoints_,currFrame2DPoints,status,err,winSize,maxLevel,termcrit,0.001); + std::cout<< "currFrame2DPoints size : "<< currFrame2DPoints.size()< matcher_instance) -{ +cv::Rect VisualOdometry::computeROIDisparityMap(cv::Size2i src_sz, + cv::Ptr matcher_instance) { int min_disparity = matcher_instance->getMinDisparity(); int num_disparities = matcher_instance->getNumDisparities(); int block_size = matcher_instance->getBlockSize(); @@ -42,19 +38,22 @@ cv::Rect VisualOdometry::computeROIDisparityMap(cv::Size2i src_sz, cv::Ptr sgbm = cv::stereo::StereoBinarySGBM::create(min_disparity, number_of_disparities, kernel_size); // setting the penalties for sgbm - sgbm->setP1(8*std::pow(kernel_size, 2)); - sgbm->setP2(32*std::pow(kernel_size, 2)); + + sgbm->setMode(cv::StereoSGBM::MODE_SGBM_3WAY); + sgbm->setP1(8*std::pow(kernel_size,2)); + sgbm->setP2(32*std::pow(kernel_size,2)); sgbm->setMinDisparity(min_disparity); sgbm->setUniquenessRatio(3); sgbm->setSpeckleWindowSize(200); @@ -62,12 +61,11 @@ cv::Mat VisualOdometry::getDisparityMap(const cv::Mat image_left, const cv::Mat sgbm->setDisp12MaxDiff(1); sgbm->setSpekleRemovalTechnique(cv::stereo::CV_SPECKLE_REMOVAL_AVG_ALGORITHM); sgbm->setSubPixelInterpolationMethod(cv::stereo::CV_SIMETRICV_INTERPOLATION); - // setting the penalties for sgbm ROI = computeROIDisparityMap(image_left.size(),sgbm); cv::Ptr wls_filter; wls_filter = cv::ximgproc::createDisparityWLSFilterGeneric(false); - wls_filter->setDepthDiscontinuityRadius(1); + wls_filter->setDepthDiscontinuityRadius(2); sgbm->compute(image_left, image_right, disparity); wls_filter->setLambda(8000.0); @@ -81,193 +79,114 @@ cv::Mat VisualOdometry::getDisparityMap(const cv::Mat image_left, const cv::Mat cv::imshow("filtered disparity", filtered_disp_vis); cv::waitKey(); */ - filtered_disp_vis.convertTo(true_dmap, CV_32F, 1.0/16.0, 0.0); - return true_dmap; -} - -void VisualOdometry::extractORBFeatures(cv::Mat frame_new, std::vector& keypoints_new, cv::Mat& descriptors_new){ - int max_features = 500; - // Detect ORB features and compute descriptors. - cv::Ptr orb = cv::ORB::create(max_features); - orb->detectAndCompute(frame_new, cv::Mat(), keypoints_new, descriptors_new); + filtered_disp_vis.convertTo(true_dmap, CV_32F, 1.0, 0.0); + disparityMap = true_dmap ; } - -std::vector VisualOdometry::findGoodORBFeatureMatches(std::vector keypoints_new, cv::Mat descriptors_new){ - const float good_match_ratio = 0.8; - std::vector matches; - - cv::Ptr matcher = cv::DescriptorMatcher::create("BruteForce-Hamming(2)"); - matcher->match(refFrame.descriptor, descriptors_new, matches); - - // Sort matches by score - std::sort(matches.begin(), matches.end()); - // Remove not so good matches - const int numGoodMatches = matches.size() * good_match_ratio; - matches.erase(matches.begin()+numGoodMatches, matches.end()); - - return matches; -} - -void VisualOdometry::get3D2DCorrespondences(std::vector keypoints_new, std::vector matches, - std::vector& p3d, std::vector& p2d, - cv::Mat disparity_map, Eigen::Matrix3d K){ - if (matches.empty()){ - throw std::runtime_error("get3d2dCorrespondences() : Input vector with keypoint matching is empty"); - } - - double b = 0.53716; - double fx = K(0,0); - double fy = K(1,1); - double cx = K(0,2); - double cy = K(1,2); - - double f = (fx + fy) / 2; - // prepare data - for (auto &m: matches) { - cv::Point2d p1 = refFrame.keypoints[m.queryIdx].pt; - cv::Point2d p2 = keypoints_new[m.trainIdx].pt; - float disparity = disparity_map.at(p2.y, p2.x); - if (disparity){ - double z = f*b/disparity; - double x = z*(p2.x - cx) / fx; - double y = z*(p2.y - cy) / fy; - //std::cout << x << " " << y << " " << z << std::endl; - cv::Point3d p2_3d(x, y, z); - p3d.push_back(p2_3d); - p2d.push_back(p1); +//TO DO get3DPoints +std::vector VisualOdometry::getDepth3DPointsFromCurrImage(std::vector &currFrame2DPoints, + Eigen::Matrix3d K) { + float fx = K(0,0); + float fy = K(1,1); + float cx = K(0,2); + float cy = K(1,2); +// std::cout<<"fx is : "<0 ; + double minValue; + cv::minMaxLoc(disparityMap,&minValue,NULL,NULL,NULL,mask); + std::vector Points; + for (int i = 0; i < currFrame2DPoints.size() ; ++i) { + + cv::Point2f point2D = currFrame2DPoints[i]; + float disparity = disparityMap.at(point2D.y, point2D.x); + if (disparity < 0.1) + { + float neighborsDisparity[4]; + if (point2D.x - 1 > 0){ + neighborsDisparity[0] = disparityMap.at(point2D.y , point2D.x - 1); + } + if (point2D.x + 1 < disparityMap.cols ) { + neighborsDisparity[1] = disparityMap.at(point2D.y , point2D.x + 1); + } + if (point2D.y -1 > 0){ + neighborsDisparity[2] = disparityMap.at(point2D.y-1 , point2D.x ); + + } + if (point2D.y + 1 < disparityMap.rows) { + neighborsDisparity[3] = disparityMap.at(point2D.y+1 , point2D.x ); + } + disparity = (neighborsDisparity[0] + neighborsDisparity[1] + neighborsDisparity[2] + neighborsDisparity[3] +neighborsDisparity[4])/4; } - } - - //computeAndShowPointCloud(refFrame.image, refFrame.disparity_map, b, K); - -} + if (disparity < 0.1 ){ + disparity = minValue; + } + float x,y,z; + z = fx * baseline / disparity; + x = ( point2D.x -cx ) * z /fx; + y = ( point2D.y -cy ) * z /fy; -void VisualOdometry::get2D2DCorrespondences(std::vector keypoints_new, std::vector matches, std::vector& p2d_1, std::vector& p2d_2){ - if (matches.empty()){ - throw std::runtime_error("get2d2dCorrespondences() : Input vector with keypoint matching is empty"); + cv::Point3f Point3D(x , y , z); + Points.push_back(Point3D); } - for (auto &m: matches) { - cv::Point2f p1 = refFrame.keypoints[m.queryIdx].pt; - cv::Point2f p2 = keypoints_new[m.trainIdx].pt; - p2d_1.push_back(p1); - p2d_2.push_back(p2); - } + return Points; } -std::vector VisualOdometry::estimatePose3D2D(std::vector p3d, std::vector p2d, Eigen::Matrix3d K){ - cv::Mat cameraMatrix; - cv::Mat distCoeffs = cv::Mat::zeros(4,1,CV_64F); - cv::Mat rvec,tvec,rot_matrix,inliers; - Eigen::Matrix3d R = Eigen::Matrix3d::Identity(); - Eigen::Vector3d t(0,0,0); - -// std::vector inliers_index; // changed by feng to Mat inliers - - cv::eigen2cv(K, cameraMatrix); - std::cout<<"p3d.size()"< &p3d, std::vector &p2d,Eigen::Matrix3d K) { + + Eigen::Matrix3d R21; + Eigen::Vector3d t21; + cv::Mat K_cv; + cv::eigen2cv(K,K_cv); +// cv::Mat dist_coeffs = cv::Mat::zeros(4,1,cv::DataType::type); // Assuming no lens distortion + cv::Mat dist_coeffs = cv::Mat::zeros(4,1,CV_64F); + cv::Mat rotationVector; + cv::Mat translationVector; + cv::Mat R; + std::vector inliers; + bool result = cv::solvePnPRansac(p3d,p2d,K_cv,dist_coeffs,rotationVector,translationVector, false,100,4.0,0.99,inliers); if (result){ - cv::Rodrigues(rvec, rot_matrix); - cv::cv2eigen(rot_matrix, R); - cv::cv2eigen(tvec,t); + cv::Rodrigues(rotationVector,R); + cv::cv2eigen(R,R21); + cv::cv2eigen(translationVector,t21); } - pose = Sophus::SE3d(R, t); - std::cout << "3D-2D Pnp solved Pose: "< inliers_index; - for ( int i=0; i(i,0)); - } - return inliers_index; - -} - -void VisualOdometry::estimatePose2D2D(std::vector p2d_1, std::vector p2d_2, Eigen::Matrix3d K){ - cv::Mat tvec,rot_matrix; - cv::Mat cameraMatrix; - Eigen::Matrix3d R = Eigen::Matrix3d::Identity(); - Eigen::Vector3d t; - - cv::eigen2cv(K, cameraMatrix); - - double focal = (K(0,0) + K(1,1)) / 2; - cv::Point2d princip_point = cv::Point2d(K(0,2), K(1,2)); + Sophus::SE3 posePnp(R21,t21); +// std::cout<<"pose inverse: "< MAXPoseNorm){ +// posePnp = prevPose; +// } - cv::cv2eigen(rot_matrix, R); - cv::cv2eigen(tvec,t); - - pose = Sophus::SE3d(R, t); - - std::cout << pose.matrix() << std::endl; -} + return posePnp; -void VisualOdometry::trackFeatures(){ - // TODO } -void VisualOdometry::computeAndShowPointCloud(const cv::Mat image_left, const cv::Mat disparity, const float baseline, Eigen::Matrix3d K) { - std::vector> pointcloud; - double fx = K(0,0); - double fy = K(1,1); - double cx = K(0,2); - double cy = K(1,2); - // TODO Compute point cloud using disparity - // NOTE if your computer is slow, change v++ and u++ to v++2 and u+=2 to generate a sparser point cloud - for (int v = 0; v < image_left.rows; v++) - for (int u = 0; u < image_left.cols; u++) { - /// start your code here (~6 lines) - - double z = fx*baseline/(disparity.at(v,u)); - double x = (u - cx)*z / fx; - double y = (v - cy)*z / fy; - - Eigen::Vector4d point(x, y, z, - image_left.at(v, u) / 255.0); // first three components are XYZ and the last is color - pointcloud.push_back(point); - /// end your code here - } - - // draw the point cloud - - pangolin::CreateWindowAndBind("Point Cloud Viewer", 1024, 768); - glEnable(GL_DEPTH_TEST); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - pangolin::OpenGlRenderState s_cam( - pangolin::ProjectionMatrix(1024, 768, 500, 500, 512, 389, 0.1, 1000), - pangolin::ModelViewLookAt(0, -0.1, -1.8, 0, 0, 0, 0.0, -1.0, 0.0) - ); - - pangolin::View &d_cam = pangolin::CreateDisplay() - .SetBounds(0.0, 1.0, pangolin::Attach::Pix(175), 1.0, -1024.0f / 768.0f) - .SetHandler(new pangolin::Handler3D(s_cam)); - - while (pangolin::ShouldQuit() == false) { - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - d_cam.Activate(s_cam); - glClearColor(1.0f, 1.0f, 1.0f, 1.0f); - - glPointSize(2); - glBegin(GL_POINTS); - for (auto &p: pointcloud) { - glColor3f(p[3], p[3], p[3]); - glVertex3d(p[0], p[1], p[2]); - } - glEnd(); - pangolin::FinishFrame(); - usleep(5000); // sleep 5 ms - } - return; -} +//// visualization +//void VisualOdometry::plotTrajectoryNextStep(cv::Mat& window, int index, Eigen::Vector3d& translGTAccumulated, Eigen::Vector3d& translEstimAccumulated, +// Sophus::SE3 groundTruthPose, Sophus::SE3 groundTruthPrevPose, Eigen::Matrix3d& cumR, Sophus::SE3 estimPose, Sophus::SE3 estimPrevPose){ +// int offsetX = 300; +// int offsetY = 300; +// +// Sophus::SE3 pose = estimPose.inverse(); +// Sophus::SE3 prevPose = estimPrevPose.inverse(); +// +// if (index == 0){ +// translGTAccumulated = groundTruthPose.translation(); +// translEstimAccumulated = pose.translation(); +// } else { +// translGTAccumulated = translGTAccumulated + (groundTruthPose.so3().inverse()*groundTruthPrevPose.so3())*(groundTruthPose.translation() - groundTruthPrevPose.translation()); +// translEstimAccumulated = translGTAccumulated + (pose.so3().inverse()*groundTruthPrevPose.so3())*(pose.translation() - prevPose.translation()); +// } +// cv::circle(window, cv::Point2d(offsetX + translGTAccumulated[0], offsetY + translGTAccumulated[2]), 3, cv::Scalar(0,0,255), -1); +// cv::circle(window, cv::Point2f(offsetX + translEstimAccumulated[0], offsetY + translEstimAccumulated[2]), 3, cv::Scalar(0,255,0), -1); +// cv::imshow("Trajectory", window); +// cv::waitKey(3); +// cumR = cumR*pose.so3().matrix(); +//} \ No newline at end of file diff --git a/src/VisualSLAM.cpp b/src/VisualSLAM.cpp index 97b7407..56bcc71 100644 --- a/src/VisualSLAM.cpp +++ b/src/VisualSLAM.cpp @@ -1,231 +1,204 @@ #include "VisualSLAM.h" -VisualSLAM::VisualSLAM() { - K = Eigen::Matrix3d::Identity(); -} - -Sophus::SE3d VisualSLAM::getPose(int index) { - if (index < 0 || index >= historyPoses.size()){ - throw std::runtime_error("VisualSLAM::getPose() : Index out of bounds"); - } - - return historyPoses.at(index); -} +VisualSLAM::VisualSLAM() {} -int VisualSLAM::getNumberPoses() const{ - return historyPoses.size(); +int VisualSLAM::getTestValueFromMap() { + return map.getValue(); } -Eigen::Matrix3d VisualSLAM::getCameraMatrix() const { +//TO DO get camera intrinsics +Eigen::Matrix3d VisualSLAM::getCameraIntrinsics(std::string camera_intrinsics_path) { +// std::cout<<"camera_intrinsics_path is: "<>number>>fx>>K01>>cx>>K10>>K11>>fy>>cy>>K21>>K22; + K << fx , 0 , cx , + 0 , fy ,cy , + 0 , 0 , 1 ; +// std::cout<<"camera intrinsics: "<< K << std::endl; return K; + } -double VisualSLAM::getFocalLength() const { - return (K(0,0) + K(1,1)) / 2.0; -} - -void VisualSLAM::readCameraIntrisics(std::string camera_file_path){ - std::ifstream file; - file.open(camera_file_path, std::ifstream::in); - - if (!file){ - throw std::runtime_error("Cannot read the file with camera intrinsics"); - } +void VisualSLAM::readGroundTruthData(std::string fileName, int numberFrames, std::vector& groundTruthData){ + std::ifstream inFile; + inFile.open(fileName, std::ifstream::in); - std::string prefix; - double data[12]; - - file >> prefix; - for (int i = 0; i < 12; i++){ - file >> data[i]; - } - - // fx, fy, cx, cy - K(0,0) = data[0]; - K(1,1) = data[5]; - K(0,2) = data[2]; - K(1,2) = data[6]; + if (!inFile){ + throw std::runtime_error("readGroundTruthData() : Cannot read the file with ground truth data"); + } + if (numberFrames <= 0){ + throw std::runtime_error("readGroundTruthData() : Number of frames is non-positive!"); + } + groundTruthData.clear(); + + int i = 0; + while(i < numberFrames && !inFile.eof()){ + double rotationElements[9], translationElements[3]; + int k = 0; + for (int j = 1; j <= 12; j++){ + if (j % 4 == 0){ + inFile >> translationElements[j / 4 - 1]; + } else { + inFile >> rotationElements[k++]; + } + } + cv::Mat R_CV = cv::Mat(3,3, CV_64F, rotationElements); + Eigen::Matrix3d R_Eigen; + cv::cv2eigen(R_CV, R_Eigen); + Sophus::SE3 newPose = Sophus::SE3(Eigen::Quaterniond(R_Eigen), Eigen::Vector3d(translationElements)); + groundTruthData.push_back(newPose); + i++; + } } -void VisualSLAM::performFrontEndStep(cv::Mat image_left, cv::Mat image_right){ - std::vector keypoints_new; - cv::Mat descriptors_new; - - VO.extractORBFeatures(image_left, keypoints_new, descriptors_new); - KeyFrame refFrame = VO.getReferenceFrame(); - cv::Mat disparity_map = VO.getDisparityMap(image_left, image_right); - if (refFrame.keypoints.empty()){ - VO.setReferenceFrame(image_left, disparity_map, keypoints_new, descriptors_new); - return; - } +//TO DO poseEstimate3D2DFrontEndWithOpicalFlow() return pose + //TO DO harrisDection + //TO DO featureTracking + //TO DO poseEstimate2D3DPnp + //TO DO reInitialize +Sophus::SE3 VisualSLAM::estimate3D2DFrontEndWithOpicalFlow(cv::Mat leftImage_, cv::Mat rightImage, + std::vector &previousFrame2DPoints, + std::vector &currFrame2DPoints, + cv::Mat& previousImage) { + cv::Mat leftImage = leftImage_; + Sophus::SE3 pose; + int maxCorners=500; + cv::Size subPixel(10,10); + cv::TermCriteria termcrit(cv::TermCriteria::COUNT|cv::TermCriteria::EPS,20,0.03); + + if( previousFrame2DPoints.empty()){ + std::cout<<"The first image "< p3d; + VO.generateDisparityMap(leftImage,rightImage); + p3d = VO.getDepth3DPointsFromCurrImage(currFrame2DPoints,K); + int maxDistance = 150 ; + for (int i = 0; i maxDistance ){ +// std::cout<<"depth "< matches = VO.findGoodORBFeatureMatches(keypoints_new, descriptors_new); + map.updatePoseIndex(); + map.updateCumPose(pose); - // Draw top matches + leftImage.copyTo(previousImage); + previousFrame2DPoints.clear(); + previousFrame2DPoints=currFrame2DPoints; +// currFrame2DPoints.clear(); + return pose; + } + //TO DO featureTracking + std::vector status; + status = VO.corr2DPointsFromPreFrame2DPoints(previousImage,leftImage,previousFrame2DPoints,currFrame2DPoints); + std::vector trackedCurrFrame2DPoints , trackedPreviousFrame2DPoints; + for (int i = 0; i p3DCurrFrame; + VO.generateDisparityMap(leftImage,rightImage); + p3DCurrFrame = VO.getDepth3DPointsFromCurrImage(trackedCurrFrame2DPoints,K); +// int maxDistance = 150 ; +// for (int i = 0; i maxDistance ){ +//// std::cout<<"depth "< p3d_prevFrame; - std::vector p2d_currFrame; - std::vector inlier_index; - VO.get3D2DCorrespondences(keypoints_new, matches, p3d_prevFrame, p2d_currFrame, disparity_map, K); + std::cout<<"previousFrame2DPoints size "< p3d_prevFrame_inlier; - std::vector p2d_currFrame_inlier; - for(int i=0;i(); -// glMultMatrixf((GLfloat *) m.data()); -// glColor3f(1, 0, 0); -// glLineWidth(2); -// glBegin(GL_LINES); -// glVertex3f(0, 0, 0); -// glVertex3f(sz * (0 - cx) / fx, sz * (0 - cy) / fy, sz); -// glVertex3f(0, 0, 0); -// glVertex3f(sz * (0 - cx) / fx, sz * (height - 1 - cy) / fy, sz); -// glVertex3f(0, 0, 0); -// glVertex3f(sz * (width - 1 - cx) / fx, sz * (height - 1 - cy) / fy, sz); -// glVertex3f(0, 0, 0); -// glVertex3f(sz * (width - 1 - cx) / fx, sz * (0 - cy) / fy, sz); -// glVertex3f(sz * (width - 1 - cx) / fx, sz * (0 - cy) / fy, sz); -// glVertex3f(sz * (width - 1 - cx) / fx, sz * (height - 1 - cy) / fy, sz); -// glVertex3f(sz * (width - 1 - cx) / fx, sz * (height - 1 - cy) / fy, sz); -// glVertex3f(sz * (0 - cx) / fx, sz * (height - 1 - cy) / fy, sz); -// glVertex3f(sz * (0 - cx) / fx, sz * (height - 1 - cy) / fy, sz); -// glVertex3f(sz * (0 - cx) / fx, sz * (0 - cy) / fy, sz); -// glVertex3f(sz * (0 - cx) / fx, sz * (0 - cy) / fy, sz); -// glVertex3f(sz * (width - 1 - cx) / fx, sz * (0 - cy) / fy, sz); -// glEnd(); -// glPopMatrix(); -// } +// visualization +void VisualSLAM::plotTrajectoryNextStep(cv::Mat& window, int index, Eigen::Vector3d& translGTAccumulated, Eigen::Vector3d& translEstimAccumulated, + Sophus::SE3 groundTruthPose, Sophus::SE3 groundTruthPrevPose, Eigen::Matrix3d& cumR, Sophus::SE3 estimPose, Sophus::SE3 estimPrevPose){ + int offsetX = 300; + int offsetY = 300; -// pangolin::FinishFrame(); -// usleep(5000); // sleep 5 ms -// } -//} -void VisualSLAM::visualizeAllPoses(){ - // create pangolin window and plot the trajectory - pangolin::CreateWindowAndBind("VisualSLAM Viewer", 1024, 768); - glEnable(GL_DEPTH_TEST); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - pangolin::OpenGlRenderState s_cam( - pangolin::ProjectionMatrix(1024, 768, 500, 500, 512, 389, 0.1, 1000), - pangolin::ModelViewLookAt(0, -0.1, -1.8, 0, 0, 0, 0.0, -1.0, 0.0) - ); - - pangolin::View &d_cam = pangolin::CreateDisplay() - .SetBounds(0.0, 1.0, pangolin::Attach::Pix(175), 1.0, -1024.0f / 768.0f) - .SetHandler(new pangolin::Handler3D(s_cam)); - - double fx = K(0,0); - double fy = K(1,1); - double cx = K(0,2); - double cy = K(1,2); - - while (pangolin::ShouldQuit() == false) - { - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - d_cam.Activate(s_cam); - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - - // draw poses - float sz = 0.1; - int width = 640, height = 480; - for (auto &Tcw: historyPoses) - { - glPushMatrix(); - Sophus::Matrix4f m = Tcw.inverse().matrix().cast(); - glMultMatrixf((GLfloat *) m.data()); - glColor3f(1, 0, 0); - glLineWidth(2); - glBegin(GL_LINES); - glVertex3f(0, 0, 0); - glVertex3f(sz * (0 - cx) / fx, sz * (0 - cy) / fy, sz); - glVertex3f(0, 0, 0); - glVertex3f(sz * (0 - cx) / fx, sz * (height - 1 - cy) / fy, sz); - glVertex3f(0, 0, 0); - glVertex3f(sz * (width - 1 - cx) / fx, sz * (height - 1 - cy) / fy, sz); - glVertex3f(0, 0, 0); - glVertex3f(sz * (width - 1 - cx) / fx, sz * (0 - cy) / fy, sz); - glVertex3f(sz * (width - 1 - cx) / fx, sz * (0 - cy) / fy, sz); - glVertex3f(sz * (width - 1 - cx) / fx, sz * (height - 1 - cy) / fy, sz); - glVertex3f(sz * (width - 1 - cx) / fx, sz * (height - 1 - cy) / fy, sz); - glVertex3f(sz * (0 - cx) / fx, sz * (height - 1 - cy) / fy, sz); - glVertex3f(sz * (0 - cx) / fx, sz * (height - 1 - cy) / fy, sz); - glVertex3f(sz * (0 - cx) / fx, sz * (0 - cy) / fy, sz); - glVertex3f(sz * (0 - cx) / fx, sz * (0 - cy) / fy, sz); - glVertex3f(sz * (width - 1 - cx) / fx, sz * (0 - cy) / fy, sz); - glEnd(); - glPopMatrix(); - } + Sophus::SE3 pose = estimPose.inverse(); + Sophus::SE3 prevPose = estimPrevPose.inverse(); - pangolin::FinishFrame(); - usleep(5000); // sleep 5 ms + if (index == 0){ + translGTAccumulated = groundTruthPose.translation(); + translEstimAccumulated = pose.translation(); + } else { + translGTAccumulated = translGTAccumulated + (groundTruthPose.so3().inverse()*groundTruthPrevPose.so3())*(groundTruthPose.translation() - groundTruthPrevPose.translation()); + translEstimAccumulated = translGTAccumulated + (pose.so3().inverse()*groundTruthPrevPose.so3())*(pose.translation() - prevPose.translation()); } + cv::circle(window, cv::Point2d(offsetX + translGTAccumulated[0], offsetY + translGTAccumulated[2]), 3, cv::Scalar(0,0,255), -1); + cv::circle(window, cv::Point2f(offsetX + translEstimAccumulated[0], offsetY + translEstimAccumulated[2]), 3, cv::Scalar(0,255,0), -1); + cv::imshow("Trajectory", window); + cv::waitKey(3); + cumR = cumR*pose.so3().matrix(); } + +Sophus::SE3 VisualSLAM::getPose(int k) { + if (k<0 || k > map.getCumPose().size()){ + throw std::runtime_error("VisualSLAM::getPose(int k): Index out of the bounds"); + } + return map.getCumPose().at(k); +} \ No newline at end of file From 189e0bffa3301d379452a6c9c8b549cab9704496 Mon Sep 17 00:00:00 2001 From: yinglongfeng <33148859+yinglongfeng@users.noreply.github.com> Date: Sun, 1 Jul 2018 19:07:32 +0200 Subject: [PATCH 07/10] Add files via upload --- CMakeLists.txt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d068b3..bd82858 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,11 @@ cmake_minimum_required(VERSION 2.8) project(VisualSLAM) -set(CMAKE_CXX_FLAGS "-std=c++11 -g -O3 -march=native ") +set(CMAKE_CXX_FLAGS "-std=c++11 -O3 -march=native ") #-O3 -march=native +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) # opencv find_package(OpenCV REQUIRED) include_directories(${OpenCV_INCLUDE_DIRS}) - # pangolin find_package(Pangolin REQUIRED) include_directories(${Pangolin_INCLUDE_DIRS}) @@ -21,7 +21,8 @@ include_directories(${Sophus_INCLUDE_DIRS}) find_package(Ceres REQUIRED) include_directories(${CERES_INCLUDE_DIRS}) +## project h file include_directories("./include") - -add_executable(slam testVisualSLAM.cpp src/VisualSLAM.cpp src/Map.cpp src/VisualOdometry.cpp src/BundleAdjuster.cpp) -target_link_libraries(slam ${OpenCV_LIBS} ${CERES_LIBRARIES} ${Pangolin_LIBRARIES} ${Sophus_LIBRARIES}) +## +add_executable(testVSLAM testVSLAM.cpp src/VisualSLAM.cpp src/VisualOdometry.cpp src/Map.cpp src/BundleAdjuster.cpp) +target_link_libraries(testVSLAM ${OpenCV_LIBS} ${Pangolin_LIBRARIES} ${Sophus_LIBRARIES} ${CERES_INCLUDE_DIRS}) From 8d862bb94c795843682f90e5f311521c7eb5e0e7 Mon Sep 17 00:00:00 2001 From: yinglongfeng <33148859+yinglongfeng@users.noreply.github.com> Date: Sun, 1 Jul 2018 20:36:18 +0200 Subject: [PATCH 08/10] add new results --- ResultsPictures/withoutBA_norm_3.png | Bin 0 -> 6526 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 ResultsPictures/withoutBA_norm_3.png diff --git a/ResultsPictures/withoutBA_norm_3.png b/ResultsPictures/withoutBA_norm_3.png new file mode 100644 index 0000000000000000000000000000000000000000..7031d20e72fb93b90b343f7f53cb10153b67f9e0 GIT binary patch literal 6526 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 J^;!>~{0ERwzLx+1 literal 0 HcmV?d00001 From 79dc82f09ad99f04bb28bd14b5135e5ac37f56b1 Mon Sep 17 00:00:00 2001 From: yinglongfeng <33148859+yinglongfeng@users.noreply.github.com> Date: Sun, 1 Jul 2018 20:37:24 +0200 Subject: [PATCH 09/10] solve the problem of outlier by using pose norm --- include/Map.h | 8 +++---- include/VisualOdometry.h | 10 +++++--- include/VisualSLAM.h | 16 ++++++------- src/Map.cpp | 4 ++-- src/VisualOdometry.cpp | 32 ++++++++++++++++++------- src/VisualSLAM.cpp | 51 ++++++++++++++++++++++++++++++++-------- testVSLAM.cpp | 14 ++++++----- 7 files changed, 93 insertions(+), 42 deletions(-) diff --git a/include/Map.h b/include/Map.h index e39e647..2d58cce 100644 --- a/include/Map.h +++ b/include/Map.h @@ -1,4 +1,4 @@ -#include +#include #include @@ -6,15 +6,15 @@ class Map{ private: int test_a; - std::vector cumPose; + std::vector cumPose; int poseIndex; public: Map(); int getValue(); - void updateCumPose(Sophus::SE3 newPose); + void updateCumPose(Sophus::SE3d newPose); void updatePoseIndex(); - std::vector getCumPose(); + std::vector getCumPose(); }; \ No newline at end of file diff --git a/include/VisualOdometry.h b/include/VisualOdometry.h index 5f16cb3..5d72807 100644 --- a/include/VisualOdometry.h +++ b/include/VisualOdometry.h @@ -12,14 +12,16 @@ #include #include //sophus -#include +#include class VisualOdometry{ private: // std::vector status; float baseline = 0.53716; cv::Mat disparityMap; -// std::vector historyPose; +// std::vector historyPose; + bool reInitial; + int thresholdFeactures=100; public: VisualOdometry() {}; @@ -35,6 +37,8 @@ class VisualOdometry{ std::vector getDepth3DPointsFromCurrImage(std::vector& currFrame2DPoints,Eigen::Matrix3d K); //TO DO poseEstimate2D3DPnp - Sophus::SE3 poseEstimate2D3DPNP(std::vector& p3d, std::vector& p2d,Eigen::Matrix3d K); + Sophus::SE3d poseEstimate2D3DPNP(std::vector& p3d, std::vector& p2d,Eigen::Matrix3d K,Sophus::SE3d prePose); + //TO DO getReIntial + bool getReInital(); }; \ No newline at end of file diff --git a/include/VisualSLAM.h b/include/VisualSLAM.h index 994af47..063be2a 100644 --- a/include/VisualSLAM.h +++ b/include/VisualSLAM.h @@ -15,7 +15,7 @@ #include #include "opencv2/imgproc/imgproc.hpp" // Sophus -#include +#include class VisualSLAM{ @@ -24,7 +24,7 @@ class VisualSLAM{ Map map; VisualOdometry VO; Eigen::Matrix3d K; - std::vector groundTruthData; + std::vector groundTruthData; public: @@ -32,16 +32,16 @@ class VisualSLAM{ int getTestValueFromMap(); //TO DO get camera intrinsics Eigen::Matrix3d getCameraIntrinsics(std::string camera_intrinsics_path); - Sophus::SE3 getPose(int k ); + Sophus::SE3d getPose(int k ); //TO DO estimate3D2DFrontEndWithOpicalFlow() - Sophus::SE3 estimate3D2DFrontEndWithOpicalFlow(cv::Mat leftImage_, cv::Mat rightImage, std::vector - &previousFrame2DPoints, std::vector&currFrame2DPoints,cv::Mat& previousImage); + Sophus::SE3d estimate3D2DFrontEndWithOpicalFlow(cv::Mat leftImage_, cv::Mat rightImage, std::vector + &previousFrame2DPoints, std::vector&currFrame2DPoints,cv::Mat& previousImage,Sophus::SE3d prePose ); - void readGroundTruthData(std::string fileName, int numberFrames, std::vector& groundTruthData); + void readGroundTruthData(std::string fileName, int numberFrames, std::vector& groundTruthData); void plotTrajectoryNextStep(cv::Mat& window, int index, Eigen::Vector3d& translGTAccumulated, Eigen::Vector3d& translEstimAccumulated, - Sophus::SE3 groundTruthPose, Sophus::SE3 groundTruthPrevPose, Eigen::Matrix3d& cumR, Sophus::SE3 pose, - Sophus::SE3 prevPose = Sophus::SE3(Eigen::Matrix3d::Identity(), Eigen::Vector3d(0,0,0))); + Sophus::SE3d groundTruthPose, Sophus::SE3d groundTruthPrevPose, Eigen::Matrix3d& cumR, Sophus::SE3d pose, + Sophus::SE3d prevPose = Sophus::SE3d(Eigen::Matrix3d::Identity(), Eigen::Vector3d(0,0,0))); }; \ No newline at end of file diff --git a/src/Map.cpp b/src/Map.cpp index 1fb9516..9703074 100644 --- a/src/Map.cpp +++ b/src/Map.cpp @@ -12,7 +12,7 @@ int Map::getValue() { return test_a; } -void Map::updateCumPose(Sophus::SE3 newPose) { +void Map::updateCumPose(Sophus::SE3d newPose) { if (cumPose.empty()){ cumPose.push_back(newPose); @@ -26,6 +26,6 @@ void Map::updatePoseIndex() { poseIndex = poseIndex + 1 ; } -std::vector Map::getCumPose() { +std::vector Map::getCumPose() { return cumPose; } \ No newline at end of file diff --git a/src/VisualOdometry.cpp b/src/VisualOdometry.cpp index 3e89d29..29ed3a7 100644 --- a/src/VisualOdometry.cpp +++ b/src/VisualOdometry.cpp @@ -18,10 +18,24 @@ std::vector VisualOdometry::corr2DPointsFromPreFrame2DPoints(cv::Mat prev // trackedCurrFrame2DPoints // std::cout<<" trackedCurrFrame2DPoints size "< matcher_instance) { int min_disparity = matcher_instance->getMinDisparity(); @@ -134,7 +148,7 @@ std::vector VisualOdometry::getDepth3DPointsFromCurrImage(std::vect //TO DO poseEstimate2D3DPnp -Sophus::SE3 VisualOdometry::poseEstimate2D3DPNP(std::vector &p3d, std::vector &p2d,Eigen::Matrix3d K) { +Sophus::SE3d VisualOdometry::poseEstimate2D3DPNP(std::vector &p3d, std::vector &p2d,Eigen::Matrix3d K,Sophus::SE3d prePose ) { Eigen::Matrix3d R21; Eigen::Vector3d t21; @@ -153,15 +167,15 @@ Sophus::SE3 VisualOdometry::poseEstimate2D3DPNP(std::vector &p3d, s cv::cv2eigen(translationVector,t21); } - Sophus::SE3 posePnp(R21,t21); + Sophus::SE3d posePnp(R21,t21); // std::cout<<"pose inverse: "< MAXPoseNorm){ -// posePnp = prevPose; -// } + int MAXPoseNorm = 1; + if (posePnp.log().norm() > MAXPoseNorm){ + posePnp = prePose; + } return posePnp; @@ -170,12 +184,12 @@ Sophus::SE3 VisualOdometry::poseEstimate2D3DPNP(std::vector &p3d, s //// visualization //void VisualOdometry::plotTrajectoryNextStep(cv::Mat& window, int index, Eigen::Vector3d& translGTAccumulated, Eigen::Vector3d& translEstimAccumulated, -// Sophus::SE3 groundTruthPose, Sophus::SE3 groundTruthPrevPose, Eigen::Matrix3d& cumR, Sophus::SE3 estimPose, Sophus::SE3 estimPrevPose){ +// Sophus::SE3d groundTruthPose, Sophus::SE3d groundTruthPrevPose, Eigen::Matrix3d& cumR, Sophus::SE3d estimPose, Sophus::SE3d estimPrevPose){ // int offsetX = 300; // int offsetY = 300; // -// Sophus::SE3 pose = estimPose.inverse(); -// Sophus::SE3 prevPose = estimPrevPose.inverse(); +// Sophus::SE3d pose = estimPose.inverse(); +// Sophus::SE3d prevPose = estimPrevPose.inverse(); // // if (index == 0){ // translGTAccumulated = groundTruthPose.translation(); diff --git a/src/VisualSLAM.cpp b/src/VisualSLAM.cpp index 56bcc71..d5de935 100644 --- a/src/VisualSLAM.cpp +++ b/src/VisualSLAM.cpp @@ -22,7 +22,7 @@ Eigen::Matrix3d VisualSLAM::getCameraIntrinsics(std::string camera_intrinsics_pa } -void VisualSLAM::readGroundTruthData(std::string fileName, int numberFrames, std::vector& groundTruthData){ +void VisualSLAM::readGroundTruthData(std::string fileName, int numberFrames, std::vector& groundTruthData){ std::ifstream inFile; inFile.open(fileName, std::ifstream::in); @@ -49,7 +49,7 @@ void VisualSLAM::readGroundTruthData(std::string fileName, int numberFrames, std cv::Mat R_CV = cv::Mat(3,3, CV_64F, rotationElements); Eigen::Matrix3d R_Eigen; cv::cv2eigen(R_CV, R_Eigen); - Sophus::SE3 newPose = Sophus::SE3(Eigen::Quaterniond(R_Eigen), Eigen::Vector3d(translationElements)); + Sophus::SE3d newPose = Sophus::SE3d(Eigen::Quaterniond(R_Eigen), Eigen::Vector3d(translationElements)); groundTruthData.push_back(newPose); i++; } @@ -61,12 +61,12 @@ void VisualSLAM::readGroundTruthData(std::string fileName, int numberFrames, std //TO DO featureTracking //TO DO poseEstimate2D3DPnp //TO DO reInitialize -Sophus::SE3 VisualSLAM::estimate3D2DFrontEndWithOpicalFlow(cv::Mat leftImage_, cv::Mat rightImage, +Sophus::SE3d VisualSLAM::estimate3D2DFrontEndWithOpicalFlow(cv::Mat leftImage_, cv::Mat rightImage, std::vector &previousFrame2DPoints, std::vector &currFrame2DPoints, - cv::Mat& previousImage) { + cv::Mat& previousImage, Sophus::SE3d prePose ) { cv::Mat leftImage = leftImage_; - Sophus::SE3 pose; + Sophus::SE3d pose; int maxCorners=500; cv::Size subPixel(10,10); cv::TermCriteria termcrit(cv::TermCriteria::COUNT|cv::TermCriteria::EPS,20,0.03); @@ -159,11 +159,42 @@ Sophus::SE3 VisualSLAM::estimate3D2DFrontEndWithOpicalFlow(cv::Mat leftImage_, c std::cout<<"p3d size "< p3d; + VO.generateDisparityMap(leftImage,rightImage); + p3d = VO.getDepth3DPointsFromCurrImage(currFrame2DPoints,K); + int maxDistance = 150 ; + for (int i = 0; i maxDistance ){ +// std::cout<<"depth "< map.getCumPose().size()){ throw std::runtime_error("VisualSLAM::getPose(int k): Index out of the bounds"); } diff --git a/testVSLAM.cpp b/testVSLAM.cpp index e46d35f..eb29f34 100644 --- a/testVSLAM.cpp +++ b/testVSLAM.cpp @@ -25,7 +25,7 @@ int main(int argc, char const *argv[]){ throw std::runtime_error("The number of images is smaller than 0 "); } - std::vector groundTruthPoses; + std::vector groundTruthPoses; if (argc >= 6){ std::string ground_truth_path = argv[5]; @@ -70,25 +70,27 @@ int main(int argc, char const *argv[]){ if(rightImage.cols <= 0 || rightImage.rows <= 0){ throw std::runtime_error("can not read rightImage and its path is : "+ right_image_name); } - Sophus::SE3 estimatedPose; - estimatedPose = slam.estimate3D2DFrontEndWithOpicalFlow(leftImage, rightImage, previousFrame2DPoints, currFrame2DPoints, previousImage); + Sophus::SE3d estimatedPose; + Sophus::SE3d prePose; + estimatedPose = slam.estimate3D2DFrontEndWithOpicalFlow(leftImage, rightImage, previousFrame2DPoints, currFrame2DPoints, previousImage,prePose); + prePose = estimatedPose; //visualization - Sophus::SE3 cumPose; + Sophus::SE3d cumPose; if (i != 0){ cumPose = slam.getPose(i); } std::cout<<"pose "<< i << std::endl< Date: Sun, 1 Jul 2018 20:44:47 +0200 Subject: [PATCH 10/10] Update VisualSLAM.cpp --- src/VisualSLAM.cpp | 60 +++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/VisualSLAM.cpp b/src/VisualSLAM.cpp index d5de935..72fd9bf 100644 --- a/src/VisualSLAM.cpp +++ b/src/VisualSLAM.cpp @@ -164,35 +164,35 @@ Sophus::SE3d VisualSLAM::estimate3D2DFrontEndWithOpicalFlow(cv::Mat leftImage_, map.updatePoseIndex(); map.updateCumPose(pose); - //TO DO reInitial - if (VO.getReInital()){ - //TO DO Harris Detection - std::cout << "re-initial " << std::endl; - cv::goodFeaturesToTrack(leftImage,currFrame2DPoints,maxCorners,0.01,10,cv::Mat(),3,3,false,0.04); - cv::cornerSubPix(leftImage,currFrame2DPoints,subPixel,cv::Size(-1,-1),termcrit); - std::cout << "step1 check feature size " << currFrame2DPoints.size() << std::endl; - - //TO DO getDisparityMapFromCurrImage - std::vector p3d; - VO.generateDisparityMap(leftImage,rightImage); - p3d = VO.getDepth3DPointsFromCurrImage(currFrame2DPoints,K); - int maxDistance = 150 ; - for (int i = 0; i maxDistance ){ -// std::cout<<"depth "< p3d; +// VO.generateDisparityMap(leftImage,rightImage); +// p3d = VO.getDepth3DPointsFromCurrImage(currFrame2DPoints,K); +// int maxDistance = 150 ; +// for (int i = 0; i maxDistance ){ +// // std::cout<<"depth "<