mirror of
https://github.com/introlab/rtabmap.git
synced 2026-10-06 18:17:47 +08:00
* added doc and tests for util2d.h * updated cmake-ros ci * Added util3d.h doc and tests * util3d_transforms.h: Added doc and tests * util3d_filtering.h: started doc and test * util3d_filtering.h: more tests and doc * Added more doc/tests * finished util3d_filtering doc and tests * added test for util2d::depthBleedingFiltering * Added util3d_registration tests * Added util3d_features.h doc/tests * added doc/tests for util3d_correspondences.h * added doc/gtest for util3d_mapping.h (missing hpp functions) * finished testing util3d_mapping.hpp * Added util3d_motion_estimation.h tests (2D->3D done) * finished util3d_motion_estimation.h tests * minimal util3d_surface.h * Added Transform and VisualWord tests * Added doc for CameraModel and StereoCameraModel * Added more logs in ros ci * Passing tests on fical * improved all devcontainer * added devcontainer kilted, fixed source setup.bash, removed ldconfig in ros-cmake workflow * cleanup * source ros * Added utilite tests * Added testing to appveyor, github actions cancellable on re-commit on same branch * appveyor testing without all targets * appveyor: specifying ALL_BUILD target * Fixed Util2dTest.NMSImageBoundsRespected test * Fixing PCL Indices error on old pcl * Added VWDictionary tests and doc. Fixed LSH not working (fix from https://github.com/flann-lib/flann/pull/472 * fixing some appveyor CI errors, added test to check dictionary serialization against all type * Added StereoDense, StereoBM and StereoSGBM doc and tests * Added Stereo tests * Added CameraModel and StereoCameraModel tests * Added doc and test for Statistics * Added doc/tests for Signature * Added doc/test for SensorEvent, added doc for SensorCaptureInfo * Added doc to SensorData * Added SensorData tests * Added SensorCapture and SensorCaptureThread doc and tests * fixed sensordata test * updated SSC test and doc * Added doc and tests for BayesFilter class * Enabled testing on mac, updated windows testing like on linux * added test_link * fixed unresolved on windows * fixed ThreadHandle error on macos ci * Added GPS and GeodeticCoords tests * Added tests for compression * Added Odometry tests (base class only) * Added DBDriver tests * Added coverage report * uniformized test names * fixing concurancy and coverage ci * dont built tools, examples and app for coverage build * fixed report tool rebuilt without qt compilation error * updated coverage option * updated coverage config * added doc CI job * fixing windows and mac ci errors * Added DBDriverSqlite3 tests * Added IMU tests * Added Graph tests * fixing flaky macos test * Added IMUThread and IMUFilter tests * Added Landmarks tests * Added LASWriter tests * fixing seed flaky test * fixing flaky macos timing tests * Added LocalGrid tests * Added LocalGridMaker tests * fixing ci errors * Added GlobalMap tests * Added doc for EnvSensor * Added Features2D tests * Added Registration tests * Added RegistrationVis tests * Added doc for Rtabmap and Memory classes * Added Memory and Rtabmap tests * making some tests less flaky * lcov 1.14 support * updated compatible tool arguments * Added integration tests (RGB-D, Stereo, Lidar2d, Lidar3d) * More octomap checks * Refactored how/when python interpretor is created to simplify library usage * Added python tests * fixed some flaky tests * suppressed some third party related warnings * fixed ceres tests * more flaky fixes * Fixing tests without libpointmatcher * Added RANSAC rejection filter to PCL ICP * fixing multi platform flakiness * Added test to detect regression * Fixing windows pcl link error * fixed some macos flakiness * bigger 2D2D registration error on opencv 4.6.0 * flakiness * fixing flaky tests on windows and mac * flaky thread test on slow mac VM * windows slow test * fixing more ci erros * fxing temp dir on windows * Added Optimizer tests and discovered some bugs (fixed) * fixing flaky tests in mac and windows * Added Optimizer doc * Added GTSAM BA, updated Ceres to use g2o ba parameters. Renamed g2o's ba related parameters to Optimizer group and used by both gtsam and ceres. * fixing build without gtsam * fixing home dir * fixing python ci isssues * Added multicam ba tests * Added Ceres multicam BA support * Aligned BundleAdjustment parameters with Optimizer/Strategy to avoid confusion in the code * Added BA integration test * Added robust graph optimization integration test * Added loop3it test * Added stereo20Hz test * Added smartfactor gtsam * Fixed bugged check and warn if python didn't return any descriptors * Fixing gtsam version build issues * fixing tilt on windows ci * loosing ceres integration test for ci * mac ci flakiness * updating missing param in gui * updating test bound for mac * added appearance-based tests, set min gftt quality to quality level * testing more stuff * improving features2d tests * ci flakiness * fixing flaky ci * ci fixes * flaky fixes * Added RegistrationIcp tests * Added icp integration test with real-worl corridor like env * intermediate nodes * fixing enum * Updated test to catch #1714 * Fixed 2d corridor failing on pcl * flaky pnp test * flaky brisk test * Set rtabmap_integration test as long * updating loop closure test * flaky ci tests * TEsting roundtrip g2o/toro save/load * loosing test bound * fixed cuda capable checks * flaky tests * Debugging test hanging * more debugging stuff * updating limit * windows: disabled cuda on ci to avoid incompatible driver issue. Fixing a bad test mem allocation * trying fixing cuda hanging issue * fixing ci flakyness * flaky tests * Updated BOW flaky tests by checking min precision/recall instead of recall@100precision. Fixed signature test * CameraModel::load() test initRectificationMap param * test dbdriver load dictionary idsOnly * Memory: test keepLinkedInDb param * added dummyDictionary tests * test intermediate nodes count * Added MarkerDetector tests * reverted breaking change of UMutex and USemaphore * Features2d: fixed compiltion warnings with clang about override * clang warnings * fixing test build with pcl 1.8 * g2o and gtsam build errors on android * opencv5 test fixes * disabled testing for ios and android builds * normalized endline characters for easier diff * added LF CRLF rule * bump 0.23.10. fixing doc version * Publish rtabmap website doc from ci * fixing MSCVC build error * macos icp flaky test * fixing ceres macos test bound * ficing more flaky tests * fixing opencv5 related test errors. Also fixed an actual bug in ENU_WGS84ToGeocentric_WGS84() * added comment about mrpt change * removed rosdoc2 (will add it for rtabmap_ros later) * fixing website style * updated download links * locally deployable website with api * sweep doxygen issues * improved/revised doxygen main pages * removed examples empty page * Updated doxygen style * more concise doxygen groups * added api link on main readme * fixing utilite test error * fixing CommonFilteringGroundNormalsUp test * updated precisionRecall test bounds for Freak and brief descriptors * fixing scale check in ba tests * disabled tests on windows cuda build (missing dlls amd runner cannot test cuda anyway) * ceres: missing suitesparse dep in windows ci * adjusting recall thr for fast/freak * ficing more flaky tests * fixing flaky tests * disabled coverage in ros ci * Enable integration tests for ros ci jobs * loosing up some threshold for failing tests * trigger cache * fixing test data in ros ci. Updated flaky test for mac * slaking some test limit * Fixed rtabmap-detectMoreLoopClosures inverted output value * loosing up sift recall on mac * optimizer re-ordered distribution for reproducible results (mac g2o) * macos dump test crash log * combining all tests to save time on shared library reload. Also fixed Logs with missing arguments. * Added ENABLE_FORMAT_ERRORS cmake option * do test only one time * fixed all format warnings * format security android build errors * less verbose tests * updated ImuUThread test * fixed a log * Fixed libpointmatcher 2d normals eigen issue * Fixing libpointmatcher conversion issues * fixing libpointmatcher test on windows ci * cleanup comments, relax some test thr * disabled sequoia-intel ci build (too flaky, would need extensive testing directly on that machine)
830 lines
23 KiB
C++
830 lines
23 KiB
C++
/*
|
|
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/CameraModel.h>
|
|
#include <rtabmap/core/Version.h>
|
|
#include <rtabmap/utilite/ULogger.h>
|
|
#include <rtabmap/utilite/UDirectory.h>
|
|
#include <rtabmap/utilite/UFile.h>
|
|
#include <rtabmap/utilite/UConversion.h>
|
|
#include <rtabmap/utilite/UMath.h>
|
|
#include <rtabmap/utilite/UStl.h>
|
|
#include <opencv2/imgproc/imgproc.hpp>
|
|
|
|
namespace rtabmap {
|
|
|
|
CameraModel::CameraModel() :
|
|
localTransform_(opticalRotation())
|
|
{
|
|
|
|
}
|
|
|
|
CameraModel::CameraModel(
|
|
const std::string & cameraName,
|
|
const cv::Size & imageSize,
|
|
const cv::Mat & K,
|
|
const cv::Mat & D,
|
|
const cv::Mat & R,
|
|
const cv::Mat & P,
|
|
const Transform & localTransform) :
|
|
name_(cameraName),
|
|
imageSize_(imageSize),
|
|
K_(K),
|
|
D_(D),
|
|
R_(R),
|
|
P_(P),
|
|
localTransform_(localTransform)
|
|
{
|
|
UASSERT(K_.empty() || (K_.rows == 3 && K_.cols == 3 && K_.type() == CV_64FC1));
|
|
UASSERT(D_.empty() || (D_.rows == 1 && (D_.cols == 4 || D_.cols == 5 || D_.cols == 6 || D_.cols == 8 || D_.cols == 12 || D_.cols == 14) && D_.type() == CV_64FC1));
|
|
UASSERT(R_.empty() || (R_.rows == 3 && R_.cols == 3 && R_.type() == CV_64FC1));
|
|
UASSERT(P_.empty() || (P_.rows == 3 && P_.cols == 4 && P_.type() == CV_64FC1));
|
|
}
|
|
|
|
CameraModel::CameraModel(
|
|
double fx,
|
|
double fy,
|
|
double cx,
|
|
double cy,
|
|
const Transform & localTransform,
|
|
double Tx,
|
|
const cv::Size & imageSize) :
|
|
imageSize_(imageSize),
|
|
K_(cv::Mat::eye(3, 3, CV_64FC1)),
|
|
localTransform_(localTransform)
|
|
{
|
|
UASSERT_MSG(fx > 0.0, uFormat("fx=%f", fx).c_str());
|
|
UASSERT_MSG(fy > 0.0, uFormat("fy=%f", fy).c_str());
|
|
UASSERT_MSG(cx >= 0.0 && imageSize.width>=0, uFormat("cx=%f imageSize.width=%d", cx, imageSize.width).c_str());
|
|
UASSERT_MSG(cy >= 0.0 && imageSize.height>=0, uFormat("cy=%f imageSize.height=%d", cy, imageSize.height).c_str());
|
|
UASSERT(!localTransform.isNull());
|
|
|
|
if(cx==0.0 && imageSize.width > 0)
|
|
{
|
|
cx = double(imageSize.width)/2.0-0.5;
|
|
}
|
|
if(cy==0.0 && imageSize.height > 0)
|
|
{
|
|
cy = double(imageSize.height)/2.0-0.5;
|
|
}
|
|
|
|
if(Tx != 0.0)
|
|
{
|
|
P_ = cv::Mat::eye(3, 4, CV_64FC1),
|
|
P_.at<double>(0,0) = fx;
|
|
P_.at<double>(1,1) = fy;
|
|
P_.at<double>(0,2) = cx;
|
|
P_.at<double>(1,2) = cy;
|
|
P_.at<double>(0,3) = Tx;
|
|
}
|
|
|
|
K_.at<double>(0,0) = fx;
|
|
K_.at<double>(1,1) = fy;
|
|
K_.at<double>(0,2) = cx;
|
|
K_.at<double>(1,2) = cy;
|
|
}
|
|
|
|
CameraModel::CameraModel(
|
|
const std::string & name,
|
|
double fx,
|
|
double fy,
|
|
double cx,
|
|
double cy,
|
|
const Transform & localTransform,
|
|
double Tx,
|
|
const cv::Size & imageSize) :
|
|
name_(name),
|
|
imageSize_(imageSize),
|
|
K_(cv::Mat::eye(3, 3, CV_64FC1)),
|
|
localTransform_(localTransform)
|
|
{
|
|
UASSERT_MSG(fx > 0.0, uFormat("fx=%f", fx).c_str());
|
|
UASSERT_MSG(fy > 0.0, uFormat("fy=%f", fy).c_str());
|
|
UASSERT_MSG(cx >= 0.0 && imageSize.width>=0, uFormat("cx=%f imageSize.width=%d", cx, imageSize.width).c_str());
|
|
UASSERT_MSG(cy >= 0.0 && imageSize.height>=0, uFormat("cy=%f imageSize.height=%d", cy, imageSize.height).c_str());
|
|
UASSERT(!localTransform.isNull());
|
|
|
|
if(cx==0.0 && imageSize.width > 0)
|
|
{
|
|
cx = double(imageSize.width)/2.0-0.5;
|
|
}
|
|
if(cy==0.0 && imageSize.height > 0)
|
|
{
|
|
cy = double(imageSize.height)/2.0-0.5;
|
|
}
|
|
|
|
if(Tx != 0.0)
|
|
{
|
|
P_ = cv::Mat::eye(3, 4, CV_64FC1),
|
|
P_.at<double>(0,0) = fx;
|
|
P_.at<double>(1,1) = fy;
|
|
P_.at<double>(0,2) = cx;
|
|
P_.at<double>(1,2) = cy;
|
|
P_.at<double>(0,3) = Tx;
|
|
}
|
|
|
|
K_.at<double>(0,0) = fx;
|
|
K_.at<double>(1,1) = fy;
|
|
K_.at<double>(0,2) = cx;
|
|
K_.at<double>(1,2) = cy;
|
|
}
|
|
|
|
bool CameraModel::initRectificationMap()
|
|
{
|
|
UASSERT(imageSize_.height > 0 && imageSize_.width > 0);
|
|
UASSERT(D_.rows == 1 && (D_.cols == 4 || D_.cols == 5 || D_.cols == 6 || D_.cols == 8 || D_.cols == 12 || D_.cols == 14));
|
|
UASSERT(R_.rows == 3 && R_.cols == 3);
|
|
UASSERT(P_.rows == 3 && P_.cols == 4);
|
|
// init rectification map
|
|
UINFO("Initialize rectify map");
|
|
if(D_.cols == 6)
|
|
{
|
|
#if CV_MAJOR_VERSION > 2 or (CV_MAJOR_VERSION == 2 and (CV_MINOR_VERSION >4 or (CV_MINOR_VERSION == 4 and CV_SUBMINOR_VERSION >=10)))
|
|
// Equidistant / FishEye
|
|
// get only k parameters (k1,k2,p1,p2,k3,k4)
|
|
cv::Mat D(1, 4, CV_64FC1);
|
|
D.at<double>(0,0) = D_.at<double>(0,0);
|
|
D.at<double>(0,1) = D_.at<double>(0,1);
|
|
D.at<double>(0,2) = D_.at<double>(0,4);
|
|
D.at<double>(0,3) = D_.at<double>(0,5);
|
|
cv::fisheye::initUndistortRectifyMap(K_, D, R_, P_, imageSize_, CV_32FC1, mapX_, mapY_);
|
|
}
|
|
else
|
|
#else
|
|
UWARN("Too old opencv version (%d,%d,%d) to support fisheye model (min 2.4.10 required)!",
|
|
CV_MAJOR_VERSION, CV_MINOR_VERSION, CV_SUBMINOR_VERSION);
|
|
}
|
|
#endif
|
|
{
|
|
// RadialTangential
|
|
cv::initUndistortRectifyMap(K_, D_, R_, P_, imageSize_, CV_32FC1, mapX_, mapY_);
|
|
}
|
|
return isRectificationMapInitialized();
|
|
}
|
|
|
|
void CameraModel::setImageSize(const cv::Size & size)
|
|
{
|
|
UASSERT((size.height > 0 && size.width > 0) || (size.height == 0 && size.width == 0));
|
|
imageSize_ = size;
|
|
double ncx = cx();
|
|
double ncy = cy();
|
|
if(ncx==0.0 && imageSize_.width > 0)
|
|
{
|
|
ncx = double(imageSize_.width)/2.0-0.5;
|
|
}
|
|
if(ncy==0.0 && imageSize_.height > 0)
|
|
{
|
|
ncy = double(imageSize_.height)/2.0-0.5;
|
|
}
|
|
if(!P_.empty())
|
|
{
|
|
P_.at<double>(0,2) = ncx;
|
|
P_.at<double>(1,2) = ncy;
|
|
}
|
|
if(!K_.empty())
|
|
{
|
|
K_.at<double>(0,2) = ncx;
|
|
K_.at<double>(1,2) = ncy;
|
|
}
|
|
}
|
|
|
|
bool CameraModel::load(const std::string & filePath, bool initRectificationMaps)
|
|
{
|
|
K_ = cv::Mat();
|
|
D_ = cv::Mat();
|
|
R_ = cv::Mat();
|
|
P_ = cv::Mat();
|
|
mapX_ = cv::Mat();
|
|
mapY_ = cv::Mat();
|
|
name_.clear();
|
|
imageSize_ = cv::Size();
|
|
|
|
if(UFile::exists(filePath))
|
|
{
|
|
try
|
|
{
|
|
UINFO("Reading calibration file \"%s\"", filePath.c_str());
|
|
cv::FileStorage fs(filePath, cv::FileStorage::READ);
|
|
|
|
cv::FileNode n,n2;
|
|
|
|
n = fs["camera_name"];
|
|
if(n.type() != cv::FileNode::NONE)
|
|
{
|
|
name_ = (std::string)n;
|
|
}
|
|
else
|
|
{
|
|
UWARN("Missing \"camera_name\" field in \"%s\"", filePath.c_str());
|
|
}
|
|
|
|
n = fs["image_width"];
|
|
n2 = fs["image_height"];
|
|
if(n.type() != cv::FileNode::NONE)
|
|
{
|
|
imageSize_.width = (int)fs["image_width"];
|
|
imageSize_.height = (int)fs["image_height"];
|
|
}
|
|
else
|
|
{
|
|
UWARN("Missing \"image_width\" and/or \"image_height\" fields in \"%s\"", filePath.c_str());
|
|
}
|
|
|
|
// import from ROS calibration format
|
|
n = fs["camera_matrix"];
|
|
if(n.type() != cv::FileNode::NONE)
|
|
{
|
|
int rows = (int)n["rows"];
|
|
int cols = (int)n["cols"];
|
|
std::vector<double> data;
|
|
n["data"] >> data;
|
|
UASSERT(rows*cols == (int)data.size());
|
|
UASSERT(rows == 3 && cols == 3);
|
|
K_ = cv::Mat(rows, cols, CV_64FC1, data.data()).clone();
|
|
}
|
|
else
|
|
{
|
|
UWARN("Missing \"camera_matrix\" field in \"%s\"", filePath.c_str());
|
|
}
|
|
|
|
n = fs["distortion_coefficients"];
|
|
if(n.type() != cv::FileNode::NONE)
|
|
{
|
|
int rows = (int)n["rows"];
|
|
int cols = (int)n["cols"];
|
|
std::vector<double> data;
|
|
n["data"] >> data;
|
|
UASSERT(rows*cols == (int)data.size());
|
|
UASSERT(rows == 1 && (cols == 4 || cols == 5 || cols == 8 || cols == 12 || cols == 14));
|
|
D_ = cv::Mat(rows, cols, CV_64FC1, data.data()).clone();
|
|
}
|
|
else
|
|
{
|
|
UWARN("Missing \"distorsion_coefficients\" field in \"%s\"", filePath.c_str());
|
|
}
|
|
|
|
n = fs["distortion_model"];
|
|
if(n.type() != cv::FileNode::NONE)
|
|
{
|
|
std::string distortionModel = (std::string)n;
|
|
if(D_.cols>=4 &&
|
|
(uStrContains(distortionModel, "fisheye") ||
|
|
uStrContains(distortionModel, "equidistant")))
|
|
{
|
|
cv::Mat D = cv::Mat::zeros(1,6,CV_64FC1);
|
|
D.at<double>(0,0) = D_.at<double>(0,0);
|
|
D.at<double>(0,1) = D_.at<double>(0,1);
|
|
D.at<double>(0,4) = D_.at<double>(0,2);
|
|
D.at<double>(0,5) = D_.at<double>(0,3);
|
|
D_ = D;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UWARN("Missing \"distortion_model\" field in \"%s\"", filePath.c_str());
|
|
}
|
|
|
|
n = fs["rectification_matrix"];
|
|
if(n.type() != cv::FileNode::NONE)
|
|
{
|
|
int rows = (int)n["rows"];
|
|
int cols = (int)n["cols"];
|
|
std::vector<double> data;
|
|
n["data"] >> data;
|
|
UASSERT(rows*cols == (int)data.size());
|
|
UASSERT(rows == 3 && cols == 3);
|
|
R_ = cv::Mat(rows, cols, CV_64FC1, data.data()).clone();
|
|
}
|
|
else
|
|
{
|
|
UWARN("Missing \"rectification_matrix\" field in \"%s\"", filePath.c_str());
|
|
}
|
|
|
|
n = fs["projection_matrix"];
|
|
if(n.type() != cv::FileNode::NONE)
|
|
{
|
|
int rows = (int)n["rows"];
|
|
int cols = (int)n["cols"];
|
|
std::vector<double> data;
|
|
n["data"] >> data;
|
|
UASSERT(rows*cols == (int)data.size());
|
|
UASSERT(rows == 3 && cols == 4);
|
|
P_ = cv::Mat(rows, cols, CV_64FC1, data.data()).clone();
|
|
}
|
|
else
|
|
{
|
|
UWARN("Missing \"projection_matrix\" field in \"%s\"", filePath.c_str());
|
|
}
|
|
|
|
n = fs["local_transform"];
|
|
if(n.type() != cv::FileNode::NONE)
|
|
{
|
|
int rows = (int)n["rows"];
|
|
int cols = (int)n["cols"];
|
|
std::vector<float> data;
|
|
n["data"] >> data;
|
|
UASSERT(rows*cols == (int)data.size());
|
|
UASSERT(rows == 3 && cols == 4);
|
|
localTransform_ = Transform(
|
|
data[0], data[1], data[2], data[3],
|
|
data[4], data[5], data[6], data[7],
|
|
data[8], data[9], data[10], data[11]);
|
|
Transform detCheck = localTransform_.clone();
|
|
localTransform_.normalizeRotation(); /// Normalize by default
|
|
float det = detCheck.toEigen3f().linear().determinant();
|
|
if(fabs(det - 1.0f) > 0.0001)
|
|
{
|
|
std::stringstream streamBefore, streamAfter;
|
|
streamBefore << detCheck << std::endl;
|
|
streamAfter << localTransform_ << std::endl;
|
|
UWARN("The camera model's local_transform from \"%s\" doesn't "
|
|
"have a normalized rotation matrix (dertminant=%f). We will normalize "
|
|
"it for convenience.\nWas:\n%sNow\n%s",
|
|
filePath.c_str(),
|
|
det,
|
|
streamBefore.str().c_str(),
|
|
streamAfter.str().c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UWARN("Missing \"local_transform\" field in \"%s\"", filePath.c_str());
|
|
}
|
|
|
|
fs.release();
|
|
|
|
if(initRectificationMaps && isValidForRectification())
|
|
{
|
|
initRectificationMap();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch(const cv::Exception & e)
|
|
{
|
|
UERROR("Error reading calibration file \"%s\": %s (Make sure the first line of the yaml file is \"%%YAML:1.0\")", filePath.c_str(), e.what());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UWARN("Could not load calibration file \"%s\".", filePath.c_str());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CameraModel::load(const std::string & directory, const std::string & cameraName, bool initRectificationMaps)
|
|
{
|
|
return load(directory+"/"+cameraName+".yaml", initRectificationMaps);
|
|
}
|
|
|
|
bool CameraModel::save(const std::string & directory) const
|
|
{
|
|
if(name_.empty())
|
|
{
|
|
UWARN("Camera name is empty, will use general \"camera\" as name.");
|
|
}
|
|
std::string filePath = directory+"/"+(name_.empty()?"camera":name_)+".yaml";
|
|
if(!filePath.empty() && (!K_.empty() || !D_.empty() || !R_.empty() || !P_.empty()))
|
|
{
|
|
UINFO("Saving calibration to file \"%s\"", filePath.c_str());
|
|
cv::FileStorage fs(filePath, cv::FileStorage::WRITE);
|
|
|
|
// export in ROS calibration format
|
|
|
|
if(!name_.empty())
|
|
{
|
|
fs << "camera_name" << name_;
|
|
}
|
|
if(imageSize_.width>0 && imageSize_.height>0)
|
|
{
|
|
fs << "image_width" << imageSize_.width;
|
|
fs << "image_height" << imageSize_.height;
|
|
}
|
|
|
|
if(!K_.empty())
|
|
{
|
|
fs << "camera_matrix" << "{";
|
|
fs << "rows" << K_.rows;
|
|
fs << "cols" << K_.cols;
|
|
fs << "data" << std::vector<double>((double*)K_.data, ((double*)K_.data)+(K_.rows*K_.cols));
|
|
fs << "}";
|
|
}
|
|
|
|
if(!D_.empty())
|
|
{
|
|
cv::Mat D = D_;
|
|
if(D_.cols == 6)
|
|
{
|
|
D = cv::Mat(1,4,CV_64FC1);
|
|
D.at<double>(0,0) = D_.at<double>(0,0);
|
|
D.at<double>(0,1) = D_.at<double>(0,1);
|
|
D.at<double>(0,2) = D_.at<double>(0,4);
|
|
D.at<double>(0,3) = D_.at<double>(0,5);
|
|
}
|
|
fs << "distortion_coefficients" << "{";
|
|
fs << "rows" << D.rows;
|
|
fs << "cols" << D.cols;
|
|
fs << "data" << std::vector<double>((double*)D.data, ((double*)D.data)+(D.rows*D.cols));
|
|
fs << "}";
|
|
|
|
// compaibility with ROS
|
|
if(D_.cols == 6)
|
|
{
|
|
fs << "distortion_model" << "equidistant"; // equidistant, fisheye
|
|
}
|
|
else if(D.cols > 5)
|
|
{
|
|
fs << "distortion_model" << "rational_polynomial"; // rad tan
|
|
}
|
|
else
|
|
{
|
|
fs << "distortion_model" << "plumb_bob"; // rad tan
|
|
}
|
|
}
|
|
|
|
if(!R_.empty())
|
|
{
|
|
fs << "rectification_matrix" << "{";
|
|
fs << "rows" << R_.rows;
|
|
fs << "cols" << R_.cols;
|
|
fs << "data" << std::vector<double>((double*)R_.data, ((double*)R_.data)+(R_.rows*R_.cols));
|
|
fs << "}";
|
|
}
|
|
|
|
if(!P_.empty())
|
|
{
|
|
fs << "projection_matrix" << "{";
|
|
fs << "rows" << P_.rows;
|
|
fs << "cols" << P_.cols;
|
|
fs << "data" << std::vector<double>((double*)P_.data, ((double*)P_.data)+(P_.rows*P_.cols));
|
|
fs << "}";
|
|
}
|
|
|
|
if(!localTransform_.isNull())
|
|
{
|
|
fs << "local_transform" << "{";
|
|
fs << "rows" << 3;
|
|
fs << "cols" << 4;
|
|
fs << "data" << std::vector<float>((float*)localTransform_.data(), ((float*)localTransform_.data())+12);
|
|
fs << "}";
|
|
}
|
|
|
|
fs.release();
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
UERROR("Cannot save calibration to \"%s\" because it is empty.", filePath.c_str());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::vector<unsigned char> CameraModel::serialize() const
|
|
{
|
|
const int headerSize = 11;
|
|
int header[headerSize] = {
|
|
RTABMAP_VERSION_MAJOR, RTABMAP_VERSION_MINOR, RTABMAP_VERSION_PATCH, // 0,1,2
|
|
0, //mono // 3,
|
|
imageSize_.width, imageSize_.height, // 4,5
|
|
(int)K_.total(), (int)D_.total(), (int)R_.total(), (int)P_.total(), // 6,7,8,9
|
|
localTransform_.isNull()?0:localTransform_.size()}; // 10
|
|
UDEBUG("Header: %d %d %d %d %d %d %d %d %d %d %d", header[0],header[1],header[2],header[3],header[4],header[5],header[6],header[7],header[8],header[9],header[10]);
|
|
std::vector<unsigned char> data(
|
|
sizeof(int)*headerSize +
|
|
sizeof(double)*(K_.total()+D_.total()+R_.total()+P_.total()) +
|
|
(localTransform_.isNull()?0:sizeof(float)*localTransform_.size()));
|
|
memcpy(data.data(), header, sizeof(int)*headerSize);
|
|
int index = sizeof(int)*headerSize;
|
|
if(!K_.empty())
|
|
{
|
|
memcpy(data.data()+index, K_.data, sizeof(double)*(K_.total()));
|
|
index+=sizeof(double)*(K_.total());
|
|
}
|
|
if(!D_.empty())
|
|
{
|
|
memcpy(data.data()+index, D_.data, sizeof(double)*(D_.total()));
|
|
index+=sizeof(double)*(D_.total());
|
|
}
|
|
if(!R_.empty())
|
|
{
|
|
memcpy(data.data()+index, R_.data, sizeof(double)*(R_.total()));
|
|
index+=sizeof(double)*(R_.total());
|
|
}
|
|
if(!P_.empty())
|
|
{
|
|
memcpy(data.data()+index, P_.data, sizeof(double)*(P_.total()));
|
|
index+=sizeof(double)*(P_.total());
|
|
}
|
|
if(!localTransform_.isNull())
|
|
{
|
|
memcpy(data.data()+index, localTransform_.data(), sizeof(float)*(localTransform_.size()));
|
|
index+=sizeof(float)*(localTransform_.size());
|
|
}
|
|
return data;
|
|
}
|
|
|
|
unsigned int CameraModel::deserialize(const std::vector<unsigned char>& data)
|
|
{
|
|
return deserialize(data.data(), data.size());
|
|
}
|
|
unsigned int CameraModel::deserialize(const unsigned char * data, unsigned int dataSize)
|
|
{
|
|
*this = CameraModel();
|
|
int headerSize = 11;
|
|
if(dataSize >= sizeof(int)*headerSize)
|
|
{
|
|
UASSERT(data != 0);
|
|
const int * header = (const int *)data;
|
|
int type = header[3];
|
|
if(type == 0)
|
|
{
|
|
imageSize_.width = header[4];
|
|
imageSize_.height = header[5];
|
|
int iK = 6;
|
|
int iD = 7;
|
|
int iR = 8;
|
|
int iP = 9;
|
|
int iL = 10;
|
|
//UDEBUG("Header: %d %d %d %d %d %d %d %d %d %d %d", header[0],header[1],header[2],header[3],header[4],header[5],header[6],header[7],header[8],header[9],header[10]);
|
|
unsigned int requiredDataSize = sizeof(int)*headerSize +
|
|
sizeof(double)*(header[iK]+header[iD]+header[iR]+header[iP]) +
|
|
sizeof(float)*header[iL];
|
|
UASSERT_MSG(dataSize >= requiredDataSize,
|
|
uFormat("dataSize=%d != required=%d (header: version %d.%d.%d %dx%d type=%d K=%d D=%d R=%d P=%d L=%d)",
|
|
dataSize,
|
|
requiredDataSize,
|
|
header[0], header[1], header[2], header[4], header[5], header[3],
|
|
header[iK], header[iD], header[iR],header[iP], header[iL]).c_str());
|
|
unsigned int index = sizeof(int)*headerSize;
|
|
if(header[iK] != 0)
|
|
{
|
|
UASSERT(header[iK] == 9);
|
|
K_ = cv::Mat(3, 3, CV_64FC1, (void*)(data+index)).clone();
|
|
index+=sizeof(double)*(K_.total());
|
|
}
|
|
if(header[iD] != 0)
|
|
{
|
|
D_ = cv::Mat(1, header[iD], CV_64FC1, (void*)(data+index)).clone();
|
|
index+=sizeof(double)*(D_.total());
|
|
}
|
|
if(header[iR] != 0)
|
|
{
|
|
UASSERT(header[iR] == 9);
|
|
R_ = cv::Mat(3, 3, CV_64FC1, (void*)(data+index)).clone();
|
|
index+=sizeof(double)*(R_.total());
|
|
}
|
|
if(header[iP] != 0)
|
|
{
|
|
UASSERT(header[iP] == 12);
|
|
P_ = cv::Mat(3, 4, CV_64FC1, (void*)(data+index)).clone();
|
|
index+=sizeof(double)*(P_.total());
|
|
}
|
|
if(header[iL] != 0)
|
|
{
|
|
UASSERT(header[iL] == 12);
|
|
memcpy(localTransform_.data(), data+index, sizeof(float)*localTransform_.size());
|
|
index+=sizeof(float)*localTransform_.size();
|
|
}
|
|
UASSERT(index <= dataSize);
|
|
return index;
|
|
}
|
|
else
|
|
{
|
|
UERROR("Serialized calibration is not mono (type=%d), use the appropriate class matching the type to deserialize.", type);
|
|
}
|
|
}
|
|
UERROR("Wrong serialized calibration data format detected (size in bytes=%d)! Cannot deserialize the data.", (int)dataSize);
|
|
return 0;
|
|
}
|
|
|
|
CameraModel CameraModel::scaled(double scale) const
|
|
{
|
|
CameraModel scaledModel = *this;
|
|
UASSERT(scale > 0.0);
|
|
if(this->isValidForProjection())
|
|
{
|
|
// has only effect on K and P
|
|
cv::Mat K;
|
|
if(!K_.empty())
|
|
{
|
|
K = K_.clone();
|
|
K.at<double>(0,0) *= scale;
|
|
K.at<double>(1,1) *= scale;
|
|
K.at<double>(0,2) *= scale;
|
|
K.at<double>(1,2) *= scale;
|
|
}
|
|
|
|
cv::Mat P;
|
|
if(!P_.empty())
|
|
{
|
|
P = P_.clone();
|
|
P.at<double>(0,0) *= scale;
|
|
P.at<double>(1,1) *= scale;
|
|
P.at<double>(0,2) *= scale;
|
|
P.at<double>(1,2) *= scale;
|
|
P.at<double>(0,3) *= scale;
|
|
P.at<double>(1,3) *= scale;
|
|
}
|
|
scaledModel = CameraModel(name_, cv::Size(double(imageSize_.width)*scale, double(imageSize_.height)*scale), K, D_, R_, P, localTransform_);
|
|
}
|
|
else
|
|
{
|
|
UWARN("Trying to scale a camera model not valid! Ignoring scaling...");
|
|
}
|
|
return scaledModel;
|
|
}
|
|
|
|
CameraModel CameraModel::roi(const cv::Rect & roi) const
|
|
{
|
|
CameraModel roiModel = *this;
|
|
if(this->isValidForProjection())
|
|
{
|
|
// has only effect on cx and cy
|
|
cv::Mat K;
|
|
if(!K_.empty())
|
|
{
|
|
K = K_.clone();
|
|
K.at<double>(0,2) -= roi.x;
|
|
K.at<double>(1,2) -= roi.y;
|
|
}
|
|
|
|
cv::Mat P;
|
|
if(!P_.empty())
|
|
{
|
|
P = P_.clone();
|
|
P.at<double>(0,2) -= roi.x;
|
|
P.at<double>(1,2) -= roi.y;
|
|
}
|
|
roiModel = CameraModel(name_, roi.size(), K, D_, R_, P, localTransform_);
|
|
}
|
|
else
|
|
{
|
|
UWARN("Trying to extract roi from a camera model not valid! Ignoring roi...");
|
|
}
|
|
return roiModel;
|
|
}
|
|
|
|
double CameraModel::fovX() const
|
|
{
|
|
return imageSize_.width>0 && fx()>0?2.0*atan(imageSize_.width/(fx()*2.0)):0.0;
|
|
}
|
|
double CameraModel::fovY() const
|
|
{
|
|
return imageSize_.height>0 && fy()>0?2.0*atan(imageSize_.height/(fy()*2.0)):0.0;
|
|
}
|
|
|
|
double CameraModel::horizontalFOV() const
|
|
{
|
|
return fovX()*180.0/CV_PI;
|
|
}
|
|
|
|
double CameraModel::verticalFOV() const
|
|
{
|
|
return fovY()*180.0/CV_PI;
|
|
}
|
|
|
|
cv::Mat CameraModel::rectifyImage(const cv::Mat & raw, int interpolation) const
|
|
{
|
|
UDEBUG("");
|
|
if(!mapX_.empty() && !mapY_.empty())
|
|
{
|
|
cv::Mat rectified;
|
|
cv::remap(raw, rectified, mapX_, mapY_, interpolation);
|
|
return rectified;
|
|
}
|
|
else
|
|
{
|
|
UERROR("Cannot rectify image because the rectify map is not initialized.");
|
|
return raw.clone();
|
|
}
|
|
}
|
|
|
|
//inspired from https://github.com/code-iai/iai_kinect2/blob/master/depth_registration/src/depth_registration_cpu.cpp
|
|
cv::Mat CameraModel::rectifyDepth(const cv::Mat & raw) const
|
|
{
|
|
UDEBUG("");
|
|
UASSERT(raw.type() == CV_16UC1);
|
|
if(!mapX_.empty() && !mapY_.empty())
|
|
{
|
|
cv::Mat rectified = cv::Mat::zeros(mapX_.rows, mapX_.cols, raw.type());
|
|
for(int y=0; y<mapX_.rows; ++y)
|
|
{
|
|
for(int x=0; x<mapX_.cols; ++x)
|
|
{
|
|
cv::Point2f pt(mapX_.at<float>(y,x), mapY_.at<float>(y,x));
|
|
int xL = (int)floor(pt.x);
|
|
int xH = (int)ceil(pt.x);
|
|
int yL = (int)floor(pt.y);
|
|
int yH = (int)ceil(pt.y);
|
|
if(xL >= 0 && yL >= 0 && xH < raw.cols && yH < raw.rows)
|
|
{
|
|
const unsigned short & pLT = raw.at<unsigned short>(yL, xL);
|
|
const unsigned short & pRT = raw.at<unsigned short>(yL, xH);
|
|
const unsigned short & pLB = raw.at<unsigned short>(yH, xL);
|
|
const unsigned short & pRB = raw.at<unsigned short>(yH, xH);
|
|
if(pLT > 0 && pRT > 0 && pLB > 0 && pRB > 0)
|
|
{
|
|
unsigned short avg = (pLT + pRT + pLB + pRB) / 4;
|
|
unsigned short thres = 0.01 * avg;
|
|
if( abs(pLT - avg) < thres &&
|
|
abs(pRT - avg) < thres &&
|
|
abs(pLB - avg) < thres &&
|
|
abs(pRB - avg) < thres)
|
|
{
|
|
//bilinear interpolation
|
|
float a = pt.x - (float)xL;
|
|
float c = pt.y - (float)yL;
|
|
|
|
//http://stackoverflow.com/questions/13299409/how-to-get-the-image-pixel-at-real-locations-in-opencv
|
|
rectified.at<unsigned short>(y,x) =
|
|
(pLT * (1.f - a) + pRT * a) * (1.f - c) +
|
|
(pLB * (1.f - a) + pRB * a) * c;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return rectified;
|
|
}
|
|
else
|
|
{
|
|
UERROR("Cannot rectify image because the rectify map is not initialized.");
|
|
return raw.clone();
|
|
}
|
|
}
|
|
|
|
// resulting 3D point is in /camera_link frame
|
|
void CameraModel::project(float u, float v, float depth, float & x, float & y, float & z) const
|
|
{
|
|
if(depth > 0.0f)
|
|
{
|
|
// Fill in XYZ
|
|
x = (u - cx()) * depth / fx();
|
|
y = (v - cy()) * depth / fy();
|
|
z = depth;
|
|
}
|
|
else
|
|
{
|
|
x = y = z = std::numeric_limits<float>::quiet_NaN();
|
|
}
|
|
}
|
|
// 3D point is in /camera_link frame
|
|
void CameraModel::reproject(float x, float y, float z, float & u, float & v) const
|
|
{
|
|
UASSERT(z!=0.0f);
|
|
float invZ = 1.0f/z;
|
|
u = (fx()*x)*invZ + cx();
|
|
v = (fy()*y)*invZ + cy();
|
|
}
|
|
void CameraModel::reproject(float x, float y, float z, int & u, int & v) const
|
|
{
|
|
UASSERT(z!=0.0f);
|
|
float invZ = 1.0f/z;
|
|
u = (fx()*x)*invZ + cx();
|
|
v = (fy()*y)*invZ + cy();
|
|
}
|
|
|
|
bool CameraModel::inFrame(int u, int v) const
|
|
{
|
|
return uIsInBounds(u, 0, imageWidth()) && uIsInBounds(v, 0, imageHeight());
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& os, const CameraModel& model)
|
|
{
|
|
os << "Name: " << model.name().c_str() << std::endl
|
|
<< "Size: " << model.imageWidth() << "x" << model.imageHeight() << std::endl
|
|
<< "K= " << model.K_raw() << std::endl
|
|
<< "D= " << model.D_raw() << std::endl
|
|
<< "R= " << model.R() << std::endl
|
|
<< "P= " << model.P() << std::endl
|
|
<< "LocalTransform= " << model.localTransform();
|
|
return os;
|
|
}
|
|
|
|
} /* namespace rtabmap */
|