mirror of
https://github.com/introlab/rtabmap.git
synced 2026-09-03 01:50:24 +08:00
Camera refactoring (#315)
* Camera Refactoring part 1 (Mac OS X) * fixed camera*** -> Camera*** * Fixed build for cameras Zed/RealSense/RealSense2 * increased version to 0.17.7 * fixed build for cameras K4W2 and FlyCapture2
This commit is contained in:
278
corelib/src/odometry/OdometryDVO.cpp
Normal file
278
corelib/src/odometry/OdometryDVO.cpp
Normal file
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Universite de Sherbrooke nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "rtabmap/core/odometry/OdometryDVO.h"
|
||||
#include "rtabmap/core/OdometryInfo.h"
|
||||
#include "rtabmap/core/util2d.h"
|
||||
#include "rtabmap/utilite/ULogger.h"
|
||||
#include "rtabmap/utilite/UTimer.h"
|
||||
#include "rtabmap/utilite/UStl.h"
|
||||
|
||||
#ifdef RTABMAP_DVO
|
||||
#include <dvo/dense_tracking.h>
|
||||
#include <dvo/core/surface_pyramid.h>
|
||||
#include <dvo/core/rgbd_image.h>
|
||||
#endif
|
||||
|
||||
namespace rtabmap {
|
||||
|
||||
OdometryDVO::OdometryDVO(const ParametersMap & parameters) :
|
||||
Odometry(parameters),
|
||||
#ifdef RTABMAP_DVO
|
||||
dvo_(0),
|
||||
reference_(0),
|
||||
camera_(0),
|
||||
lost_(false),
|
||||
#endif
|
||||
motionFromKeyFrame_(Transform::getIdentity())
|
||||
{
|
||||
}
|
||||
|
||||
OdometryDVO::~OdometryDVO()
|
||||
{
|
||||
#ifdef RTABMAP_DVO
|
||||
delete dvo_;
|
||||
delete reference_;
|
||||
delete camera_;
|
||||
#endif
|
||||
}
|
||||
|
||||
void OdometryDVO::reset(const Transform & initialPose)
|
||||
{
|
||||
Odometry::reset(initialPose);
|
||||
#ifdef RTABMAP_DVO
|
||||
if(dvo_)
|
||||
{
|
||||
delete dvo_;
|
||||
dvo_ = 0;
|
||||
}
|
||||
if(reference_)
|
||||
{
|
||||
delete reference_;
|
||||
reference_ = 0;
|
||||
}
|
||||
if(camera_)
|
||||
{
|
||||
delete camera_;
|
||||
camera_ = 0;
|
||||
}
|
||||
lost_ = false;
|
||||
motionFromKeyFrame_.setIdentity();
|
||||
previousLocalTransform_.setNull();
|
||||
#endif
|
||||
}
|
||||
|
||||
// return not null transform if odometry is correctly computed
|
||||
Transform OdometryDVO::computeTransform(
|
||||
SensorData & data,
|
||||
const Transform & guess,
|
||||
OdometryInfo * info)
|
||||
{
|
||||
Transform t;
|
||||
|
||||
#ifdef RTABMAP_DVO
|
||||
UTimer timer;
|
||||
|
||||
if(data.imageRaw().empty() ||
|
||||
data.imageRaw().rows != data.depthOrRightRaw().rows ||
|
||||
data.imageRaw().cols != data.depthOrRightRaw().cols)
|
||||
{
|
||||
UERROR("Not supported input!");
|
||||
return t;
|
||||
}
|
||||
|
||||
if(!(data.cameraModels().size() == 1 && data.cameraModels()[0].isValidForReprojection()))
|
||||
{
|
||||
UERROR("Invalid camera model! Only single RGB-D camera supported by DVO. Try another odometry approach.");
|
||||
return t;
|
||||
}
|
||||
|
||||
if(dvo_ == 0)
|
||||
{
|
||||
dvo::DenseTracker::Config cfg = dvo::DenseTracker::getDefaultConfig();
|
||||
|
||||
dvo_ = new dvo::DenseTracker(cfg);
|
||||
}
|
||||
|
||||
cv::Mat grey, grey_s16, depth_inpainted, depth_mask, depth_mono, depth_float;
|
||||
if(data.imageRaw().type() != CV_32FC1)
|
||||
{
|
||||
if(data.imageRaw().type() == CV_8UC3)
|
||||
{
|
||||
cv::cvtColor(data.imageRaw(), grey, CV_BGR2GRAY);
|
||||
}
|
||||
else
|
||||
{
|
||||
grey = data.imageRaw();
|
||||
}
|
||||
|
||||
grey.convertTo(grey_s16, CV_32F);
|
||||
}
|
||||
else
|
||||
{
|
||||
grey_s16 = data.imageRaw();
|
||||
}
|
||||
|
||||
// make sure all zeros are NAN
|
||||
if(data.depthRaw().type() == CV_32FC1)
|
||||
{
|
||||
depth_float = data.depthRaw();
|
||||
for(int i=0; i<depth_float.rows; ++i)
|
||||
{
|
||||
for(int j=0; j<depth_float.cols; ++j)
|
||||
{
|
||||
float & d = depth_float.at<float>(i,j);
|
||||
if(d == 0.0f)
|
||||
{
|
||||
d = NAN;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(data.depthRaw().type() == CV_16UC1)
|
||||
{
|
||||
depth_float = cv::Mat(data.depthRaw().size(), CV_32FC1);
|
||||
for(int i=0; i<data.depthRaw().rows; ++i)
|
||||
{
|
||||
for(int j=0; j<data.depthRaw().cols; ++j)
|
||||
{
|
||||
float d = float(data.depthRaw().at<unsigned short>(i,j))/1000.0f;
|
||||
depth_float.at<float>(i, j) = d==0.0f?NAN:d;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UFATAL("Unknown depth format!");
|
||||
}
|
||||
|
||||
if(camera_ == 0)
|
||||
{
|
||||
dvo::core::IntrinsicMatrix intrinsics = dvo::core::IntrinsicMatrix::create(
|
||||
data.cameraModels()[0].fx(),
|
||||
data.cameraModels()[0].fy(),
|
||||
data.cameraModels()[0].cx(),
|
||||
data.cameraModels()[0].cy());
|
||||
camera_ = new dvo::core::RgbdCameraPyramid(
|
||||
data.cameraModels()[0].imageWidth(),
|
||||
data.cameraModels()[0].imageHeight(),
|
||||
intrinsics);
|
||||
}
|
||||
|
||||
dvo::core::RgbdImagePyramid * current = new dvo::core::RgbdImagePyramid(*camera_, grey_s16, depth_float);
|
||||
|
||||
const Transform & localTransform = data.cameraModels()[0].localTransform();
|
||||
cv::Mat covariance;
|
||||
if(reference_ == 0)
|
||||
{
|
||||
reference_ = current;
|
||||
if(!lost_)
|
||||
{
|
||||
t.setIdentity();
|
||||
}
|
||||
covariance = cv::Mat::eye(6,6,CV_64FC1) * 9999.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
dvo::DenseTracker::Result result;
|
||||
dvo_->match(*reference_, *current, result);
|
||||
t = Transform::fromEigen3d(result.Transformation);
|
||||
|
||||
if(result.Information(0,0) > 0.0 && result.Information(0,0) != 1.0)
|
||||
{
|
||||
lost_ = false;
|
||||
cv::Mat information = cv::Mat::eye(6,6, CV_64FC1);
|
||||
memcpy(information.data, result.Information.data(), 36*sizeof(double));
|
||||
//copy only diagonal to avoid g2o/gtsam errors on graph optimization
|
||||
covariance = cv::Mat::eye(6,6,CV_64FC1);
|
||||
covariance = information.inv().mul(covariance);
|
||||
//covariance *= 100.0; // to be in the same scale than loop closure detection
|
||||
|
||||
Transform currentMotion = t;
|
||||
t = motionFromKeyFrame_.inverse() * t;
|
||||
|
||||
// TODO make parameters?
|
||||
if(currentMotion.getNorm() > 0.01 || currentMotion.getAngle() > 0.01)
|
||||
{
|
||||
if(info)
|
||||
{
|
||||
info->keyFrameAdded = true;
|
||||
}
|
||||
// new keyframe
|
||||
delete reference_;
|
||||
reference_ = current;
|
||||
motionFromKeyFrame_.setIdentity();
|
||||
}
|
||||
else
|
||||
{
|
||||
delete current;
|
||||
motionFromKeyFrame_ = currentMotion;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lost_ = true;
|
||||
delete reference_;
|
||||
delete current;
|
||||
reference_ = 0; // this will make restart from the next frame
|
||||
motionFromKeyFrame_.setIdentity();
|
||||
t.setNull();
|
||||
previousLocalTransform_.setNull();
|
||||
covariance = cv::Mat::eye(6,6,CV_64FC1) * 9999.0;
|
||||
UWARN("dvo failed to estimate motion, tracking will be reinitialized on next frame.");
|
||||
}
|
||||
|
||||
if(!t.isNull() && !t.isIdentity() && !localTransform.isIdentity() && !localTransform.isNull())
|
||||
{
|
||||
// from camera frame to base frame
|
||||
if(!previousLocalTransform_.isNull())
|
||||
{
|
||||
t = previousLocalTransform_ * t * localTransform.inverse();
|
||||
}
|
||||
else
|
||||
{
|
||||
t = localTransform * t * localTransform.inverse();
|
||||
}
|
||||
previousLocalTransform_ = localTransform;
|
||||
}
|
||||
}
|
||||
|
||||
if(info)
|
||||
{
|
||||
info->type = (int)kTypeDVO;
|
||||
info->reg.covariance = covariance;
|
||||
}
|
||||
|
||||
UINFO("Odom update time = %fs", timer.elapsed());
|
||||
|
||||
#else
|
||||
UERROR("RTAB-Map is not built with DVO support! Select another visual odometry approach.");
|
||||
#endif
|
||||
return t;
|
||||
}
|
||||
|
||||
} // namespace rtabmap
|
||||
319
corelib/src/odometry/OdometryF2F.cpp
Normal file
319
corelib/src/odometry/OdometryF2F.cpp
Normal file
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Universite de Sherbrooke nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "rtabmap/core/odometry/OdometryF2F.h"
|
||||
#include "rtabmap/core/OdometryInfo.h"
|
||||
#include "rtabmap/core/Registration.h"
|
||||
#include "rtabmap/core/EpipolarGeometry.h"
|
||||
#include "rtabmap/core/util3d_transforms.h"
|
||||
#include "rtabmap/utilite/ULogger.h"
|
||||
#include "rtabmap/utilite/UTimer.h"
|
||||
#include "rtabmap/utilite/UStl.h"
|
||||
|
||||
namespace rtabmap {
|
||||
|
||||
OdometryF2F::OdometryF2F(const ParametersMap & parameters) :
|
||||
Odometry(parameters),
|
||||
keyFrameThr_(Parameters::defaultOdomKeyFrameThr()),
|
||||
visKeyFrameThr_(Parameters::defaultOdomVisKeyFrameThr()),
|
||||
scanKeyFrameThr_(Parameters::defaultOdomScanKeyFrameThr())
|
||||
{
|
||||
registrationPipeline_ = Registration::create(parameters);
|
||||
Parameters::parse(parameters, Parameters::kOdomKeyFrameThr(), keyFrameThr_);
|
||||
Parameters::parse(parameters, Parameters::kOdomVisKeyFrameThr(), visKeyFrameThr_);
|
||||
Parameters::parse(parameters, Parameters::kOdomScanKeyFrameThr(), scanKeyFrameThr_);
|
||||
UASSERT(keyFrameThr_>=0.0f && keyFrameThr_<=1.0f);
|
||||
UASSERT(visKeyFrameThr_>=0);
|
||||
UASSERT(scanKeyFrameThr_>=0.0f && scanKeyFrameThr_<=1.0f);
|
||||
|
||||
parameters_ = parameters;
|
||||
}
|
||||
|
||||
OdometryF2F::~OdometryF2F()
|
||||
{
|
||||
delete registrationPipeline_;
|
||||
}
|
||||
|
||||
void OdometryF2F::reset(const Transform & initialPose)
|
||||
{
|
||||
Odometry::reset(initialPose);
|
||||
refFrame_ = Signature();
|
||||
lastKeyFramePose_.setNull();
|
||||
}
|
||||
|
||||
// return not null transform if odometry is correctly computed
|
||||
Transform OdometryF2F::computeTransform(
|
||||
SensorData & data,
|
||||
const Transform & guess,
|
||||
OdometryInfo * info)
|
||||
{
|
||||
UTimer timer;
|
||||
Transform output;
|
||||
if(!data.rightRaw().empty() && !data.stereoCameraModel().isValidForProjection())
|
||||
{
|
||||
UERROR("Calibrated stereo camera required");
|
||||
return output;
|
||||
}
|
||||
if(!data.depthRaw().empty() &&
|
||||
(data.cameraModels().size() != 1 || !data.cameraModels()[0].isValidForProjection()))
|
||||
{
|
||||
UERROR("Calibrated camera required (multi-cameras not supported).");
|
||||
return output;
|
||||
}
|
||||
|
||||
bool addKeyFrame = false;
|
||||
RegistrationInfo regInfo;
|
||||
|
||||
UASSERT(!this->getPose().isNull());
|
||||
if(lastKeyFramePose_.isNull())
|
||||
{
|
||||
lastKeyFramePose_ = this->getPose(); // reset to current pose
|
||||
}
|
||||
Transform motionSinceLastKeyFrame = lastKeyFramePose_.inverse()*this->getPose();
|
||||
|
||||
Signature newFrame(data);
|
||||
if(refFrame_.sensorData().isValid())
|
||||
{
|
||||
float maxCorrespondenceDistance = 0.0f;
|
||||
float pmOutlierRatio = 0.0f;
|
||||
if(guess.isNull() &&
|
||||
!registrationPipeline_->isImageRequired() &&
|
||||
registrationPipeline_->isScanRequired() &&
|
||||
this->framesProcessed() < 2)
|
||||
{
|
||||
// only on initialization (first frame to register), increase icp max correspondences in case the robot is already moving
|
||||
maxCorrespondenceDistance = Parameters::defaultIcpMaxCorrespondenceDistance();
|
||||
pmOutlierRatio = Parameters::defaultIcpPMOutlierRatio();
|
||||
Parameters::parse(parameters_, Parameters::kIcpMaxCorrespondenceDistance(), maxCorrespondenceDistance);
|
||||
Parameters::parse(parameters_, Parameters::kIcpPMOutlierRatio(), pmOutlierRatio);
|
||||
ParametersMap params;
|
||||
params.insert(ParametersPair(Parameters::kIcpMaxCorrespondenceDistance(), uNumber2Str(maxCorrespondenceDistance*3.0f)));
|
||||
params.insert(ParametersPair(Parameters::kIcpPMOutlierRatio(), uNumber2Str(0.95f)));
|
||||
registrationPipeline_->parseParameters(params);
|
||||
}
|
||||
|
||||
Signature tmpRefFrame = refFrame_;
|
||||
output = registrationPipeline_->computeTransformationMod(
|
||||
tmpRefFrame,
|
||||
newFrame,
|
||||
// special case for ICP-only odom, set guess to identity if we just started or reset
|
||||
!guess.isNull()?motionSinceLastKeyFrame*guess:!registrationPipeline_->isImageRequired()&&this->framesProcessed()<2?motionSinceLastKeyFrame:Transform(),
|
||||
®Info);
|
||||
|
||||
if(maxCorrespondenceDistance>0.0f)
|
||||
{
|
||||
// set it back
|
||||
ParametersMap params;
|
||||
params.insert(ParametersPair(Parameters::kIcpMaxCorrespondenceDistance(), uNumber2Str(maxCorrespondenceDistance)));
|
||||
params.insert(ParametersPair(Parameters::kIcpPMOutlierRatio(), uNumber2Str(pmOutlierRatio)));
|
||||
registrationPipeline_->parseParameters(params);
|
||||
}
|
||||
|
||||
if(output.isNull() && !guess.isNull() && registrationPipeline_->isImageRequired())
|
||||
{
|
||||
tmpRefFrame = refFrame_;
|
||||
// reset matches, but keep already extracted features in newFrame.sensorData()
|
||||
newFrame.setWords(std::multimap<int, cv::KeyPoint>());
|
||||
newFrame.setWords3(std::multimap<int, cv::Point3f>());
|
||||
newFrame.setWordsDescriptors(std::multimap<int, cv::Mat>());
|
||||
UWARN("Failed to find a transformation with the provided guess (%s), trying again without a guess.", guess.prettyPrint().c_str());
|
||||
// If optical flow is used, switch temporary to feature matching
|
||||
int visCorTypeBackup = Parameters::defaultVisCorType();
|
||||
Parameters::parse(parameters_, Parameters::kVisCorType(), visCorTypeBackup);
|
||||
if(visCorTypeBackup == 1)
|
||||
{
|
||||
ParametersMap params;
|
||||
params.insert(ParametersPair(Parameters::kVisCorType(), "0"));
|
||||
registrationPipeline_->parseParameters(params);
|
||||
}
|
||||
|
||||
output = registrationPipeline_->computeTransformationMod(
|
||||
tmpRefFrame,
|
||||
newFrame,
|
||||
Transform(), // null guess
|
||||
®Info);
|
||||
|
||||
if(visCorTypeBackup == 1)
|
||||
{
|
||||
ParametersMap params;
|
||||
params.insert(ParametersPair(Parameters::kVisCorType(), "1"));
|
||||
registrationPipeline_->parseParameters(params);
|
||||
}
|
||||
|
||||
if(output.isNull())
|
||||
{
|
||||
UWARN("Trial with no guess still fail.");
|
||||
}
|
||||
else
|
||||
{
|
||||
UWARN("Trial with no guess succeeded.");
|
||||
}
|
||||
}
|
||||
|
||||
if(info && this->isInfoDataFilled())
|
||||
{
|
||||
std::list<std::pair<int, std::pair<cv::KeyPoint, cv::KeyPoint> > > pairs;
|
||||
EpipolarGeometry::findPairsUnique(tmpRefFrame.getWords(), newFrame.getWords(), pairs);
|
||||
info->refCorners.resize(pairs.size());
|
||||
info->newCorners.resize(pairs.size());
|
||||
std::map<int, int> idToIndex;
|
||||
int i=0;
|
||||
for(std::list<std::pair<int, std::pair<cv::KeyPoint, cv::KeyPoint> > >::iterator iter=pairs.begin();
|
||||
iter!=pairs.end();
|
||||
++iter)
|
||||
{
|
||||
info->refCorners[i] = iter->second.first.pt;
|
||||
info->newCorners[i] = iter->second.second.pt;
|
||||
idToIndex.insert(std::make_pair(iter->first, i));
|
||||
++i;
|
||||
}
|
||||
info->cornerInliers.resize(regInfo.inliersIDs.size(), 1);
|
||||
i=0;
|
||||
for(; i<(int)regInfo.inliersIDs.size(); ++i)
|
||||
{
|
||||
info->cornerInliers[i] = idToIndex.at(regInfo.inliersIDs[i]);
|
||||
}
|
||||
|
||||
Transform t = this->getPose()*motionSinceLastKeyFrame.inverse();
|
||||
for(std::multimap<int, cv::Point3f>::const_iterator iter=tmpRefFrame.getWords3().begin(); iter!=tmpRefFrame.getWords3().end(); ++iter)
|
||||
{
|
||||
info->localMap.insert(std::make_pair(iter->first, util3d::transformPoint(iter->second, t)));
|
||||
}
|
||||
info->localMapSize = tmpRefFrame.getWords3().size();
|
||||
info->words = newFrame.getWords();
|
||||
|
||||
info->localScanMapSize = tmpRefFrame.sensorData().laserScanRaw().size();
|
||||
|
||||
info->localScanMap = util3d::transformLaserScan(tmpRefFrame.sensorData().laserScanRaw(), tmpRefFrame.sensorData().laserScanRaw().localTransform().inverse()*t*tmpRefFrame.sensorData().laserScanRaw().localTransform());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//return Identity
|
||||
output = Transform::getIdentity();
|
||||
// a very high variance tells that the new pose is not linked with the previous one
|
||||
regInfo.covariance = cv::Mat::eye(6,6,CV_64FC1)*9999.0;
|
||||
}
|
||||
|
||||
if(!output.isNull())
|
||||
{
|
||||
output = motionSinceLastKeyFrame.inverse() * output;
|
||||
|
||||
// new key-frame?
|
||||
if( (registrationPipeline_->isImageRequired() &&
|
||||
(keyFrameThr_ == 0.0f ||
|
||||
visKeyFrameThr_ == 0 ||
|
||||
float(regInfo.inliers) <= keyFrameThr_*float(refFrame_.sensorData().keypoints().size()) ||
|
||||
regInfo.inliers <= visKeyFrameThr_)) ||
|
||||
(registrationPipeline_->isScanRequired() && (scanKeyFrameThr_ == 0.0f || regInfo.icpInliersRatio <= scanKeyFrameThr_)))
|
||||
{
|
||||
UDEBUG("Update key frame");
|
||||
int features = newFrame.getWordsDescriptors().size();
|
||||
if(registrationPipeline_->isImageRequired() && features == 0)
|
||||
{
|
||||
newFrame = Signature(data);
|
||||
// this will generate features only for the first frame or if optical flow was used (no 3d words)
|
||||
Signature dummy;
|
||||
registrationPipeline_->computeTransformationMod(
|
||||
newFrame,
|
||||
dummy);
|
||||
features = (int)newFrame.sensorData().keypoints().size();
|
||||
}
|
||||
|
||||
if((features >= registrationPipeline_->getMinVisualCorrespondences()) &&
|
||||
(registrationPipeline_->getMinGeometryCorrespondencesRatio()==0.0f ||
|
||||
(newFrame.sensorData().laserScanRaw().size() &&
|
||||
(newFrame.sensorData().laserScanRaw().maxPoints() == 0 || float(newFrame.sensorData().laserScanRaw().size())/float(newFrame.sensorData().laserScanRaw().maxPoints())>=registrationPipeline_->getMinGeometryCorrespondencesRatio()))))
|
||||
{
|
||||
refFrame_ = newFrame;
|
||||
|
||||
refFrame_.setWords(std::multimap<int, cv::KeyPoint>());
|
||||
refFrame_.setWords3(std::multimap<int, cv::Point3f>());
|
||||
refFrame_.setWordsDescriptors(std::multimap<int, cv::Mat>());
|
||||
|
||||
//reset motion
|
||||
lastKeyFramePose_.setNull();
|
||||
|
||||
addKeyFrame = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!refFrame_.sensorData().isValid())
|
||||
{
|
||||
// Don't send odometry if we don't have a keyframe yet
|
||||
output.setNull();
|
||||
}
|
||||
|
||||
if(features < registrationPipeline_->getMinVisualCorrespondences())
|
||||
{
|
||||
UWARN("Too low 2D features (%d), keeping last key frame...", features);
|
||||
}
|
||||
|
||||
if(registrationPipeline_->getMinGeometryCorrespondencesRatio()>0.0f && newFrame.sensorData().laserScanRaw().size()==0)
|
||||
{
|
||||
UWARN("Too low scan points (%d), keeping last key frame...", newFrame.sensorData().laserScanRaw().size());
|
||||
}
|
||||
else if(registrationPipeline_->getMinGeometryCorrespondencesRatio()>0.0f && newFrame.sensorData().laserScanRaw().maxPoints() != 0 && float(newFrame.sensorData().laserScanRaw().size())/float(newFrame.sensorData().laserScanRaw().maxPoints())<registrationPipeline_->getMinGeometryCorrespondencesRatio())
|
||||
{
|
||||
UWARN("Too low scan points ratio (%d < %d), keeping last key frame...", float(newFrame.sensorData().laserScanRaw().size())/float(newFrame.sensorData().laserScanRaw().maxPoints()), registrationPipeline_->getMinGeometryCorrespondencesRatio());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(!regInfo.rejectedMsg.empty())
|
||||
{
|
||||
UWARN("Registration failed: \"%s\"", regInfo.rejectedMsg.c_str());
|
||||
}
|
||||
|
||||
data.setFeatures(newFrame.sensorData().keypoints(), newFrame.sensorData().keypoints3D(), newFrame.sensorData().descriptors());
|
||||
|
||||
if(info)
|
||||
{
|
||||
info->type = kTypeF2F;
|
||||
info->features = newFrame.sensorData().keypoints().size();
|
||||
info->keyFrameAdded = addKeyFrame;
|
||||
if(this->isInfoDataFilled())
|
||||
{
|
||||
info->reg = regInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
info->reg = regInfo.copyWithoutData();
|
||||
}
|
||||
}
|
||||
|
||||
UINFO("Odom update time = %fs lost=%s inliers=%d, ref frame corners=%d, transform accepted=%s",
|
||||
timer.elapsed(),
|
||||
output.isNull()?"true":"false",
|
||||
(int)regInfo.inliers,
|
||||
(int)newFrame.sensorData().keypoints().size(),
|
||||
!output.isNull()?"true":"false");
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
} // namespace rtabmap
|
||||
1140
corelib/src/odometry/OdometryF2M.cpp
Normal file
1140
corelib/src/odometry/OdometryF2M.cpp
Normal file
File diff suppressed because it is too large
Load Diff
415
corelib/src/odometry/OdometryFovis.cpp
Normal file
415
corelib/src/odometry/OdometryFovis.cpp
Normal file
@@ -0,0 +1,415 @@
|
||||
/*
|
||||
Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Universite de Sherbrooke nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "rtabmap/core/odometry/OdometryFovis.h"
|
||||
#include "rtabmap/core/OdometryInfo.h"
|
||||
#include "rtabmap/core/util2d.h"
|
||||
#include "rtabmap/utilite/ULogger.h"
|
||||
#include "rtabmap/utilite/UTimer.h"
|
||||
#include "rtabmap/utilite/UStl.h"
|
||||
|
||||
#ifdef RTABMAP_FOVIS
|
||||
#include <libfovis/fovis.hpp>
|
||||
#endif
|
||||
|
||||
namespace rtabmap {
|
||||
|
||||
OdometryFovis::OdometryFovis(const ParametersMap & parameters) :
|
||||
Odometry(parameters)
|
||||
#ifdef RTABMAP_FOVIS
|
||||
,
|
||||
fovis_(0),
|
||||
rect_(0),
|
||||
stereoCalib_(0),
|
||||
depthImage_(0),
|
||||
stereoDepth_(0),
|
||||
lost_(false)
|
||||
#endif
|
||||
{
|
||||
fovisParameters_ = Parameters::filterParameters(parameters, "OdomFovis");
|
||||
if(parameters.find(Parameters::kOdomVisKeyFrameThr()) != parameters.end())
|
||||
{
|
||||
fovisParameters_.insert(*parameters.find(Parameters::kOdomVisKeyFrameThr()));
|
||||
}
|
||||
}
|
||||
|
||||
OdometryFovis::~OdometryFovis()
|
||||
{
|
||||
#ifdef RTABMAP_FOVIS
|
||||
delete fovis_;
|
||||
delete rect_;
|
||||
delete stereoCalib_;
|
||||
delete depthImage_;
|
||||
delete stereoDepth_;
|
||||
#endif
|
||||
}
|
||||
|
||||
void OdometryFovis::reset(const Transform & initialPose)
|
||||
{
|
||||
Odometry::reset(initialPose);
|
||||
#ifdef RTABMAP_FOVIS
|
||||
if(fovis_)
|
||||
{
|
||||
delete fovis_;
|
||||
fovis_ = 0;
|
||||
}
|
||||
if(rect_)
|
||||
{
|
||||
delete rect_;
|
||||
rect_ = 0;
|
||||
}
|
||||
if(stereoCalib_)
|
||||
{
|
||||
delete stereoCalib_;
|
||||
stereoCalib_ = 0;
|
||||
}
|
||||
if(depthImage_)
|
||||
{
|
||||
delete depthImage_;
|
||||
depthImage_ = 0;
|
||||
}
|
||||
if(stereoDepth_)
|
||||
{
|
||||
delete stereoDepth_;
|
||||
stereoDepth_ = 0;
|
||||
}
|
||||
lost_ = false;
|
||||
previousLocalTransform_.setNull();
|
||||
#endif
|
||||
}
|
||||
|
||||
// return not null transform if odometry is correctly computed
|
||||
Transform OdometryFovis::computeTransform(
|
||||
SensorData & data,
|
||||
const Transform & guess,
|
||||
OdometryInfo * info)
|
||||
{
|
||||
UDEBUG("");
|
||||
Transform t;
|
||||
|
||||
#ifdef RTABMAP_FOVIS
|
||||
UTimer timer;
|
||||
|
||||
if(data.imageRaw().empty() ||
|
||||
data.imageRaw().rows != data.depthOrRightRaw().rows ||
|
||||
data.imageRaw().cols != data.depthOrRightRaw().cols)
|
||||
{
|
||||
UERROR("Not supported input!");
|
||||
return t;
|
||||
}
|
||||
|
||||
if(!((data.cameraModels().size() == 1 &&
|
||||
data.cameraModels()[0].isValidForReprojection()) ||
|
||||
(data.stereoCameraModel().isValidForProjection() &&
|
||||
data.stereoCameraModel().left().isValidForReprojection() &&
|
||||
data.stereoCameraModel().right().isValidForReprojection())))
|
||||
{
|
||||
UERROR("Invalid camera model! Mono cameras=%d (reproj=%d), Stereo camera=%d (reproj=%d|%d)",
|
||||
(int)data.cameraModels().size(),
|
||||
data.cameraModels().size() && data.cameraModels()[0].isValidForReprojection()?1:0,
|
||||
data.stereoCameraModel().isValidForProjection()?1:0,
|
||||
data.stereoCameraModel().left().isValidForReprojection()?1:0,
|
||||
data.stereoCameraModel().right().isValidForReprojection()?1:0);
|
||||
return t;
|
||||
}
|
||||
|
||||
cv::Mat gray;
|
||||
if(data.imageRaw().type() == CV_8UC3)
|
||||
{
|
||||
cv::cvtColor(data.imageRaw(), gray, CV_BGR2GRAY);
|
||||
}
|
||||
else if(data.imageRaw().type() == CV_8UC1)
|
||||
{
|
||||
gray = data.imageRaw();
|
||||
}
|
||||
else
|
||||
{
|
||||
UFATAL("Not supported color type!");
|
||||
}
|
||||
|
||||
fovis::VisualOdometryOptions options;
|
||||
if(fovis_ == 0 || (data.cameraModels().size() != 1 && stereoDepth_ == 0))
|
||||
{
|
||||
options = fovis::VisualOdometry::getDefaultOptions();
|
||||
|
||||
ParametersMap defaults = Parameters::getDefaultParameters("OdomFovis");
|
||||
options["feature-window-size"] = uValue(fovisParameters_, Parameters::kOdomFovisFeatureWindowSize(), defaults.at(Parameters::kOdomFovisFeatureWindowSize()));
|
||||
options["max-pyramid-level"] = uValue(fovisParameters_, Parameters::kOdomFovisMaxPyramidLevel(), defaults.at(Parameters::kOdomFovisMaxPyramidLevel()));
|
||||
options["min-pyramid-level"] = uValue(fovisParameters_, Parameters::kOdomFovisMinPyramidLevel(), defaults.at(Parameters::kOdomFovisMinPyramidLevel()));
|
||||
options["target-pixels-per-feature"] = uValue(fovisParameters_, Parameters::kOdomFovisTargetPixelsPerFeature(), defaults.at(Parameters::kOdomFovisTargetPixelsPerFeature()));
|
||||
options["fast-threshold"] = uValue(fovisParameters_, Parameters::kOdomFovisFastThreshold(), defaults.at(Parameters::kOdomFovisFastThreshold()));
|
||||
options["use-adaptive-threshold"] = uValue(fovisParameters_, Parameters::kOdomFovisUseAdaptiveThreshold(), defaults.at(Parameters::kOdomFovisUseAdaptiveThreshold()));
|
||||
options["fast-threshold-adaptive-gain"] = uValue(fovisParameters_, Parameters::kOdomFovisFastThresholdAdaptiveGain(), defaults.at(Parameters::kOdomFovisFastThresholdAdaptiveGain()));
|
||||
options["use-homography-initialization"] = uValue(fovisParameters_, Parameters::kOdomFovisUseHomographyInitialization(), defaults.at(Parameters::kOdomFovisUseHomographyInitialization()));
|
||||
options["ref-frame-change-threshold"] = uValue(fovisParameters_, Parameters::kOdomVisKeyFrameThr(), uNumber2Str(Parameters::defaultOdomVisKeyFrameThr()));
|
||||
|
||||
// OdometryFrame
|
||||
options["use-bucketing"] = uValue(fovisParameters_, Parameters::kOdomFovisUseBucketing(), defaults.at(Parameters::kOdomFovisUseBucketing()));
|
||||
options["bucket-width"] = uValue(fovisParameters_, Parameters::kOdomFovisBucketWidth(), defaults.at(Parameters::kOdomFovisBucketWidth()));
|
||||
options["bucket-height"] = uValue(fovisParameters_, Parameters::kOdomFovisBucketHeight(), defaults.at(Parameters::kOdomFovisBucketHeight()));
|
||||
options["max-keypoints-per-bucket"] = uValue(fovisParameters_, Parameters::kOdomFovisMaxKeypointsPerBucket(), defaults.at(Parameters::kOdomFovisMaxKeypointsPerBucket()));
|
||||
options["use-image-normalization"] = uValue(fovisParameters_, Parameters::kOdomFovisUseImageNormalization(), defaults.at(Parameters::kOdomFovisUseImageNormalization()));
|
||||
|
||||
// MotionEstimator
|
||||
options["inlier-max-reprojection-error"] = uValue(fovisParameters_, Parameters::kOdomFovisInlierMaxReprojectionError(), defaults.at(Parameters::kOdomFovisInlierMaxReprojectionError()));
|
||||
options["clique-inlier-threshold"] = uValue(fovisParameters_, Parameters::kOdomFovisCliqueInlierThreshold(), defaults.at(Parameters::kOdomFovisCliqueInlierThreshold()));
|
||||
options["min-features-for-estimate"] = uValue(fovisParameters_, Parameters::kOdomFovisMinFeaturesForEstimate(), defaults.at(Parameters::kOdomFovisMinFeaturesForEstimate()));
|
||||
options["max-mean-reprojection-error"] = uValue(fovisParameters_, Parameters::kOdomFovisMaxMeanReprojectionError(), defaults.at(Parameters::kOdomFovisMaxMeanReprojectionError()));
|
||||
options["use-subpixel-refinement"] = uValue(fovisParameters_, Parameters::kOdomFovisUseSubpixelRefinement(), defaults.at(Parameters::kOdomFovisUseSubpixelRefinement()));
|
||||
options["feature-search-window"] = uValue(fovisParameters_, Parameters::kOdomFovisFeatureSearchWindow(), defaults.at(Parameters::kOdomFovisFeatureSearchWindow()));
|
||||
options["update-target-features-with-refined"] = uValue(fovisParameters_, Parameters::kOdomFovisUpdateTargetFeaturesWithRefined(), defaults.at(Parameters::kOdomFovisUpdateTargetFeaturesWithRefined()));
|
||||
|
||||
// StereoDepth
|
||||
options["stereo-require-mutual-match"] = uValue(fovisParameters_, Parameters::kOdomFovisStereoRequireMutualMatch(), defaults.at(Parameters::kOdomFovisStereoRequireMutualMatch()));
|
||||
options["stereo-max-dist-epipolar-line"] = uValue(fovisParameters_, Parameters::kOdomFovisStereoMaxDistEpipolarLine(), defaults.at(Parameters::kOdomFovisStereoMaxDistEpipolarLine()));
|
||||
options["stereo-max-refinement-displacement"] = uValue(fovisParameters_, Parameters::kOdomFovisStereoMaxRefinementDisplacement(), defaults.at(Parameters::kOdomFovisStereoMaxRefinementDisplacement()));
|
||||
options["stereo-max-disparity"] = uValue(fovisParameters_, Parameters::kOdomFovisStereoMaxDisparity(), defaults.at(Parameters::kOdomFovisStereoMaxDisparity()));
|
||||
}
|
||||
|
||||
fovis::DepthSource * depthSource = 0;
|
||||
cv::Mat depth;
|
||||
cv::Mat right;
|
||||
Transform localTransform = Transform::getIdentity();
|
||||
if(data.cameraModels().size() == 1) //depth
|
||||
{
|
||||
UDEBUG("");
|
||||
fovis::CameraIntrinsicsParameters rgb_params;
|
||||
memset(&rgb_params, 0, sizeof(fovis::CameraIntrinsicsParameters));
|
||||
rgb_params.width = data.cameraModels()[0].imageWidth();
|
||||
rgb_params.height = data.cameraModels()[0].imageHeight();
|
||||
|
||||
rgb_params.fx = data.cameraModels()[0].fx();
|
||||
rgb_params.fy = data.cameraModels()[0].fy();
|
||||
rgb_params.cx = data.cameraModels()[0].cx()==0.0?double(rgb_params.width) / 2.0:data.cameraModels()[0].cx();
|
||||
rgb_params.cy = data.cameraModels()[0].cy()==0.0?double(rgb_params.height) / 2.0:data.cameraModels()[0].cy();
|
||||
localTransform = data.cameraModels()[0].localTransform();
|
||||
|
||||
if(rect_ == 0)
|
||||
{
|
||||
UINFO("Init rgbd fovis: %dx%d fx=%f fy=%f cx=%f cy=%f", rgb_params.width, rgb_params.height, rgb_params.fx, rgb_params.fy, rgb_params.cx, rgb_params.cy);
|
||||
rect_ = new fovis::Rectification(rgb_params);
|
||||
}
|
||||
|
||||
if(depthImage_ == 0)
|
||||
{
|
||||
depthImage_ = new fovis::DepthImage(rgb_params, rgb_params.width, rgb_params.height);
|
||||
}
|
||||
// make sure all zeros are NAN
|
||||
if(data.depthRaw().type() == CV_32FC1)
|
||||
{
|
||||
depth = data.depthRaw();
|
||||
for(int i=0; i<depth.rows; ++i)
|
||||
{
|
||||
for(int j=0; j<depth.cols; ++j)
|
||||
{
|
||||
float & d = depth.at<float>(i,j);
|
||||
if(d == 0.0f)
|
||||
{
|
||||
d = NAN;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(data.depthRaw().type() == CV_16UC1)
|
||||
{
|
||||
depth = cv::Mat(data.depthRaw().size(), CV_32FC1);
|
||||
for(int i=0; i<data.depthRaw().rows; ++i)
|
||||
{
|
||||
for(int j=0; j<data.depthRaw().cols; ++j)
|
||||
{
|
||||
float d = float(data.depthRaw().at<unsigned short>(i,j))/1000.0f;
|
||||
depth.at<float>(i, j) = d==0.0f?NAN:d;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UFATAL("Unknown depth format!");
|
||||
}
|
||||
depthImage_->setDepthImage((float*)depth.data);
|
||||
depthSource = depthImage_;
|
||||
}
|
||||
else // stereo
|
||||
{
|
||||
UDEBUG("");
|
||||
// initialize left camera parameters
|
||||
fovis::CameraIntrinsicsParameters left_parameters;
|
||||
left_parameters.width = data.stereoCameraModel().left().imageWidth();
|
||||
left_parameters.height = data.stereoCameraModel().left().imageHeight();
|
||||
left_parameters.fx = data.stereoCameraModel().left().fx();
|
||||
left_parameters.fy = data.stereoCameraModel().left().fy();
|
||||
left_parameters.cx = data.stereoCameraModel().left().cx()==0.0?double(left_parameters.width) / 2.0:data.stereoCameraModel().left().cx();
|
||||
left_parameters.cy = data.stereoCameraModel().left().cy()==0.0?double(left_parameters.height) / 2.0:data.stereoCameraModel().left().cy();
|
||||
localTransform = data.stereoCameraModel().localTransform();
|
||||
|
||||
if(rect_ == 0)
|
||||
{
|
||||
UINFO("Init stereo fovis: %dx%d fx=%f fy=%f cx=%f cy=%f", left_parameters.width, left_parameters.height, left_parameters.fx, left_parameters.fy, left_parameters.cx, left_parameters.cy);
|
||||
rect_ = new fovis::Rectification(left_parameters);
|
||||
}
|
||||
|
||||
if(stereoCalib_ == 0)
|
||||
{
|
||||
// initialize right camera parameters
|
||||
fovis::CameraIntrinsicsParameters right_parameters;
|
||||
right_parameters.width = data.stereoCameraModel().right().imageWidth();
|
||||
right_parameters.height = data.stereoCameraModel().right().imageHeight();
|
||||
right_parameters.fx = data.stereoCameraModel().right().fx();
|
||||
right_parameters.fy = data.stereoCameraModel().right().fy();
|
||||
right_parameters.cx = data.stereoCameraModel().right().cx()==0.0?double(right_parameters.width) / 2.0:data.stereoCameraModel().right().cx();
|
||||
right_parameters.cy = data.stereoCameraModel().right().cy()==0.0?double(right_parameters.height) / 2.0:data.stereoCameraModel().right().cy();
|
||||
|
||||
// as we use rectified images, rotation is identity
|
||||
// and translation is baseline only
|
||||
fovis::StereoCalibrationParameters stereo_parameters;
|
||||
stereo_parameters.left_parameters = left_parameters;
|
||||
stereo_parameters.right_parameters = right_parameters;
|
||||
stereo_parameters.right_to_left_rotation[0] = 1.0;
|
||||
stereo_parameters.right_to_left_rotation[1] = 0.0;
|
||||
stereo_parameters.right_to_left_rotation[2] = 0.0;
|
||||
stereo_parameters.right_to_left_rotation[3] = 0.0;
|
||||
stereo_parameters.right_to_left_translation[0] = -data.stereoCameraModel().baseline();
|
||||
stereo_parameters.right_to_left_translation[1] = 0.0;
|
||||
stereo_parameters.right_to_left_translation[2] = 0.0;
|
||||
|
||||
stereoCalib_ = new fovis::StereoCalibration(stereo_parameters);
|
||||
}
|
||||
|
||||
if(stereoDepth_ == 0)
|
||||
{
|
||||
stereoDepth_ = new fovis::StereoDepth(stereoCalib_, options);
|
||||
}
|
||||
if(data.rightRaw().type() == CV_8UC3)
|
||||
{
|
||||
cv::cvtColor(data.rightRaw(), right, CV_BGR2GRAY);
|
||||
}
|
||||
else if(data.rightRaw().type() == CV_8UC1)
|
||||
{
|
||||
right = data.rightRaw();
|
||||
}
|
||||
else
|
||||
{
|
||||
UFATAL("Not supported color type!");
|
||||
}
|
||||
stereoDepth_->setRightImage(right.data);
|
||||
depthSource = stereoDepth_;
|
||||
}
|
||||
|
||||
if(fovis_ == 0)
|
||||
{
|
||||
fovis_ = new fovis::VisualOdometry(rect_, options);
|
||||
}
|
||||
|
||||
UDEBUG("");
|
||||
fovis_->processFrame(gray.data, depthSource);
|
||||
|
||||
// get the motion estimate for this frame to the previous frame.
|
||||
t = Transform::fromEigen3d(fovis_->getMotionEstimate());
|
||||
|
||||
cv::Mat covariance;
|
||||
fovis::MotionEstimateStatusCode statusCode = fovis_->getMotionEstimator()->getMotionEstimateStatus();
|
||||
if(statusCode > fovis::SUCCESS)
|
||||
{
|
||||
UWARN("Fovis error status: %s", fovis::MotionEstimateStatusCodeStrings[statusCode]);
|
||||
t.setNull();
|
||||
lost_ = true;
|
||||
covariance = cv::Mat::eye(6,6, CV_64FC1)*9999.0;
|
||||
previousLocalTransform_.setNull();
|
||||
}
|
||||
else if(lost_)
|
||||
{
|
||||
lost_ = false;
|
||||
// we are not lost anymore but we don't know where we are now according to last valid pose
|
||||
covariance = cv::Mat::eye(6,6, CV_64FC1)*9999.0;
|
||||
previousLocalTransform_.setNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
const Eigen::MatrixXd& cov = fovis_->getMotionEstimator()->getMotionEstimateCov();
|
||||
if(cov.cols() == 6 && cov.rows() == 6 && cov(0,0) > 0.0)
|
||||
{
|
||||
covariance = cv::Mat::eye(6,6, CV_64FC1);
|
||||
memcpy(covariance.data, cov.data(), 36*sizeof(double));
|
||||
covariance *= 100.0; // to be in the same scale than loop closure detection
|
||||
}
|
||||
}
|
||||
|
||||
if(!t.isNull() && !t.isIdentity() && !localTransform.isIdentity() && !localTransform.isNull())
|
||||
{
|
||||
// from camera frame to base frame
|
||||
if(!previousLocalTransform_.isNull())
|
||||
{
|
||||
t = previousLocalTransform_ * t * localTransform.inverse();
|
||||
}
|
||||
else
|
||||
{
|
||||
t = localTransform * t * localTransform.inverse();
|
||||
}
|
||||
previousLocalTransform_ = localTransform;
|
||||
}
|
||||
|
||||
if(info)
|
||||
{
|
||||
info->type = (int)kTypeFovis;
|
||||
info->keyFrameAdded = fovis_->getChangeReferenceFrames();
|
||||
info->features = fovis_->getTargetFrame()->getNumDetectedKeypoints();
|
||||
info->reg.matches = fovis_->getMotionEstimator()->getNumMatches();
|
||||
info->reg.inliers = fovis_->getMotionEstimator()->getNumInliers();
|
||||
info->reg.covariance = covariance;
|
||||
|
||||
if(this->isInfoDataFilled())
|
||||
{
|
||||
const fovis::FeatureMatch * matches = fovis_->getMotionEstimator()->getMatches();
|
||||
int numMatches = fovis_->getMotionEstimator()->getNumMatches();
|
||||
if(matches && numMatches>0)
|
||||
{
|
||||
info->refCorners.resize(numMatches);
|
||||
info->newCorners.resize(numMatches);
|
||||
info->cornerInliers.resize(numMatches);
|
||||
int oi=0;
|
||||
for (int i = 0; i < numMatches; ++i)
|
||||
{
|
||||
info->refCorners[i].x = matches[i].ref_keypoint->base_uv[0];
|
||||
info->refCorners[i].y = matches[i].ref_keypoint->base_uv[1];
|
||||
info->newCorners[i].x = matches[i].target_keypoint->base_uv[0];
|
||||
info->newCorners[i].y = matches[i].target_keypoint->base_uv[1];
|
||||
info->cornerInliers[oi++] = i;
|
||||
}
|
||||
info->cornerInliers.resize(oi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UINFO("Odom update time = %fs status=%s", timer.elapsed(), fovis::MotionEstimateStatusCodeStrings[statusCode]);
|
||||
|
||||
#else
|
||||
UERROR("RTAB-Map is not built with FOVIS support! Select another visual odometry approach.");
|
||||
#endif
|
||||
return t;
|
||||
}
|
||||
|
||||
} // namespace rtabmap
|
||||
304
corelib/src/odometry/OdometryLOAM.cpp
Normal file
304
corelib/src/odometry/OdometryLOAM.cpp
Normal file
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Universite de Sherbrooke nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "rtabmap/core/odometry/OdometryLOAM.h"
|
||||
#include "rtabmap/core/OdometryInfo.h"
|
||||
#include "rtabmap/core/util2d.h"
|
||||
#include "rtabmap/utilite/ULogger.h"
|
||||
#include "rtabmap/utilite/UTimer.h"
|
||||
#include "rtabmap/utilite/UStl.h"
|
||||
#include "rtabmap/core/util3d.h"
|
||||
#include <pcl/common/transforms.h>
|
||||
|
||||
float SCAN_PERIOD = 0.1f;
|
||||
|
||||
namespace rtabmap {
|
||||
|
||||
/**
|
||||
* https://github.com/laboshinl/loam_velodyne/pull/66
|
||||
*/
|
||||
|
||||
OdometryLOAM::OdometryLOAM(const ParametersMap & parameters) :
|
||||
Odometry(parameters)
|
||||
#ifdef RTABMAP_LOAM
|
||||
,lastPose_(Transform::getIdentity())
|
||||
,scanPeriod_(Parameters::defaultOdomLOAMScanPeriod())
|
||||
,linVar_(Parameters::defaultOdomLOAMLinVar())
|
||||
,angVar_(Parameters::defaultOdomLOAMAngVar())
|
||||
,localMapping_(Parameters::defaultOdomLOAMLocalMapping())
|
||||
,lost_(false)
|
||||
#endif
|
||||
{
|
||||
#ifdef RTABMAP_LOAM
|
||||
int velodyneType = 0;
|
||||
Parameters::parse(parameters, Parameters::kOdomLOAMSensor(), velodyneType);
|
||||
Parameters::parse(parameters, Parameters::kOdomLOAMScanPeriod(), scanPeriod_);
|
||||
UASSERT(scanPeriod_>0.0f);
|
||||
Parameters::parse(parameters, Parameters::kOdomLOAMLinVar(), linVar_);
|
||||
UASSERT(linVar_>0.0f);
|
||||
Parameters::parse(parameters, Parameters::kOdomLOAMAngVar(), angVar_);
|
||||
UASSERT(angVar_>0.0f);
|
||||
Parameters::parse(parameters, Parameters::kOdomLOAMLocalMapping(), localMapping_);
|
||||
if(velodyneType == 1)
|
||||
{
|
||||
scanMapper_ = loam::MultiScanMapper::Velodyne_HDL_32();
|
||||
}
|
||||
else if(velodyneType == 2)
|
||||
{
|
||||
scanMapper_ = loam::MultiScanMapper::Velodyne_HDL_64E();
|
||||
}
|
||||
else
|
||||
{
|
||||
scanMapper_ = loam::MultiScanMapper::Velodyne_VLP_16();
|
||||
}
|
||||
laserOdometry_ = new loam::BasicLaserOdometry(scanPeriod_);
|
||||
laserMapping_ = new loam::BasicLaserMapping(scanPeriod_);
|
||||
#endif
|
||||
}
|
||||
|
||||
OdometryLOAM::~OdometryLOAM()
|
||||
{
|
||||
#ifdef RTABMAP_LOAM
|
||||
delete laserOdometry_;
|
||||
delete laserMapping_;
|
||||
#endif
|
||||
}
|
||||
|
||||
void OdometryLOAM::reset(const Transform & initialPose)
|
||||
{
|
||||
Odometry::reset(initialPose);
|
||||
#ifdef RTABMAP_LOAM
|
||||
lastPose_.setIdentity();
|
||||
scanRegistration_ = loam::BasicScanRegistration();
|
||||
loam::RegistrationParams regParams;
|
||||
regParams.scanPeriod = scanPeriod_;
|
||||
scanRegistration_.configure(regParams);
|
||||
delete laserOdometry_;
|
||||
laserOdometry_ = new loam::BasicLaserOdometry(scanPeriod_);
|
||||
delete laserMapping_;
|
||||
laserMapping_ = new loam::BasicLaserMapping(scanPeriod_);
|
||||
transformMaintenance_ = loam::BasicTransformMaintenance();
|
||||
lost_ = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef RTABMAP_LOAM
|
||||
std::vector<pcl::PointCloud<pcl::PointXYZI> > OdometryLOAM::segmentScanRings(const pcl::PointCloud<pcl::PointXYZ> & laserCloudIn)
|
||||
{
|
||||
std::vector<pcl::PointCloud<pcl::PointXYZI> > laserCloudScans;
|
||||
|
||||
size_t cloudSize = laserCloudIn.size();
|
||||
|
||||
// determine scan start and end orientations
|
||||
float startOri = -std::atan2(laserCloudIn[0].y, laserCloudIn[0].x);
|
||||
float endOri = -std::atan2(laserCloudIn[cloudSize - 1].y,
|
||||
laserCloudIn[cloudSize - 1].x) + 2 * float(M_PI);
|
||||
if (endOri - startOri > 3 * M_PI) {
|
||||
endOri -= 2 * M_PI;
|
||||
} else if (endOri - startOri < M_PI) {
|
||||
endOri += 2 * M_PI;
|
||||
}
|
||||
|
||||
bool halfPassed = false;
|
||||
pcl::PointXYZI point;
|
||||
laserCloudScans.resize(scanMapper_.getNumberOfScanRings());
|
||||
// clear all scanline points
|
||||
std::for_each(laserCloudScans.begin(), laserCloudScans.end(), [](auto&&v) {v.clear(); });
|
||||
|
||||
// extract valid points from input cloud
|
||||
for (size_t i = 0; i < cloudSize; i++) {
|
||||
point.x = laserCloudIn[i].y;
|
||||
point.y = laserCloudIn[i].z;
|
||||
point.z = laserCloudIn[i].x;
|
||||
|
||||
// skip NaN and INF valued points
|
||||
if (!pcl_isfinite(point.x) ||
|
||||
!pcl_isfinite(point.y) ||
|
||||
!pcl_isfinite(point.z)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip zero valued points
|
||||
if (point.x * point.x + point.y * point.y + point.z * point.z < 0.0001) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// calculate vertical point angle and scan ID
|
||||
float angle = std::atan(point.y / std::sqrt(point.x * point.x + point.z * point.z));
|
||||
int scanID = scanMapper_.getRingForAngle(angle);
|
||||
if (scanID >= scanMapper_.getNumberOfScanRings() || scanID < 0 ){
|
||||
continue;
|
||||
}
|
||||
|
||||
// calculate horizontal point angle
|
||||
float ori = -std::atan2(point.x, point.z);
|
||||
if (!halfPassed) {
|
||||
if (ori < startOri - M_PI / 2) {
|
||||
ori += 2 * M_PI;
|
||||
} else if (ori > startOri + M_PI * 3 / 2) {
|
||||
ori -= 2 * M_PI;
|
||||
}
|
||||
|
||||
if (ori - startOri > M_PI) {
|
||||
halfPassed = true;
|
||||
}
|
||||
} else {
|
||||
ori += 2 * M_PI;
|
||||
|
||||
if (ori < endOri - M_PI * 3 / 2) {
|
||||
ori += 2 * M_PI;
|
||||
} else if (ori > endOri + M_PI / 2) {
|
||||
ori -= 2 * M_PI;
|
||||
}
|
||||
}
|
||||
|
||||
// calculate relative scan time based on point orientation
|
||||
float relTime = SCAN_PERIOD * (ori - startOri) / (endOri - startOri);
|
||||
point.intensity = scanID + relTime;
|
||||
|
||||
// imu not used...
|
||||
//scanRegistration_.projectPointToStartOfSweep(point, relTime);
|
||||
|
||||
laserCloudScans[scanID].push_back(point);
|
||||
}
|
||||
|
||||
return laserCloudScans;
|
||||
}
|
||||
#endif
|
||||
|
||||
// return not null transform if odometry is correctly computed
|
||||
Transform OdometryLOAM::computeTransform(
|
||||
SensorData & data,
|
||||
const Transform & guess,
|
||||
OdometryInfo * info)
|
||||
{
|
||||
Transform t;
|
||||
#ifdef RTABMAP_LOAM
|
||||
UTimer timer;
|
||||
|
||||
if(data.laserScanRaw().isEmpty())
|
||||
{
|
||||
UERROR("LOAM works only with laser scans and the current input is empty. Aborting odometry update...");
|
||||
return t;
|
||||
}
|
||||
else if(data.laserScanRaw().is2d())
|
||||
{
|
||||
UERROR("LOAM version used works only with 3D laser scans from Velodyne. Aborting odometry update...");
|
||||
return t;
|
||||
}
|
||||
|
||||
cv::Mat covariance = cv::Mat::eye(6,6,CV_64FC1)*9999;
|
||||
if(!lost_)
|
||||
{
|
||||
pcl::PointCloud<pcl::PointXYZ>::Ptr laserCloudInPtr = util3d::laserScanToPointCloud(data.laserScanRaw());
|
||||
std::vector<pcl::PointCloud<pcl::PointXYZI> > laserCloudScans = segmentScanRings(*laserCloudInPtr);
|
||||
|
||||
ros::Time stampT;
|
||||
stampT.fromSec(data.stamp());
|
||||
loam::Time scanTime = loam::fromROSTime(stampT);
|
||||
scanRegistration_.processScanlines(scanTime, laserCloudScans);
|
||||
|
||||
*laserOdometry_->cornerPointsSharp() = scanRegistration_.cornerPointsSharp();
|
||||
*laserOdometry_->cornerPointsLessSharp() = scanRegistration_.cornerPointsLessSharp();
|
||||
*laserOdometry_->surfPointsFlat() = scanRegistration_.surfacePointsFlat();
|
||||
*laserOdometry_->surfPointsLessFlat() = scanRegistration_.surfacePointsLessFlat();
|
||||
*laserOdometry_->laserCloud() = scanRegistration_.laserCloud();
|
||||
pcl::PointCloud<pcl::PointXYZ> imuTrans;
|
||||
imuTrans.resize(4);
|
||||
laserOdometry_->updateIMU(imuTrans);
|
||||
laserOdometry_->process();
|
||||
|
||||
if(localMapping_)
|
||||
{
|
||||
laserMapping_->laserCloudCornerLast() = *laserOdometry_->lastCornerCloud();
|
||||
laserMapping_->laserCloudSurfLast() = *laserOdometry_->lastSurfaceCloud();
|
||||
laserMapping_->laserCloud() = *laserOdometry_->laserCloud();
|
||||
laserMapping_->updateOdometry(laserOdometry_->transformSum());
|
||||
laserMapping_->process(scanTime);
|
||||
}
|
||||
|
||||
transformMaintenance_.updateOdometry(
|
||||
laserOdometry_->transformSum().rot_x.rad(),
|
||||
laserOdometry_->transformSum().rot_y.rad(),
|
||||
laserOdometry_->transformSum().rot_z.rad(),
|
||||
laserOdometry_->transformSum().pos.x(),
|
||||
laserOdometry_->transformSum().pos.y(),
|
||||
laserOdometry_->transformSum().pos.z());
|
||||
transformMaintenance_.updateMappingTransform(laserMapping_->transformAftMapped(), laserMapping_->transformBefMapped());
|
||||
transformMaintenance_.transformAssociateToMap();
|
||||
const float * tm = transformMaintenance_.transformMapped();
|
||||
Transform pose = Transform(tm[5], tm[3], tm[4], tm[2], tm[0], tm[1]);
|
||||
|
||||
if(!pose.isNull())
|
||||
{
|
||||
covariance = cv::Mat::eye(6,6,CV_64FC1);
|
||||
covariance(cv::Range(0,3), cv::Range(0,3)) *= linVar_;
|
||||
covariance(cv::Range(3,6), cv::Range(3,6)) *= angVar_;
|
||||
|
||||
t = lastPose_.inverse() * pose; // incremental
|
||||
lastPose_ = pose;
|
||||
|
||||
const Transform & localTransform = data.laserScanRaw().localTransform();
|
||||
if(!t.isNull() && !t.isIdentity() && !localTransform.isIdentity() && !localTransform.isNull())
|
||||
{
|
||||
// from laser frame to base frame
|
||||
t = localTransform * t * localTransform.inverse();
|
||||
}
|
||||
|
||||
if(info)
|
||||
{
|
||||
info->type = (int)kTypeLOAM;
|
||||
info->localScanMapSize = laserMapping_->laserCloudSurroundDS().size();
|
||||
if(covariance.cols == 6 && covariance.rows == 6 && covariance.type() == CV_64FC1)
|
||||
{
|
||||
info->reg.covariance = covariance;
|
||||
}
|
||||
|
||||
if(this->isInfoDataFilled())
|
||||
{
|
||||
Transform rot(0,0,1,0,1,0,0,0,0,1,0,0);
|
||||
pcl::PointCloud<pcl::PointXYZI> out;
|
||||
pcl::transformPointCloud(laserMapping_->laserCloudSurroundDS(), out, rot.toEigen3f());
|
||||
info->localScanMap = LaserScan::backwardCompatibility(util3d::laserScanFromPointCloud(out), 0, data.laserScanRaw().maxRange(), data.laserScanRaw().localTransform());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lost_ = true;
|
||||
UWARN("LOAM failed to register the latest scan, odometry should be reset.");
|
||||
}
|
||||
}
|
||||
UINFO("Odom update time = %fs, lost=%s", timer.elapsed(), lost_?"true":"false");
|
||||
|
||||
#else
|
||||
UERROR("RTAB-Map is not built with LOAM support! Select another odometry approach.");
|
||||
#endif
|
||||
return t;
|
||||
}
|
||||
|
||||
} // namespace rtabmap
|
||||
994
corelib/src/odometry/OdometryMSCKF.cpp
Normal file
994
corelib/src/odometry/OdometryMSCKF.cpp
Normal file
@@ -0,0 +1,994 @@
|
||||
/*
|
||||
Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Universite de Sherbrooke nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "rtabmap/core/odometry/OdometryMSCKF.h"
|
||||
#include "rtabmap/core/OdometryInfo.h"
|
||||
#include "rtabmap/core/util3d_transforms.h"
|
||||
#include "rtabmap/utilite/ULogger.h"
|
||||
#include "rtabmap/utilite/UTimer.h"
|
||||
#include "rtabmap/utilite/UStl.h"
|
||||
#include "rtabmap/utilite/UThread.h"
|
||||
|
||||
#ifdef RTABMAP_MSCKF_VIO
|
||||
#include <msckf_vio/image_processor.h>
|
||||
#include <msckf_vio/msckf_vio.h>
|
||||
#include <msckf_vio/math_utils.hpp>
|
||||
#include <eigen_conversions/eigen_msg.h>
|
||||
#include <boost/math/distributions/chi_squared.hpp>
|
||||
#include <pcl/common/transforms.h>
|
||||
#endif
|
||||
|
||||
namespace rtabmap {
|
||||
|
||||
#ifdef RTABMAP_MSCKF_VIO
|
||||
class ImageProcessorNoROS: public msckf_vio::ImageProcessor
|
||||
{
|
||||
public:
|
||||
ImageProcessorNoROS(
|
||||
const ParametersMap & parameters_in,
|
||||
const Transform & imuLocalTransform,
|
||||
const StereoCameraModel & model,
|
||||
bool rectified) :
|
||||
msckf_vio::ImageProcessor(0)
|
||||
{
|
||||
UDEBUG("");
|
||||
// Camera calibration parameters
|
||||
if(model.left().D_raw().cols == 6)
|
||||
{
|
||||
//equidistant
|
||||
cam0_distortion_model = "equidistant";
|
||||
cam0_distortion_coeffs[0] = rectified?0:model.left().D_raw().at<double>(0,0);
|
||||
cam0_distortion_coeffs[1] = rectified?0:model.left().D_raw().at<double>(0,1);
|
||||
cam0_distortion_coeffs[2] = rectified?0:model.left().D_raw().at<double>(0,4);
|
||||
cam0_distortion_coeffs[3] = rectified?0:model.left().D_raw().at<double>(0,5);
|
||||
}
|
||||
else
|
||||
{
|
||||
//radtan
|
||||
cam0_distortion_model = "radtan";
|
||||
cam0_distortion_coeffs[0] = rectified?0:model.left().D_raw().at<double>(0,0);
|
||||
cam0_distortion_coeffs[1] = rectified?0:model.left().D_raw().at<double>(0,1);
|
||||
cam0_distortion_coeffs[2] = rectified?0:model.left().D_raw().at<double>(0,2);
|
||||
cam0_distortion_coeffs[3] = rectified?0:model.left().D_raw().at<double>(0,3);
|
||||
}
|
||||
if(model.right().D_raw().cols == 6)
|
||||
{
|
||||
//equidistant
|
||||
cam1_distortion_model = "equidistant";
|
||||
cam1_distortion_coeffs[0] = rectified?0:model.right().D_raw().at<double>(0,0);
|
||||
cam1_distortion_coeffs[1] = rectified?0:model.right().D_raw().at<double>(0,1);
|
||||
cam1_distortion_coeffs[2] = rectified?0:model.right().D_raw().at<double>(0,4);
|
||||
cam1_distortion_coeffs[3] = rectified?0:model.right().D_raw().at<double>(0,5);
|
||||
}
|
||||
else
|
||||
{
|
||||
//radtan
|
||||
cam1_distortion_model = "radtan";
|
||||
cam1_distortion_coeffs[0] = rectified?0:model.right().D_raw().at<double>(0,0);
|
||||
cam1_distortion_coeffs[1] = rectified?0:model.right().D_raw().at<double>(0,1);
|
||||
cam1_distortion_coeffs[2] = rectified?0:model.right().D_raw().at<double>(0,2);
|
||||
cam1_distortion_coeffs[3] = rectified?0:model.right().D_raw().at<double>(0,3);
|
||||
}
|
||||
|
||||
cam0_resolution[0] = model.left().imageWidth();
|
||||
cam0_resolution[1] = model.left().imageHeight();
|
||||
|
||||
cam1_resolution[0] = model.right().imageWidth();
|
||||
cam1_resolution[1] = model.right().imageHeight();
|
||||
|
||||
cam0_intrinsics[0] = rectified?model.left().fx():model.left().K_raw().at<double>(0,0);
|
||||
cam0_intrinsics[1] = rectified?model.left().fy():model.left().K_raw().at<double>(1,1);
|
||||
cam0_intrinsics[2] = rectified?model.left().cx():model.left().K_raw().at<double>(0,2);
|
||||
cam0_intrinsics[3] = rectified?model.left().cy():model.left().K_raw().at<double>(1,2);
|
||||
|
||||
cam1_intrinsics[0] = rectified?model.right().fx():model.right().K_raw().at<double>(0,0);
|
||||
cam1_intrinsics[1] = rectified?model.right().fy():model.right().K_raw().at<double>(1,1);
|
||||
cam1_intrinsics[2] = rectified?model.right().cx():model.right().K_raw().at<double>(0,2);
|
||||
cam1_intrinsics[3] = rectified?model.right().cy():model.right().K_raw().at<double>(1,2);
|
||||
|
||||
Transform imuCam = model.localTransform().inverse() * imuLocalTransform;
|
||||
cv::Mat T_imu_cam0 = imuCam.dataMatrix();
|
||||
cv::Matx33d R_imu_cam0(T_imu_cam0(cv::Rect(0,0,3,3)));
|
||||
cv::Vec3d t_imu_cam0 = T_imu_cam0(cv::Rect(3,0,1,3));
|
||||
R_cam0_imu = R_imu_cam0.t();
|
||||
t_cam0_imu = -R_imu_cam0.t() * t_imu_cam0;
|
||||
|
||||
Transform cam0cam1;
|
||||
if(rectified)
|
||||
{
|
||||
cam0cam1 = Transform(
|
||||
1, 0, 0, -model.baseline(),
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
cam0cam1 = model.stereoTransform();
|
||||
}
|
||||
|
||||
UASSERT(!cam0cam1.isNull());
|
||||
Transform imuCam1 = cam0cam1 * imuCam;
|
||||
cv::Mat T_imu_cam1 = imuCam1.dataMatrix();
|
||||
cv::Matx33d R_imu_cam1(T_imu_cam1(cv::Rect(0,0,3,3)));
|
||||
cv::Vec3d t_imu_cam1 = T_imu_cam1(cv::Rect(3,0,1,3));
|
||||
R_cam1_imu = R_imu_cam1.t();
|
||||
t_cam1_imu = -R_imu_cam1.t() * t_imu_cam1;
|
||||
// Processor parameters
|
||||
// get all OdomMSCFK group to make sure all parameters are set
|
||||
ParametersMap parameters = Parameters::getDefaultParameters("OdomMSCKF");
|
||||
uInsert(parameters, parameters_in);
|
||||
|
||||
Parameters::parse(parameters, Parameters::kOdomMSCKFGridRow(), processor_config.grid_row); //4
|
||||
Parameters::parse(parameters, Parameters::kOdomMSCKFGridCol(), processor_config.grid_col); //4
|
||||
Parameters::parse(parameters, Parameters::kOdomMSCKFGridMinFeatureNum(), processor_config.grid_min_feature_num); //2
|
||||
Parameters::parse(parameters, Parameters::kOdomMSCKFGridMaxFeatureNum(), processor_config.grid_max_feature_num); //4
|
||||
Parameters::parse(parameters, Parameters::kOdomMSCKFPyramidLevels(), processor_config.pyramid_levels); //3
|
||||
Parameters::parse(parameters, Parameters::kOdomMSCKFPatchSize(), processor_config.patch_size); //31
|
||||
Parameters::parse(parameters, Parameters::kOdomMSCKFFastThreshold(), processor_config.fast_threshold); //20
|
||||
Parameters::parse(parameters, Parameters::kOdomMSCKFMaxIteration(), processor_config.max_iteration); //30
|
||||
Parameters::parse(parameters, Parameters::kOdomMSCKFTrackPrecision(), processor_config.track_precision); //0.01
|
||||
Parameters::parse(parameters, Parameters::kOdomMSCKFRansacThreshold(), processor_config.ransac_threshold); //3
|
||||
Parameters::parse(parameters, Parameters::kOdomMSCKFStereoThreshold(), processor_config.stereo_threshold); //3
|
||||
|
||||
UINFO("===========================================");
|
||||
UINFO("cam0_resolution: %d, %d",
|
||||
cam0_resolution[0], cam0_resolution[1]);
|
||||
UINFO("cam0_intrinscs: %f, %f, %f, %f",
|
||||
cam0_intrinsics[0], cam0_intrinsics[1],
|
||||
cam0_intrinsics[2], cam0_intrinsics[3]);
|
||||
UINFO("cam0_distortion_model: %s",
|
||||
cam0_distortion_model.c_str());
|
||||
UINFO("cam0_distortion_coefficients: %f, %f, %f, %f",
|
||||
cam0_distortion_coeffs[0], cam0_distortion_coeffs[1],
|
||||
cam0_distortion_coeffs[2], cam0_distortion_coeffs[3]);
|
||||
|
||||
UINFO("cam1_resolution: %d, %d",
|
||||
cam1_resolution[0], cam1_resolution[1]);
|
||||
UINFO("cam1_intrinscs: %f, %f, %f, %f",
|
||||
cam1_intrinsics[0], cam1_intrinsics[1],
|
||||
cam1_intrinsics[2], cam1_intrinsics[3]);
|
||||
UINFO("cam1_distortion_model: %s",
|
||||
cam1_distortion_model.c_str());
|
||||
UINFO("cam1_distortion_coefficients: %f, %f, %f, %f",
|
||||
cam1_distortion_coeffs[0], cam1_distortion_coeffs[1],
|
||||
cam1_distortion_coeffs[2], cam1_distortion_coeffs[3]);
|
||||
|
||||
std::cout << "R_imu_cam0: " << R_imu_cam0 << std::endl;
|
||||
std::cout << "t_imu_cam0.t(): " << t_imu_cam0.t() << std::endl;
|
||||
std::cout << "R_imu_cam1: " << R_imu_cam1 << std::endl;
|
||||
std::cout << "t_imu_cam1.t(): " << t_imu_cam1.t() << std::endl;
|
||||
|
||||
UINFO("grid_row: %d",
|
||||
processor_config.grid_row);
|
||||
UINFO("grid_col: %d",
|
||||
processor_config.grid_col);
|
||||
UINFO("grid_min_feature_num: %d",
|
||||
processor_config.grid_min_feature_num);
|
||||
UINFO("grid_max_feature_num: %d",
|
||||
processor_config.grid_max_feature_num);
|
||||
UINFO("pyramid_levels: %d",
|
||||
processor_config.pyramid_levels);
|
||||
UINFO("patch_size: %d",
|
||||
processor_config.patch_size);
|
||||
UINFO("fast_threshold: %d",
|
||||
processor_config.fast_threshold);
|
||||
UINFO("max_iteration: %d",
|
||||
processor_config.max_iteration);
|
||||
UINFO("track_precision: %f",
|
||||
processor_config.track_precision);
|
||||
UINFO("ransac_threshold: %f",
|
||||
processor_config.ransac_threshold);
|
||||
UINFO("stereo_threshold: %f",
|
||||
processor_config.stereo_threshold);
|
||||
UINFO("===========================================");
|
||||
|
||||
// Create feature detector.
|
||||
detector_ptr = cv::FastFeatureDetector::create(
|
||||
processor_config.fast_threshold);
|
||||
}
|
||||
|
||||
virtual ~ImageProcessorNoROS() {}
|
||||
|
||||
msckf_vio::CameraMeasurementPtr stereoCallback2(
|
||||
const sensor_msgs::ImageConstPtr& cam0_img,
|
||||
const sensor_msgs::ImageConstPtr& cam1_img) {
|
||||
|
||||
|
||||
//cout << "==================================" << endl;
|
||||
|
||||
// Get the current image.
|
||||
cam0_curr_img_ptr = cv_bridge::toCvShare(cam0_img,
|
||||
sensor_msgs::image_encodings::MONO8);
|
||||
cam1_curr_img_ptr = cv_bridge::toCvShare(cam1_img,
|
||||
sensor_msgs::image_encodings::MONO8);
|
||||
|
||||
// Build the image pyramids once since they're used at multiple places
|
||||
createImagePyramids();
|
||||
|
||||
// Detect features in the first frame.
|
||||
if (is_first_img) {
|
||||
//ros::Time start_time = ros::Time::now();
|
||||
initializeFirstFrame();
|
||||
//UINFO("Detection time: %f",
|
||||
// (ros::Time::now()-start_time).toSec());
|
||||
is_first_img = false;
|
||||
|
||||
// Draw results.
|
||||
//start_time = ros::Time::now();
|
||||
//drawFeaturesStereo();
|
||||
//UINFO("Draw features: %f",
|
||||
// (ros::Time::now()-start_time).toSec());
|
||||
} else {
|
||||
// Track the feature in the previous image.
|
||||
//ros::Time start_time = ros::Time::now();
|
||||
trackFeatures();
|
||||
//UINFO("Tracking time: %f",
|
||||
// (ros::Time::now()-start_time).toSec());
|
||||
|
||||
// Add new features into the current image.
|
||||
//start_time = ros::Time::now();
|
||||
addNewFeatures();
|
||||
//UINFO("Addition time: %f",
|
||||
// (ros::Time::now()-start_time).toSec());
|
||||
|
||||
// Add new features into the current image.
|
||||
//start_time = ros::Time::now();
|
||||
pruneGridFeatures();
|
||||
//UINFO("Prune grid features: %f",
|
||||
// (ros::Time::now()-start_time).toSec());
|
||||
|
||||
// Draw results.
|
||||
//start_time = ros::Time::now();
|
||||
//drawFeaturesStereo();
|
||||
//UINFO("Draw features: %f",
|
||||
// (ros::Time::now()-start_time).toSec());
|
||||
}
|
||||
|
||||
//ros::Time start_time = ros::Time::now();
|
||||
//updateFeatureLifetime();
|
||||
//UINFO("Statistics: %f",
|
||||
// (ros::Time::now()-start_time).toSec());
|
||||
|
||||
// Publish features in the current image.
|
||||
//ros::Time start_time = ros::Time::now();
|
||||
msckf_vio::CameraMeasurementPtr measurements = publish();
|
||||
//UINFO("Publishing: %f",
|
||||
// (ros::Time::now()-start_time).toSec());
|
||||
|
||||
// Update the previous image and previous features.
|
||||
cam0_prev_img_ptr = cam0_curr_img_ptr;
|
||||
prev_features_ptr = curr_features_ptr;
|
||||
std::swap(prev_cam0_pyramid_, curr_cam0_pyramid_);
|
||||
|
||||
// Initialize the current features to empty vectors.
|
||||
curr_features_ptr.reset(new GridFeatures());
|
||||
for (int code = 0; code <
|
||||
processor_config.grid_row*processor_config.grid_col; ++code) {
|
||||
(*curr_features_ptr)[code] = std::vector<FeatureMetaData>(0);
|
||||
}
|
||||
|
||||
return measurements;
|
||||
}
|
||||
|
||||
msckf_vio::CameraMeasurementPtr publish() {
|
||||
|
||||
// Publish features.
|
||||
msckf_vio::CameraMeasurementPtr feature_msg_ptr(new msckf_vio::CameraMeasurement);
|
||||
feature_msg_ptr->header.stamp = cam0_curr_img_ptr->header.stamp;
|
||||
|
||||
std::vector<FeatureIDType> curr_ids(0);
|
||||
std::vector<cv::Point2f> curr_cam0_points(0);
|
||||
std::vector<cv::Point2f> curr_cam1_points(0);
|
||||
|
||||
for (const auto& grid_features : (*curr_features_ptr)) {
|
||||
for (const auto& feature : grid_features.second) {
|
||||
curr_ids.push_back(feature.id);
|
||||
curr_cam0_points.push_back(feature.cam0_point);
|
||||
curr_cam1_points.push_back(feature.cam1_point);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<cv::Point2f> curr_cam0_points_undistorted(0);
|
||||
std::vector<cv::Point2f> curr_cam1_points_undistorted(0);
|
||||
|
||||
undistortPoints(
|
||||
curr_cam0_points, cam0_intrinsics, cam0_distortion_model,
|
||||
cam0_distortion_coeffs, curr_cam0_points_undistorted);
|
||||
undistortPoints(
|
||||
curr_cam1_points, cam1_intrinsics, cam1_distortion_model,
|
||||
cam1_distortion_coeffs, curr_cam1_points_undistorted);
|
||||
|
||||
for (unsigned int i = 0; i < curr_ids.size(); ++i) {
|
||||
feature_msg_ptr->features.push_back(msckf_vio::FeatureMeasurement());
|
||||
feature_msg_ptr->features[i].id = curr_ids[i];
|
||||
feature_msg_ptr->features[i].u0 = curr_cam0_points_undistorted[i].x;
|
||||
feature_msg_ptr->features[i].v0 = curr_cam0_points_undistorted[i].y;
|
||||
feature_msg_ptr->features[i].u1 = curr_cam1_points_undistorted[i].x;
|
||||
feature_msg_ptr->features[i].v1 = curr_cam1_points_undistorted[i].y;
|
||||
}
|
||||
|
||||
//feature_pub.publish(feature_msg_ptr);
|
||||
|
||||
// Publish tracking info.
|
||||
/*TrackingInfoPtr tracking_info_msg_ptr(new TrackingInfo());
|
||||
tracking_info_msg_ptr->header.stamp = cam0_curr_img_ptr->header.stamp;
|
||||
tracking_info_msg_ptr->before_tracking = before_tracking;
|
||||
tracking_info_msg_ptr->after_tracking = after_tracking;
|
||||
tracking_info_msg_ptr->after_matching = after_matching;
|
||||
tracking_info_msg_ptr->after_ransac = after_ransac;
|
||||
tracking_info_pub.publish(tracking_info_msg_ptr);*/
|
||||
|
||||
return feature_msg_ptr;
|
||||
}
|
||||
};
|
||||
|
||||
class MsckfVioNoROS: public msckf_vio::MsckfVio
|
||||
{
|
||||
public:
|
||||
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
|
||||
|
||||
MsckfVioNoROS(const ParametersMap & parameters_in,
|
||||
const Transform & imuLocalTransform,
|
||||
const StereoCameraModel & model,
|
||||
bool rectified) :
|
||||
msckf_vio::MsckfVio(0)
|
||||
{
|
||||
UDEBUG("");
|
||||
// get all OdomMSCFK group to make sure all parameters are set
|
||||
parameters_ = Parameters::getDefaultParameters("OdomMSCKF");
|
||||
uInsert(parameters_, parameters_in);
|
||||
|
||||
// Frame id
|
||||
publish_tf = false;
|
||||
frame_rate = 1.0;
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFPositionStdThreshold(), position_std_threshold); //8.0
|
||||
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFRotationThreshold(), rotation_threshold); //0.2618
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFTranslationThreshold(), translation_threshold); //0.4
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFTrackingRateThreshold(), tracking_rate_threshold); //0.5
|
||||
|
||||
// Feature optimization parameters
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFOptTranslationThreshold(), msckf_vio::Feature::optimization_config.translation_threshold); //0.2
|
||||
|
||||
// Noise related parameters
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFNoiseGyro(), msckf_vio::IMUState::gyro_noise); //0.001
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFNoiseAcc(), msckf_vio::IMUState::acc_noise); //0.01
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFNoiseGyroBias(), msckf_vio::IMUState::gyro_bias_noise); //0.001
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFNoiseAccBias(), msckf_vio::IMUState::acc_bias_noise); //0.01
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFNoiseFeature(), msckf_vio::Feature::observation_noise); //0.01
|
||||
|
||||
// Use variance instead of standard deviation.
|
||||
msckf_vio::IMUState::gyro_noise *= msckf_vio::IMUState::gyro_noise;
|
||||
msckf_vio::IMUState::acc_noise *= msckf_vio::IMUState::acc_noise;
|
||||
msckf_vio::IMUState::gyro_bias_noise *= msckf_vio::IMUState::gyro_bias_noise;
|
||||
msckf_vio::IMUState::acc_bias_noise *= msckf_vio::IMUState::acc_bias_noise;
|
||||
msckf_vio::Feature::observation_noise *= msckf_vio::Feature::observation_noise;
|
||||
|
||||
// Set the initial IMU state.
|
||||
// The intial orientation and position will be set to the origin
|
||||
// implicitly. But the initial velocity and bias can be
|
||||
// set by parameters.
|
||||
// TODO: is it reasonable to set the initial bias to 0?
|
||||
//Parameters::parse(parameters_, "initial_state/velocity/x", state_server.imu_state.velocity(0)); //0.0
|
||||
//Parameters::parse(parameters_, "initial_state/velocity/y", state_server.imu_state.velocity(1)); //0.0
|
||||
//Parameters::parse(parameters_, "initial_state/velocity/z", state_server.imu_state.velocity(2)); //0.0
|
||||
|
||||
// The initial covariance of orientation and position can be
|
||||
// set to 0. But for velocity, bias and extrinsic parameters,
|
||||
// there should be nontrivial uncertainty.
|
||||
double gyro_bias_cov, acc_bias_cov, velocity_cov;
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFInitCovVel(), velocity_cov); //0.25
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFInitCovGyroBias(), gyro_bias_cov); //1e-4
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFInitCovAccBias(), acc_bias_cov); //1e-2
|
||||
|
||||
double extrinsic_rotation_cov, extrinsic_translation_cov;
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFInitCovExRot(), extrinsic_rotation_cov); //3.0462e-4
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFInitCovExTrans(), extrinsic_translation_cov); //1e-4
|
||||
|
||||
state_server.state_cov = Eigen::MatrixXd::Zero(21, 21);
|
||||
for (int i = 3; i < 6; ++i)
|
||||
state_server.state_cov(i, i) = gyro_bias_cov;
|
||||
for (int i = 6; i < 9; ++i)
|
||||
state_server.state_cov(i, i) = velocity_cov;
|
||||
for (int i = 9; i < 12; ++i)
|
||||
state_server.state_cov(i, i) = acc_bias_cov;
|
||||
for (int i = 15; i < 18; ++i)
|
||||
state_server.state_cov(i, i) = extrinsic_rotation_cov;
|
||||
for (int i = 18; i < 21; ++i)
|
||||
state_server.state_cov(i, i) = extrinsic_translation_cov;
|
||||
|
||||
// Transformation offsets between the frames involved.
|
||||
Transform imuCam = model.localTransform().inverse() * imuLocalTransform;
|
||||
Eigen::Isometry3d T_imu_cam0(imuCam.toEigen4d());
|
||||
Eigen::Isometry3d T_cam0_imu = T_imu_cam0.inverse();
|
||||
|
||||
state_server.imu_state.R_imu_cam0 = T_cam0_imu.linear().transpose();
|
||||
state_server.imu_state.t_cam0_imu = T_cam0_imu.translation();
|
||||
Transform cam0cam1;
|
||||
if(rectified)
|
||||
{
|
||||
cam0cam1 = Transform(
|
||||
1, 0, 0, -model.baseline(),
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
cam0cam1 = model.stereoTransform();
|
||||
}
|
||||
msckf_vio::CAMState::T_cam0_cam1 = cam0cam1.toEigen3d().matrix();
|
||||
msckf_vio::IMUState::T_imu_body = Transform::getIdentity().toEigen3d().matrix();
|
||||
|
||||
// Maximum number of camera states to be stored
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFMaxCamStateSize(), max_cam_state_size); //30
|
||||
|
||||
UINFO("===========================================");
|
||||
UINFO("fixed frame id: %s", fixed_frame_id.c_str());
|
||||
UINFO("child frame id: %s", child_frame_id.c_str());
|
||||
UINFO("publish tf: %d", publish_tf);
|
||||
UINFO("frame rate: %f", frame_rate);
|
||||
UINFO("position std threshold: %f", position_std_threshold);
|
||||
UINFO("Keyframe rotation threshold: %f", rotation_threshold);
|
||||
UINFO("Keyframe translation threshold: %f", translation_threshold);
|
||||
UINFO("Keyframe tracking rate threshold: %f", tracking_rate_threshold);
|
||||
UINFO("gyro noise: %.10f", msckf_vio::IMUState::gyro_noise);
|
||||
UINFO("gyro bias noise: %.10f", msckf_vio::IMUState::gyro_bias_noise);
|
||||
UINFO("acc noise: %.10f", msckf_vio::IMUState::acc_noise);
|
||||
UINFO("acc bias noise: %.10f", msckf_vio::IMUState::acc_bias_noise);
|
||||
UINFO("observation noise: %.10f", msckf_vio::Feature::observation_noise);
|
||||
UINFO("initial velocity: %f, %f, %f",
|
||||
state_server.imu_state.velocity(0),
|
||||
state_server.imu_state.velocity(1),
|
||||
state_server.imu_state.velocity(2));
|
||||
UINFO("initial gyro bias cov: %f", gyro_bias_cov);
|
||||
UINFO("initial acc bias cov: %f", acc_bias_cov);
|
||||
UINFO("initial velocity cov: %f", velocity_cov);
|
||||
UINFO("initial extrinsic rotation cov: %f",
|
||||
extrinsic_rotation_cov);
|
||||
UINFO("initial extrinsic translation cov: %f",
|
||||
extrinsic_translation_cov);
|
||||
|
||||
std::cout << "T_imu_cam0.linear(): " << T_imu_cam0.linear() << std::endl;
|
||||
std::cout << "T_imu_cam0.translation().transpose(): " << T_imu_cam0.translation().transpose() << std::endl;
|
||||
std::cout << "CAMState::T_cam0_cam1.linear(): " << msckf_vio::CAMState::T_cam0_cam1.linear() << std::endl;
|
||||
std::cout << "CAMState::T_cam0_cam1.translation().transpose(): " << msckf_vio::CAMState::T_cam0_cam1.translation().transpose() << std::endl;
|
||||
std::cout << "IMUState::T_imu_body.linear(): " << msckf_vio::IMUState::T_imu_body.linear() << std::endl;
|
||||
std::cout << "IMUState::T_imu_body.translation().transpose(): " << msckf_vio::IMUState::T_imu_body.translation().transpose() << std::endl;
|
||||
|
||||
UINFO("max camera state #: %d", max_cam_state_size);
|
||||
UINFO("===========================================");
|
||||
|
||||
//if (!loadParameters()) return false;
|
||||
//UINFO("Finish loading ROS parameters...");
|
||||
|
||||
// Initialize state server
|
||||
state_server.continuous_noise_cov =
|
||||
Eigen::Matrix<double, 12, 12>::Zero();
|
||||
state_server.continuous_noise_cov.block<3, 3>(0, 0) =
|
||||
Eigen::Matrix3d::Identity()*msckf_vio::IMUState::gyro_noise;
|
||||
state_server.continuous_noise_cov.block<3, 3>(3, 3) =
|
||||
Eigen::Matrix3d::Identity()*msckf_vio::IMUState::gyro_bias_noise;
|
||||
state_server.continuous_noise_cov.block<3, 3>(6, 6) =
|
||||
Eigen::Matrix3d::Identity()*msckf_vio::IMUState::acc_noise;
|
||||
state_server.continuous_noise_cov.block<3, 3>(9, 9) =
|
||||
Eigen::Matrix3d::Identity()*msckf_vio::IMUState::acc_bias_noise;
|
||||
|
||||
// Initialize the chi squared test table with confidence
|
||||
// level 0.95.
|
||||
for (int i = 1; i < 100; ++i) {
|
||||
boost::math::chi_squared chi_squared_dist(i);
|
||||
chi_squared_test_table[i] =
|
||||
boost::math::quantile(chi_squared_dist, 0.05);
|
||||
}
|
||||
|
||||
// if (!createRosIO()) return false;
|
||||
//UINFO("Finish creating ROS IO...");
|
||||
}
|
||||
virtual ~MsckfVioNoROS() {}
|
||||
|
||||
|
||||
nav_msgs::Odometry featureCallback2(
|
||||
const msckf_vio::CameraMeasurementConstPtr& msg,
|
||||
pcl::PointCloud<pcl::PointXYZ>::Ptr & localMap) {
|
||||
|
||||
nav_msgs::Odometry odom;
|
||||
|
||||
// Return if the gravity vector has not been set.
|
||||
if (!is_gravity_set)
|
||||
{
|
||||
UINFO("Gravity not set yet... waiting for 200 IMU msgs (%d/200)...", (int)imu_msg_buffer.size());
|
||||
return odom;
|
||||
}
|
||||
|
||||
// Start the system if the first image is received.
|
||||
// The frame where the first image is received will be
|
||||
// the origin.
|
||||
if (is_first_img) {
|
||||
is_first_img = false;
|
||||
state_server.imu_state.time = msg->header.stamp.toSec();
|
||||
}
|
||||
|
||||
//static double max_processing_time = 0.0;
|
||||
//static int critical_time_cntr = 0;
|
||||
//double processing_start_time = ros::Time::now().toSec();
|
||||
|
||||
// Propogate the IMU state.
|
||||
// that are received before the image msg.
|
||||
//ros::Time start_time = ros::Time::now();
|
||||
batchImuProcessing(msg->header.stamp.toSec());
|
||||
|
||||
//double imu_processing_time = (
|
||||
// ros::Time::now()-start_time).toSec();
|
||||
|
||||
// Augment the state vector.
|
||||
//start_time = ros::Time::now();
|
||||
stateAugmentation(msg->header.stamp.toSec());
|
||||
//double state_augmentation_time = (
|
||||
// ros::Time::now()-start_time).toSec();
|
||||
|
||||
// Add new observations for existing features or new
|
||||
// features in the map server.
|
||||
//start_time = ros::Time::now();
|
||||
addFeatureObservations(msg);
|
||||
//double add_observations_time = (
|
||||
// ros::Time::now()-start_time).toSec();
|
||||
|
||||
// Perform measurement update if necessary.
|
||||
//start_time = ros::Time::now();
|
||||
removeLostFeatures();
|
||||
//double remove_lost_features_time = (
|
||||
// ros::Time::now()-start_time).toSec();
|
||||
|
||||
//start_time = ros::Time::now();
|
||||
pruneCamStateBuffer();
|
||||
//double prune_cam_states_time = (
|
||||
// ros::Time::now()-start_time).toSec();
|
||||
|
||||
// Publish the odometry.
|
||||
//start_time = ros::Time::now();
|
||||
odom = publish(localMap);
|
||||
//double publish_time = (
|
||||
// ros::Time::now()-start_time).toSec();
|
||||
|
||||
// Reset the system if necessary.
|
||||
onlineReset2();
|
||||
|
||||
/*double processing_end_time = ros::Time::now().toSec();
|
||||
double processing_time =
|
||||
processing_end_time - processing_start_time;
|
||||
if (processing_time > 1.0/frame_rate) {
|
||||
++critical_time_cntr;
|
||||
UINFO("\033[1;31mTotal processing time %f/%d...\033[0m",
|
||||
processing_time, critical_time_cntr);
|
||||
//printf("IMU processing time: %f/%f\n",
|
||||
// imu_processing_time, imu_processing_time/processing_time);
|
||||
//printf("State augmentation time: %f/%f\n",
|
||||
// state_augmentation_time, state_augmentation_time/processing_time);
|
||||
//printf("Add observations time: %f/%f\n",
|
||||
// add_observations_time, add_observations_time/processing_time);
|
||||
printf("Remove lost features time: %f/%f\n",
|
||||
remove_lost_features_time, remove_lost_features_time/processing_time);
|
||||
printf("Remove camera states time: %f/%f\n",
|
||||
prune_cam_states_time, prune_cam_states_time/processing_time);
|
||||
//printf("Publish time: %f/%f\n",
|
||||
// publish_time, publish_time/processing_time);
|
||||
}*/
|
||||
|
||||
return odom;
|
||||
}
|
||||
|
||||
void onlineReset2() {
|
||||
|
||||
// Never perform online reset if position std threshold
|
||||
// is non-positive.
|
||||
if (position_std_threshold <= 0) return;
|
||||
static long long int online_reset_counter = 0;
|
||||
|
||||
// Check the uncertainty of positions to determine if
|
||||
// the system can be reset.
|
||||
double position_x_std = std::sqrt(state_server.state_cov(12, 12));
|
||||
double position_y_std = std::sqrt(state_server.state_cov(13, 13));
|
||||
double position_z_std = std::sqrt(state_server.state_cov(14, 14));
|
||||
|
||||
if (position_x_std < position_std_threshold &&
|
||||
position_y_std < position_std_threshold &&
|
||||
position_z_std < position_std_threshold) return;
|
||||
|
||||
UWARN("Start %lld online reset procedure...",
|
||||
++online_reset_counter);
|
||||
UINFO("Stardard deviation in xyz: %f, %f, %f",
|
||||
position_x_std, position_y_std, position_z_std);
|
||||
|
||||
// Remove all existing camera states.
|
||||
state_server.cam_states.clear();
|
||||
|
||||
// Clear all exsiting features in the map.
|
||||
map_server.clear();
|
||||
|
||||
// Reset the state covariance.
|
||||
double gyro_bias_cov, acc_bias_cov, velocity_cov;
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFInitCovVel(), velocity_cov); //0.25
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFInitCovGyroBias(), gyro_bias_cov); //1e-4
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFInitCovAccBias(), acc_bias_cov); //1e-2
|
||||
|
||||
double extrinsic_rotation_cov, extrinsic_translation_cov;
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFInitCovExRot(), extrinsic_rotation_cov); //3.0462e-4
|
||||
Parameters::parse(parameters_, Parameters::kOdomMSCKFInitCovExTrans(), extrinsic_translation_cov); //1e-4
|
||||
|
||||
|
||||
state_server.state_cov = Eigen::MatrixXd::Zero(21, 21);
|
||||
for (int i = 3; i < 6; ++i)
|
||||
state_server.state_cov(i, i) = gyro_bias_cov;
|
||||
for (int i = 6; i < 9; ++i)
|
||||
state_server.state_cov(i, i) = velocity_cov;
|
||||
for (int i = 9; i < 12; ++i)
|
||||
state_server.state_cov(i, i) = acc_bias_cov;
|
||||
for (int i = 15; i < 18; ++i)
|
||||
state_server.state_cov(i, i) = extrinsic_rotation_cov;
|
||||
for (int i = 18; i < 21; ++i)
|
||||
state_server.state_cov(i, i) = extrinsic_translation_cov;
|
||||
|
||||
UWARN("%lld online reset complete...", online_reset_counter);
|
||||
return;
|
||||
}
|
||||
|
||||
nav_msgs::Odometry publish(pcl::PointCloud<pcl::PointXYZ>::Ptr & feature_msg_ptr) {
|
||||
|
||||
// Convert the IMU frame to the body frame.
|
||||
const msckf_vio::IMUState& imu_state = state_server.imu_state;
|
||||
Eigen::Isometry3d T_i_w = Eigen::Isometry3d::Identity();
|
||||
T_i_w.linear() = msckf_vio::quaternionToRotation(imu_state.orientation).transpose();
|
||||
T_i_w.translation() = imu_state.position;
|
||||
|
||||
Eigen::Isometry3d T_b_w = msckf_vio::IMUState::T_imu_body * T_i_w *
|
||||
msckf_vio::IMUState::T_imu_body.inverse();
|
||||
Eigen::Vector3d body_velocity =
|
||||
msckf_vio::IMUState::T_imu_body.linear() * imu_state.velocity;
|
||||
|
||||
// Publish tf
|
||||
/*if (publish_tf) {
|
||||
tf::Transform T_b_w_tf;
|
||||
tf::transformEigenToTF(T_b_w, T_b_w_tf);
|
||||
tf_pub.sendTransform(tf::StampedTransform(
|
||||
T_b_w_tf, time, fixed_frame_id, child_frame_id));
|
||||
}*/
|
||||
|
||||
// Publish the odometry
|
||||
nav_msgs::Odometry odom_msg;
|
||||
//odom_msg.header.stamp = time;
|
||||
odom_msg.header.frame_id = fixed_frame_id;
|
||||
odom_msg.child_frame_id = child_frame_id;
|
||||
|
||||
tf::poseEigenToMsg(T_b_w, odom_msg.pose.pose);
|
||||
tf::vectorEigenToMsg(body_velocity, odom_msg.twist.twist.linear);
|
||||
|
||||
// Convert the covariance.
|
||||
Eigen::Matrix3d P_oo = state_server.state_cov.block<3, 3>(0, 0);
|
||||
Eigen::Matrix3d P_op = state_server.state_cov.block<3, 3>(0, 12);
|
||||
Eigen::Matrix3d P_po = state_server.state_cov.block<3, 3>(12, 0);
|
||||
Eigen::Matrix3d P_pp = state_server.state_cov.block<3, 3>(12, 12);
|
||||
Eigen::Matrix<double, 6, 6> P_imu_pose = Eigen::Matrix<double, 6, 6>::Zero();
|
||||
P_imu_pose << P_pp, P_po, P_op, P_oo;
|
||||
|
||||
Eigen::Matrix<double, 6, 6> H_pose = Eigen::Matrix<double, 6, 6>::Zero();
|
||||
H_pose.block<3, 3>(0, 0) = msckf_vio::IMUState::T_imu_body.linear();
|
||||
H_pose.block<3, 3>(3, 3) = msckf_vio::IMUState::T_imu_body.linear();
|
||||
Eigen::Matrix<double, 6, 6> P_body_pose = H_pose *
|
||||
P_imu_pose * H_pose.transpose();
|
||||
|
||||
for (int i = 0; i < 6; ++i)
|
||||
for (int j = 0; j < 6; ++j)
|
||||
odom_msg.pose.covariance[6*i+j] = P_body_pose(i, j);
|
||||
|
||||
// Construct the covariance for the velocity.
|
||||
Eigen::Matrix3d P_imu_vel = state_server.state_cov.block<3, 3>(6, 6);
|
||||
Eigen::Matrix3d H_vel = msckf_vio::IMUState::T_imu_body.linear();
|
||||
Eigen::Matrix3d P_body_vel = H_vel * P_imu_vel * H_vel.transpose();
|
||||
for (int i = 0; i < 3; ++i)
|
||||
for (int j = 0; j < 3; ++j)
|
||||
odom_msg.twist.covariance[i*6+j] = P_body_vel(i, j);
|
||||
|
||||
// odom_pub.publish(odom_msg);
|
||||
|
||||
// Publish the 3D positions of the features that
|
||||
// has been initialized.
|
||||
feature_msg_ptr.reset(new pcl::PointCloud<pcl::PointXYZ>());
|
||||
feature_msg_ptr->header.frame_id = fixed_frame_id;
|
||||
feature_msg_ptr->height = 1;
|
||||
for (const auto& item : map_server) {
|
||||
const auto& feature = item.second;
|
||||
if (feature.is_initialized) {
|
||||
Eigen::Vector3d feature_position =
|
||||
msckf_vio::IMUState::T_imu_body.linear() * feature.position;
|
||||
feature_msg_ptr->points.push_back(pcl::PointXYZ(
|
||||
feature_position(0), feature_position(1), feature_position(2)));
|
||||
}
|
||||
}
|
||||
feature_msg_ptr->width = feature_msg_ptr->points.size();
|
||||
|
||||
//feature_pub.publish(feature_msg_ptr);
|
||||
|
||||
return odom_msg;
|
||||
}
|
||||
|
||||
private:
|
||||
ParametersMap parameters_;
|
||||
};
|
||||
#endif
|
||||
|
||||
OdometryMSCKF::OdometryMSCKF(const ParametersMap & parameters) :
|
||||
Odometry(parameters)
|
||||
#ifdef RTABMAP_MSCKF_VIO
|
||||
,
|
||||
imageProcessor_(0),
|
||||
msckf_(0),
|
||||
parameters_(parameters),
|
||||
flipXY_(-1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0),
|
||||
previousPose_(Transform::getIdentity()),
|
||||
initGravity_(false)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
OdometryMSCKF::~OdometryMSCKF()
|
||||
{
|
||||
UDEBUG("");
|
||||
#ifdef RTABMAP_MSCKF_VIO
|
||||
delete imageProcessor_;
|
||||
delete msckf_;
|
||||
#endif
|
||||
}
|
||||
|
||||
void OdometryMSCKF::reset(const Transform & initialPose)
|
||||
{
|
||||
Odometry::reset(initialPose);
|
||||
#ifdef RTABMAP_MSCKF_VIO
|
||||
if(!initGravity_)
|
||||
{
|
||||
if(imageProcessor_)
|
||||
{
|
||||
delete imageProcessor_;
|
||||
imageProcessor_ = 0;
|
||||
}
|
||||
if(msckf_)
|
||||
{
|
||||
delete msckf_;
|
||||
msckf_ = 0;
|
||||
}
|
||||
lastImu_ = IMU();
|
||||
previousPose_.setIdentity();
|
||||
}
|
||||
initGravity_ = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// return not null transform if odometry is correctly computed
|
||||
Transform OdometryMSCKF::computeTransform(
|
||||
SensorData & data,
|
||||
const Transform & guess,
|
||||
OdometryInfo * info)
|
||||
{
|
||||
UDEBUG("");
|
||||
Transform t;
|
||||
|
||||
#ifdef RTABMAP_MSCKF_VIO
|
||||
UTimer timer;
|
||||
|
||||
if(!data.imu().empty())
|
||||
{
|
||||
UDEBUG("IMU update stamp=%f acc=%f %f %f gyr=%f %f %f", data.stamp(),
|
||||
data.imu().linearAcceleration()[0],
|
||||
data.imu().linearAcceleration()[1],
|
||||
data.imu().linearAcceleration()[2],
|
||||
data.imu().angularVelocity()[0],
|
||||
data.imu().angularVelocity()[1],
|
||||
data.imu().angularVelocity()[2]);
|
||||
if(imageProcessor_ && msckf_)
|
||||
{
|
||||
sensor_msgs::ImuPtr msg(new sensor_msgs::Imu);
|
||||
msg->angular_velocity.x = data.imu().angularVelocity()[0];
|
||||
msg->angular_velocity.y = data.imu().angularVelocity()[1];
|
||||
msg->angular_velocity.z = data.imu().angularVelocity()[2];
|
||||
msg->linear_acceleration.x = data.imu().linearAcceleration()[0];
|
||||
msg->linear_acceleration.y = data.imu().linearAcceleration()[1];
|
||||
msg->linear_acceleration.z = data.imu().linearAcceleration()[2];
|
||||
msg->header.stamp.fromSec(data.stamp());
|
||||
imageProcessor_->imuCallback(msg);
|
||||
msckf_->imuCallback(msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
UWARN("Ignoring IMU, waiting for an image to initialize...");
|
||||
lastImu_ = data.imu();
|
||||
}
|
||||
}
|
||||
|
||||
if(!data.imageRaw().empty() && !data.rightRaw().empty())
|
||||
{
|
||||
UDEBUG("Image update stamp=%f", data.stamp());
|
||||
if(data.stereoCameraModel().isValidForProjection())
|
||||
{
|
||||
if(msckf_ == 0)
|
||||
{
|
||||
UINFO("Initialization");
|
||||
if(lastImu_.empty())
|
||||
{
|
||||
UWARN("Ignoring Image, waiting for imu to initialize...");
|
||||
return t;
|
||||
}
|
||||
UINFO("Creating ImageProcessorNoROS...");
|
||||
imageProcessor_ = new ImageProcessorNoROS(
|
||||
parameters_,
|
||||
lastImu_.localTransform(),
|
||||
data.stereoCameraModel(),
|
||||
this->imagesAlreadyRectified());
|
||||
UINFO("Creating MsckfVioNoROS...");
|
||||
msckf_ = new MsckfVioNoROS(
|
||||
parameters_,
|
||||
lastImu_.localTransform(),
|
||||
data.stereoCameraModel(),
|
||||
this->imagesAlreadyRectified());
|
||||
}
|
||||
|
||||
// Convert to ROS
|
||||
cv_bridge::CvImage cam0;
|
||||
cv_bridge::CvImage cam1;
|
||||
cam0.header.stamp.fromSec(data.stamp());
|
||||
cam1.header.stamp.fromSec(data.stamp());
|
||||
|
||||
if(data.imageRaw().type() == CV_8UC3)
|
||||
{
|
||||
cv::cvtColor(data.imageRaw(), cam0.image, CV_BGR2GRAY);
|
||||
}
|
||||
else
|
||||
{
|
||||
cam0.image = data.imageRaw();
|
||||
}
|
||||
if(data.rightRaw().type() == CV_8UC3)
|
||||
{
|
||||
cv::cvtColor(data.rightRaw(), cam1.image, CV_BGR2GRAY);
|
||||
}
|
||||
else
|
||||
{
|
||||
cam1.image = data.rightRaw();
|
||||
}
|
||||
|
||||
sensor_msgs::ImagePtr cam0Msg(new sensor_msgs::Image);
|
||||
sensor_msgs::ImagePtr cam1Msg(new sensor_msgs::Image);
|
||||
cam0.toImageMsg(*cam0Msg);
|
||||
cam1.toImageMsg(*cam1Msg);
|
||||
cam0Msg->encoding = sensor_msgs::image_encodings::MONO8;
|
||||
cam1Msg->encoding = sensor_msgs::image_encodings::MONO8;
|
||||
|
||||
msckf_vio::CameraMeasurementPtr measurements = imageProcessor_->stereoCallback2(cam0Msg, cam1Msg);
|
||||
pcl::PointCloud<pcl::PointXYZ>::Ptr localMap;
|
||||
nav_msgs::Odometry odom = msckf_->featureCallback2(measurements, localMap);
|
||||
|
||||
Transform p = Transform(
|
||||
odom.pose.pose.position.x,
|
||||
odom.pose.pose.position.y,
|
||||
odom.pose.pose.position.z,
|
||||
odom.pose.pose.orientation.x,
|
||||
odom.pose.pose.orientation.y,
|
||||
odom.pose.pose.orientation.z,
|
||||
odom.pose.pose.orientation.w);
|
||||
|
||||
if(!p.isNull())
|
||||
{
|
||||
// pose in rtabmap/ros coordinates
|
||||
p = flipXY_*p*lastImu_.localTransform();
|
||||
|
||||
if(this->getPose().rotation().isIdentity())
|
||||
{
|
||||
initGravity_ = true;
|
||||
this->reset(this->getPose()*p.rotation());
|
||||
}
|
||||
|
||||
if(previousPose_.isIdentity())
|
||||
{
|
||||
previousPose_ = p;
|
||||
}
|
||||
|
||||
// make it incremental
|
||||
Transform previousPoseInv = previousPose_.inverse();
|
||||
t = previousPoseInv*p;
|
||||
previousPose_ = p;
|
||||
|
||||
if(info)
|
||||
{
|
||||
info->type = this->getType();
|
||||
info->features = measurements->features.size();
|
||||
|
||||
info->reg.covariance = cv::Mat::zeros(6, 6, CV_64FC1);
|
||||
cv::Mat twistCov(6,6,CV_64FC1, odom.twist.covariance.elems);
|
||||
// twist covariance is not in base frame, but in world frame,
|
||||
// we have to convert the covariance in base frame
|
||||
cv::Matx31f covWorldFrame(twistCov.at<double>(0, 0),
|
||||
twistCov.at<double>(1, 1),
|
||||
twistCov.at<double>(2, 2));
|
||||
cv::Matx31f covBaseFrame = cv::Matx33f(previousPoseInv.rotationMatrix()) * covWorldFrame;
|
||||
// we set only diagonal values as there is an issue with g2o and off-diagonal values
|
||||
info->reg.covariance.at<double>(0, 0) = fabs(covBaseFrame.val[0])/10.0;
|
||||
info->reg.covariance.at<double>(1, 1) = fabs(covBaseFrame.val[1])/10.0;
|
||||
info->reg.covariance.at<double>(2, 2) = fabs(covBaseFrame.val[2])/10.0;
|
||||
if(info->reg.covariance.at<double>(0, 0) < 0.0001)
|
||||
{
|
||||
info->reg.covariance.at<double>(0, 0) = 0.0001;
|
||||
}
|
||||
if(info->reg.covariance.at<double>(1, 1) < 0.0001)
|
||||
{
|
||||
info->reg.covariance.at<double>(1, 1) = 0.0001;
|
||||
}
|
||||
if(info->reg.covariance.at<double>(2, 2) < 0.0001)
|
||||
{
|
||||
info->reg.covariance.at<double>(2, 2) = 0.0001;
|
||||
}
|
||||
info->reg.covariance.at<double>(3, 3) = msckf_vio::IMUState::gyro_noise*10.0;
|
||||
info->reg.covariance.at<double>(4, 4) = info->reg.covariance.at<double>(3, 3);
|
||||
info->reg.covariance.at<double>(5, 5) = info->reg.covariance.at<double>(3, 3);
|
||||
|
||||
if(this->isInfoDataFilled())
|
||||
{
|
||||
if(localMap.get() && localMap->size())
|
||||
{
|
||||
Eigen::Affine3f flip = (this->getPose()*previousPoseInv*flipXY_).toEigen3f();
|
||||
for(unsigned int i=0; i<localMap->size(); ++i)
|
||||
{
|
||||
pcl::PointXYZ pt = pcl::transformPoint(localMap->at(i), flip);
|
||||
info->localMap.insert(std::make_pair(i, cv::Point3f(pt.x, pt.y, pt.z)));
|
||||
}
|
||||
}
|
||||
if(this->imagesAlreadyRectified())
|
||||
{
|
||||
info->newCorners.resize(measurements->features.size());
|
||||
float fx = data.stereoCameraModel().left().fx();
|
||||
float fy = data.stereoCameraModel().left().fy();
|
||||
float cx = data.stereoCameraModel().left().cx();
|
||||
float cy = data.stereoCameraModel().left().cy();
|
||||
info->reg.inliersIDs.resize(measurements->features.size());
|
||||
for(unsigned int i=0; i<measurements->features.size(); ++i)
|
||||
{
|
||||
info->newCorners[i].x = measurements->features[i].u0*fx+cx;
|
||||
info->newCorners[i].y = measurements->features[i].v0*fy+cy;
|
||||
info->reg.inliersIDs[i] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UINFO("Odom update time = %fs p=%s", timer.elapsed(), p.prettyPrint().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
UERROR("RTAB-Map is not built with MSCKF_VIO support! Select another visual odometry approach.");
|
||||
#endif
|
||||
return t;
|
||||
}
|
||||
|
||||
} // namespace rtabmap
|
||||
1035
corelib/src/odometry/OdometryMono.cpp
Normal file
1035
corelib/src/odometry/OdometryMono.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1031
corelib/src/odometry/OdometryORBSLAM2.cpp
Normal file
1031
corelib/src/odometry/OdometryORBSLAM2.cpp
Normal file
File diff suppressed because it is too large
Load Diff
500
corelib/src/odometry/OdometryOkvis.cpp
Normal file
500
corelib/src/odometry/OdometryOkvis.cpp
Normal file
@@ -0,0 +1,500 @@
|
||||
/*
|
||||
Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Universite de Sherbrooke nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "rtabmap/core/odometry/OdometryOkvis.h"
|
||||
#include "rtabmap/core/OdometryInfo.h"
|
||||
#include "rtabmap/core/util3d_transforms.h"
|
||||
#include "rtabmap/utilite/ULogger.h"
|
||||
#include "rtabmap/utilite/UTimer.h"
|
||||
#include "rtabmap/utilite/UStl.h"
|
||||
#include "rtabmap/utilite/UThread.h"
|
||||
|
||||
#ifdef RTABMAP_OKVIS
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <stdlib.h>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
|
||||
#include <Eigen/Core>
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
|
||||
#pragma GCC diagnostic ignored "-Woverloaded-virtual"
|
||||
#include <opencv2/opencv.hpp>
|
||||
#pragma GCC diagnostic pop
|
||||
#include <okvis/VioParametersReader.hpp>
|
||||
#include <okvis/ThreadedKFVio.hpp>
|
||||
#include <okvis/cameras/PinholeCamera.hpp>
|
||||
#include <okvis/cameras/NoDistortion.hpp>
|
||||
#include <okvis/cameras/RadialTangentialDistortion.hpp>
|
||||
#include <okvis/cameras/EquidistantDistortion.hpp>
|
||||
#include <okvis/cameras/RadialTangentialDistortion8.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#endif
|
||||
|
||||
namespace rtabmap {
|
||||
|
||||
#ifdef RTABMAP_OKVIS
|
||||
class OkvisCallbackHandler
|
||||
{
|
||||
public:
|
||||
OkvisCallbackHandler()
|
||||
{
|
||||
}
|
||||
|
||||
Transform getLastTransform()
|
||||
{
|
||||
UDEBUG("");
|
||||
Transform tf;
|
||||
mutex_.lock();
|
||||
tf = transform_;
|
||||
mutex_.unlock();
|
||||
|
||||
return tf;
|
||||
}
|
||||
|
||||
std::map<int, cv::Point3f> getLastLandmarks()
|
||||
{
|
||||
UDEBUG("");
|
||||
std::map<int, cv::Point3f> landmarks;
|
||||
mutexLandmarks_.lock();
|
||||
landmarks = landmarks_;
|
||||
mutexLandmarks_.unlock();
|
||||
return landmarks;
|
||||
}
|
||||
|
||||
public:
|
||||
void fullStateCallback(
|
||||
const okvis::Time & t, const okvis::kinematics::Transformation & T_WS,
|
||||
const Eigen::Matrix<double, 9, 1> & /*speedAndBiases*/,
|
||||
const Eigen::Matrix<double, 3, 1> & /*omega_S*/)
|
||||
{
|
||||
UDEBUG("");
|
||||
Transform tf = Transform::fromEigen4d(T_WS.T());
|
||||
|
||||
mutex_.lock();
|
||||
transform_ = tf;
|
||||
mutex_.unlock();
|
||||
}
|
||||
|
||||
void landmarksCallback(const okvis::Time & t,
|
||||
const okvis::MapPointVector & landmarksVector,
|
||||
const okvis::MapPointVector & /*transferredLandmarks*/)
|
||||
{
|
||||
UDEBUG("");
|
||||
mutexLandmarks_.lock();
|
||||
landmarks_.clear();
|
||||
for(unsigned int i=0; i<landmarksVector.size(); ++i)
|
||||
{
|
||||
landmarks_.insert(std::make_pair((int)landmarksVector[i].id, cv::Point3f(landmarksVector[i].point[0], landmarksVector[i].point[1], landmarksVector[i].point[2])));
|
||||
}
|
||||
mutexLandmarks_.unlock();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private:
|
||||
Transform transform_;
|
||||
std::map<int, cv::Point3f> landmarks_;
|
||||
UMutex mutex_;
|
||||
UMutex mutexLandmarks_;
|
||||
};
|
||||
#endif
|
||||
|
||||
OdometryOkvis::OdometryOkvis(const ParametersMap & parameters) :
|
||||
Odometry(parameters),
|
||||
#ifdef RTABMAP_OKVIS
|
||||
okvisCallbackHandler_(new OkvisCallbackHandler),
|
||||
okvisEstimator_(0),
|
||||
imagesProcessed_(0),
|
||||
initGravity_(false),
|
||||
#endif
|
||||
okvisParameters_(parameters),
|
||||
previousPose_(Transform::getIdentity())
|
||||
{
|
||||
#ifdef RTABMAP_OKVIS
|
||||
Parameters::parse(parameters, Parameters::kOdomOKVISConfigPath(), configFilename_);
|
||||
if(configFilename_.empty())
|
||||
{
|
||||
UERROR("OKVIS config file is empty (%s)!", Parameters::kOdomOKVISConfigPath().c_str());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
OdometryOkvis::~OdometryOkvis()
|
||||
{
|
||||
UDEBUG("");
|
||||
#ifdef RTABMAP_OKVIS
|
||||
delete okvisEstimator_;
|
||||
delete okvisCallbackHandler_;
|
||||
#endif
|
||||
}
|
||||
|
||||
void OdometryOkvis::reset(const Transform & initialPose)
|
||||
{
|
||||
Odometry::reset(initialPose);
|
||||
#ifdef RTABMAP_OKVIS
|
||||
if(!initGravity_)
|
||||
{
|
||||
if(okvisEstimator_)
|
||||
{
|
||||
delete okvisEstimator_;
|
||||
okvisEstimator_ = 0;
|
||||
}
|
||||
lastImu_ = IMU();
|
||||
imagesProcessed_ = 0;
|
||||
previousPose_.setIdentity();
|
||||
|
||||
delete okvisCallbackHandler_;
|
||||
okvisCallbackHandler_ = new OkvisCallbackHandler();
|
||||
}
|
||||
initGravity_ = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// return not null transform if odometry is correctly computed
|
||||
Transform OdometryOkvis::computeTransform(
|
||||
SensorData & data,
|
||||
const Transform & guess,
|
||||
OdometryInfo * info)
|
||||
{
|
||||
UDEBUG("");
|
||||
Transform t;
|
||||
#ifdef RTABMAP_OKVIS
|
||||
UTimer timer;
|
||||
|
||||
okvis::Time timeOkvis = okvis::Time(data.stamp());
|
||||
|
||||
bool imuUpdated = false;
|
||||
if(!data.imu().empty())
|
||||
{
|
||||
UDEBUG("IMU update stamp=%f acc=%f %f %f gyr=%f %f %f", data.stamp(),
|
||||
data.imu().linearAcceleration()[0],
|
||||
data.imu().linearAcceleration()[1],
|
||||
data.imu().linearAcceleration()[2],
|
||||
data.imu().angularVelocity()[0],
|
||||
data.imu().angularVelocity()[1],
|
||||
data.imu().angularVelocity()[2]);
|
||||
if(okvisEstimator_ != 0)
|
||||
{
|
||||
Eigen::Vector3d acc(data.imu().linearAcceleration()[0], data.imu().linearAcceleration()[1], data.imu().linearAcceleration()[2]);
|
||||
Eigen::Vector3d ang(data.imu().angularVelocity()[0], data.imu().angularVelocity()[1], data.imu().angularVelocity()[2]);
|
||||
imuUpdated = okvisEstimator_->addImuMeasurement(timeOkvis, acc, ang);
|
||||
}
|
||||
else
|
||||
{
|
||||
UWARN("Ignoring IMU, waiting for an image to initialize...");
|
||||
lastImu_ = data.imu();
|
||||
}
|
||||
}
|
||||
|
||||
bool imageUpdated = false;
|
||||
if(!data.imageRaw().empty())
|
||||
{
|
||||
UDEBUG("Image update stamp=%f", data.stamp());
|
||||
std::vector<cv::Mat> images;
|
||||
std::vector<CameraModel> models;
|
||||
if(data.stereoCameraModel().isValidForProjection())
|
||||
{
|
||||
images.push_back(data.imageRaw());
|
||||
images.push_back(data.rightRaw());
|
||||
CameraModel mleft = data.stereoCameraModel().left();
|
||||
// should be transform between IMU and camera
|
||||
mleft.setLocalTransform(lastImu_.localTransform().inverse()*mleft.localTransform());
|
||||
models.push_back(mleft);
|
||||
CameraModel mright = data.stereoCameraModel().right();
|
||||
|
||||
// To support not rectified images
|
||||
if(!imagesAlreadyRectified())
|
||||
{
|
||||
cv::Mat R = data.stereoCameraModel().R();
|
||||
cv::Mat T = data.stereoCameraModel().T();
|
||||
UASSERT(R.cols==3 && R.rows == 3);
|
||||
UASSERT(T.cols==1 && T.rows == 3);
|
||||
Transform extrinsics(R.at<double>(0,0), R.at<double>(0,1), R.at<double>(0,2), T.at<double>(0,0),
|
||||
R.at<double>(1,0), R.at<double>(1,1), R.at<double>(1,2), T.at<double>(1,0),
|
||||
R.at<double>(2,0), R.at<double>(2,1), R.at<double>(2,2), T.at<double>(2,0));
|
||||
mright.setLocalTransform(mleft.localTransform() * extrinsics.inverse());
|
||||
}
|
||||
else
|
||||
{
|
||||
Transform extrinsics(1, 0, 0, 0,
|
||||
0, 1, 0, data.stereoCameraModel().baseline(),
|
||||
0, 0, 1, 0);
|
||||
mright.setLocalTransform(extrinsics * mleft.localTransform());
|
||||
}
|
||||
|
||||
|
||||
models.push_back(mright);
|
||||
}
|
||||
else
|
||||
{
|
||||
UASSERT(int((data.imageRaw().cols/data.cameraModels().size())*data.cameraModels().size()) == data.imageRaw().cols);
|
||||
int subImageWidth = data.imageRaw().cols/data.cameraModels().size();
|
||||
for(unsigned int i=0; i<data.cameraModels().size(); ++i)
|
||||
{
|
||||
if(data.cameraModels()[i].isValidForProjection())
|
||||
{
|
||||
images.push_back(cv::Mat(data.imageRaw(), cv::Rect(subImageWidth*i, 0, subImageWidth, data.imageRaw().rows)));
|
||||
CameraModel m = data.cameraModels()[i];
|
||||
// should be transform between IMU and camera
|
||||
m.setLocalTransform(lastImu_.localTransform().inverse()*m.localTransform());
|
||||
models.push_back(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(images.size())
|
||||
{
|
||||
// initialization
|
||||
if(okvisEstimator_ == 0)
|
||||
{
|
||||
UDEBUG("Initialization");
|
||||
if(lastImu_.empty())
|
||||
{
|
||||
UWARN("Ignoring Image, waiting for imu to initialize...");
|
||||
return t;
|
||||
}
|
||||
|
||||
okvis::VioParameters parameters;
|
||||
if(configFilename_.empty())
|
||||
{
|
||||
UERROR("OKVIS config file is empty (%s)!", Parameters::kOdomOKVISConfigPath().c_str());
|
||||
return t;
|
||||
}
|
||||
else
|
||||
{
|
||||
okvis::VioParametersReader vio_parameters_reader(configFilename_);
|
||||
vio_parameters_reader.getParameters(parameters);
|
||||
if(parameters.nCameraSystem.numCameras() > 0)
|
||||
{
|
||||
UWARN("Camera calibration included in OKVIS is ignored as calibration from received images will be used instead.");
|
||||
}
|
||||
parameters.nCameraSystem = okvis::cameras::NCameraSystem();
|
||||
}
|
||||
|
||||
parameters.publishing.publishRate = parameters.imu.rate; // rate at which odometry updates are published only works properly if imu_rate/publish_rate is an integer!!
|
||||
parameters.publishing.publishLandmarks = true; // select, if you want to publish landmarks at all
|
||||
parameters.publishing.publishImuPropagatedState = true; // Should the state that is propagated with IMU messages be published? Or just the optimized ones?
|
||||
parameters.publishing.landmarkQualityThreshold = 1.0e-2; // landmark with lower quality will not be published
|
||||
parameters.publishing.maxLandmarkQuality = 0.05; // landmark with higher quality will be published with the maximum colour intensity
|
||||
parameters.publishing.trackedBodyFrame = okvis::FrameName::B; // B or S, the frame of reference that will be expressed relative to the selected worldFrame
|
||||
parameters.publishing.velocitiesFrame = okvis::FrameName::B; // Wc, B or S, the frames in which the velocities of the selected trackedBodyFrame will be expressed in
|
||||
|
||||
// non-hard coded parameters
|
||||
parameters.imu.T_BS = okvis::kinematics::Transformation(lastImu_.localTransform().toEigen4d());
|
||||
UINFO("Images are already rectified = %s", imagesAlreadyRectified()?"true":"false");
|
||||
for(unsigned int i=0; i<models.size(); ++i)
|
||||
{
|
||||
okvis::cameras::NCameraSystem::DistortionType distType = okvis::cameras::NCameraSystem::NoDistortion;
|
||||
std::shared_ptr<const okvis::cameras::CameraBase> cam;
|
||||
if(!imagesAlreadyRectified())
|
||||
{
|
||||
if(models[i].D_raw().cols == 8)
|
||||
{
|
||||
okvis::cameras::RadialTangentialDistortion8 dist(
|
||||
models[i].D_raw().at<double>(0,0),
|
||||
models[i].D_raw().at<double>(0,1),
|
||||
models[i].D_raw().at<double>(0,2),
|
||||
models[i].D_raw().at<double>(0,3),
|
||||
models[i].D_raw().at<double>(0,4),
|
||||
models[i].D_raw().at<double>(0,5),
|
||||
models[i].D_raw().at<double>(0,6),
|
||||
models[i].D_raw().at<double>(0,7));
|
||||
cam.reset(
|
||||
new okvis::cameras::PinholeCamera<okvis::cameras::RadialTangentialDistortion8>(
|
||||
models[i].imageWidth(),
|
||||
models[i].imageHeight(),
|
||||
models[i].K_raw().at<double>(0,0),
|
||||
models[i].K_raw().at<double>(1,1),
|
||||
models[i].K_raw().at<double>(0,2),
|
||||
models[i].K_raw().at<double>(1,2),
|
||||
dist));
|
||||
distType = okvis::cameras::NCameraSystem::RadialTangential8;
|
||||
UINFO("RadialTangential8");
|
||||
}
|
||||
else if(models[i].D_raw().cols == 6)
|
||||
{
|
||||
okvis::cameras::EquidistantDistortion dist(
|
||||
models[i].D_raw().at<double>(0,0),
|
||||
models[i].D_raw().at<double>(0,1),
|
||||
models[i].D_raw().at<double>(0,4),
|
||||
models[i].D_raw().at<double>(0,5));
|
||||
cam.reset(new okvis::cameras::PinholeCamera<okvis::cameras::EquidistantDistortion>(
|
||||
models[i].imageWidth(),
|
||||
models[i].imageHeight(),
|
||||
models[i].K_raw().at<double>(0,0),
|
||||
models[i].K_raw().at<double>(1,1),
|
||||
models[i].K_raw().at<double>(0,2),
|
||||
models[i].K_raw().at<double>(1,2),
|
||||
dist));
|
||||
distType = okvis::cameras::NCameraSystem::Equidistant;
|
||||
UINFO("Equidistant");
|
||||
}
|
||||
else if(models[i].D_raw().cols >= 4)
|
||||
{
|
||||
// To support not rectified images
|
||||
okvis::cameras::RadialTangentialDistortion dist(
|
||||
models[i].D_raw().at<double>(0,0),
|
||||
models[i].D_raw().at<double>(0,1),
|
||||
models[i].D_raw().at<double>(0,2),
|
||||
models[i].D_raw().at<double>(0,3));
|
||||
cam.reset(
|
||||
new okvis::cameras::PinholeCamera<okvis::cameras::RadialTangentialDistortion>(
|
||||
models[i].imageWidth(),
|
||||
models[i].imageHeight(),
|
||||
models[i].K_raw().at<double>(0,0),
|
||||
models[i].K_raw().at<double>(1,1),
|
||||
models[i].K_raw().at<double>(0,2),
|
||||
models[i].K_raw().at<double>(1,2),
|
||||
dist));
|
||||
distType = okvis::cameras::NCameraSystem::RadialTangential;
|
||||
UINFO("RadialTangential");
|
||||
}
|
||||
}
|
||||
else // no distortion, rectified images
|
||||
{
|
||||
okvis::cameras::RadialTangentialDistortion dist(0,0,0,0);
|
||||
cam.reset(
|
||||
new okvis::cameras::PinholeCamera<okvis::cameras::RadialTangentialDistortion>(
|
||||
models[i].imageWidth(),
|
||||
models[i].imageHeight(),
|
||||
models[i].K().at<double>(0,0),
|
||||
models[i].K().at<double>(1,1),
|
||||
models[i].K().at<double>(0,2),
|
||||
models[i].K().at<double>(1,2),
|
||||
dist));
|
||||
distType = okvis::cameras::NCameraSystem::RadialTangential;
|
||||
}
|
||||
|
||||
if(cam.get())
|
||||
{
|
||||
UINFO("model %d: %s", i, models[i].localTransform().prettyPrint().c_str());
|
||||
|
||||
Eigen::Vector3d r(models[i].localTransform().x(), models[i].localTransform().y(), models[i].localTransform().z());
|
||||
parameters.nCameraSystem.addCamera(
|
||||
std::shared_ptr<const okvis::kinematics::Transformation>(new okvis::kinematics::Transformation(r, models[i].localTransform().getQuaterniond().normalized())),
|
||||
cam,
|
||||
distType);
|
||||
}
|
||||
}
|
||||
|
||||
okvisEstimator_ = new okvis::ThreadedKFVio(parameters);
|
||||
|
||||
okvisEstimator_->setFullStateCallback(
|
||||
std::bind(&OkvisCallbackHandler::fullStateCallback, okvisCallbackHandler_,
|
||||
std::placeholders::_1, std::placeholders::_2,
|
||||
std::placeholders::_3, std::placeholders::_4));
|
||||
|
||||
okvisEstimator_->setLandmarksCallback(
|
||||
std::bind(&OkvisCallbackHandler::landmarksCallback, okvisCallbackHandler_,
|
||||
std::placeholders::_1, std::placeholders::_2,
|
||||
std::placeholders::_3));
|
||||
|
||||
okvisEstimator_->setBlocking(true);
|
||||
}
|
||||
|
||||
for(unsigned int i=0; i<images.size(); ++i)
|
||||
{
|
||||
cv::Mat gray;
|
||||
if(images[i].type() == CV_8UC3)
|
||||
{
|
||||
cv::cvtColor(images[i], gray, CV_BGR2GRAY);
|
||||
}
|
||||
else if(images[i].type() == CV_8UC1)
|
||||
{
|
||||
gray = images[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
UFATAL("Not supported color type!");
|
||||
}
|
||||
|
||||
imageUpdated = okvisEstimator_->addImage(timeOkvis, i, gray);
|
||||
if(!imageUpdated)
|
||||
{
|
||||
UWARN("Image update with stamp %f delayed...", data.stamp());
|
||||
}
|
||||
}
|
||||
if(imageUpdated)
|
||||
{
|
||||
++imagesProcessed_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if((imageUpdated || imuUpdated) && imagesProcessed_ > 10)
|
||||
{
|
||||
Transform fixPos(-1,0,0,0, 0,-1,0,0, 0,0,1,0);
|
||||
Transform fixRot(0,0,1,0, 0,-1,0,0, 1,0,0,0);
|
||||
Transform p = okvisCallbackHandler_->getLastTransform();
|
||||
if(!p.isNull())
|
||||
{
|
||||
p = fixPos * p * fixRot;
|
||||
|
||||
if(this->getPose().rotation().isIdentity())
|
||||
{
|
||||
initGravity_ = true;
|
||||
this->reset(this->getPose()*p.rotation());
|
||||
}
|
||||
|
||||
if(previousPose_.isIdentity())
|
||||
{
|
||||
previousPose_ = p;
|
||||
}
|
||||
|
||||
// make it incremental
|
||||
t = previousPose_.inverse()*p;
|
||||
previousPose_ = p;
|
||||
|
||||
if(info)
|
||||
{
|
||||
info->reg.covariance = cv::Mat::eye(6,6, CV_64FC1);
|
||||
info->reg.covariance *= this->framesProcessed() == 0?9999:0.0001;
|
||||
|
||||
// FIXME: the scale of landmarks doesn't seem to fit well the environment...
|
||||
/*info->localMap = okvisCallbackHandler_->getLastLandmarks();
|
||||
info->localMapSize = info->localMap.size();
|
||||
for(std::map<int, cv::Point3f>::iterator iter=info->localMap.begin(); iter!=info->localMap.end(); ++iter)
|
||||
{
|
||||
iter->second = util3d::transformPoint(iter->second, fixPos);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
if(imageUpdated)
|
||||
{
|
||||
UINFO("Odom update time = %fs p=%s", timer.elapsed(), p.prettyPrint().c_str());
|
||||
}
|
||||
}
|
||||
#else
|
||||
UERROR("RTAB-Map is not built with OKVIS support! Select another visual odometry approach.");
|
||||
#endif
|
||||
return t;
|
||||
}
|
||||
|
||||
} // namespace rtabmap
|
||||
308
corelib/src/odometry/OdometryViso2.cpp
Normal file
308
corelib/src/odometry/OdometryViso2.cpp
Normal file
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
Copyright (c) 2010-2016, Mathieu Labbe - IntRoLab - Universite de Sherbrooke
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Universite de Sherbrooke nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "rtabmap/core/odometry/OdometryViso2.h"
|
||||
#include "rtabmap/core/OdometryInfo.h"
|
||||
#include "rtabmap/core/util2d.h"
|
||||
#include "rtabmap/utilite/ULogger.h"
|
||||
#include "rtabmap/utilite/UTimer.h"
|
||||
#include "rtabmap/utilite/UStl.h"
|
||||
|
||||
#ifdef RTABMAP_VISO2
|
||||
#include <viso_stereo.h>
|
||||
|
||||
double computeFeatureFlow(const std::vector<Matcher::p_match>& matches)
|
||||
{
|
||||
double total_flow = 0.0;
|
||||
for (size_t i = 0; i < matches.size(); ++i)
|
||||
{
|
||||
double x_diff = matches[i].u1c - matches[i].u1p;
|
||||
double y_diff = matches[i].v1c - matches[i].v1p;
|
||||
total_flow += sqrt(x_diff * x_diff + y_diff * y_diff);
|
||||
}
|
||||
return total_flow / matches.size();
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace rtabmap {
|
||||
|
||||
OdometryViso2::OdometryViso2(const ParametersMap & parameters) :
|
||||
Odometry(parameters),
|
||||
#ifdef RTABMAP_VISO2
|
||||
viso2_(0),
|
||||
ref_frame_change_method_(0),
|
||||
ref_frame_inlier_threshold_(Parameters::defaultOdomVisKeyFrameThr()),
|
||||
ref_frame_motion_threshold_(5.0),
|
||||
lost_(false),
|
||||
keep_reference_frame_(false),
|
||||
#endif
|
||||
reference_motion_(Transform::getIdentity())
|
||||
{
|
||||
#ifdef RTABMAP_VISO2
|
||||
Parameters::parse(parameters, Parameters::kOdomVisKeyFrameThr(), ref_frame_inlier_threshold_);
|
||||
#endif
|
||||
viso2Parameters_ = Parameters::filterParameters(parameters, "OdomViso2");
|
||||
}
|
||||
|
||||
OdometryViso2::~OdometryViso2()
|
||||
{
|
||||
#ifdef RTABMAP_VISO2
|
||||
delete viso2_;
|
||||
#endif
|
||||
}
|
||||
|
||||
void OdometryViso2::reset(const Transform & initialPose)
|
||||
{
|
||||
Odometry::reset(initialPose);
|
||||
#ifdef RTABMAP_VISO2
|
||||
if(viso2_)
|
||||
{
|
||||
delete viso2_;
|
||||
viso2_ = 0;
|
||||
}
|
||||
lost_ = false;
|
||||
reference_motion_.setIdentity();
|
||||
previousLocalTransform_.setNull();
|
||||
#endif
|
||||
}
|
||||
|
||||
// return not null transform if odometry is correctly computed
|
||||
Transform OdometryViso2::computeTransform(
|
||||
SensorData & data,
|
||||
const Transform & guess,
|
||||
OdometryInfo * info)
|
||||
{
|
||||
Transform t;
|
||||
#ifdef RTABMAP_VISO2
|
||||
//based on https://github.com/srv/viso2/blob/indigo/viso2_ros/src/stereo_odometer.cpp
|
||||
|
||||
UTimer timer;
|
||||
|
||||
if(!data.depthRaw().empty())
|
||||
{
|
||||
UERROR("viso2 odometry doesn't support RGB-D data, only stereo. Aborting odometry update...");
|
||||
return t;
|
||||
}
|
||||
|
||||
if(data.imageRaw().empty() ||
|
||||
data.imageRaw().rows != data.rightRaw().rows ||
|
||||
data.imageRaw().cols != data.rightRaw().cols)
|
||||
{
|
||||
UERROR("Not compatible left (%dx%d) or right (%dx%d) image.",
|
||||
data.imageRaw().rows,
|
||||
data.imageRaw().cols,
|
||||
data.rightRaw().rows,
|
||||
data.rightRaw().cols);
|
||||
return t;
|
||||
}
|
||||
|
||||
if(!(data.stereoCameraModel().isValidForProjection() &&
|
||||
data.stereoCameraModel().left().isValidForReprojection() &&
|
||||
data.stereoCameraModel().right().isValidForReprojection()))
|
||||
{
|
||||
UERROR("Invalid stereo camera model!");
|
||||
return t;
|
||||
}
|
||||
|
||||
cv::Mat leftGray;
|
||||
if(data.imageRaw().type() == CV_8UC3)
|
||||
{
|
||||
cv::cvtColor(data.imageRaw(), leftGray, CV_BGR2GRAY);
|
||||
}
|
||||
else if(data.imageRaw().type() == CV_8UC1)
|
||||
{
|
||||
leftGray = data.imageRaw();
|
||||
}
|
||||
else
|
||||
{
|
||||
UFATAL("Not supported color type!");
|
||||
}
|
||||
cv::Mat rightGray;
|
||||
if(data.rightRaw().type() == CV_8UC3)
|
||||
{
|
||||
cv::cvtColor(data.rightRaw(), rightGray, CV_BGR2GRAY);
|
||||
}
|
||||
else if(data.rightRaw().type() == CV_8UC1)
|
||||
{
|
||||
rightGray = data.rightRaw();
|
||||
}
|
||||
else
|
||||
{
|
||||
UFATAL("Not supported color type!");
|
||||
}
|
||||
|
||||
int32_t dims[] = {leftGray.cols, leftGray.rows, leftGray.cols};
|
||||
cv::Mat covariance;
|
||||
if(viso2_ == 0)
|
||||
{
|
||||
VisualOdometryStereo::parameters params;
|
||||
params.base = params.match.base = data.stereoCameraModel().baseline();
|
||||
params.calib.cu = params.match.cu = data.stereoCameraModel().left().cx();
|
||||
params.calib.cv = params.match.cv = data.stereoCameraModel().left().cy();
|
||||
params.calib.f = params.match.f = data.stereoCameraModel().left().fx();
|
||||
|
||||
Parameters::parse(viso2Parameters_, Parameters::kOdomViso2RansacIters(), params.ransac_iters);
|
||||
Parameters::parse(viso2Parameters_, Parameters::kOdomViso2InlierThreshold(), params.inlier_threshold);
|
||||
Parameters::parse(viso2Parameters_, Parameters::kOdomViso2Reweighting(), params.reweighting);
|
||||
|
||||
Parameters::parse(viso2Parameters_, Parameters::kOdomViso2MatchNmsN(), params.match.nms_n);
|
||||
Parameters::parse(viso2Parameters_, Parameters::kOdomViso2MatchNmsTau(), params.match.nms_tau);
|
||||
Parameters::parse(viso2Parameters_, Parameters::kOdomViso2MatchBinsize(), params.match.match_binsize);
|
||||
Parameters::parse(viso2Parameters_, Parameters::kOdomViso2MatchRadius(), params.match.match_radius);
|
||||
Parameters::parse(viso2Parameters_, Parameters::kOdomViso2MatchDispTolerance(), params.match.match_disp_tolerance);
|
||||
Parameters::parse(viso2Parameters_, Parameters::kOdomViso2MatchOutlierDispTolerance(), params.match.outlier_disp_tolerance);
|
||||
Parameters::parse(viso2Parameters_, Parameters::kOdomViso2MatchOutlierFlowTolerance(), params.match.outlier_flow_tolerance);
|
||||
bool multistage = Parameters::defaultOdomViso2MatchMultiStage();
|
||||
bool halfResolution = Parameters::defaultOdomViso2MatchHalfResolution();
|
||||
Parameters::parse(viso2Parameters_, Parameters::kOdomViso2MatchMultiStage(), multistage);
|
||||
Parameters::parse(viso2Parameters_, Parameters::kOdomViso2MatchHalfResolution() , halfResolution);
|
||||
params.match.multi_stage = multistage?1:0;
|
||||
params.match.half_resolution = halfResolution?1:0;
|
||||
Parameters::parse(viso2Parameters_, Parameters::kOdomViso2MatchRefinement(), params.match.refinement);
|
||||
|
||||
Parameters::parse(viso2Parameters_, Parameters::kOdomViso2BucketMaxFeatures(), params.bucket.max_features);
|
||||
Parameters::parse(viso2Parameters_, Parameters::kOdomViso2BucketWidth(), params.bucket.bucket_width);
|
||||
Parameters::parse(viso2Parameters_, Parameters::kOdomViso2BucketHeight(), params.bucket.bucket_height);
|
||||
|
||||
viso2_ = new VisualOdometryStereo(params);
|
||||
|
||||
viso2_->process(leftGray.data, rightGray.data, dims);
|
||||
t.setIdentity();
|
||||
covariance = cv::Mat::eye(6,6, CV_64FC1)*9999.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool success = viso2_->process(leftGray.data, rightGray.data, dims, lost_ || keep_reference_frame_);
|
||||
if (success)
|
||||
{
|
||||
Matrix motionViso = Matrix::inv(viso2_->getMotion());
|
||||
Transform motion(motionViso.val[0][0], motionViso.val[0][1], motionViso.val[0][2],motionViso.val[0][3],
|
||||
motionViso.val[1][0], motionViso.val[1][1], motionViso.val[1][2],motionViso.val[1][3],
|
||||
motionViso.val[2][0], motionViso.val[2][1], motionViso.val[2][2], motionViso.val[2][3]);
|
||||
Transform camera_motion;
|
||||
|
||||
if(lost_ || keep_reference_frame_)
|
||||
{
|
||||
camera_motion = reference_motion_.inverse() * motion;
|
||||
}
|
||||
else
|
||||
{
|
||||
camera_motion = motion;
|
||||
}
|
||||
reference_motion_ = motion; // store last motion as reference
|
||||
|
||||
t=camera_motion;
|
||||
|
||||
//based on values set in viso2_ros
|
||||
covariance = cv::Mat::eye(6,6, CV_64FC1);
|
||||
covariance.at<double>(0,0) = 0.002;
|
||||
covariance.at<double>(1,1) = 0.002;
|
||||
covariance.at<double>(2,2) = 0.05;
|
||||
covariance.at<double>(3,3) = 0.09;
|
||||
covariance.at<double>(4,4) = 0.09;
|
||||
covariance.at<double>(5,5) = 0.09;
|
||||
|
||||
lost_=false;
|
||||
}
|
||||
else
|
||||
{
|
||||
covariance = cv::Mat::eye(6,6, CV_64FC1)*9999.0;
|
||||
lost_ = true;
|
||||
}
|
||||
|
||||
if(success)
|
||||
{
|
||||
// Proceed depending on the reference frame change method
|
||||
if(ref_frame_change_method_==1)
|
||||
{
|
||||
// calculate current feature flow
|
||||
double feature_flow = computeFeatureFlow(viso2_->getMatches());
|
||||
keep_reference_frame_ = (feature_flow < ref_frame_motion_threshold_);
|
||||
}
|
||||
else
|
||||
{
|
||||
keep_reference_frame_ = ref_frame_inlier_threshold_==0 || viso2_->getNumberOfInliers() > ref_frame_inlier_threshold_;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
keep_reference_frame_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
const Transform & localTransform = data.stereoCameraModel().localTransform();
|
||||
if(!t.isNull() && !t.isIdentity() && !localTransform.isIdentity() && !localTransform.isNull())
|
||||
{
|
||||
// from camera frame to base frame
|
||||
if(!previousLocalTransform_.isNull())
|
||||
{
|
||||
t = previousLocalTransform_ * t * localTransform.inverse();
|
||||
}
|
||||
else
|
||||
{
|
||||
t = localTransform * t * localTransform.inverse();
|
||||
}
|
||||
previousLocalTransform_ = localTransform;
|
||||
}
|
||||
|
||||
if(info)
|
||||
{
|
||||
info->type = (int)kTypeViso2;
|
||||
info->keyFrameAdded = !keep_reference_frame_;
|
||||
info->reg.matches = viso2_->getNumberOfMatches();
|
||||
info->reg.inliers = viso2_->getNumberOfInliers();
|
||||
if(covariance.cols == 6 && covariance.rows == 6 && covariance.type() == CV_64FC1)
|
||||
{
|
||||
info->reg.covariance = covariance;
|
||||
}
|
||||
|
||||
if(this->isInfoDataFilled())
|
||||
{
|
||||
std::vector<Matcher::p_match> matches = viso2_->getMatches();
|
||||
info->refCorners.resize(matches.size());
|
||||
info->newCorners.resize(matches.size());
|
||||
info->cornerInliers.resize(matches.size());
|
||||
for (size_t i = 0; i < matches.size(); ++i)
|
||||
{
|
||||
info->refCorners[i].x = matches[i].u1p;
|
||||
info->refCorners[i].y = matches[i].v1p;
|
||||
info->newCorners[i].x = matches[i].u1c;
|
||||
info->newCorners[i].y = matches[i].v1c;
|
||||
info->cornerInliers[i] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UINFO("Odom update time = %fs lost=%s", timer.elapsed(), lost_?"true":"false");
|
||||
|
||||
#else
|
||||
UERROR("RTAB-Map is not built with VISO2 support! Select another visual odometry approach.");
|
||||
#endif
|
||||
return t;
|
||||
}
|
||||
|
||||
} // namespace rtabmap
|
||||
Reference in New Issue
Block a user