mirror of
https://github.com/introlab/rtabmap.git
synced 2026-09-08 12:30:20 +08:00
CameraImages: support reading multicamera image and calibration files (#1721)
* CameraImages: support reading multicamera image and calibration files * Added multi camera support for CameraStereoImages. Refactored single calib per frame option. * refactored * refactor * bump 0.23.8
This commit is contained in:
+1
-1
@@ -22,7 +22,7 @@ SET(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake_modules")
|
||||
#######################
|
||||
SET(RTABMAP_MAJOR_VERSION 0)
|
||||
SET(RTABMAP_MINOR_VERSION 23)
|
||||
SET(RTABMAP_PATCH_VERSION 7)
|
||||
SET(RTABMAP_PATCH_VERSION 8)
|
||||
SET(RTABMAP_VERSION
|
||||
${RTABMAP_MAJOR_VERSION}.${RTABMAP_MINOR_VERSION}.${RTABMAP_PATCH_VERSION})
|
||||
|
||||
|
||||
@@ -126,8 +126,10 @@ public:
|
||||
double verticalFOV() const; // in degrees
|
||||
bool isFisheye() const {return D_.cols == 6;}
|
||||
|
||||
bool load(const std::string & filePath);
|
||||
bool load(const std::string & directory, const std::string & cameraName);
|
||||
// Set initRectificationMaps=false to skip building the (potentially large)
|
||||
// rectification maps when rectification won't be used (saves time and memory).
|
||||
bool load(const std::string & filePath, bool initRectificationMaps = true);
|
||||
bool load(const std::string & directory, const std::string & cameraName, bool initRectificationMaps = true);
|
||||
bool save(const std::string & directory) const;
|
||||
std::vector<unsigned char> serialize() const;
|
||||
unsigned int deserialize(const std::vector<unsigned char>& data);
|
||||
|
||||
@@ -94,7 +94,9 @@ public:
|
||||
// backward compatibility
|
||||
void setImageSize(const cv::Size & size) {left_.setImageSize(size); right_.setImageSize(size);}
|
||||
|
||||
bool load(const std::string & directory, const std::string & cameraName, bool ignoreStereoTransform = true);
|
||||
// Set initRectificationMaps=false to skip building the (potentially large) left/right
|
||||
// rectification maps when rectification won't be used (saves time and memory).
|
||||
bool load(const std::string & directory, const std::string & cameraName, bool ignoreStereoTransform = true, bool initRectificationMaps = true);
|
||||
bool save(const std::string & directory, bool ignoreStereoTransform = true) const;
|
||||
bool saveStereoTransform(const std::string & directory) const;
|
||||
std::vector<unsigned char> serialize() const;
|
||||
|
||||
@@ -76,6 +76,28 @@ public:
|
||||
{
|
||||
_hasConfigForEachFrame = value;
|
||||
}
|
||||
bool isConfigForEachFrame() const {return _hasConfigForEachFrame;}
|
||||
|
||||
// Enable multi-camera mode. Each image in the folder is expected to be the
|
||||
// horizontal concatenation of N sub-camera images of a rig, sharing the same
|
||||
// base name (timestamp or node id), e.g. "1780687370.031791.jpg" or "1.jpg".
|
||||
// One calibration file per sub-camera must exist in the calibrationFolder
|
||||
// passed to init(), named "<prefix>_<index>.yaml" with index starting at 0
|
||||
// (e.g. "1_0.yaml", "1_1.yaml", ...). The number of cameras is auto-detected.
|
||||
// Each sub-image width is taken from the corresponding model's calibrated image
|
||||
// size; if a model has no size, a uniform split (stackedWidth / N) is assumed.
|
||||
// If setConfigForEachFrame() is enabled, one calibration set is loaded per frame
|
||||
// using each image's base name as prefix. Otherwise a single calibration set is
|
||||
// loaded and reused for all frames, using the cameraName passed to init() as
|
||||
// prefix (it may differ from any image name), falling back to the first image's
|
||||
// base name when cameraName is empty.
|
||||
// Note that it is not recommended to use setConfigForEachFrame() if images have
|
||||
// to be rectified, a rectification matrix would need to be re-initilaized for
|
||||
// each frame.
|
||||
void setMultiCameraCalibration(bool enabled)
|
||||
{
|
||||
_multiCameraCalib = enabled;
|
||||
}
|
||||
|
||||
void setScanPath(
|
||||
const std::string & dir,
|
||||
@@ -121,6 +143,22 @@ public:
|
||||
protected:
|
||||
virtual SensorData captureImage(SensorCaptureInfo * info = 0);
|
||||
|
||||
// File name (with extension, no directory) of the image returned by the last
|
||||
// captureImage() call. Used by subclasses (e.g. stereo) to key per-frame
|
||||
// calibration loaded on demand. Empty if no image was read.
|
||||
const std::string & lastImageFileName() const {return _lastImageFileName;}
|
||||
|
||||
// Calibration folder passed to init(), kept to load per-frame calibration on
|
||||
// demand. Shared with subclasses (e.g. stereo) that load calibration themselves.
|
||||
std::string _calibrationFolder;
|
||||
|
||||
// Multi-camera mode state, shared with subclasses (e.g. stereo) that layer their
|
||||
// own multi-camera handling over the base reader. Such subclasses temporarily set
|
||||
// _multiCameraCalib to false while delegating to base init()/captureImage() so the
|
||||
// base returns the raw stacked image instead of doing its own split, then restore it.
|
||||
bool _multiCameraCalib;
|
||||
int _multiCameraCount; // number of sub-cameras detected in multi-camera mode
|
||||
|
||||
private:
|
||||
bool readPoses(
|
||||
std::list<Transform> & outputPoses,
|
||||
@@ -129,6 +167,16 @@ private:
|
||||
int format,
|
||||
double maxTimeDiff) const;
|
||||
|
||||
// Load the sub-camera models of a multi-camera rig from calibration files named
|
||||
// "<baseName>_<index>.yaml" (index 0.._multiCameraCount-1) in _calibrationFolder.
|
||||
// Returns an empty vector (and logs an error) if any model is missing or invalid.
|
||||
std::vector<CameraModel> loadMultiCameraModels(const std::string & baseName) const;
|
||||
|
||||
// Load the single-camera model for one frame from a per-frame config file (RTAB-Map
|
||||
// calibration or 3DScannerApp format). Returns an invalid model (and logs an error)
|
||||
// on failure.
|
||||
CameraModel loadConfigModel(const std::string & filePath);
|
||||
|
||||
private:
|
||||
std::string _path;
|
||||
int _startAt;
|
||||
@@ -144,6 +192,7 @@ private:
|
||||
int _framesPublished;
|
||||
UDirectory * _dir;
|
||||
std::string _lastFileName;
|
||||
std::string _lastImageFileName; // file name of the image returned by the last captureImage()
|
||||
|
||||
int _countScan;
|
||||
UDirectory * _scanDir;
|
||||
@@ -173,7 +222,9 @@ private:
|
||||
std::list<cv::Mat> covariances_;
|
||||
std::list<Transform> groundTruth_;
|
||||
CameraModel _model;
|
||||
std::list<CameraModel> _models;
|
||||
std::list<std::string> _modelFileNames; // per-frame single-camera config file paths (config-for-each-frame)
|
||||
bool _configLocalTransformWarned; // warn only once when a per-frame config has no local_transform
|
||||
std::vector<CameraModel> _multiModels; // sub-camera models, shared by all frames (multi-camera mode); empty when loaded per-frame
|
||||
|
||||
UTimer _captureTimer;
|
||||
double _captureDelay;
|
||||
|
||||
@@ -58,6 +58,24 @@ public:
|
||||
|
||||
void setRightGrayScale(bool enabled = true) {rightGrayScale_ = enabled;}
|
||||
|
||||
// Enable multi-camera stereo mode. Each left/right image in the folders is
|
||||
// expected to be the horizontal concatenation of N sub-camera images of a
|
||||
// stereo rig, all sharing the same base name (timestamp or node id), e.g.
|
||||
// "1779318290.502227.jpg". Two calibration files per sub-camera must exist in
|
||||
// the calibrationFolder passed to init(), named "<prefix>_<index>_left.yaml"
|
||||
// and "<prefix>_<index>_right.yaml" with index starting at 0. The number of
|
||||
// cameras is auto-detected. Sub-images are split using a uniform width
|
||||
// (stackedWidth / N).
|
||||
// If setConfigForEachFrame() is enabled, one calibration set is loaded per frame
|
||||
// using each image's base name as prefix. Otherwise a single calibration set is
|
||||
// loaded and reused for all frames, using the cameraName passed to init() as
|
||||
// prefix (it may differ from any image name), falling back to the first image's
|
||||
// base name when cameraName is empty.
|
||||
// Note that it is not recommended to use setConfigForEachFrame() if images have
|
||||
// to be rectified, a rectification matrix would need to be re-initilaized for each
|
||||
// frame.
|
||||
void setMultiCameraCalibration(bool enabled) {CameraImages::setMultiCameraCalibration(enabled);}
|
||||
|
||||
virtual bool init(const std::string & calibrationFolder = ".", const std::string & cameraName = "");
|
||||
virtual bool isCalibrated() const;
|
||||
virtual std::string getSerial() const;
|
||||
@@ -68,10 +86,17 @@ public:
|
||||
protected:
|
||||
virtual SensorData captureImage(SensorCaptureInfo * info = 0);
|
||||
|
||||
private:
|
||||
// Load the sub-camera stereo models of a frame from calibration files named
|
||||
// "<baseName>_<index>_{left,right}.yaml" (index 0.._multiCameraCount-1) in
|
||||
// _calibrationFolder. Returns an empty vector (and logs an error) on failure.
|
||||
std::vector<StereoCameraModel> loadStereoCameraModels(const std::string & baseName, bool rectify) const;
|
||||
|
||||
private:
|
||||
CameraImages * camera2_;
|
||||
StereoCameraModel stereoModel_;
|
||||
bool rightGrayScale_;
|
||||
std::vector<StereoCameraModel> multiStereoModels_; // sub-camera stereo models shared by all frames (multi-camera mode); empty when loaded per-frame
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@ void CameraModel::setImageSize(const cv::Size & size)
|
||||
}
|
||||
}
|
||||
|
||||
bool CameraModel::load(const std::string & filePath)
|
||||
bool CameraModel::load(const std::string & filePath, bool initRectificationMaps)
|
||||
{
|
||||
K_ = cv::Mat();
|
||||
D_ = cv::Mat();
|
||||
@@ -377,7 +377,7 @@ bool CameraModel::load(const std::string & filePath)
|
||||
|
||||
fs.release();
|
||||
|
||||
if(isValidForRectification())
|
||||
if(initRectificationMaps && isValidForRectification())
|
||||
{
|
||||
initRectificationMap();
|
||||
}
|
||||
@@ -396,9 +396,9 @@ bool CameraModel::load(const std::string & filePath)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CameraModel::load(const std::string & directory, const std::string & cameraName)
|
||||
bool CameraModel::load(const std::string & directory, const std::string & cameraName, bool initRectificationMaps)
|
||||
{
|
||||
return load(directory+"/"+cameraName+".yaml");
|
||||
return load(directory+"/"+cameraName+".yaml", initRectificationMaps);
|
||||
}
|
||||
|
||||
bool CameraModel::save(const std::string & directory) const
|
||||
|
||||
@@ -220,11 +220,11 @@ void StereoCameraModel::updateStereoRectification()
|
||||
right_ = CameraModel(right_.name(), right_.imageSize(), right_.K_raw(), right_.D_raw(), R2, P2, right_.localTransform());
|
||||
}
|
||||
|
||||
bool StereoCameraModel::load(const std::string & directory, const std::string & cameraName, bool ignoreStereoTransform)
|
||||
bool StereoCameraModel::load(const std::string & directory, const std::string & cameraName, bool ignoreStereoTransform, bool initRectificationMaps)
|
||||
{
|
||||
name_ = cameraName;
|
||||
bool leftLoaded = left_.load(directory, cameraName+"_"+getLeftSuffix());
|
||||
bool rightLoaded = right_.load(directory, cameraName+"_"+getRightSuffix());
|
||||
bool leftLoaded = left_.load(directory, cameraName+"_"+getLeftSuffix(), initRectificationMaps);
|
||||
bool rightLoaded = right_.load(directory, cameraName+"_"+getRightSuffix(), initRectificationMaps);
|
||||
if(leftLoaded && rightLoaded)
|
||||
{
|
||||
if(ignoreStereoTransform)
|
||||
|
||||
+366
-122
@@ -41,6 +41,8 @@ namespace rtabmap
|
||||
{
|
||||
|
||||
CameraImages::CameraImages() :
|
||||
_multiCameraCalib(false),
|
||||
_multiCameraCount(0),
|
||||
_startAt(0),
|
||||
_maxFrames(0),
|
||||
_refreshDir(false),
|
||||
@@ -71,6 +73,8 @@ CameraImages::CameraImages(const std::string & path,
|
||||
float imageRate,
|
||||
const Transform & localTransform) :
|
||||
Camera(imageRate, localTransform),
|
||||
_multiCameraCalib(false),
|
||||
_multiCameraCount(0),
|
||||
_path(path),
|
||||
_startAt(0),
|
||||
_maxFrames(0),
|
||||
@@ -111,14 +115,22 @@ CameraImages::~CameraImages()
|
||||
bool CameraImages::init(const std::string & calibrationFolder, const std::string & cameraName)
|
||||
{
|
||||
_lastFileName.clear();
|
||||
_lastImageFileName.clear();
|
||||
_lastScanFileName.clear();
|
||||
_count = 0;
|
||||
_countScan = 0;
|
||||
_captureDelay = 0.0;
|
||||
_framesPublished=0;
|
||||
_model = cameraModel();
|
||||
_models.clear();
|
||||
_modelFileNames.clear();
|
||||
_configLocalTransformWarned = false;
|
||||
_multiModels.clear();
|
||||
_calibrationFolder.clear();
|
||||
_multiCameraCount = 0;
|
||||
covariances_.clear();
|
||||
_stamps.clear();
|
||||
odometry_.clear();
|
||||
groundTruth_.clear();
|
||||
|
||||
UDEBUG("");
|
||||
if(_dir)
|
||||
@@ -199,107 +211,117 @@ bool CameraImages::init(const std::string & calibrationFolder, const std::string
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = _dir|| _scanDir;
|
||||
|
||||
if(_dir)
|
||||
{
|
||||
// look for calibration files
|
||||
UINFO("calibration folder=%s name=%s", calibrationFolder.c_str(), cameraName.c_str());
|
||||
if(!calibrationFolder.empty() && !cameraName.empty())
|
||||
if(_multiCameraCalib)
|
||||
{
|
||||
if(!_model.load(calibrationFolder, cameraName))
|
||||
// Multi-camera mode: each image is a horizontal stack of N sub-camera
|
||||
// images. Load one CameraModel per sub-camera from calibration files
|
||||
// named "<prefix>_<index>.yaml" (index starting at 0). In config-for-each-frame
|
||||
// mode the prefix is each image's base name (one calibration set per frame);
|
||||
// otherwise a single calibration set is shared by all frames, using cameraName
|
||||
// as the prefix when provided (it may differ from any image name), falling back
|
||||
// to the first image's base name.
|
||||
if(calibrationFolder.empty())
|
||||
{
|
||||
UWARN("Missing calibration files for camera \"%s\" in \"%s\" folder, you should calibrate the camera!",
|
||||
cameraName.c_str(), calibrationFolder.c_str());
|
||||
UERROR("Multi-camera calibration is enabled but no calibration folder was provided.");
|
||||
return false;
|
||||
}
|
||||
const std::list<std::string> & imageFiles = _dir->getFileNames();
|
||||
std::string firstBase = imageFiles.front().substr(0, imageFiles.front().find_last_of('.'));
|
||||
|
||||
// In config-for-each-frame mode the prefix is each image's base name. Otherwise
|
||||
// the shared prefix is cameraName when provided. cameraName may point to a specific
|
||||
// sub-camera calibration "<rig>_<index>" (e.g. when a single calibration file is
|
||||
// selected); in that case strip the trailing "_<index>" to recover the rig prefix
|
||||
// shared by all sub-cameras. Fall back to the first image's base name if empty.
|
||||
std::string sharedBase = cameraName;
|
||||
if(!sharedBase.empty() && !UFile::exists(calibrationFolder + "/" + sharedBase + "_0.yaml"))
|
||||
{
|
||||
std::size_t us = sharedBase.find_last_of('_');
|
||||
if(us != std::string::npos && us+1 < sharedBase.size() &&
|
||||
sharedBase.find_first_not_of("0123456789", us+1) == std::string::npos &&
|
||||
UFile::exists(calibrationFolder + "/" + sharedBase.substr(0, us) + "_0.yaml"))
|
||||
{
|
||||
sharedBase = sharedBase.substr(0, us);
|
||||
}
|
||||
}
|
||||
if(sharedBase.empty())
|
||||
{
|
||||
sharedBase = firstBase;
|
||||
}
|
||||
|
||||
// auto-detect the number of cameras
|
||||
int numCameras = 0;
|
||||
std::string detectBase = _hasConfigForEachFrame ? firstBase : sharedBase;
|
||||
while(UFile::exists(calibrationFolder + "/" + detectBase + "_" + uNumber2Str(numCameras) + ".yaml"))
|
||||
{
|
||||
++numCameras;
|
||||
}
|
||||
|
||||
if(numCameras == 0)
|
||||
{
|
||||
UERROR("Multi-camera calibration is enabled but no calibration file matching "
|
||||
"\"%s/%s_<index>.yaml\" was found.",
|
||||
calibrationFolder.c_str(), detectBase.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
UINFO("Multi-camera mode: %d sub-cameras detected from \"%s\" (%s).", numCameras, calibrationFolder.c_str(),
|
||||
_hasConfigForEachFrame?"one calibration per frame, loaded on demand":
|
||||
uFormat("calibration \"%s_<index>\" reused for all frames", sharedBase.c_str()).c_str());
|
||||
_calibrationFolder = calibrationFolder;
|
||||
_multiCameraCount = numCameras;
|
||||
if(_hasConfigForEachFrame)
|
||||
{
|
||||
// Validate the first frame now; the per-frame sub-camera models are loaded
|
||||
// on demand in captureImage() (keyed by each image's base name) so we don't
|
||||
// keep every frame's models - and their rectification maps - in memory.
|
||||
if(loadMultiCameraModels(firstBase).empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UINFO("Camera parameters: fx=%f fy=%f cx=%f cy=%f",
|
||||
_model.fx(),
|
||||
_model.fy(),
|
||||
_model.cx(),
|
||||
_model.cy());
|
||||
|
||||
cv::FileStorage fs(calibrationFolder+"/"+cameraName+".yaml", 0);
|
||||
cv::FileNode poseNode = fs["local_transform"];
|
||||
if(!poseNode.isNone())
|
||||
// A single calibration set is shared by all frames: load it once.
|
||||
std::vector<CameraModel> models = loadMultiCameraModels(sharedBase);
|
||||
if(models.empty())
|
||||
{
|
||||
UWARN("Using local transform from calibration file (%s) instead of the parameter one (%s).",
|
||||
_model.localTransform().prettyPrint().c_str(),
|
||||
this->getLocalTransform().prettyPrint().c_str());
|
||||
this->setLocalTransform(_model.localTransform());
|
||||
return false;
|
||||
}
|
||||
_multiModels = models;
|
||||
}
|
||||
}
|
||||
_model.setName(cameraName);
|
||||
|
||||
_model.setLocalTransform(this->getLocalTransform());
|
||||
if(_rectifyImages && !_model.isValidForRectification())
|
||||
{
|
||||
UERROR("Parameter \"rectifyImages\" is set, but no camera model is loaded or valid.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool success = _dir|| _scanDir;
|
||||
_stamps.clear();
|
||||
odometry_.clear();
|
||||
groundTruth_.clear();
|
||||
if(success)
|
||||
{
|
||||
if(_dir && _hasConfigForEachFrame)
|
||||
else if(_hasConfigForEachFrame)
|
||||
{
|
||||
// Per-frame calibration: one config file per image, taken from the
|
||||
// calibration folder (same convention as the single/multi-camera cases).
|
||||
// If the config files are in the same folder than the images, just set the
|
||||
// calibration folder to the images folder.
|
||||
#if CV_MAJOR_VERSION < 3 || (CV_MAJOR_VERSION == 3 && CV_MAJOR_VERSION < 2)
|
||||
UDirectory dirJson(_path, "yaml xml");
|
||||
UDirectory dirJson(calibrationFolder, "yaml xml");
|
||||
#else
|
||||
UDirectory dirJson(_path, "yaml xml json");
|
||||
UDirectory dirJson(calibrationFolder, "yaml xml json");
|
||||
#endif
|
||||
if(dirJson.getFileNames().size() == _dir->getFileNames().size())
|
||||
{
|
||||
bool modelsWarned = false;
|
||||
bool localTWarned = false;
|
||||
// The per-frame models are loaded on demand in captureImage() (see
|
||||
// loadConfigModel) so we don't keep every frame's model - and its
|
||||
// rectification map - in memory. Only the stamps and poses of the
|
||||
// 3DScannerApp format are read eagerly here (needed for synchronization).
|
||||
for(std::list<std::string>::const_iterator iter=dirJson.getFileNames().begin(); iter!=dirJson.getFileNames().end() && success; ++iter)
|
||||
{
|
||||
std::string filePath = _path+"/"+*iter;
|
||||
std::string filePath = calibrationFolder+"/"+*iter;
|
||||
cv::FileStorage fs(filePath, 0);
|
||||
cv::FileNode poseNode = fs["cameraPoseARFrame"]; // Check if it is 3DScannerApp(iOS) format
|
||||
if(poseNode.isNone())
|
||||
{
|
||||
cv::FileNode n = fs["local_transform"];
|
||||
bool hasLocalTransform = !n.isNone();
|
||||
|
||||
fs.release();
|
||||
if(_model.isValidForProjection() && !modelsWarned)
|
||||
{
|
||||
UWARN("Camera model loaded for each frame is overridden by "
|
||||
"general calibration file provided. Remove general calibration "
|
||||
"file to use camera model of each frame. This warning will "
|
||||
"be shown only one time.");
|
||||
modelsWarned = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
CameraModel model;
|
||||
model.load(filePath);
|
||||
|
||||
if(!hasLocalTransform)
|
||||
{
|
||||
if(!localTWarned)
|
||||
{
|
||||
UWARN("Loaded calibration file doesn't have local_transform field, "
|
||||
"the global local_transform parameter is used by default (%s).",
|
||||
this->getLocalTransform().prettyPrint().c_str());
|
||||
localTWarned = true;
|
||||
}
|
||||
model.setLocalTransform(this->getLocalTransform());
|
||||
}
|
||||
|
||||
_models.push_back(model);
|
||||
}
|
||||
}
|
||||
else
|
||||
if(!poseNode.isNone())
|
||||
{
|
||||
cv::FileNode timeNode = fs["time"];
|
||||
cv::FileNode intrinsicsNode = fs["intrinsics"];
|
||||
if(poseNode.isNone() || poseNode.size() != 16)
|
||||
if(poseNode.size() != 16)
|
||||
{
|
||||
UERROR("Failed reading \"cameraPoseARFrame\" parameter, it should have 16 values (file=%s)", filePath.c_str());
|
||||
success = false;
|
||||
@@ -320,23 +342,6 @@ bool CameraImages::init(const std::string & calibrationFolder, const std::string
|
||||
else
|
||||
{
|
||||
_stamps.push_back((double)timeNode);
|
||||
if(_model.isValidForProjection() && !modelsWarned)
|
||||
{
|
||||
UWARN("Camera model loaded for each frame is overridden by "
|
||||
"general calibration file provided. Remove general calibration "
|
||||
"file to use camera model of each frame. This warning will "
|
||||
"be shown only one time.");
|
||||
modelsWarned = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_models.push_back(CameraModel(
|
||||
(double)intrinsicsNode[0], //fx
|
||||
(double)intrinsicsNode[4], //fy
|
||||
(double)intrinsicsNode[2], //cx
|
||||
(double)intrinsicsNode[5], //cy
|
||||
CameraModel::opticalRotation()));
|
||||
}
|
||||
// we need to rotate from opengl world to rtabmap world
|
||||
Transform pose(
|
||||
(float)poseNode[0], (float)poseNode[1], (float)poseNode[2], (float)poseNode[3],
|
||||
@@ -346,12 +351,18 @@ bool CameraImages::init(const std::string & calibrationFolder, const std::string
|
||||
odometry_.push_back(pose);
|
||||
}
|
||||
}
|
||||
_modelFileNames.push_back(filePath);
|
||||
}
|
||||
// Validate the first frame's calibration now to fail early.
|
||||
if(success && !_modelFileNames.empty() && !loadConfigModel(_modelFileNames.front()).isValidForProjection())
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
if(!success)
|
||||
{
|
||||
odometry_.clear();
|
||||
_stamps.clear();
|
||||
_models.clear();
|
||||
_modelFileNames.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -365,12 +376,60 @@ bool CameraImages::init(const std::string & calibrationFolder, const std::string
|
||||
"of images (%d) in this directory \"%s\".%s",
|
||||
(int)dirJson.getFileNames().size(),
|
||||
(int)_dir->getFileNames().size(),
|
||||
_path.c_str(),
|
||||
calibrationFolder.c_str(),
|
||||
opencv32warn.c_str());
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Config-for-each-frame is disabled: load a single general calibration
|
||||
// model (the per-frame branch above handles the config-for-each-frame case).
|
||||
// look for calibration files
|
||||
UINFO("calibration folder=%s name=%s", calibrationFolder.c_str(), cameraName.c_str());
|
||||
if(!calibrationFolder.empty() && !cameraName.empty())
|
||||
{
|
||||
if(!_model.load(calibrationFolder, cameraName, _rectifyImages))
|
||||
{
|
||||
UWARN("Missing calibration files for camera \"%s\" in \"%s\" folder, you should calibrate the camera!",
|
||||
cameraName.c_str(), calibrationFolder.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
UINFO("Camera parameters: fx=%f fy=%f cx=%f cy=%f",
|
||||
_model.fx(),
|
||||
_model.fy(),
|
||||
_model.cx(),
|
||||
_model.cy());
|
||||
|
||||
cv::FileStorage fs(calibrationFolder+"/"+cameraName+".yaml", 0);
|
||||
cv::FileNode poseNode = fs["local_transform"];
|
||||
if(!poseNode.isNone())
|
||||
{
|
||||
UWARN("Using local transform from calibration file (%s) instead of the parameter one (%s).",
|
||||
_model.localTransform().prettyPrint().c_str(),
|
||||
this->getLocalTransform().prettyPrint().c_str());
|
||||
this->setLocalTransform(_model.localTransform());
|
||||
}
|
||||
}
|
||||
|
||||
// Only validate rectification when a general calibration was requested.
|
||||
// Without it (e.g. images that are already rectified) the single model is
|
||||
// expected to be empty and must not fail init here.
|
||||
if(_rectifyImages && !_model.isValidForRectification())
|
||||
{
|
||||
UERROR("Parameter \"rectifyImages\" is set, but no camera model is loaded or valid.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_model.setName(cameraName);
|
||||
|
||||
_model.setLocalTransform(this->getLocalTransform());
|
||||
}
|
||||
}
|
||||
|
||||
if(success)
|
||||
{
|
||||
if(_stamps.empty())
|
||||
{
|
||||
if(_filenamesAreTimestamps)
|
||||
@@ -653,10 +712,97 @@ bool CameraImages::readPoses(
|
||||
|
||||
bool CameraImages::isCalibrated() const
|
||||
{
|
||||
return (_dir && (_model.isValidForProjection() || (_models.size() && _models.front().isValidForProjection()))) ||
|
||||
return (_dir && (_model.isValidForProjection() ||
|
||||
_modelFileNames.size() || // per-frame single-camera models loaded on demand (validated in init)
|
||||
(!_multiModels.empty() && _multiModels.front().isValidForProjection()) ||
|
||||
(_multiCameraCalib && _hasConfigForEachFrame && _multiCameraCount > 0))) || // per-frame multi-camera models loaded on demand
|
||||
_scanDir;
|
||||
}
|
||||
|
||||
std::vector<CameraModel> CameraImages::loadMultiCameraModels(const std::string & baseName) const
|
||||
{
|
||||
std::vector<CameraModel> models(_multiCameraCount);
|
||||
for(int i=0; i<_multiCameraCount; ++i)
|
||||
{
|
||||
std::string name = baseName + "_" + uNumber2Str(i);
|
||||
if(!models[i].load(_calibrationFolder, name, _rectifyImages) || !models[i].isValidForProjection())
|
||||
{
|
||||
UERROR("Failed to load a valid calibration \"%s/%s.yaml\" for multi-camera frame base \"%s\".",
|
||||
_calibrationFolder.c_str(), name.c_str(), baseName.c_str());
|
||||
return std::vector<CameraModel>();
|
||||
}
|
||||
if(models[i].localTransform().isNull())
|
||||
{
|
||||
// In multi-camera mode the rig extrinsics are required: each
|
||||
// sub-camera calibration must provide a "local_transform".
|
||||
UERROR("Calibration \"%s/%s.yaml\" has no \"local_transform\"; it is "
|
||||
"required in multi-camera mode (rig extrinsics).",
|
||||
_calibrationFolder.c_str(), name.c_str());
|
||||
return std::vector<CameraModel>();
|
||||
}
|
||||
if(_rectifyImages && !models[i].isValidForRectification())
|
||||
{
|
||||
UERROR("Parameter \"rectifyImages\" is set, but calibration \"%s/%s.yaml\" is not valid for rectification.",
|
||||
_calibrationFolder.c_str(), name.c_str());
|
||||
return std::vector<CameraModel>();
|
||||
}
|
||||
}
|
||||
return models;
|
||||
}
|
||||
|
||||
CameraModel CameraImages::loadConfigModel(const std::string & filePath)
|
||||
{
|
||||
cv::FileStorage fs(filePath, 0);
|
||||
cv::FileNode poseNode = fs["cameraPoseARFrame"]; // Check if it is 3DScannerApp(iOS) format
|
||||
CameraModel model;
|
||||
if(poseNode.isNone())
|
||||
{
|
||||
// RTAB-Map calibration format
|
||||
bool hasLocalTransform = !fs["local_transform"].isNone();
|
||||
fs.release();
|
||||
model.load(filePath, _rectifyImages);
|
||||
if(!hasLocalTransform)
|
||||
{
|
||||
if(!_configLocalTransformWarned)
|
||||
{
|
||||
UWARN("Loaded calibration file doesn't have local_transform field, "
|
||||
"the global local_transform parameter is used by default (%s).",
|
||||
this->getLocalTransform().prettyPrint().c_str());
|
||||
_configLocalTransformWarned = true;
|
||||
}
|
||||
model.setLocalTransform(this->getLocalTransform());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 3DScannerApp(iOS) format
|
||||
cv::FileNode intrinsicsNode = fs["intrinsics"];
|
||||
if(intrinsicsNode.isNone() || intrinsicsNode.size()!=9)
|
||||
{
|
||||
UERROR("Failed reading \"intrinsics\" parameter (file=%s)", filePath.c_str());
|
||||
return CameraModel();
|
||||
}
|
||||
model = CameraModel(
|
||||
(double)intrinsicsNode[0], //fx
|
||||
(double)intrinsicsNode[4], //fy
|
||||
(double)intrinsicsNode[2], //cx
|
||||
(double)intrinsicsNode[5], //cy
|
||||
CameraModel::opticalRotation());
|
||||
}
|
||||
if(!model.isValidForProjection())
|
||||
{
|
||||
UERROR("Camera model loaded from \"%s\" is not valid for projection.", filePath.c_str());
|
||||
return CameraModel();
|
||||
}
|
||||
if(_rectifyImages && !model.isValidForRectification())
|
||||
{
|
||||
UERROR("Parameter \"rectifyImages\" is set, but camera model loaded from \"%s\" is not valid for rectification.",
|
||||
filePath.c_str());
|
||||
return CameraModel();
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
std::string CameraImages::getSerial() const
|
||||
{
|
||||
return _model.name();
|
||||
@@ -728,7 +874,13 @@ SensorData CameraImages::captureImage(SensorCaptureInfo * info)
|
||||
cv::Mat covariance;
|
||||
Transform groundTruthPose;
|
||||
cv::Mat depthFromScan;
|
||||
CameraModel model = _model;
|
||||
std::vector<CameraModel> models; // one entry per camera (single entry in single-camera mode)
|
||||
if(!_multiCameraCalib)
|
||||
{
|
||||
// Single-camera mode keeps exactly one model in the vector; in multi-camera
|
||||
// mode the per-frame vector is taken from _multiModels below.
|
||||
models.push_back(_model);
|
||||
}
|
||||
UDEBUG("");
|
||||
if(_dir || _scanDir)
|
||||
{
|
||||
@@ -784,7 +936,8 @@ SensorData CameraImages::captureImage(SensorCaptureInfo * info)
|
||||
{
|
||||
UERROR("groundTruth cannot be used when startAt < 0");
|
||||
}
|
||||
if(_models.size() && !model.isValidForProjection())
|
||||
if(_modelFileNames.size() || !_multiModels.empty() ||
|
||||
(_multiCameraCalib && _hasConfigForEachFrame))
|
||||
{
|
||||
UERROR("models cannot be used when startAt < 0");
|
||||
}
|
||||
@@ -826,12 +979,24 @@ SensorData CameraImages::captureImage(SensorCaptureInfo * info)
|
||||
groundTruthPose = groundTruth_.front();
|
||||
groundTruth_.pop_front();
|
||||
}
|
||||
if(_models.size() && !model.isValidForProjection())
|
||||
if(_modelFileNames.size())
|
||||
{
|
||||
UASSERT_MSG(stampsSize==0 || stampsSize == _models.size(),
|
||||
uFormat("Stamps=%ld models=%ld", _stamps.size(), _models.size()).c_str());
|
||||
model = _models.front();
|
||||
_models.pop_front();
|
||||
UASSERT_MSG(stampsSize==0 || stampsSize == _modelFileNames.size(),
|
||||
uFormat("Stamps=%ld models=%ld", _stamps.size(), _modelFileNames.size()).c_str());
|
||||
// per-frame calibration loaded on demand (not kept in memory)
|
||||
models.back() = loadConfigModel(_modelFileNames.front());
|
||||
_modelFileNames.pop_front();
|
||||
}
|
||||
if(_multiCameraCalib && _hasConfigForEachFrame)
|
||||
{
|
||||
// Per-frame calibration loaded on demand (not kept in memory),
|
||||
// keyed by the current image's base name.
|
||||
models = loadMultiCameraModels(imageFileName.substr(0, imageFileName.find_last_of('.')));
|
||||
}
|
||||
else if(!_multiModels.empty())
|
||||
{
|
||||
// calibration is shared across all frames
|
||||
models = _multiModels;
|
||||
}
|
||||
|
||||
while(_count++ < _startAt)
|
||||
@@ -875,14 +1040,27 @@ SensorData CameraImages::captureImage(SensorCaptureInfo * info)
|
||||
groundTruthPose = groundTruth_.front();
|
||||
groundTruth_.pop_front();
|
||||
}
|
||||
if(_models.size() && !model.isValidForProjection())
|
||||
if(_modelFileNames.size())
|
||||
{
|
||||
UASSERT_MSG(stampsSize==0 || stampsSize == _models.size(),
|
||||
uFormat("Stamps=%ld models=%ld", _stamps.size(), _models.size()).c_str());
|
||||
model = _models.front();
|
||||
_models.pop_front();
|
||||
UASSERT_MSG(stampsSize==0 || stampsSize == _modelFileNames.size(),
|
||||
uFormat("Stamps=%ld models=%ld", _stamps.size(), _modelFileNames.size()).c_str());
|
||||
// per-frame calibration loaded on demand (not kept in memory)
|
||||
models.back() = loadConfigModel(_modelFileNames.front());
|
||||
_modelFileNames.pop_front();
|
||||
}
|
||||
if(_multiCameraCalib && _hasConfigForEachFrame)
|
||||
{
|
||||
// Per-frame calibration loaded on demand (not kept in memory),
|
||||
// keyed by the current image's base name.
|
||||
models = loadMultiCameraModels(imageFileName.substr(0, imageFileName.find_last_of('.')));
|
||||
}
|
||||
else if(!_multiModels.empty())
|
||||
{
|
||||
// calibration is shared across all frames
|
||||
models = _multiModels;
|
||||
}
|
||||
}
|
||||
_lastImageFileName = imageFileName; // expose the current frame name to subclasses
|
||||
}
|
||||
}
|
||||
|
||||
@@ -951,9 +1129,67 @@ SensorData CameraImages::captureImage(SensorCaptureInfo * info)
|
||||
}
|
||||
}
|
||||
|
||||
if(!img.empty() && model.isValidForRectification() && _rectifyImages)
|
||||
if(!img.empty() && !models.empty())
|
||||
{
|
||||
img = model.rectifyImage(img);
|
||||
// The image is a horizontal stack of N sub-images (N==1 in
|
||||
// single-camera mode). Each sub-camera's width comes from its
|
||||
// calibrated image size; if a model has no size (==0), assume a
|
||||
// uniform split (stackedWidth / N) and set it. If requested, each
|
||||
// sub-image is rectified independently with its model and written
|
||||
// into a new stacked image of the same layout.
|
||||
cv::Mat rectified;
|
||||
int offset = 0;
|
||||
bool rectifyAborted = false;
|
||||
for(size_t i=0; i<models.size(); ++i)
|
||||
{
|
||||
if(models[i].imageWidth()==0 || models[i].imageHeight()==0)
|
||||
{
|
||||
models[i].setImageSize(cv::Size(img.cols/(int)models.size(), img.rows));
|
||||
}
|
||||
int subWidth = models[i].imageWidth();
|
||||
if(offset+subWidth > img.cols)
|
||||
{
|
||||
UERROR("Multi-camera: sum of sub-image widths (%d) exceeds the stacked "
|
||||
"image width (%d) at camera %d.", offset+subWidth, img.cols, (int)i);
|
||||
rectified = cv::Mat();
|
||||
rectifyAborted = true;
|
||||
break;
|
||||
}
|
||||
if(_rectifyImages)
|
||||
{
|
||||
if(!models[i].isValidForRectification())
|
||||
{
|
||||
// In multi-camera mode a valid calibration is expected for each
|
||||
// sub-camera. In single-camera mode this is a passthrough (e.g. the
|
||||
// base reader of a stereo/RGBD subclass that rectifies itself, or
|
||||
// images that are already rectified): skip silently to keep the
|
||||
// backward-compatible behavior.
|
||||
if(_multiCameraCalib)
|
||||
{
|
||||
UERROR("Parameter \"rectifyImages\" is set, but camera model %d is not valid for rectification.", (int)i);
|
||||
}
|
||||
rectified = cv::Mat();
|
||||
rectifyAborted = true;
|
||||
break;
|
||||
}
|
||||
if(rectified.empty())
|
||||
{
|
||||
rectified = cv::Mat::zeros(img.rows, img.cols, img.type());
|
||||
}
|
||||
cv::Rect roi(offset, 0, subWidth, img.rows);
|
||||
models[i].rectifyImage(img(roi)).copyTo(rectified(roi));
|
||||
}
|
||||
offset += subWidth;
|
||||
}
|
||||
if(!rectifyAborted && offset != img.cols)
|
||||
{
|
||||
UWARN("Multi-camera: sum of sub-image widths (%d) does not match the "
|
||||
"stacked image width (%d).", offset, img.cols);
|
||||
}
|
||||
if(!rectified.empty())
|
||||
{
|
||||
img = rectified;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -966,7 +1202,7 @@ SensorData CameraImages::captureImage(SensorCaptureInfo * info)
|
||||
if(_depthFromScan && !img.empty())
|
||||
{
|
||||
UDEBUG("Computing depth from scan...");
|
||||
if(!model.isValidForProjection())
|
||||
if(models.empty() || !models.front().isValidForProjection())
|
||||
{
|
||||
UWARN("Depth from laser scan: Camera model should be valid.");
|
||||
}
|
||||
@@ -977,10 +1213,23 @@ SensorData CameraImages::captureImage(SensorCaptureInfo * info)
|
||||
else
|
||||
{
|
||||
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = util3d::laserScanToPointCloud(scan, scan.localTransform());
|
||||
depthFromScan = util3d::projectCloudToCamera(img.size(), model.K(), cloud, model.localTransform());
|
||||
if(_depthFromScanFillHoles!=0)
|
||||
// There is no multi-camera version of projectCloudToCamera(): project
|
||||
// the scan into each (sub-)camera and stack the registered depth images
|
||||
// horizontally, matching the RGB layout (single iteration in single-camera
|
||||
// mode). Sub-image widths come from each model's (already set) image size.
|
||||
// Holes are filled per sub-image so they don't bleed across cameras.
|
||||
depthFromScan = cv::Mat::zeros(img.rows, img.cols, CV_32FC1);
|
||||
int offset = 0;
|
||||
for(size_t i=0; i<models.size() && offset+models[i].imageWidth()<=img.cols; ++i)
|
||||
{
|
||||
util3d::fillProjectedCloudHoles(depthFromScan, _depthFromScanFillHoles>0, _depthFromScanFillHolesFromBorder);
|
||||
int subWidth = models[i].imageWidth();
|
||||
cv::Mat subDepth = util3d::projectCloudToCamera(cv::Size(subWidth, img.rows), models[i].K(), cloud, models[i].localTransform());
|
||||
if(_depthFromScanFillHoles!=0)
|
||||
{
|
||||
util3d::fillProjectedCloudHoles(subDepth, _depthFromScanFillHoles>0, _depthFromScanFillHolesFromBorder);
|
||||
}
|
||||
subDepth.copyTo(depthFromScan(cv::Rect(offset, 0, subWidth, img.rows)));
|
||||
offset += subWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -992,15 +1241,10 @@ SensorData CameraImages::captureImage(SensorCaptureInfo * info)
|
||||
UWARN("Directory is not set, camera must be initialized.");
|
||||
}
|
||||
|
||||
if(model.imageHeight() == 0 || model.imageWidth() == 0)
|
||||
{
|
||||
model.setImageSize(img.size());
|
||||
}
|
||||
|
||||
SensorData data;
|
||||
if(!img.empty() || !scan.empty())
|
||||
{
|
||||
data = SensorData(scan, _isDepth?cv::Mat():img, _isDepth?img:depthFromScan, model, this->getNextSeqID(), stamp);
|
||||
data = SensorData(scan, _isDepth?cv::Mat():img, _isDepth?img:depthFromScan, models, this->getNextSeqID(), stamp);
|
||||
data.setGroundTruth(groundTruthPose);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <rtabmap/core/camera/CameraStereoImages.h>
|
||||
#include <rtabmap/utilite/UStl.h>
|
||||
#include <rtabmap/utilite/UFile.h>
|
||||
#include <rtabmap/utilite/UConversion.h>
|
||||
#include <opencv2/imgproc/types_c.h>
|
||||
|
||||
namespace rtabmap
|
||||
@@ -87,10 +89,12 @@ bool CameraStereoImages::init(const std::string & calibrationFolder, const std::
|
||||
{
|
||||
UINFO("Calibration folder: \"%s\", name=\"%s\"", calibrationFolder.c_str(), cameraName.c_str());
|
||||
|
||||
multiStereoModels_.clear();
|
||||
|
||||
// look for calibration files
|
||||
if(!calibrationFolder.empty() && !cameraName.empty())
|
||||
if(!_multiCameraCalib && !calibrationFolder.empty() && !cameraName.empty())
|
||||
{
|
||||
if(!stereoModel_.load(calibrationFolder, cameraName, false) && !stereoModel_.isValidForProjection())
|
||||
if(!stereoModel_.load(calibrationFolder, cameraName, false, this->isImagesRectified()) && !stereoModel_.isValidForProjection())
|
||||
{
|
||||
UWARN("Missing calibration files for camera \"%s\" in \"%s\" folder, you should calibrate the camera!",
|
||||
cameraName.c_str(), calibrationFolder.c_str());
|
||||
@@ -105,16 +109,32 @@ bool CameraStereoImages::init(const std::string & calibrationFolder, const std::
|
||||
}
|
||||
}
|
||||
|
||||
stereoModel_.setLocalTransform(this->getLocalTransform());
|
||||
stereoModel_.setName(cameraName);
|
||||
if(this->isImagesRectified() && !stereoModel_.isValidForRectification())
|
||||
if(!_multiCameraCalib)
|
||||
{
|
||||
UWARN("Parameter \"rectifyImages\" is set, but no stereo model is loaded or valid for rectification. This can be ignored if input images are already rectified.");
|
||||
stereoModel_.setLocalTransform(this->getLocalTransform());
|
||||
stereoModel_.setName(cameraName);
|
||||
if(this->isImagesRectified() && !stereoModel_.isValidForRectification())
|
||||
{
|
||||
UWARN("Parameter \"rectifyImages\" is set, but no stereo model is loaded or valid for rectification. This can be ignored if input images are already rectified.");
|
||||
}
|
||||
}
|
||||
|
||||
//desactivate before init as we will do it in this class instead for convenience
|
||||
bool rectify = this->isImagesRectified();
|
||||
this->setImagesRectified(false);
|
||||
// The base reader is used here only to enumerate/read the left images; in
|
||||
// multi-camera mode this class loads the calibration and splits the images
|
||||
// itself, so prevent the base from doing its own multi-camera handling and
|
||||
// per-frame config loading (which would look for config files inside the image
|
||||
// directory). Both flags are restored below: _multiCameraCalib still drives the
|
||||
// per-frame vs. shared calibration loading in this class.
|
||||
bool configForEachFrame = this->isConfigForEachFrame();
|
||||
bool multiCameraCalib = _multiCameraCalib;
|
||||
if(multiCameraCalib)
|
||||
{
|
||||
this->setConfigForEachFrame(false);
|
||||
}
|
||||
_multiCameraCalib = false;
|
||||
|
||||
bool success = false;
|
||||
if(CameraImages::init())
|
||||
@@ -144,13 +164,141 @@ bool CameraStereoImages::init(const std::string & calibrationFolder, const std::
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
// restore the flags: _multiCameraCalib drives the per-frame vs. shared calibration loading below
|
||||
this->setConfigForEachFrame(configForEachFrame);
|
||||
_multiCameraCalib = multiCameraCalib;
|
||||
|
||||
if(success && _multiCameraCalib)
|
||||
{
|
||||
// Multi-camera stereo mode: each left/right image is a horizontal stack of N
|
||||
// sub-camera images. Load one StereoCameraModel per sub-camera from calibration
|
||||
// files named "<imageBaseName>_<index>_left.yaml" and "<imageBaseName>_<index>_right.yaml"
|
||||
// (index starting at 0).
|
||||
if(calibrationFolder.empty())
|
||||
{
|
||||
UERROR("Multi-camera calibration is enabled but no calibration folder was provided.");
|
||||
success = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::vector<std::string> imageFiles = this->filenames();
|
||||
std::string firstBase = imageFiles.front().substr(0, imageFiles.front().find_last_of('.'));
|
||||
|
||||
// In config-for-each-frame mode the prefix is each image's base name (one
|
||||
// calibration set per frame). Otherwise a single calibration set is shared by all
|
||||
// frames, using cameraName as the prefix when provided. cameraName may point to a
|
||||
// specific sub-camera calibration "<rig>_<index>" (e.g. when a single calibration
|
||||
// file is selected); in that case strip the trailing "_<index>" to recover the rig
|
||||
// prefix shared by all sub-cameras. Fall back to the first image's base name if empty.
|
||||
std::string leftSuffix = "_0_" + stereoModel_.getLeftSuffix() + ".yaml";
|
||||
std::string sharedBase = cameraName;
|
||||
if(!sharedBase.empty() && !UFile::exists(calibrationFolder + "/" + sharedBase + leftSuffix))
|
||||
{
|
||||
std::size_t us = sharedBase.find_last_of('_');
|
||||
if(us != std::string::npos && us+1 < sharedBase.size() &&
|
||||
sharedBase.find_first_not_of("0123456789", us+1) == std::string::npos &&
|
||||
UFile::exists(calibrationFolder + "/" + sharedBase.substr(0, us) + leftSuffix))
|
||||
{
|
||||
sharedBase = sharedBase.substr(0, us);
|
||||
}
|
||||
}
|
||||
if(sharedBase.empty())
|
||||
{
|
||||
sharedBase = firstBase;
|
||||
}
|
||||
|
||||
// auto-detect the number of cameras
|
||||
int numCameras = 0;
|
||||
std::string detectBase = this->isConfigForEachFrame() ? firstBase : sharedBase;
|
||||
while(UFile::exists(calibrationFolder + "/" + detectBase + "_" + uNumber2Str(numCameras) + "_" + stereoModel_.getLeftSuffix() + ".yaml"))
|
||||
{
|
||||
++numCameras;
|
||||
}
|
||||
|
||||
if(numCameras == 0)
|
||||
{
|
||||
UERROR("Multi-camera calibration is enabled but no calibration file matching "
|
||||
"\"%s/%s_<index>_%s.yaml\" was found.",
|
||||
calibrationFolder.c_str(), detectBase.c_str(), stereoModel_.getLeftSuffix().c_str());
|
||||
success = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
UINFO("Multi-camera stereo mode: %d sub-cameras detected from \"%s\" (%s).", numCameras, calibrationFolder.c_str(),
|
||||
this->isConfigForEachFrame()?"one calibration per frame, loaded on demand":
|
||||
uFormat("calibration \"%s_<index>\" reused for all frames", sharedBase.c_str()).c_str());
|
||||
_calibrationFolder = calibrationFolder;
|
||||
_multiCameraCount = numCameras;
|
||||
if(this->isConfigForEachFrame())
|
||||
{
|
||||
// Validate the first frame now; the per-frame sub-camera stereo models are
|
||||
// loaded on demand in captureImage() (keyed by each image's base name) so we
|
||||
// don't keep every frame's models - and their rectification maps - in memory.
|
||||
if(loadStereoCameraModels(firstBase, rectify).empty())
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// A single calibration set is shared by all frames: load it once.
|
||||
std::vector<StereoCameraModel> models = loadStereoCameraModels(sharedBase, rectify);
|
||||
if(models.empty())
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
multiStereoModels_ = models;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->setImagesRectified(rectify); // reset the flag
|
||||
return success;
|
||||
}
|
||||
|
||||
bool CameraStereoImages::isCalibrated() const
|
||||
{
|
||||
return stereoModel_.isValidForProjection();
|
||||
return stereoModel_.isValidForProjection() ||
|
||||
(!multiStereoModels_.empty() && multiStereoModels_.front().isValidForProjection()) ||
|
||||
(_multiCameraCalib && this->isConfigForEachFrame() && _multiCameraCount > 0); // per-frame models loaded on demand
|
||||
}
|
||||
|
||||
std::vector<StereoCameraModel> CameraStereoImages::loadStereoCameraModels(const std::string & baseName, bool rectify) const
|
||||
{
|
||||
std::vector<StereoCameraModel> models(_multiCameraCount);
|
||||
for(int i=0; i<_multiCameraCount; ++i)
|
||||
{
|
||||
std::string name = baseName + "_" + uNumber2Str(i);
|
||||
if(!models[i].load(_calibrationFolder, name, true /*ignoreStereoTransform*/, rectify) || !models[i].isValidForProjection())
|
||||
{
|
||||
UERROR("Failed to load a valid stereo calibration \"%s/%s_{%s,%s}.yaml\" for multi-camera frame base \"%s\".",
|
||||
_calibrationFolder.c_str(), name.c_str(),
|
||||
models[i].getLeftSuffix().c_str(), models[i].getRightSuffix().c_str(), baseName.c_str());
|
||||
return std::vector<StereoCameraModel>();
|
||||
}
|
||||
if(models[i].localTransform().isNull())
|
||||
{
|
||||
// In multi-camera mode the rig extrinsics are required: each
|
||||
// sub-camera calibration must provide a "local_transform".
|
||||
UERROR("Stereo calibration \"%s/%s_%s.yaml\" has no \"local_transform\"; it is "
|
||||
"required in multi-camera mode (rig extrinsics).",
|
||||
_calibrationFolder.c_str(), name.c_str(), models[i].getLeftSuffix().c_str());
|
||||
return std::vector<StereoCameraModel>();
|
||||
}
|
||||
if(rectify && !models[i].isValidForRectification())
|
||||
{
|
||||
UERROR("Parameter \"rectifyImages\" is set, but stereo calibration \"%s/%s_{%s,%s}.yaml\" is not valid for rectification.",
|
||||
_calibrationFolder.c_str(), name.c_str(),
|
||||
models[i].getLeftSuffix().c_str(), models[i].getRightSuffix().c_str());
|
||||
return std::vector<StereoCameraModel>();
|
||||
}
|
||||
}
|
||||
return models;
|
||||
}
|
||||
|
||||
std::string CameraStereoImages::getSerial() const
|
||||
@@ -163,6 +311,11 @@ SensorData CameraStereoImages::captureImage(SensorCaptureInfo * info)
|
||||
SensorData data;
|
||||
|
||||
SensorData left, right;
|
||||
// Disable the base reader's own multi-camera handling while reading the stacked
|
||||
// left/right images: this class splits and rectifies them itself below. Restored
|
||||
// before the multi-camera branch, which relies on the real flag value.
|
||||
bool multiCameraCalib = _multiCameraCalib;
|
||||
_multiCameraCalib = false;
|
||||
left = CameraImages::captureImage(info);
|
||||
if(!left.imageRaw().empty())
|
||||
{
|
||||
@@ -175,18 +328,86 @@ SensorData CameraStereoImages::captureImage(SensorCaptureInfo * info)
|
||||
{
|
||||
right = this->takeImage(info);
|
||||
}
|
||||
}
|
||||
_multiCameraCalib = multiCameraCalib;
|
||||
|
||||
if(!right.imageRaw().empty())
|
||||
if(!left.imageRaw().empty() && !right.imageRaw().empty())
|
||||
{
|
||||
// Rectification
|
||||
cv::Mat leftImage = left.imageRaw();
|
||||
cv::Mat rightImage = right.imageRaw();
|
||||
if(rightImage.type() != CV_8UC1 && rightGrayScale_)
|
||||
{
|
||||
// Rectification
|
||||
cv::Mat leftImage = left.imageRaw();
|
||||
cv::Mat rightImage = right.imageRaw();
|
||||
if(rightImage.type() != CV_8UC1 && rightGrayScale_)
|
||||
cv::Mat tmp;
|
||||
cv::cvtColor(rightImage, tmp, CV_BGR2GRAY);
|
||||
rightImage = tmp;
|
||||
}
|
||||
|
||||
if(multiCameraCalib)
|
||||
{
|
||||
// Multi-camera stereo mode: left and right images are horizontal stacks
|
||||
// of N sub-images (one stereo pair per sub-camera). Each pair is rectified
|
||||
// independently with its own model and written back into a stacked image of
|
||||
// the same layout. Sub-images are split using a uniform width (cols / N),
|
||||
// matching the convention used downstream to de-stack the images.
|
||||
std::vector<StereoCameraModel> models;
|
||||
if(this->isConfigForEachFrame())
|
||||
{
|
||||
cv::Mat tmp;
|
||||
cv::cvtColor(rightImage, tmp, CV_BGR2GRAY);
|
||||
rightImage = tmp;
|
||||
// Per-frame calibration loaded on demand (not kept in memory),
|
||||
// keyed by the current image's base name.
|
||||
std::string base = this->lastImageFileName();
|
||||
base = base.substr(0, base.find_last_of('.'));
|
||||
models = loadStereoCameraModels(base, this->isImagesRectified());
|
||||
}
|
||||
else
|
||||
{
|
||||
// a single calibration set is shared by all frames
|
||||
UASSERT(!multiStereoModels_.empty());
|
||||
models = multiStereoModels_;
|
||||
}
|
||||
if(models.empty())
|
||||
{
|
||||
return data;
|
||||
}
|
||||
int n = (int)models.size();
|
||||
|
||||
if(leftImage.cols % n != 0 || rightImage.cols % n != 0)
|
||||
{
|
||||
UERROR("Multi-camera stereo: stacked image width (left=%d, right=%d) is not "
|
||||
"divisible by the number of cameras (%d).", leftImage.cols, rightImage.cols, n);
|
||||
return data;
|
||||
}
|
||||
int subWidthLeft = leftImage.cols/n;
|
||||
int subWidthRight = rightImage.cols/n;
|
||||
|
||||
if(this->isImagesRectified())
|
||||
{
|
||||
cv::Mat leftRect(leftImage.rows, leftImage.cols, leftImage.type());
|
||||
cv::Mat rightRect(rightImage.rows, rightImage.cols, rightImage.type());
|
||||
for(int i=0; i<n; ++i)
|
||||
{
|
||||
cv::Rect roiL(subWidthLeft*i, 0, subWidthLeft, leftImage.rows);
|
||||
cv::Rect roiR(subWidthRight*i, 0, subWidthRight, rightImage.rows);
|
||||
models[i].left().rectifyImage(leftImage(roiL)).copyTo(leftRect(roiL));
|
||||
models[i].right().rectifyImage(rightImage(roiR)).copyTo(rightRect(roiR));
|
||||
}
|
||||
leftImage = leftRect;
|
||||
rightImage = rightRect;
|
||||
}
|
||||
|
||||
for(int i=0; i<n; ++i)
|
||||
{
|
||||
if(models[i].left().imageHeight() == 0 || models[i].left().imageWidth() == 0)
|
||||
{
|
||||
models[i].setImageSize(cv::Size(subWidthLeft, leftImage.rows));
|
||||
}
|
||||
}
|
||||
|
||||
data = SensorData(left.laserScanRaw(), leftImage, rightImage, models, left.id()/(camera2_?1:2), left.stamp());
|
||||
data.setGroundTruth(left.groundTruth());
|
||||
}
|
||||
else
|
||||
{
|
||||
if(this->isImagesRectified() && stereoModel_.isValidForRectification())
|
||||
{
|
||||
leftImage = stereoModel_.left().rectifyImage(leftImage);
|
||||
|
||||
@@ -1665,12 +1665,12 @@ void DatabaseViewer::extractImages()
|
||||
}
|
||||
StereoCameraModel model(
|
||||
cameraName,
|
||||
data.imageRaw().size(),
|
||||
data.stereoCameraModels()[i].left().imageSize(),
|
||||
data.stereoCameraModels()[i].left().K_raw(),
|
||||
data.stereoCameraModels()[i].left().D_raw(),
|
||||
data.stereoCameraModels()[i].left().R(),
|
||||
data.stereoCameraModels()[i].left().P(),
|
||||
data.rightRaw().size(),
|
||||
data.stereoCameraModels()[i].right().imageSize(),
|
||||
data.stereoCameraModels()[i].right().K_raw(),
|
||||
data.stereoCameraModels()[i].right().D_raw(),
|
||||
data.stereoCameraModels()[i].right().R(),
|
||||
@@ -1730,7 +1730,7 @@ void DatabaseViewer::extractImages()
|
||||
cameraName+="_"+uNumber2Str((int)i);
|
||||
}
|
||||
CameraModel model(cameraName,
|
||||
data.imageRaw().size(),
|
||||
data.cameraModels()[i].imageSize(),
|
||||
data.cameraModels()[i].K_raw(),
|
||||
data.cameraModels()[i].D_raw(),
|
||||
data.cameraModels()[i].R(),
|
||||
|
||||
@@ -843,6 +843,7 @@ PreferencesDialog::PreferencesDialog(QWidget * parent) :
|
||||
connect(_ui->lineEdit_cameraRGBDImages_path_rgb, SIGNAL(textChanged(const QString &)), this, SLOT(makeObsoleteSourcePanel()));
|
||||
connect(_ui->lineEdit_cameraRGBDImages_path_depth, SIGNAL(textChanged(const QString &)), this, SLOT(makeObsoleteSourcePanel()));
|
||||
connect(_ui->checkBox_cameraImages_configForEachFrame, SIGNAL(stateChanged(int)), this, SLOT(makeObsoleteSourcePanel()));
|
||||
connect(_ui->checkBox_cameraImages_multiCameraCalibration, SIGNAL(stateChanged(int)), this, SLOT(makeObsoleteSourcePanel()));
|
||||
connect(_ui->checkBox_cameraImages_timestamps, SIGNAL(stateChanged(int)), this, SLOT(makeObsoleteSourcePanel()));
|
||||
connect(_ui->checkBox_cameraImages_syncTimeStamps, SIGNAL(stateChanged(int)), this, SLOT(makeObsoleteSourcePanel()));
|
||||
connect(_ui->doubleSpinBox_cameraRGBDImages_scale, SIGNAL(valueChanged(double)), this, SLOT(makeObsoleteSourcePanel()));
|
||||
@@ -2389,6 +2390,7 @@ void PreferencesDialog::resetSettings(QGroupBox * groupBox)
|
||||
_ui->lineEdit_depthai_blob_path->clear();
|
||||
|
||||
_ui->checkBox_cameraImages_configForEachFrame->setChecked(false);
|
||||
_ui->checkBox_cameraImages_multiCameraCalibration->setChecked(false);
|
||||
_ui->checkBox_cameraImages_timestamps->setChecked(false);
|
||||
_ui->checkBox_cameraImages_syncTimeStamps->setChecked(true);
|
||||
_ui->lineEdit_cameraImages_timestamps->setText("");
|
||||
@@ -2924,6 +2926,7 @@ void PreferencesDialog::readCameraSettings(const QString & filePath)
|
||||
_ui->comboBox_cameraImages_bayerMode->setCurrentIndex(settings.value("bayerMode",_ui->comboBox_cameraImages_bayerMode->currentIndex()).toInt());
|
||||
|
||||
_ui->checkBox_cameraImages_configForEachFrame->setChecked(settings.value("config_each_frame",_ui->checkBox_cameraImages_configForEachFrame->isChecked()).toBool());
|
||||
_ui->checkBox_cameraImages_multiCameraCalibration->setChecked(settings.value("multi_camera_calibration",_ui->checkBox_cameraImages_multiCameraCalibration->isChecked()).toBool());
|
||||
_ui->checkBox_cameraImages_timestamps->setChecked(settings.value("filenames_as_stamps",_ui->checkBox_cameraImages_timestamps->isChecked()).toBool());
|
||||
_ui->checkBox_cameraImages_syncTimeStamps->setChecked(settings.value("sync_stamps",_ui->checkBox_cameraImages_syncTimeStamps->isChecked()).toBool());
|
||||
_ui->lineEdit_cameraImages_timestamps->setText(settings.value("stamps", _ui->lineEdit_cameraImages_timestamps->text()).toString());
|
||||
@@ -3546,6 +3549,7 @@ void PreferencesDialog::writeCameraSettings(const QString & filePath) const
|
||||
settings.setValue("maxFrames", _ui->source_images_spinBox_maxFrames->value());
|
||||
settings.setValue("bayerMode", _ui->comboBox_cameraImages_bayerMode->currentIndex());
|
||||
settings.setValue("config_each_frame", _ui->checkBox_cameraImages_configForEachFrame->isChecked());
|
||||
settings.setValue("multi_camera_calibration", _ui->checkBox_cameraImages_multiCameraCalibration->isChecked());
|
||||
settings.setValue("filenames_as_stamps", _ui->checkBox_cameraImages_timestamps->isChecked());
|
||||
settings.setValue("sync_stamps", _ui->checkBox_cameraImages_syncTimeStamps->isChecked());
|
||||
settings.setValue("stamps", _ui->lineEdit_cameraImages_timestamps->text());
|
||||
@@ -4622,7 +4626,12 @@ void PreferencesDialog::selectCalibrationPath()
|
||||
{
|
||||
dir = getWorkingDirectory()+"/camera_info/"+dir;
|
||||
}
|
||||
QString path = QFileDialog::getOpenFileName(this, tr("Select file"), dir, tr("Calibration file (*.yaml)"));
|
||||
#if CV_MAJOR_VERSION < 3 || (CV_MAJOR_VERSION == 3 && CV_MAJOR_VERSION < 2)
|
||||
QString path = QFileDialog::getOpenFileName(this, tr("Select file"), dir, tr("Calibration file (*.yaml *.xml)"));
|
||||
#else
|
||||
QString path = QFileDialog::getOpenFileName(this, tr("Select file"), dir, tr("Calibration file (*.yaml *.xml *.json)"));
|
||||
#endif
|
||||
|
||||
if(path.size())
|
||||
{
|
||||
_ui->lineEdit_calibrationFile->setText(path);
|
||||
@@ -7069,6 +7078,7 @@ Camera * PreferencesDialog::createCamera(
|
||||
_ui->lineEdit_cameraImages_timestamps->text().toStdString(),
|
||||
_ui->checkBox_cameraImages_syncTimeStamps->isChecked());
|
||||
((CameraRGBDImages*)camera)->setConfigForEachFrame(_ui->checkBox_cameraImages_configForEachFrame->isChecked());
|
||||
((CameraRGBDImages*)camera)->setMultiCameraCalibration(_ui->checkBox_cameraImages_multiCameraCalibration->isChecked());
|
||||
}
|
||||
else if(driver == kSrcDC1394)
|
||||
{
|
||||
@@ -7115,6 +7125,7 @@ Camera * PreferencesDialog::createCamera(
|
||||
_ui->lineEdit_cameraImages_timestamps->text().toStdString(),
|
||||
_ui->checkBox_cameraImages_syncTimeStamps->isChecked());
|
||||
((CameraStereoImages*)camera)->setConfigForEachFrame(_ui->checkBox_cameraImages_configForEachFrame->isChecked());
|
||||
((CameraStereoImages*)camera)->setMultiCameraCalibration(_ui->checkBox_cameraImages_multiCameraCalibration->isChecked());
|
||||
((CameraStereoImages*)camera)->setRightGrayScale(_ui->checkBox_stereo_rightGrayScale->isChecked());
|
||||
}
|
||||
else if (driver == kSrcStereoUsb)
|
||||
@@ -7319,7 +7330,8 @@ Camera * PreferencesDialog::createCamera(
|
||||
_ui->checkBox_cameraImages_timestamps->isChecked(),
|
||||
_ui->lineEdit_cameraImages_timestamps->text().toStdString(),
|
||||
_ui->checkBox_cameraImages_syncTimeStamps->isChecked());
|
||||
((CameraRGBDImages*)camera)->setConfigForEachFrame(_ui->checkBox_cameraImages_configForEachFrame->isChecked());
|
||||
((CameraImages*)camera)->setConfigForEachFrame(_ui->checkBox_cameraImages_configForEachFrame->isChecked());
|
||||
((CameraImages*)camera)->setMultiCameraCalibration(_ui->checkBox_cameraImages_multiCameraCalibration->isChecked());
|
||||
}
|
||||
else if(driver == kSrcDatabase)
|
||||
{
|
||||
|
||||
+413
-390
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>980</width>
|
||||
<height>925</height>
|
||||
<height>1246</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@@ -63,9 +63,9 @@
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>-648</y>
|
||||
<y>-1365</y>
|
||||
<width>684</width>
|
||||
<height>5218</height>
|
||||
<height>5343</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_16">
|
||||
@@ -95,7 +95,7 @@
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>19</number>
|
||||
<number>5</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="page_22">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_29" stretch="0,0">
|
||||
@@ -3469,7 +3469,7 @@ when using the file type, logs are saved in LogRtabmap.txt (located in the worki
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="stackedWidget_src">
|
||||
<property name="currentIndex">
|
||||
<number>3</number>
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="page_41">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_64">
|
||||
@@ -3590,7 +3590,7 @@ when using the file type, logs are saved in LogRtabmap.txt (located in the worki
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="stackedWidget_rgbd">
|
||||
<property name="currentIndex">
|
||||
<number>9</number>
|
||||
<number>7</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="page_32">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_63">
|
||||
@@ -7556,279 +7556,6 @@ when using the file type, logs are saved in LogRtabmap.txt (located in the worki
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_68" columnstretch="0,0,1">
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_cameraImages_syncTimeStamps">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="1">
|
||||
<widget class="QDoubleSpinBox" name="doubleSpinBox_maxPoseTimeDiff">
|
||||
<property name="suffix">
|
||||
<string> s</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>9.990000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.010000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>0.020000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_cameraImages_configForEachFrame">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="15" column="0">
|
||||
<widget class="QToolButton" name="toolButton_cameraImages_path_imu">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="15" column="2">
|
||||
<widget class="QLabel" name="label_463">
|
||||
<property name="text">
|
||||
<string>Path to file containing optional IMU data (*.csv [EuRoC format]). Mouse over the box to show formats.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QComboBox" name="comboBox_cameraImages_odomFormat">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Raw Format (3 values): x y z<br/>Raw Format (6 values): x y z roll pitch yaw<br/>Raw Format (7 values): x y z qx qy qz qw<br/>Raw Format (9 values, 3x3 rotation): r11 r12 r13 r21 r22 r23 r31 r32 r33<br/>Raw Format (12 values, 3x4 transform): r11 r12 r13 tx r21 r22 r23 ty r31 r32 r33 tz</p><p>RGBD-SLAM (stamp tx ty tz qx qy qz qw)<br/>RGBD-SLAM + ID (stamp id tx ty tz qx qy qz qw)<br/>KITTI (stamp + 12 values transform)<br/>TORO<br/>g2o<br/>NewCollege (stamp x y)<br/>Malaga Urban (GPS)<br/>St Lucia Stereo (INS)<br/>EuRoC MAV (stamp,tx,ty,tz,qw,qx,qy,qz...)</p></body></html></string>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Raw</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>RGBD-SLAM (motion capture)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>KITTI</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>TORO</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>g2o</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>NewCollege</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Malaga Urban</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>St Lucia Stereo</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Karlsruhe</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>EuRoC MAV</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>RGBD-SLAM</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>RGBD-SLAM + ID</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>rgbd_bonn</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="17" column="2">
|
||||
<widget class="QLabel" name="label_465">
|
||||
<property name="text">
|
||||
<string>IMU Rate. To synchronize capture rate with IMU timestamps, set to 0. This can be set a little over the actual IMU rate to keep up with camera capture rate if images are dropped by odometry.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QLabel" name="label_256">
|
||||
<property name="text">
|
||||
<string>Synchronize capture rate with timestamps.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="2">
|
||||
<widget class="QLabel" name="label_294">
|
||||
<property name="text">
|
||||
<string>Local transform from /base_link to /laser_link. Mouse over the box to show formats.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="2">
|
||||
<widget class="QLabel" name="label_348">
|
||||
<property name="text">
|
||||
<string>Odometry file. Select the correct format below.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="15" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_cameraImages_path_imu">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>cvs format (comma split): &quot;stamp_sec,gyro_x,gyro_y,gyro_z,acc_x,acc_y,acc_z&quot;</p><p>EuRoC format: &quot;stamp_nanosec,gyro_x,gyro_y,gyro_z,acc_x,acc_y,acc_z&quot;</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="16" column="2">
|
||||
<widget class="QLabel" name="label_464">
|
||||
<property name="text">
|
||||
<string>Local transform from /base_link to /imu_link. Mouse over the box to show formats.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_cameraImages_path_scans">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_cameraImages_laser_transform">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Format (3 values): x y z<br/>Format (6 values): x y z roll pitch yaw<br/>Format (7 values): x y z qx qy qz qw<br/>Format (9 values, 3x3 rotation): r11 r12 r13 r21 r22 r23 r31 r32 r33<br/>Format (12 values, 3x4 transform): r11 r12 r13 tx r21 r22 r23 ty r31 r32 r33 tz</p><p>KITTI: /base_link to /scan = -0.27 0 0.08 0 0 0<br/>KITTI: /base_footprint to /scan = -0.27 0 1.75 0 0 0</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0 0 0 0 0 0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="2">
|
||||
<widget class="QLabel" name="label_293">
|
||||
<property name="text">
|
||||
<string>Path to directory containing optional laser scans (*.pcd, *.ply, *.bin [KITTI format]). The directory should have the same size has the images directory. </string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_cameraImages_timestamps">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="2">
|
||||
<widget class="QLabel" name="label_292">
|
||||
<property name="text">
|
||||
<string>Maximum laser scan points.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="1">
|
||||
<widget class="QSpinBox" name="spinBox_cameraImages_max_scan_pts">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>KITTI: 130 000 points</p></body></html></string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>99999999</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="17" column="1">
|
||||
<widget class="QSpinBox" name="spinBox_cameraImages_max_imu_rate">
|
||||
<property name="toolTip">
|
||||
<string>EuRoC: 200 Hz -> 250 Hz</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>99999999</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="comboBox_cameraImages_bayerMode">
|
||||
<property name="sizeAdjustPolicy">
|
||||
@@ -7861,47 +7588,41 @@ when using the file type, logs are saved in LogRtabmap.txt (located in the worki
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="2">
|
||||
<widget class="QLabel" name="label_443">
|
||||
<item row="14" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_cameraImages_laser_transform">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Format (3 values): x y z<br/>Format (6 values): x y z roll pitch yaw<br/>Format (7 values): x y z qx qy qz qw<br/>Format (9 values, 3x3 rotation): r11 r12 r13 r21 r22 r23 r31 r32 r33<br/>Format (12 values, 3x4 transform): r11 r12 r13 tx r21 r22 r23 ty r31 r32 r33 tz</p><p>KITTI: /base_link to /scan = -0.27 0 0.08 0 0 0<br/>KITTI: /base_footprint to /scan = -0.27 0 1.75 0 0 0</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Max time difference between data and corresponding pose for format with stamps. If delay is over this threshold, the pose won't be set on data loaded. This is used when odometry and/or ground truth files are set.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
<string>0 0 0 0 0 0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0">
|
||||
<widget class="QToolButton" name="toolButton_cameraImages_path_scans">
|
||||
<item row="15" column="1">
|
||||
<widget class="QSpinBox" name="spinBox_cameraImages_max_scan_pts">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>KITTI: 130 000 points</p></body></html></string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>99999999</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QToolButton" name="toolButton_cameraImages_gt">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_cameraImages_timestamps">
|
||||
<item row="9" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_cameraImages_gt">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QLabel" name="label_255">
|
||||
<property name="text">
|
||||
<string>Use file names as timestamps. Format is epoch time. Example: "1305031102.175304.png".</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="16" column="1">
|
||||
<item row="18" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_cameraImages_imu_transform">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Format (3 values): x y z<br/>Format (6 values): x y z roll pitch yaw<br/>Format (7 values): x y z qx qy qz qw<br/>Format (9 values, 3x3 rotation): r11 r12 r13 r21 r22 r23 r31 r32 r33<br/>Format (12 values, 3x4 transform): r11 r12 r13 tx r21 r22 r23 ty r31 r32 r33 tz</p><p>EuRoC: /base_link to /imu = 0 0 1 0 -1 0 1 0 0</p></body></html></string>
|
||||
@@ -7911,10 +7632,10 @@ when using the file type, logs are saved in LogRtabmap.txt (located in the worki
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="2">
|
||||
<widget class="QLabel" name="label_288">
|
||||
<item row="18" column="2">
|
||||
<widget class="QLabel" name="label_464">
|
||||
<property name="text">
|
||||
<string>Ground truth file. Select the correct format below.</string>
|
||||
<string>Local transform from /base_link to /imu_link. Mouse over the box to show formats.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
@@ -7924,28 +7645,53 @@ when using the file type, logs are saved in LogRtabmap.txt (located in the worki
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QToolButton" name="toolButton_cameraImages_gt">
|
||||
<item row="19" column="2">
|
||||
<widget class="QLabel" name="label_465">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
<string>IMU Rate. To synchronize capture rate with IMU timestamps, set to 0. This can be set a little over the actual IMU rate to keep up with camera capture rate if images are dropped by odometry.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_cameraImages_gt">
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="label_605">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Load config file for each frame. Config files are read from the calibration folder and should have the same name than the corresponding frame file. Currently supporting 3DScannerApp for iOS export config format (JSON, intrinsics, pose and stamp) and RTAB-Map calibration file format. </p></body></html></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_cameraImages_odom">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QToolButton" name="toolButton_cameraImages_timestamps">
|
||||
<item row="4" column="2">
|
||||
<widget class="QLabel" name="label_256">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
<string>Synchronize capture rate with timestamps.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<item row="10" column="1">
|
||||
<widget class="QComboBox" name="comboBox_cameraImages_gtFormat">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Raw Format (3 values): x y z<br/>Raw Format (6 values): x y z roll pitch yaw<br/>Raw Format (7 values): x y z qx qy qz qw<br/>Raw Format (9 values, 3x3 rotation): r11 r12 r13 r21 r22 r23 r31 r32 r33<br/>Raw Format (12 values, 3x4 transform): r11 r12 r13 tx r21 r22 r23 ty r31 r32 r33 tz</p><p>RGBD-SLAM (stamp tx ty tz qx qy qz qw)<br/>RGBD-SLAM + ID (stamp id tx ty tz qx qy qz qw)<br/>KITTI (stamp + 12 values transform)<br/>TORO<br/>g2o<br/>NewCollege (stamp x y)<br/>Malaga Urban (GPS)<br/>St Lucia Stereo (INS)<br/>EuRoC MAV (stamp,tx,ty,tz,qw,qx,qy,qz...)</p></body></html></string>
|
||||
@@ -8020,72 +7766,6 @@ when using the file type, logs are saved in LogRtabmap.txt (located in the worki
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="label_605">
|
||||
<property name="text">
|
||||
<string>Load config file for each frame. For calibration, global calibration path above should be empty. Config files should be in the same directory than RGB frames and they should have the same name than the corresponding frame file. Currently supporting 3DScannerApp for iOS export config format (JSON, intrinsics, pose and stamp) and RTAB-Map calibration file format.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QToolButton" name="toolButton_cameraImages_odom">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="2">
|
||||
<widget class="QLabel" name="label_349">
|
||||
<property name="text">
|
||||
<string>Odometry format. See tool tip for more details on formats. Note that formats without stamps should have the same number of values than the source images.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_cameraImages_odom">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="2">
|
||||
<widget class="QLabel" name="label_289">
|
||||
<property name="text">
|
||||
<string>Ground truth format. See tool tip for more details on formats. Note that formats without stamps should have the same number of values than the source images.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QLabel" name="label_251">
|
||||
<property name="text">
|
||||
<string>Timestamps file (*.txt). The file should contain one column. The number of rows should be the same than the number of images in the folder. Not used if "Use file names as timestamps" above is checked. </string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="label_265">
|
||||
<property name="text">
|
||||
@@ -8099,7 +7779,347 @@ when using the file type, logs are saved in LogRtabmap.txt (located in the worki
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QLabel" name="label_255">
|
||||
<property name="text">
|
||||
<string>Use file names as timestamps. Format is epoch time. Example: "1305031102.175304.png".</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="0">
|
||||
<widget class="QToolButton" name="toolButton_cameraImages_path_scans">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QLabel" name="label_798">
|
||||
<property name="toolTip">
|
||||
<string>Naming <prefix>_<index>.yaml, or <prefix>_<index>_left.yaml and <prefix>_<index>_right.yaml for stereo, index starting at 0</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Multi-camera mode: each image is a horizontal stack of N sub-camera images (for stereo, both left and right images are stacked the same way). One calibration file per sub-camera (see naming format on tooltip) must be in the calibration folder. A local_transform (rig extrinsics) is required in each file.</p></body></html></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="17" column="0">
|
||||
<widget class="QToolButton" name="toolButton_cameraImages_path_imu">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="2">
|
||||
<widget class="QLabel" name="label_348">
|
||||
<property name="text">
|
||||
<string>Odometry file. Select the correct format below.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QToolButton" name="toolButton_cameraImages_odom">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="19" column="1">
|
||||
<widget class="QSpinBox" name="spinBox_cameraImages_max_imu_rate">
|
||||
<property name="toolTip">
|
||||
<string>EuRoC: 200 Hz -> 250 Hz</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>99999999</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="2">
|
||||
<widget class="QLabel" name="label_349">
|
||||
<property name="text">
|
||||
<string>Odometry format. See tool tip for more details on formats. Note that formats without stamps should have the same number of values than the source images.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="17" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_cameraImages_path_imu">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>cvs format (comma split): &quot;stamp_sec,gyro_x,gyro_y,gyro_z,acc_x,acc_y,acc_z&quot;</p><p>EuRoC format: &quot;stamp_nanosec,gyro_x,gyro_y,gyro_z,acc_x,acc_y,acc_z&quot;</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="2">
|
||||
<widget class="QLabel" name="label_289">
|
||||
<property name="text">
|
||||
<string>Ground truth format. See tool tip for more details on formats. Note that formats without stamps should have the same number of values than the source images.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="2">
|
||||
<widget class="QLabel" name="label_251">
|
||||
<property name="text">
|
||||
<string>Timestamps file (*.txt). The file should contain one column. The number of rows should be the same than the number of images in the folder. Not used if "Use file names as timestamps" above is checked. </string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_cameraImages_timestamps">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="1">
|
||||
<widget class="QDoubleSpinBox" name="doubleSpinBox_maxPoseTimeDiff">
|
||||
<property name="suffix">
|
||||
<string> s</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>9.990000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.010000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>0.020000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_cameraImages_timestamps">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_cameraImages_path_scans">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_cameraImages_multiCameraCalibration">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="2">
|
||||
<widget class="QLabel" name="label_293">
|
||||
<property name="text">
|
||||
<string>Path to directory containing optional laser scans (*.pcd, *.ply, *.bin [KITTI format]). The directory should have the same size has the images directory. </string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_cameraImages_configForEachFrame">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QToolButton" name="toolButton_cameraImages_timestamps">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="2">
|
||||
<widget class="QLabel" name="label_288">
|
||||
<property name="text">
|
||||
<string>Ground truth file. Select the correct format below.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="17" column="2">
|
||||
<widget class="QLabel" name="label_463">
|
||||
<property name="text">
|
||||
<string>Path to file containing optional IMU data (*.csv [EuRoC format]). Mouse over the box to show formats.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_cameraImages_gt_transform">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Format (3 values): x y z<br/>Format (6 values): x y z roll pitch yaw<br/>Format (7 values): x y z qx qy qz qw<br/>Format (9 values, 3x3 rotation): r11 r12 r13 r21 r22 r23 r31 r32 r33<br/>Format (12 values, 3x4 transform): r11 r12 r13 tx r21 r22 r23 ty r31 r32 r33 tz</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0 0 0 0 0 0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="2">
|
||||
<widget class="QLabel" name="label_294">
|
||||
<property name="text">
|
||||
<string>Local transform from /base_link to /laser_link. Mouse over the box to show formats.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QComboBox" name="comboBox_cameraImages_odomFormat">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Raw Format (3 values): x y z<br/>Raw Format (6 values): x y z roll pitch yaw<br/>Raw Format (7 values): x y z qx qy qz qw<br/>Raw Format (9 values, 3x3 rotation): r11 r12 r13 r21 r22 r23 r31 r32 r33<br/>Raw Format (12 values, 3x4 transform): r11 r12 r13 tx r21 r22 r23 ty r31 r32 r33 tz</p><p>RGBD-SLAM (stamp tx ty tz qx qy qz qw)<br/>RGBD-SLAM + ID (stamp id tx ty tz qx qy qz qw)<br/>KITTI (stamp + 12 values transform)<br/>TORO<br/>g2o<br/>NewCollege (stamp x y)<br/>Malaga Urban (GPS)<br/>St Lucia Stereo (INS)<br/>EuRoC MAV (stamp,tx,ty,tz,qw,qx,qy,qz...)</p></body></html></string>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToContents</enum>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Raw</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>RGBD-SLAM (motion capture)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>KITTI</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>TORO</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>g2o</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>NewCollege</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Malaga Urban</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>St Lucia Stereo</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Karlsruhe</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>EuRoC MAV</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>RGBD-SLAM</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>RGBD-SLAM + ID</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>rgbd_bonn</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_cameraImages_syncTimeStamps">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="15" column="2">
|
||||
<widget class="QLabel" name="label_292">
|
||||
<property name="text">
|
||||
<string>Maximum laser scan points.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="2">
|
||||
<widget class="QLabel" name="label_794">
|
||||
<property name="text">
|
||||
<string>Local transform from /base_link to /gt_link. Mouse over the box to show formats. By default, we assume the ground truth matches the base frame, if the ground truth refers to another frame, set this to convert the poses in base frame.</string>
|
||||
@@ -8112,13 +8132,16 @@ when using the file type, logs are saved in LogRtabmap.txt (located in the worki
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_cameraImages_gt_transform">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Format (3 values): x y z<br/>Format (6 values): x y z roll pitch yaw<br/>Format (7 values): x y z qx qy qz qw<br/>Format (9 values, 3x3 rotation): r11 r12 r13 r21 r22 r23 r31 r32 r33<br/>Format (12 values, 3x4 transform): r11 r12 r13 tx r21 r22 r23 ty r31 r32 r33 tz</p></body></html></string>
|
||||
</property>
|
||||
<item row="12" column="2">
|
||||
<widget class="QLabel" name="label_443">
|
||||
<property name="text">
|
||||
<string>0 0 0 0 0 0</string>
|
||||
<string>Max time difference between data and corresponding pose for format with stamps. If delay is over this threshold, the pose won't be set on data loaded. This is used when odometry and/or ground truth files are set.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<package format="2">
|
||||
<name>rtabmap</name>
|
||||
<version>0.23.7</version>
|
||||
<version>0.23.8</version>
|
||||
<description>RTAB-Map's standalone library. RTAB-Map is a RGB-D SLAM approach with real-time constraints.</description>
|
||||
<maintainer email="matlabbe@gmail.com">Mathieu Labbe</maintainer>
|
||||
<author>Mathieu Labbe</author>
|
||||
|
||||
Reference in New Issue
Block a user