├── README.md ├── Results ├── kitti00.png ├── kitti01.png ├── kitti02.png ├── kitti03.png ├── kitti04.png └── kitti05.png └── src ├── Frame.cpp ├── Frame.hpp ├── MathOperations.h ├── camera.cpp ├── camera.hpp └── main.cpp /README.md: -------------------------------------------------------------------------------- 1 | # SimpleStereoVO 2 | A very simple stereo visual odometer implemented in OpenCV.
3 | 4 | ## Some results: 5 | * For 'VO+IMU', I simply use the orientation provided by the IMU and the translation results from VO.
6 | * Video: http://v.youku.com/v_show/id_XMTgzMzczODI4MA==.html 7 | ![image](https://github.com/meyiao/SimpleStereoVO/blob/master/Results/kitti00.png)
8 | ![image](https://github.com/meyiao/SimpleStereoVO/blob/master/Results/kitti01.png)
9 | ![image](https://github.com/meyiao/SimpleStereoVO/blob/master/Results/kitti02.png)
10 | ![image](https://github.com/meyiao/SimpleStereoVO/blob/master/Results/kitti03.png)
11 | ![image](https://github.com/meyiao/SimpleStereoVO/blob/master/Results/kitti04.png)
12 | ![image](https://github.com/meyiao/SimpleStereoVO/blob/master/Results/kitti05.png)
13 | 14 | 15 | ## Usage 16 | Specifying your dataset parameters (marked as !!!CHANGE!!!) in main.cpp
17 | Note that the stereo images must be rectified.
18 | 19 | ## Dataset 20 | It runs properly on the KITTI benchmark dataset : http://www.cvlibs.net/datasets/kitti/eval_odometry.php
21 | 22 | 23 | -------------------------------------------------------------------------------- /Results/kitti00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meyiao/SimpleStereoVO/af410243f7c44b7073fcd81116832ea4adeeab88/Results/kitti00.png -------------------------------------------------------------------------------- /Results/kitti01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meyiao/SimpleStereoVO/af410243f7c44b7073fcd81116832ea4adeeab88/Results/kitti01.png -------------------------------------------------------------------------------- /Results/kitti02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meyiao/SimpleStereoVO/af410243f7c44b7073fcd81116832ea4adeeab88/Results/kitti02.png -------------------------------------------------------------------------------- /Results/kitti03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meyiao/SimpleStereoVO/af410243f7c44b7073fcd81116832ea4adeeab88/Results/kitti03.png -------------------------------------------------------------------------------- /Results/kitti04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meyiao/SimpleStereoVO/af410243f7c44b7073fcd81116832ea4adeeab88/Results/kitti04.png -------------------------------------------------------------------------------- /Results/kitti05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meyiao/SimpleStereoVO/af410243f7c44b7073fcd81116832ea4adeeab88/Results/kitti05.png -------------------------------------------------------------------------------- /src/Frame.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Frame.cpp 3 | // dabino 4 | // 5 | // Created by 谭智丹 on 16/3/8. 6 | // Copyright © 2016年 谭智丹. All rights reserved. 7 | // 8 | 9 | #include "Frame.hpp" 10 | 11 | Frame::Frame(Mat &img, int bs, Point2f ppt, float focalt, float baselinet) 12 | { 13 | 14 | im_width = img.cols; 15 | im_height = img.rows; 16 | block_size = bs; 17 | pp = ppt; 18 | focal = focalt; 19 | baseline = baselinet; 20 | 21 | } 22 | 23 | 24 | // Assign corners into grids. 25 | // We devide an image into small square grids. Then we assign the FAST corners into 26 | // these grids. Each element of 'subidx' is a vector, which contains all the indices for 27 | // corners that belong to this grid. 28 | void Frame::GetFeaturesIntoGrid(vector &vPoints, vector > &subidx) 29 | { 30 | subidx.clear(); 31 | int n_xSub = im_width/block_size; 32 | int n_ySub = im_height/block_size; 33 | subidx.resize(n_xSub*n_ySub); 34 | 35 | for (int i=0; i &vKeyPoints, vector > &subidx) 48 | { 49 | for (size_t i=0; i sub_response; 51 | vector dsIdx; 52 | if (subidx[i].size()>1) { 53 | for (size_t j=0; j &iPoints, vector &oPoints, vector > &subidx, int N) 70 | { 71 | for (size_t i=0; i &pts1, vector& disp ,cv::Mat &img1, cv::Mat &img2, float ythresh) 90 | { 91 | vector err; 92 | vector status; 93 | vector pts2; 94 | calcOpticalFlowPyrLK(img1, img2, pts1, pts2, status, err, Size(31,31)); 95 | 96 | size_t k=0; 97 | for (size_t i=0; i0.5) &&(diffx<150)) { 102 | pts1[k] = pts1[i]; 103 | disp.push_back(diffx); 104 | k++; 105 | } 106 | } 107 | } 108 | } 109 | 110 | pts1.resize(k); 111 | disp.resize(k); 112 | 113 | } 114 | 115 | 116 | /* 117 | void Frame::st2rt(vector &stat, cv::Mat &R, cv::Mat &T) 118 | { 119 | float rx=stat[0], ry=stat[1], rz=stat[2]; 120 | R = (Mat_(3,3)<<1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f); 121 | T = (Mat_(3,1)< 0.00001) { 125 | rx /= r; ry /= r; rz /= r; 126 | 127 | // compute some quantity to avoid repetition 128 | float ct = cos(r); 129 | float st = sin(r); 130 | float ict = 1.0f - ct; 131 | float rxy_ict = rx * ry * ict; 132 | float rxz_ict = rx * rz * ict; 133 | float ryz_ict = ry * rz * ict; 134 | float rx_st = rx * st; 135 | float ry_st = ry * st; 136 | float rz_st = rz * st; 137 | 138 | // calculate the elements of R 139 | float r11, r12, r13, r21, r22, r23, r31, r32, r33; 140 | r11 = ct + rx * rx * ict; 141 | r12 = rxy_ict + rz_st; 142 | r13 = rxz_ict - ry_st; 143 | r21 = rxy_ict - rz_st; 144 | r22 = ct + ry * ry * ict; 145 | r23 = ryz_ict + rx_st; 146 | r31 = rxz_ict + ry_st; 147 | r32 = ryz_ict - rx_st; 148 | r33 = ct + rz * rz * ict; 149 | 150 | R = (Mat_(3,3)< 11 | #include 12 | 13 | #include 14 | 15 | using namespace cv; 16 | using namespace std; 17 | 18 | class Frame 19 | { 20 | public: 21 | Frame(Mat& img, int bs, Point2f ppt, float focalt, float baselinet); 22 | 23 | // Assign detected FAST corners into grids 24 | void GetFeaturesIntoGrid(vector& vPoints, vector< vector >& subidx); 25 | 26 | // For all FAST corners belonging to the same grid, we sort them according to their 27 | // FAST-response scores. 28 | void SortSubIdx(vector& vKeyPoints, vector >& subidx); 29 | 30 | // For each grid, only keep the N FAST corners with highest scores. 31 | void SelectTopN(vector& iPoints, vector& oPoints, vector> & subidx, int N); 32 | 33 | // Calculate stereo disparities. 34 | void FindDisparity(vector& pts1, vector& disp, Mat& img1, Mat& img2, float ythresh); 35 | 36 | // Convert a state vector to rotaion matrix R, and translation vector T. 37 | // void st2rt(vector& stat, Mat& R, Mat& T); 38 | 39 | 40 | private: 41 | int im_width; // image width 42 | int im_height; // image height 43 | int block_size; // grid size 44 | Point2f pp; // principle point 45 | float focal; // focal length 46 | float baseline; // stereo base line 47 | }; 48 | 49 | #endif /* Frame_hpp */ -------------------------------------------------------------------------------- /src/MathOperations.h: -------------------------------------------------------------------------------- 1 | // 2 | // MathOperations.h 3 | // dabino 4 | // 5 | // Created by 谭智丹 on 16/4/27. 6 | // Copyright © 2016年 谭智丹. All rights reserved. 7 | // 8 | 9 | #ifndef MathOperations_h 10 | #define MathOperations_h 11 | 12 | #include 13 | #include "camera.hpp" 14 | 15 | using namespace cv; 16 | using namespace std; 17 | 18 | /* rvec2mat : convert a rvec into a rotation matrix R (R_Sn) */ 19 | void rvec2mat(Mat& rvec, Mat& R) 20 | { 21 | // rotation angle 22 | double rx = rvec.at(0,0); 23 | double ry = rvec.at(1,0); 24 | double rz = rvec.at(2,0); 25 | double theta = sqrt(rx*rx + ry*ry + rz*rz); 26 | 27 | // set rotation matrix to identity 28 | R = (Mat_(3,3)<<1, 0, 0, 0, 1, 0, 0, 0, 1); 29 | 30 | // calculate rotaion matrix 31 | if (theta > 0.0001) { 32 | rx /= theta; 33 | ry /= theta; 34 | rz /= theta; 35 | 36 | // compute some quantity to avoid repetition 37 | double ct = cos(theta); 38 | double st = sin(theta); 39 | double ict = 1 - ct; 40 | double rxy_ict = rx * ry * ict; 41 | double rxz_ict = rx * rz * ict; 42 | double ryz_ict = ry * rz * ict; 43 | double rx_st = rx * st; 44 | double ry_st = ry * st; 45 | double rz_st = rz * st; 46 | 47 | // calculate the elements of R 48 | double R00 = ct + rx * rx * ict; 49 | double R01 = rxy_ict + rz_st; 50 | double R02 = rxz_ict - ry_st; 51 | double R10 = rxy_ict - rz_st; 52 | double R11 = ct + ry * ry * ict; 53 | double R12 = ryz_ict + rx_st; 54 | double R20 = rxz_ict + ry_st; 55 | double R21 = ryz_ict - rx_st; 56 | double R22 = ct + rz * rz * ict; 57 | 58 | R = (Mat_(3,3)<< R00, R10, R20, R01, R11, R21, R02, R12, R22); 59 | 60 | } 61 | 62 | } 63 | 64 | 65 | /* Transform3D : Using R&T to transform a 3D point seen in one camera frame into another camera frame 66 | R : R_Cc0 67 | T : c0->c, described in the c0 frame 68 | */ 69 | void Transform3D(vector& oldPts3d, vector& newPts3d, Mat& R, Mat& T) 70 | { 71 | // we need to convert R and T to float type 72 | Mat fR, fT; 73 | R.convertTo(fR, CV_32F); 74 | T.convertTo(fT, CV_32F); 75 | 76 | for (size_t i=0; i(0,0),x.at(1,0),x.at(2,0))); 82 | } 83 | } 84 | 85 | 86 | void projectObj2img(vector& pts3d, vector& pts2d, camera::camera cam, vector& isInImg) 87 | { 88 | float fx = cam.fx, fy = cam.fy, cx = cam.cx, cy = cam.cy; 89 | float minu = 3, maxu = cam.imwidth -3; 90 | float minv = 3, maxv = cam.imheight-3; 91 | 92 | for (size_t i=0; imaxu || vmaxv ) 104 | isInImg.push_back(false); 105 | else 106 | isInImg.push_back(true); 107 | 108 | } 109 | } 110 | 111 | } 112 | 113 | #endif /* MathOperations_h */ 114 | -------------------------------------------------------------------------------- /src/camera.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // camera.cpp 3 | // dabino 4 | // 5 | // Created by 谭智丹 on 16/4/27. 6 | // Copyright © 2016年 谭智丹. All rights reserved. 7 | // 8 | 9 | #include "camera.hpp" 10 | 11 | /* we can use 12 | camera cam(.., .., ......); 13 | to set the camera intrinsics 14 | */ 15 | 16 | camera::camera(int width, int height, float focalx, float focaly, float ppx, float ppy) 17 | { 18 | imwidth = width; 19 | imheight = height; 20 | fx = focalx; 21 | fy = focaly; 22 | cx = ppx; 23 | cy = ppy; 24 | camMatrix = (Mat_(3,3)< 13 | 14 | #include 15 | 16 | using namespace cv; 17 | 18 | class camera 19 | { 20 | public: 21 | camera(int width, int height, float focalx, float focaly, float ppx, float ppy); 22 | float imwidth; // image width 23 | float imheight; // image height 24 | float fx; // focal length x 25 | float fy; // focal length y 26 | float cx; // principal point's x coordinate 27 | float cy; // principal point's y coordinate 28 | Mat camMatrix; // camera projection matrix 29 | }; 30 | 31 | 32 | #endif /* camera_hpp */ 33 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // main.cpp 3 | // dabino 4 | // 5 | // Created by 谭智丹 on 16/3/8. 6 | // Copyright © 2016年 谭智丹. All rights reserved. 7 | // 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "Frame.hpp" 19 | #include "camera.hpp" 20 | #include "MathOperations.h" 21 | 22 | using namespace cv; 23 | using namespace std; 24 | 25 | //------------Dataset Parameters-----------------------//!!!!!!!!!!!!!CHANGE!!!!!!!!!!!!!!!!!!! 26 | /*----------Kitti Sequence 00, 01, 02--------*/ 27 | const float focal = 718.856; 28 | const Point2f pp = Point2f(607.1928, 185.2157); 29 | 30 | /*----------Kitti Sequence 03--------*/ 31 | //const float focal = 721.5377; 32 | //const Point2f pp = Point2f(609.5593, 172.854); 33 | 34 | /*----------Kitti Sequence 04, 05, 06--------*/ 35 | //const float focal = 707.0912; 36 | //const Point2f pp = Point2f(601.8873, 183.1104); 37 | 38 | const float baseline = 0.53715; 39 | 40 | const int total_frames = 4541; // 00 41 | //const int total_frames = 1101; // 01 42 | //const int total_frames = 4661; // 02 43 | //const int total_frames = 801; // 03 44 | //const int total_frames = 271; // 04 45 | //const int total_frames = 2761; // 05 46 | //const int total_frames = 1101; // 06 47 | 48 | const Mat cam_mat = (Mat_(3,3)< old_pts; // corners' locations 83 | vector old_disp; // disparities 84 | 85 | // Termination criteria used when executing sub-pixel refinement 86 | TermCriteria crit = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01); 87 | 88 | // Pose state vector 89 | Mat oldRvec = (Mat_(3,1)<<0, 0, 0); // rotation 90 | Mat oldTvec = (Mat_(3,1)<<0, 0, 0); // translation 91 | 92 | 93 | // Here we go! 94 | for (int numFrame=start_frame; numFrame keyPts; 113 | FAST(img_l, keyPts, 20, true, 2); // threshold is 20, do non-maximun suppression, 16-9 method 114 | vector pts; 115 | KeyPoint::convert(keyPts, pts); 116 | cout<<"# detected: \t"<> subidx; 120 | frame.GetFeaturesIntoGrid(pts, subidx); 121 | 122 | // Sort the subidx 123 | frame.SortSubIdx(keyPts, subidx); 124 | int occupied=0; 125 | for (size_t i=0; i topPts; 133 | int want_in_each = MIN(1000 / occupied, 7); 134 | 135 | frame.SelectTopN(pts, topPts, subidx, want_in_each); 136 | cout << "Selected:\t" << topPts.size() << endl; 137 | 138 | // Sub-pixel refinement (very optional!) 139 | cornerSubPix(img_l, topPts, Size(5,5), Size(-1,-1), crit); 140 | 141 | // Stereo matching 142 | vector disp; 143 | frame.FindDisparity(topPts, disp, img_l, img_r, y_threshold); 144 | 145 | 146 | /////// 147 | if (numFrame > start_frame) { 148 | 149 | //---------Predict points' locations in current frame------// 150 | vector prevPts3d, prediPts3d; 151 | vector prediPts; 152 | vector canBeSeen; 153 | 154 | // 3D points described in last camera coordinates 155 | for (size_t i=0; i f_status; 180 | vector new_pts; 181 | 182 | // Optical flow tracker 183 | new_pts = prediPts; 184 | calcOpticalFlowPyrLK(old_img_l, img_l, old_pts, new_pts, f_status, err, Size(7,7)); 185 | 186 | // Check: If the distance between the tracked and the predicted locations exceeds 15, 187 | // it may be a wrong association, we just discard it. 188 | size_t tracked = 0; 189 | if (numFrame > start_frame+1) { 190 | for (size_t i=0; i pts3d; 222 | for (size_t i=0; i inliers; 233 | solvePnPRansac(pts3d, new_pts, cam_mat, Mat(), rvec, tvec, false, 500, 2.0f, 0.999, inliers, SOLVEPNP_ITERATIVE); 234 | cout<<"# inliers: \t"< start_frame+2) { 258 | double tx = tvec.at(0,0); 259 | double ty = tvec.at(1,0); 260 | double tz = tvec.at(2,0); 261 | double oldtx = oldTvec.at(0,0); 262 | double oldty = oldTvec.at(1,0); 263 | double oldtz = oldTvec.at(2,0); 264 | double difft = sqrt(pow(tx-oldtx, 2) + pow(ty-oldty, 2) + pow(tz-oldtz, 2)); 265 | if (fabs(tx)>0.5 || fabs(ty)>0.5 || fabs(tz)>3.5 || difft > 1) { 266 | rvec = oldRvec; 267 | tvec = oldTvec; 268 | } 269 | } 270 | 271 | 272 | // Save the result 273 | fout<(0,0)<<" \t"; 275 | fout<(1,0)<<" \t"; 276 | fout<(2,0)<<" \t"; 277 | fout<(0,0)<<" \t"; 278 | fout<(1,0)<<" \t"; 279 | fout<(2,0)<<" \t"; 280 | fout<