00001 #include <ros/ros.h>
00002 #include <sensor_msgs/image_encodings.h>
00003 #include <image_geometry/stereo_camera_model.h>
00004 #include <cv_bridge/cv_bridge.h>
00005 #include <pcl_ros/point_cloud.h>
00006 #include <pcl/point_types.h>
00007
00008 #include <viso_stereo.h>
00009
00010 #include <viso2_ros/VisoInfo.h>
00011
00012 #include "stereo_processor.h"
00013 #include "odometer_base.h"
00014 #include "odometry_params.h"
00015
00016
00017 #include <opencv2/highgui/highgui.hpp>
00018
00019 #include "cv.h"
00020 #include "highgui.h"
00021
00022 namespace viso2_ros
00023 {
00024
00025
00026 static const boost::array<double, 36> STANDARD_POSE_COVARIANCE =
00027 { { 0.1, 0, 0, 0, 0, 0,
00028 0, 0.1, 0, 0, 0, 0,
00029 0, 0, 0.1, 0, 0, 0,
00030 0, 0, 0, 0.17, 0, 0,
00031 0, 0, 0, 0, 0.17, 0,
00032 0, 0, 0, 0, 0, 0.17 } };
00033 static const boost::array<double, 36> STANDARD_TWIST_COVARIANCE =
00034 { { 0.05, 0, 0, 0, 0, 0,
00035 0, 0.05, 0, 0, 0, 0,
00036 0, 0, 0.05, 0, 0, 0,
00037 0, 0, 0, 0.09, 0, 0,
00038 0, 0, 0, 0, 0.09, 0,
00039 0, 0, 0, 0, 0, 0.09 } };
00040 static const boost::array<double, 36> BAD_COVARIANCE =
00041 { { 9999, 0, 0, 0, 0, 0,
00042 0, 9999, 0, 0, 0, 0,
00043 0, 0, 9999, 0, 0, 0,
00044 0, 0, 0, 9999, 0, 0,
00045 0, 0, 0, 0, 9999, 0,
00046 0, 0, 0, 0, 0, 9999 } };
00047
00048
00049 class StereoOdometer : public StereoProcessor, public OdometerBase
00050 {
00051
00052 private:
00053
00054 boost::shared_ptr<VisualOdometryStereo> visual_odometer_;
00055 VisualOdometryStereo::parameters visual_odometer_params_;
00056
00057 ros::Publisher point_cloud_pub_;
00058 ros::Publisher info_pub_;
00059
00060 bool got_lost_;
00061
00062
00063 int ref_frame_change_method_;
00064
00065 int show_matches;
00066 bool change_reference_frame_;
00067 double ref_frame_motion_threshold_;
00068 int ref_frame_inlier_threshold_;
00069 Matrix reference_motion_;
00070
00071 public:
00072
00073 typedef pcl::PointCloud<pcl::PointXYZRGB> PointCloud;
00074
00075 StereoOdometer(const std::string& transport) :
00076 StereoProcessor(transport), OdometerBase(),
00077 got_lost_(false), change_reference_frame_(false)
00078 {
00079
00080 ros::NodeHandle local_nh("~");
00081 odometry_params::loadParams(local_nh, visual_odometer_params_);
00082
00083 local_nh.param("show_matches", show_matches, 0);
00084 local_nh.param("ref_frame_change_method", ref_frame_change_method_, 1);
00085 local_nh.param("ref_frame_motion_threshold", ref_frame_motion_threshold_, 5.0);
00086 local_nh.param("ref_frame_inlier_threshold", ref_frame_inlier_threshold_, 150);
00087
00088 point_cloud_pub_ = local_nh.advertise<PointCloud>("point_cloud", 1);
00089 info_pub_ = local_nh.advertise<VisoInfo>("info", 1);
00090
00091 reference_motion_ = Matrix::eye(4);
00092 }
00093
00094 protected:
00095
00096 void initOdometer(
00097 const sensor_msgs::CameraInfoConstPtr& l_info_msg,
00098 const sensor_msgs::CameraInfoConstPtr& r_info_msg)
00099 {
00100
00101
00102 image_geometry::StereoCameraModel model;
00103 model.fromCameraInfo(*l_info_msg, *r_info_msg);
00104 visual_odometer_params_.base = model.baseline();
00105 visual_odometer_params_.calib.f = model.left().fx();
00106 visual_odometer_params_.calib.cu = model.left().cx();
00107 visual_odometer_params_.calib.cv = model.left().cy();
00108 visual_odometer_.reset(new VisualOdometryStereo(visual_odometer_params_));
00109 if (l_info_msg->header.frame_id != "") setSensorFrameId(l_info_msg->header.frame_id);
00110 ROS_INFO_STREAM("Initialized libviso2 stereo odometry "
00111 "with the following parameters:" << std::endl <<
00112 visual_odometer_params_ <<
00113 " ref_frame_change_method = " << ref_frame_change_method_ << std::endl <<
00114 " ref_frame_motion_threshold = " << ref_frame_motion_threshold_ << std::endl <<
00115 " ref_frame_inlier_threshold = " << ref_frame_inlier_threshold_);
00116 }
00117
00118 void imageCallback(
00119 const sensor_msgs::ImageConstPtr& l_image_msg,
00120 const sensor_msgs::ImageConstPtr& r_image_msg,
00121 const sensor_msgs::CameraInfoConstPtr& l_info_msg,
00122 const sensor_msgs::CameraInfoConstPtr& r_info_msg)
00123 {
00124 ros::WallTime start_time = ros::WallTime::now();
00125 bool first_run = false;
00126
00127 if (!visual_odometer_)
00128 {
00129 first_run = true;
00130 initOdometer(l_info_msg, r_info_msg);
00131 }
00132
00133
00134 uint8_t *l_image_data, *r_image_data;
00135 int l_step, r_step;
00136 cv_bridge::CvImageConstPtr l_cv_ptr, r_cv_ptr;
00137 l_cv_ptr = cv_bridge::toCvShare(l_image_msg, sensor_msgs::image_encodings::MONO8);
00138 r_cv_ptr = cv_bridge::toCvShare(r_image_msg, sensor_msgs::image_encodings::MONO8);
00139
00140 l_image_data = l_cv_ptr->image.data;
00141 l_step = l_cv_ptr->image.step[0];
00142 r_image_data = r_cv_ptr->image.data;
00143 r_step = r_cv_ptr->image.step[0];
00144 int32_t dims[] = {l_image_msg->width, l_image_msg->height, l_step};
00145
00146 ROS_ASSERT(l_step == r_step);
00147 ROS_ASSERT(l_image_msg->width == r_image_msg->width);
00148 ROS_ASSERT(l_image_msg->height == r_image_msg->height);
00149
00150
00151
00152 if (first_run || got_lost_)
00153 {
00154 visual_odometer_->process(l_image_data, r_image_data, dims);
00155 got_lost_ = false;
00156
00157 if (first_run)
00158 {
00159 tf::Transform delta_transform;
00160 delta_transform.setIdentity();
00161 integrateAndPublish(delta_transform, l_image_msg->header.stamp);
00162 }
00163 }
00164 else
00165 {
00166 bool success = visual_odometer_->process(
00167 l_image_data, r_image_data, dims, change_reference_frame_);
00168 if (success)
00169 {
00170 Matrix motion = Matrix::inv(visual_odometer_->getMotion());
00171 ROS_WARN_THROTTLE(1.0,"Found %i matches with %i inliers.",
00172 visual_odometer_->getNumberOfMatches(),
00173 visual_odometer_->getNumberOfInliers());
00174 ROS_DEBUG_STREAM("libviso2 returned the following motion:\n" << motion);
00175 Matrix camera_motion;
00176
00177
00178 if (change_reference_frame_)
00179 {
00180 camera_motion = Matrix::inv(reference_motion_) * motion;
00181 }
00182 else
00183 {
00184
00185 camera_motion = motion;
00186 }
00187 reference_motion_ = motion;
00188
00189 tf::Matrix3x3 rot_mat(
00190 camera_motion.val[0][0], camera_motion.val[0][1], camera_motion.val[0][2],
00191 camera_motion.val[1][0], camera_motion.val[1][1], camera_motion.val[1][2],
00192 camera_motion.val[2][0], camera_motion.val[2][1], camera_motion.val[2][2]);
00193 tf::Vector3 t(camera_motion.val[0][3], camera_motion.val[1][3], camera_motion.val[2][3]);
00194 tf::Transform delta_transform(rot_mat, t);
00195
00196 setPoseCovariance(STANDARD_POSE_COVARIANCE);
00197 setTwistCovariance(STANDARD_TWIST_COVARIANCE);
00198
00199 integrateAndPublish(delta_transform, l_image_msg->header.stamp);
00200
00201 if(1 == show_matches)
00202 {
00203 drawMatches(l_info_msg, l_image_msg, r_info_msg,r_image_msg,
00204 visual_odometer_->getMatches(),
00205 visual_odometer_->getInlierIndices());
00206 }
00207 else if(2 == show_matches)
00208 {
00209 drawMatchesPreAndCurrent(l_info_msg, l_image_msg, r_info_msg,r_image_msg,
00210 visual_odometer_->getMatches(),
00211 visual_odometer_->getInlierIndices());
00212 }
00213
00214 if (point_cloud_pub_.getNumSubscribers() > 0)
00215 {
00216 computeAndPublishPointCloud(l_info_msg, l_image_msg, r_info_msg,
00217 visual_odometer_->getMatches(),
00218 visual_odometer_->getInlierIndices());
00219 }
00220 }
00221 else
00222 {
00223 setPoseCovariance(BAD_COVARIANCE);
00224 setTwistCovariance(BAD_COVARIANCE);
00225 tf::Transform delta_transform;
00226 delta_transform.setIdentity();
00227 integrateAndPublish(delta_transform, l_image_msg->header.stamp);
00228
00229 ROS_DEBUG("Call to VisualOdometryStereo::process() failed.");
00230 ROS_WARN_THROTTLE(1.0, "Visual Odometer got lost!");
00231 got_lost_ = true;
00232 }
00233
00234 if(success)
00235 {
00236
00237
00238 switch ( ref_frame_change_method_ )
00239 {
00240 case 1:
00241 {
00242
00243 double feature_flow = computeFeatureFlow(visual_odometer_->getMatches());
00244 change_reference_frame_ = (feature_flow < ref_frame_motion_threshold_);
00245 ROS_DEBUG_STREAM("Feature flow is " << feature_flow
00246 << ", marking last motion as "
00247 << (change_reference_frame_ ? "small." : "normal."));
00248 break;
00249 }
00250 case 2:
00251 {
00252 change_reference_frame_ = (visual_odometer_->getNumberOfInliers() > ref_frame_inlier_threshold_);
00253 break;
00254 }
00255 default:
00256 change_reference_frame_ = false;
00257 }
00258
00259 }
00260 else
00261 change_reference_frame_ = false;
00262
00263 if(!change_reference_frame_)
00264 ROS_DEBUG_STREAM("Changing reference frame");
00265
00266
00267 VisoInfo info_msg;
00268 info_msg.header.stamp = l_image_msg->header.stamp;
00269 info_msg.got_lost = !success;
00270 info_msg.change_reference_frame = !change_reference_frame_;
00271 info_msg.num_matches = visual_odometer_->getNumberOfMatches();
00272 info_msg.num_inliers = visual_odometer_->getNumberOfInliers();
00273 ros::WallDuration time_elapsed = ros::WallTime::now() - start_time;
00274 info_msg.runtime = time_elapsed.toSec();
00275 info_pub_.publish(info_msg);
00276 }
00277 }
00278
00279 double computeFeatureFlow(
00280 const std::vector<Matcher::p_match>& matches)
00281 {
00282 double total_flow = 0.0;
00283 for (size_t i = 0; i < matches.size(); ++i)
00284 {
00285 double x_diff = matches[i].u1c - matches[i].u1p;
00286 double y_diff = matches[i].v1c - matches[i].v1p;
00287 total_flow += sqrt(x_diff * x_diff + y_diff * y_diff);
00288 }
00289 return total_flow / matches.size();
00290 }
00291
00292
00293 void drawMatches(
00294 const sensor_msgs::CameraInfoConstPtr& l_info_msg,
00295 const sensor_msgs::ImageConstPtr& l_image_msg,
00296 const sensor_msgs::CameraInfoConstPtr& r_info_msg,
00297 const sensor_msgs::ImageConstPtr& r_image_msg,
00298 const std::vector<Matcher::p_match>& matches,
00299 const std::vector<int32_t>& inlier_indices)
00300 {
00301
00302 try
00303 {
00304 using namespace cv;
00305 cv_bridge::CvImageConstPtr cv_ptr_l,cv_ptr_r;
00306 cv_ptr_l = cv_bridge::toCvShare(l_image_msg, sensor_msgs::image_encodings::RGB8);
00307 cv::Mat img_l = cv_ptr_l->image;
00308 cv_ptr_r = cv_bridge::toCvShare(r_image_msg, sensor_msgs::image_encodings::RGB8);
00309 cv::Mat img_r = cv_ptr_r->image;
00310
00311
00312 image_geometry::StereoCameraModel model;
00313 model.fromCameraInfo(*l_info_msg, *r_info_msg);
00314
00315 Mat imgResult(img_l.rows,2*img_l.cols,img_l.type());
00316 Mat roiImgResult_Left = imgResult(Rect(0,0,img_l.cols,img_l.rows));
00317 Mat roiImgResult_Right = imgResult(Rect(img_l.cols,0,img_l.cols,img_l.rows));
00318 Mat roiImg1 = img_l(Rect(0,0,img_l.cols,img_l.rows));
00319 Mat roiImg2 = img_r(Rect(0,0,img_r.cols,img_r.rows));
00320 roiImg1.copyTo(roiImgResult_Left);
00321 roiImg2.copyTo(roiImgResult_Right);
00322
00323 for (size_t i = 0; i < inlier_indices.size(); ++i)
00324 {
00325 double u1c,v1c,u2c,v2c;
00326 u1c = matches[inlier_indices[i]].u1c;
00327 v1c = matches[inlier_indices[i]].v1c;
00328 u2c = matches[inlier_indices[i]].u2c;
00329 v2c = matches[inlier_indices[i]].v2c;
00330 line( imgResult,
00331 Point(u1c,v1c),
00332 Point(u2c+img_l.cols,v2c),
00333 Scalar( 255, 0, 0 ),
00334 2 ,
00335 8 );
00336 }
00337
00338 imshow("Finalimg",imgResult);
00339 cv::waitKey(2);
00340 }
00341 catch (cv_bridge::Exception& e)
00342 {
00343 ROS_ERROR("cv_bridge exception: %s", e.what());
00344 }
00345 }
00346
00347 cv::Mat pre;
00348 void drawMatchesPreAndCurrent(
00349 const sensor_msgs::CameraInfoConstPtr& l_info_msg,
00350 const sensor_msgs::ImageConstPtr& l_image_msg,
00351 const sensor_msgs::CameraInfoConstPtr& r_info_msg,
00352 const sensor_msgs::ImageConstPtr& r_image_msg,
00353 const std::vector<Matcher::p_match>& matches,
00354 const std::vector<int32_t>& inlier_indices)
00355 {
00356
00357 try
00358 {
00359 using namespace cv;
00360 cv_bridge::CvImageConstPtr cv_ptr_l;
00361 cv_ptr_l = cv_bridge::toCvShare(l_image_msg, sensor_msgs::image_encodings::RGB8);
00362 cv::Mat img_l = cv_ptr_l->image;
00363
00364 if(pre.rows == 0)
00365 {
00366 pre = img_l;
00367 return;
00368 }
00369
00370
00371 image_geometry::StereoCameraModel model;
00372 model.fromCameraInfo(*l_info_msg, *r_info_msg);
00373
00374 Mat imgResult(img_l.rows,2*img_l.cols,img_l.type());
00375 Mat roiImgResult_Left = imgResult(Rect(0,0,img_l.cols,img_l.rows));
00376 Mat roiImgResult_Right = imgResult(Rect(img_l.cols,0,img_l.cols,img_l.rows));
00377 Mat roiImg1 = img_l(Rect(0,0,img_l.cols,img_l.rows));
00378 Mat roiImg2 = pre(Rect(0,0,pre.cols,pre.rows));
00379 roiImg1.copyTo(roiImgResult_Left);
00380 roiImg2.copyTo(roiImgResult_Right);
00381 for (size_t i = 0; i < matches.size(); ++i)
00382 {
00383 double u1c,v1c,u1p,v1p;
00384 u1c = matches[i].u1c;
00385 v1c = matches[i].v1c;
00386 u1p = matches[i].u1p;
00387 v1p = matches[i].v1p;
00388 line( imgResult,
00389 Point(u1c,v1c),
00390 Point(u1p,v1p),
00391 Scalar( 0, 0, 255),
00392 2 ,
00393 8 );
00394 }
00395 for (size_t i = 0; i < inlier_indices.size(); ++i)
00396 {
00397 double u1c,v1c,u1p,v1p;
00398 u1c = matches[inlier_indices[i]].u1c;
00399 v1c = matches[inlier_indices[i]].v1c;
00400 u1p = matches[inlier_indices[i]].u1p;
00401 v1p = matches[inlier_indices[i]].v1p;
00402 line( imgResult,
00403 Point(u1c,v1c),
00404 Point(u1p,v1p),
00405 Scalar( 0, 255, 0 ),
00406 2 ,
00407 8 );
00408 }
00409 imshow("Finalimg",imgResult);
00410
00411 cv::waitKey(2);
00412 pre = img_l;
00413 }
00414 catch (cv_bridge::Exception& e)
00415 {
00416 ROS_ERROR("cv_bridge exception: %s", e.what());
00417 }
00418 }
00419
00420
00421
00422 void computeAndPublishPointCloud(
00423 const sensor_msgs::CameraInfoConstPtr& l_info_msg,
00424 const sensor_msgs::ImageConstPtr& l_image_msg,
00425 const sensor_msgs::CameraInfoConstPtr& r_info_msg,
00426 const std::vector<Matcher::p_match>& matches,
00427 const std::vector<int32_t>& inlier_indices)
00428 {
00429 try
00430 {
00431 cv_bridge::CvImageConstPtr cv_ptr;
00432 cv_ptr = cv_bridge::toCvShare(l_image_msg, sensor_msgs::image_encodings::RGB8);
00433
00434 image_geometry::StereoCameraModel model;
00435 model.fromCameraInfo(*l_info_msg, *r_info_msg);
00436 PointCloud::Ptr point_cloud(new PointCloud());
00437 point_cloud->header.frame_id = getSensorFrameId();
00438 point_cloud->header.stamp = pcl_conversions::toPCL(l_info_msg->header).stamp;
00439 point_cloud->width = 1;
00440 point_cloud->height = inlier_indices.size();
00441 point_cloud->points.resize(inlier_indices.size());
00442
00443 for (size_t i = 0; i < inlier_indices.size(); ++i)
00444 {
00445 const Matcher::p_match& match = matches[inlier_indices[i]];
00446 cv::Point2d left_uv;
00447 left_uv.x = match.u1c;
00448 left_uv.y = match.v1c;
00449 cv::Point3d point;
00450 double disparity = match.u1c - match.u2c;
00451 model.projectDisparityTo3d(left_uv, disparity, point);
00452 point_cloud->points[i].x = point.x;
00453 point_cloud->points[i].y = point.y;
00454 point_cloud->points[i].z = point.z;
00455 cv::Vec3b color = cv_ptr->image.at<cv::Vec3b>(left_uv.y,left_uv.x);
00456 point_cloud->points[i].r = color[0];
00457 point_cloud->points[i].g = color[1];
00458 point_cloud->points[i].b = color[2];
00459 }
00460
00461 ROS_DEBUG("Publishing point cloud with %zu points.", point_cloud->size());
00462 point_cloud_pub_.publish(point_cloud);
00463 }
00464 catch (cv_bridge::Exception& e)
00465 {
00466 ROS_ERROR("cv_bridge exception: %s", e.what());
00467 }
00468 }
00469 };
00470
00471 }
00472
00473
00474 int main(int argc, char **argv)
00475 {
00476 ros::init(argc, argv, "stereo_odometer");
00477 if (ros::names::remap("stereo") == "stereo") {
00478 ROS_WARN("'stereo' has not been remapped! Example command-line usage:\n"
00479 "\t$ rosrun viso2_ros stereo_odometer stereo:=narrow_stereo image:=image_rect");
00480 }
00481 if (ros::names::remap("image").find("rect") == std::string::npos) {
00482 ROS_WARN("stereo_odometer needs rectified input images. The used image "
00483 "topic is '%s'. Are you sure the images are rectified?",
00484 ros::names::remap("image").c_str());
00485 }
00486
00487 std::string transport = argc > 1 ? argv[1] : "raw";
00488 viso2_ros::StereoOdometer odometer(transport);
00489
00490 ros::spin();
00491 return 0;
00492 }
00493