Skip to content

Commit 1c8ff20

Browse files
authored
Merge pull request #66 from rpng/pose_graph
Development v2.1 - Secondary Pose Graph Support
2 parents 2295e7f + 28f0a52 commit 1c8ff20

8 files changed

+297
-22
lines changed

ReadMe.md

+21-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Please take a look at the feature list below for full details on what the system
1919

2020
## News / Events
2121

22+
* **May 18, 2020** - Released secondary pose graph example repository [ov_secondary](https://github.com/rpng/ov_secondary) based on [VINS-Fusion](https://github.com/HKUST-Aerial-Robotics/VINS-Fusion). OpenVINS now publishes marginalized feature track, feature 3d position, and first camera intrinsics and extrinsics. See [PR#66](https://github.com/rpng/open_vins/pull/66) for details and discussion.
2223
* **April 3, 2020** - Released [v2.0](https://github.com/rpng/open_vins/releases/tag/v2.0) update to the codebase with some key refactoring, ros-free building, improved dataset support, and single inverse depth feature representation. Please check out the [release page](https://github.com/rpng/open_vins/releases/tag/v2.0) for details.
2324
* **January 21, 2020** - Our paper has been accepted for presentation in [ICRA 2020](https://www.icra2020.org/). We look forward to seeing everybody there! We have also added links to a few videos of the system running on different datasets.
2425
* **October 23, 2019** - OpenVINS placed first in the [IROS 2019 FPV Drone Racing VIO Competition
@@ -63,9 +64,27 @@ Please take a look at the feature list below for full details on what the system
6364
* Out of the box evaluation on EurocMav and TUM-VI datasets
6465
* Extensive evaluation suite (ATE, RPE, NEES, RMSE, etc..)
6566

66-
## Demo Videos
6767

6868

69+
## Codebase Extensions
70+
71+
* **[ov_secondary](https://github.com/rpng/ov_secondary)** -
72+
This is an example secondary thread which provides loop closure in a loosely coupled manner for [OpenVINS](https://github.com/rpng/open_vins).
73+
This is a modification of the code originally developed by the HKUST aerial robotics group and can be found in their [VINS-Fusion](https://github.com/HKUST-Aerial-Robotics/VINS-Fusion) repository.
74+
Here we stress that this is a loosely coupled method, thus no information is returned to the estimator to improve the underlying OpenVINS odometry.
75+
This codebase has been modified in a few key areas including: exposing more loop closure parameters, subscribing to camera intrinsics, simplifying configuration such that only topics need to be supplied, and some tweaks to the loop closure detection to improve frequency.
76+
77+
* **[ov_maplab](https://github.com/rpng/ov_maplab)** -
78+
This codebase contains the interface wrapper for exporting visual-inertial runs from [OpenVINS](https://github.com/rpng/open_vins) into the ViMap structure taken by [maplab](https://github.com/ethz-asl/maplab).
79+
The state estimates and raw images are appended to the ViMap as OpenVINS runs through a dataset.
80+
After completion of the dataset, features are re-extract and triangulate with maplab's feature system.
81+
This can be used to merge multi-session maps, or to perform a batch optimization after first running the data through OpenVINS.
82+
Some example have been provided along with a helper script to export trajectories into the standard groundtruth format.
83+
84+
85+
86+
## Demo Videos
87+
6988
[![](docs/youtube/KCX51GvYGss.jpg)](http://www.youtube.com/watch?v=KCX51GvYGss "OpenVINS - EuRoC MAV Vicon Rooms Flyby")
7089
[![](docs/youtube/Lc7VQHngSuQ.jpg)](http://www.youtube.com/watch?v=Lc7VQHngSuQ "OpenVINS - TUM VI Datasets Flyby")
7190
[![](docs/youtube/vaia7iPaRW8.jpg)](http://www.youtube.com/watch?v=vaia7iPaRW8 "OpenVINS - UZH-FPV Drone Racing Dataset Flyby")
@@ -76,6 +95,7 @@ Please take a look at the feature list below for full details on what the system
7695
[![](docs/youtube/ExPIGwORm4E.jpg)](http://www.youtube.com/watch?v=ExPIGwORm4E "OpenVINS - UZH-FPV Drone Racing Dataset Demonstration")
7796

7897

98+
7999
## Credit / Licensing
80100

81101
This code was written by the [Robot Perception and Navigation Group (RPNG)](https://sites.udel.edu/robot/) at the University of Delaware.

ov_msckf/launch/pgeneva_ros_tum.launch

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
<arg name="max_cameras" default="2" />
55
<arg name="use_stereo" default="true" />
66
<arg name="bag_start" default="0" />
7-
<arg name="bag" default="/home/patrick/datasets/tum/dataset-room2_512_16.bag" />
7+
<arg name="bag" default="/home/patrick/datasets/tum/dataset-room1_512_16.bag" />
88

99
<!-- imu starting thresholds -->
1010
<arg name="init_window_time" default="1.0" />
11-
<arg name="init_imu_thresh" default="0.75" />
11+
<arg name="init_imu_thresh" default="0.70" /> <!-- room1:0.70, room2:0.75, room3:0.70 -->
1212

1313
<!-- saving trajectory path and timing information -->
1414
<arg name="dosave" default="false" />

ov_msckf/launch/pgeneva_serial_tum.launch

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
<param name="max_cameras" type="int" value="2" />
3333
<param name="dt_slam_delay" type="double" value="3" />
3434
<param name="init_window_time" type="double" value="1.0" />
35-
<param name="init_imu_thresh" type="double" value="0.8" />
35+
<param name="init_imu_thresh" type="double" value="0.70" /> <!-- room1:0.70, room2:0.75, room3:0.70 -->
3636
<rosparam param="gravity">[0.0,0.0,9.80766]</rosparam>
3737
<param name="feat_rep_msckf" type="string" value="GLOBAL_3D" />
3838
<param name="feat_rep_slam" type="string" value="ANCHORED_FULL_INVERSE_DEPTH" />

ov_msckf/src/core/RosVisualizer.cpp

+126
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ RosVisualizer::RosVisualizer(ros::NodeHandle &nh, VioManager* app, Simulator *si
5959
pub_pathgt = nh.advertise<nav_msgs::Path>("/ov_msckf/pathgt", 2);
6060
ROS_INFO("Publishing: %s", pub_pathgt.getTopic().c_str());
6161

62+
// Keyframe publishers
63+
pub_keyframe_pose = nh.advertise<nav_msgs::Odometry>("/ov_msckf/keyframe_pose", 1000);
64+
pub_keyframe_point = nh.advertise<sensor_msgs::PointCloud>("/ov_msckf/keyframe_feats", 1000);
65+
pub_keyframe_extrinsic = nh.advertise<nav_msgs::Odometry>("/ov_msckf/keyframe_extrinsic", 1000);
66+
pub_keyframe_intrinsics = nh.advertise<sensor_msgs::CameraInfo>("/ov_msckf/keyframe_intrinsics", 1000);
67+
6268
// option to enable publishing of global to IMU transformation
6369
nh.param<bool>("publish_global_to_imu_tf", publish_global2imu_tf, true);
6470
nh.param<bool>("publish_calibration_tf", publish_calibration_tf, true);
@@ -133,6 +139,9 @@ void RosVisualizer::visualize() {
133139
// Publish gt if we have it
134140
publish_groundtruth();
135141

142+
// Publish keyframe information
143+
publish_keyframe_information();
144+
136145
// save total state
137146
if(save_total_state)
138147
sim_save_total_state_to_file();
@@ -670,6 +679,123 @@ void RosVisualizer::publish_groundtruth() {
670679

671680

672681

682+
void RosVisualizer::publish_keyframe_information() {
683+
684+
685+
// Check if we have subscribers
686+
if(pub_keyframe_pose.getNumSubscribers()==0 && pub_keyframe_point.getNumSubscribers()==0 &&
687+
pub_keyframe_extrinsic.getNumSubscribers()==0 && pub_keyframe_intrinsics.getNumSubscribers()==0)
688+
return;
689+
690+
691+
// Skip if we don't have a marginalized frame yet
692+
double hist_last_marginalized_time;
693+
Eigen::Matrix<double,7,1> stateinG;
694+
if(!_app->hist_last_marg_state(hist_last_marginalized_time, stateinG))
695+
return;
696+
697+
// Default header
698+
std_msgs::Header header;
699+
header.stamp = ros::Time(hist_last_marginalized_time);
700+
701+
//======================================================
702+
// PUBLISH IMU TO CAMERA0 EXTRINSIC
703+
// need to flip the transform to the IMU frame
704+
Eigen::Vector4d q_ItoC = _app->get_state()->_calib_IMUtoCAM.at(0)->quat();
705+
Eigen::Vector3d p_CinI = -_app->get_state()->_calib_IMUtoCAM.at(0)->Rot().transpose()*_app->get_state()->_calib_IMUtoCAM.at(0)->pos();
706+
nav_msgs::Odometry odometry_calib;
707+
odometry_calib.header = header;
708+
odometry_calib.header.frame_id = "imu";
709+
odometry_calib.pose.pose.position.x = p_CinI(0);
710+
odometry_calib.pose.pose.position.y = p_CinI(1);
711+
odometry_calib.pose.pose.position.z = p_CinI(2);
712+
odometry_calib.pose.pose.orientation.x = q_ItoC(0);
713+
odometry_calib.pose.pose.orientation.y = q_ItoC(1);
714+
odometry_calib.pose.pose.orientation.z = q_ItoC(2);
715+
odometry_calib.pose.pose.orientation.w = q_ItoC(3);
716+
pub_keyframe_extrinsic.publish(odometry_calib);
717+
718+
719+
//======================================================
720+
// PUBLISH CAMERA0 INTRINSICS
721+
sensor_msgs::CameraInfo cameraparams;
722+
cameraparams.header = header;
723+
cameraparams.header.frame_id = "imu";
724+
cameraparams.distortion_model = (_app->get_state()->_cam_intrinsics_model.at(0))? "equidistant" : "plumb_bob";
725+
Eigen::VectorXd cparams = _app->get_state()->_cam_intrinsics.at(0)->value();
726+
cameraparams.D = {cparams(4), cparams(5), cparams(6), cparams(7)};
727+
cameraparams.K = {cparams(0), 0, cparams(2), 0, cparams(1), cparams(3), 0, 0, 1};
728+
pub_keyframe_intrinsics.publish(cameraparams);
729+
730+
731+
//======================================================
732+
// PUBLISH HISTORICAL POSE ESTIMATE
733+
nav_msgs::Odometry odometry_pose;
734+
odometry_pose.header = header;
735+
odometry_pose.header.frame_id = "global";
736+
odometry_pose.pose.pose.position.x = stateinG(4);
737+
odometry_pose.pose.pose.position.y = stateinG(5);
738+
odometry_pose.pose.pose.position.z = stateinG(6);
739+
odometry_pose.pose.pose.orientation.x = stateinG(0);
740+
odometry_pose.pose.pose.orientation.y = stateinG(1);
741+
odometry_pose.pose.pose.orientation.z = stateinG(2);
742+
odometry_pose.pose.pose.orientation.w = stateinG(3);
743+
pub_keyframe_pose.publish(odometry_pose);
744+
745+
746+
//======================================================
747+
// PUBLISH FEATURE TRACKS IN THE GLOBAL FRAME OF REFERENCE
748+
749+
// Get historical feature information
750+
std::unordered_map<size_t, Eigen::Vector3d> hist_feat_posinG;
751+
std::unordered_map<size_t, std::unordered_map<size_t, std::vector<Eigen::VectorXf>>> hist_feat_uvs;
752+
std::unordered_map<size_t, std::unordered_map<size_t, std::vector<Eigen::VectorXf>>> hist_feat_uvs_norm;
753+
std::unordered_map<size_t, std::unordered_map<size_t, std::vector<double>>> hist_feat_timestamps;
754+
_app->hist_get_features(hist_feat_posinG, hist_feat_uvs, hist_feat_uvs_norm, hist_feat_timestamps);
755+
756+
// Construct the message
757+
sensor_msgs::PointCloud point_cloud;
758+
point_cloud.header = header;
759+
point_cloud.header.frame_id = "global";
760+
for(const auto &feattimes : hist_feat_timestamps) {
761+
762+
// Skip if this feature has no extraction in the "zero" camera
763+
if(feattimes.second.find(0)==feattimes.second.end())
764+
continue;
765+
766+
// Skip if this feature does not have measurement at this time
767+
auto iter = std::find(feattimes.second.at(0).begin(), feattimes.second.at(0).end(), hist_last_marginalized_time);
768+
if(iter==feattimes.second.at(0).end())
769+
continue;
770+
771+
// Get this feature information
772+
size_t featid = feattimes.first;
773+
size_t index = (size_t)std::distance(feattimes.second.at(0).begin(), iter);
774+
Eigen::VectorXf uv = hist_feat_uvs.at(featid).at(0).at(index);
775+
Eigen::VectorXf uv_n = hist_feat_uvs_norm.at(featid).at(0).at(index);
776+
Eigen::Vector3d pFinG = hist_feat_posinG.at(featid);
777+
778+
// Push back 3d point
779+
geometry_msgs::Point32 p;
780+
p.x = pFinG(0);
781+
p.y = pFinG(1);
782+
p.z = pFinG(2);
783+
point_cloud.points.push_back(p);
784+
785+
// Push back the norm, raw, and feature id
786+
sensor_msgs::ChannelFloat32 p_2d;
787+
p_2d.values.push_back(uv_n(0));
788+
p_2d.values.push_back(uv_n(1));
789+
p_2d.values.push_back(uv(0));
790+
p_2d.values.push_back(uv(1));
791+
p_2d.values.push_back(featid);
792+
point_cloud.channels.push_back(p_2d);
793+
794+
}
795+
pub_keyframe_point.publish(point_cloud);
796+
797+
798+
}
673799

674800

675801
void RosVisualizer::sim_save_total_state_to_file() {

ov_msckf/src/core/RosVisualizer.h

+9-9
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@
2727
#include <sensor_msgs/Image.h>
2828
#include <sensor_msgs/Imu.h>
2929
#include <sensor_msgs/NavSatFix.h>
30+
#include <sensor_msgs/PointCloud.h>
3031
#include <sensor_msgs/PointCloud2.h>
3132
#include <sensor_msgs/point_cloud2_iterator.h>
33+
#include <sensor_msgs/CameraInfo.h>
3234
#include <std_msgs/Float64.h>
3335
#include <nav_msgs/Path.h>
3436
#include <nav_msgs/Odometry.h>
@@ -102,6 +104,9 @@ namespace ov_msckf {
102104
/// Publish groundtruth (if we have it)
103105
void publish_groundtruth();
104106

107+
/// Publish keyframe information of the marginalized pose and tracks
108+
void publish_keyframe_information();
109+
105110
/// Save current estimate state and groundtruth including calibration
106111
void sim_save_total_state_to_file();
107112

@@ -115,23 +120,18 @@ namespace ov_msckf {
115120
Simulator* _sim;
116121

117122
// Our publishers
118-
ros::Publisher pub_poseimu;
119-
ros::Publisher pub_odomimu;
120-
ros::Publisher pub_pathimu;
121-
ros::Publisher pub_points_msckf;
122-
ros::Publisher pub_points_slam;
123-
ros::Publisher pub_points_aruco;
124-
ros::Publisher pub_points_sim;
123+
ros::Publisher pub_poseimu, pub_odomimu, pub_pathimu;
124+
ros::Publisher pub_points_msckf, pub_points_slam, pub_points_aruco, pub_points_sim;
125125
ros::Publisher pub_tracks;
126+
ros::Publisher pub_keyframe_pose, pub_keyframe_point, pub_keyframe_extrinsic, pub_keyframe_intrinsics;
126127
tf::TransformBroadcaster *mTfBr;
127128

128129
// For path viz
129130
unsigned int poses_seq_imu = 0;
130131
vector<geometry_msgs::PoseStamped> poses_imu;
131132

132133
// Groundtruth infomation
133-
ros::Publisher pub_pathgt;
134-
ros::Publisher pub_posegt;
134+
ros::Publisher pub_pathgt, pub_posegt;
135135
double summed_rmse_ori = 0.0;
136136
double summed_rmse_pos = 0.0;
137137
double summed_nees_ori = 0.0;

ov_msckf/src/core/VioManager.cpp

+95-9
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,13 @@ void VioManager::do_feature_propagate_update(double timestamp) {
494494
//===================================================================================
495495

496496

497+
// Collect all slam features into single vector
498+
std::vector<Feature*> features_used_in_update = featsup_MSCKF;
499+
features_used_in_update.insert(features_used_in_update.end(), feats_slam_UPDATE.begin(), feats_slam_UPDATE.end());
500+
features_used_in_update.insert(features_used_in_update.end(), feats_slam_DELAYED.begin(), feats_slam_DELAYED.end());
501+
update_keyframe_historical_information(features_used_in_update);
502+
503+
497504
// Save all the MSCKF features used in the update
498505
good_features_MSCKF.clear();
499506
for(Feature* feat : featsup_MSCKF) {
@@ -516,17 +523,15 @@ void VioManager::do_feature_propagate_update(double timestamp) {
516523
// First do anchor change if we are about to lose an anchor pose
517524
updaterSLAM->change_anchors(state);
518525

519-
// Marginalize the oldest clone of the state if we are at max length
520-
if((int)state->_clones_IMU.size() > state->_options.max_clone_size) {
521-
// Cleanup any features older then the marginalization time
522-
trackFEATS->get_feature_database()->cleanup_measurements(state->margtimestep());
523-
if(trackARUCO != nullptr) {
524-
trackARUCO->get_feature_database()->cleanup_measurements(state->margtimestep());
525-
}
526-
// Finally marginalize that clone
527-
StateHelper::marginalize_old_clone(state);
526+
// Cleanup any features older then the marginalization time
527+
trackFEATS->get_feature_database()->cleanup_measurements(state->margtimestep());
528+
if(trackARUCO != nullptr) {
529+
trackARUCO->get_feature_database()->cleanup_measurements(state->margtimestep());
528530
}
529531

532+
// Finally marginalize the oldest clone if needed
533+
StateHelper::marginalize_old_clone(state);
534+
530535
// Finally if we are optimizing our intrinsics, update our trackers
531536
if(state->_options.do_calib_camera_intrinsics) {
532537
// Get vectors arrays
@@ -635,10 +640,91 @@ void VioManager::do_feature_propagate_update(double timestamp) {
635640
}
636641

637642

643+
void VioManager::update_keyframe_historical_information(const std::vector<Feature*> &features) {
644+
645+
646+
// Loop through all features that have been used in the last update
647+
// We want to record their historical measurements and estimates for later use
648+
for(const auto &feat : features) {
649+
650+
// Get position of feature in the global frame of reference
651+
Eigen::Vector3d p_FinG = feat->p_FinG;
652+
653+
// If it is a slam feature, then get its best guess from the state
654+
if(state->_features_SLAM.find(feat->featid)!=state->_features_SLAM.end()) {
655+
p_FinG = state->_features_SLAM.at(feat->featid)->get_xyz(false);
656+
}
657+
658+
// Push back any new measurements if we have them
659+
// Ensure that if the feature is already added, then just append the new measurements
660+
if(hist_feat_posinG.find(feat->featid)!=hist_feat_posinG.end()) {
661+
hist_feat_posinG.at(feat->featid) = p_FinG;
662+
for(const auto &cam2uv : feat->uvs) {
663+
if(hist_feat_uvs.at(feat->featid).find(cam2uv.first)!=hist_feat_uvs.at(feat->featid).end()) {
664+
hist_feat_uvs.at(feat->featid).at(cam2uv.first).insert(hist_feat_uvs.at(feat->featid).at(cam2uv.first).end(), cam2uv.second.begin(), cam2uv.second.end());
665+
hist_feat_uvs_norm.at(feat->featid).at(cam2uv.first).insert(hist_feat_uvs_norm.at(feat->featid).at(cam2uv.first).end(), feat->uvs_norm.at(cam2uv.first).begin(), feat->uvs_norm.at(cam2uv.first).end());
666+
hist_feat_timestamps.at(feat->featid).at(cam2uv.first).insert(hist_feat_timestamps.at(feat->featid).at(cam2uv.first).end(), feat->timestamps.at(cam2uv.first).begin(), feat->timestamps.at(cam2uv.first).end());
667+
} else {
668+
hist_feat_uvs.at(feat->featid).insert(cam2uv);
669+
hist_feat_uvs_norm.at(feat->featid).insert({cam2uv.first,feat->uvs_norm.at(cam2uv.first)});
670+
hist_feat_timestamps.at(feat->featid).insert({cam2uv.first,feat->timestamps.at(cam2uv.first)});
671+
}
672+
}
673+
} else {
674+
hist_feat_posinG.insert({feat->featid,p_FinG});
675+
hist_feat_uvs.insert({feat->featid,feat->uvs});
676+
hist_feat_uvs_norm.insert({feat->featid,feat->uvs_norm});
677+
hist_feat_timestamps.insert({feat->featid,feat->timestamps});
678+
}
679+
}
680+
638681

682+
// Go through all our old historical vectors and find if any features should be removed
683+
// In this case we know that if we have no use for features that only have info older then the last marg time
684+
std::vector<size_t> ids_to_remove;
685+
for(const auto &id2feat : hist_feat_timestamps) {
686+
bool all_older = true;
687+
for(const auto &cam2time : id2feat.second) {
688+
for(const auto &time : cam2time.second) {
689+
if(time >= hist_last_marginalized_time) {
690+
all_older = false;
691+
break;
692+
}
693+
}
694+
if(!all_older) break;
695+
}
696+
if(all_older) {
697+
ids_to_remove.push_back(id2feat.first);
698+
}
699+
}
639700

701+
// Remove those features!
702+
for(const auto &id : ids_to_remove) {
703+
hist_feat_posinG.erase(id);
704+
hist_feat_uvs.erase(id);
705+
hist_feat_uvs_norm.erase(id);
706+
hist_feat_timestamps.erase(id);
707+
}
640708

709+
// Remove any historical states older then the marg time
710+
auto it0 = hist_stateinG.begin();
711+
while(it0 != hist_stateinG.end()) {
712+
if(it0->first < hist_last_marginalized_time) it0 = hist_stateinG.erase(it0);
713+
else it0++;
714+
}
715+
716+
// If we have reached our max window size record the oldest clone
717+
// This clone is expected to be marginalized from the state
718+
if ((int) state->_clones_IMU.size() > state->_options.max_clone_size) {
719+
hist_last_marginalized_time = state->margtimestep();
720+
assert(hist_last_marginalized_time != INFINITY);
721+
Eigen::Matrix<double,7,1> imustate_inG = Eigen::Matrix<double,7,1>::Zero();
722+
imustate_inG.block(0,0,4,1) = state->_clones_IMU.at(hist_last_marginalized_time)->quat();
723+
imustate_inG.block(4,0,3,1) = state->_clones_IMU.at(hist_last_marginalized_time)->pos();
724+
hist_stateinG.insert({hist_last_marginalized_time, imustate_inG});
725+
}
641726

727+
}
642728

643729

644730

0 commit comments

Comments
 (0)