New parameter: Marker/Strategy (default opencv-aruco as before). Optimized multicameras marker detection (do only once with stitched image)

This commit is contained in:
matlabbe
2026-05-14 14:12:23 -07:00
parent 19515f0c65
commit 09c8bc80c1
4 changed files with 640 additions and 536 deletions
+1 -1
View File
@@ -919,7 +919,7 @@ class RTABMAP_CORE_EXPORT Parameters
RTABMAP_PARAM(Marker, Strategy, int, 0, "Marker detection implementation: 0=OpenCV, 1=AprilTag"); RTABMAP_PARAM(Marker, Strategy, int, 0, "Marker detection implementation: 0=OpenCV, 1=AprilTag");
RTABMAP_PARAM(Marker, Dictionary, int, 0, "Dictionary to use: DICT_ARUCO_4X4_50=0, DICT_ARUCO_4X4_100=1, DICT_ARUCO_4X4_250=2, DICT_ARUCO_4X4_1000=3, DICT_ARUCO_5X5_50=4, DICT_ARUCO_5X5_100=5, DICT_ARUCO_5X5_250=6, DICT_ARUCO_5X5_1000=7, DICT_ARUCO_6X6_50=8, DICT_ARUCO_6X6_100=9, DICT_ARUCO_6X6_250=10, DICT_ARUCO_6X6_1000=11, DICT_ARUCO_7X7_50=12, DICT_ARUCO_7X7_100=13, DICT_ARUCO_7X7_250=14, DICT_ARUCO_7X7_1000=15, DICT_ARUCO_ORIGINAL = 16, DICT_APRILTAG_16h5=17, DICT_APRILTAG_25h9=18, DICT_APRILTAG_36h10=19, DICT_APRILTAG_36h11=20"); RTABMAP_PARAM(Marker, Dictionary, int, 0, "Dictionary to use: DICT_ARUCO_4X4_50=0, DICT_ARUCO_4X4_100=1, DICT_ARUCO_4X4_250=2, DICT_ARUCO_4X4_1000=3, DICT_ARUCO_5X5_50=4, DICT_ARUCO_5X5_100=5, DICT_ARUCO_5X5_250=6, DICT_ARUCO_5X5_1000=7, DICT_ARUCO_6X6_50=8, DICT_ARUCO_6X6_100=9, DICT_ARUCO_6X6_250=10, DICT_ARUCO_6X6_1000=11, DICT_ARUCO_7X7_50=12, DICT_ARUCO_7X7_100=13, DICT_ARUCO_7X7_250=14, DICT_ARUCO_7X7_1000=15, DICT_ARUCO_ORIGINAL = 16, DICT_APRILTAG_16h5=17, DICT_APRILTAG_25h9=18, DICT_APRILTAG_36h10=19, DICT_APRILTAG_36h11=20");
RTABMAP_PARAM(Marker, Length, float, 0, "The length (m) of the markers' side. 0 means automatic marker length estimation using the depth image (the camera should look at the marker perpendicularly for initialization)."); RTABMAP_PARAM(Marker, Length, float, 0, "The length (m) of the markers' side. Value <=0 means automatic marker length estimation using the depth image (the camera should look at the marker perpendicularly for initialization). If 0, the length is estimated only on the first marker detected, then re-used for all next detections (i.e., this assumes that markers have all the same length). With <0, the length is estimated once for each unique marker, then re-used for next detections with the same marker ID.");
RTABMAP_PARAM(Marker, MaxDepthError, float, 0.01, uFormat("Maximum depth error between all corners of a marker when estimating the marker length (when %s is 0). The smaller it is, the more perpendicular the camera should be toward the marker to initialize the length.", kMarkerLength().c_str())); RTABMAP_PARAM(Marker, MaxDepthError, float, 0.01, uFormat("Maximum depth error between all corners of a marker when estimating the marker length (when %s is 0). The smaller it is, the more perpendicular the camera should be toward the marker to initialize the length.", kMarkerLength().c_str()));
RTABMAP_PARAM(Marker, VarianceLinear, float, 0.001, uFormat("Linear variance to set on marker detections. If %s is enabled and %s=2 (GTSAM): it is the variance of the range factor, with 9999 to disable range factor and to do only bearing.", kMarkerVarianceOrientationIgnored().c_str(), kOptimizerStrategy().c_str())); RTABMAP_PARAM(Marker, VarianceLinear, float, 0.001, uFormat("Linear variance to set on marker detections. If %s is enabled and %s=2 (GTSAM): it is the variance of the range factor, with 9999 to disable range factor and to do only bearing.", kMarkerVarianceOrientationIgnored().c_str(), kOptimizerStrategy().c_str()));
RTABMAP_PARAM(Marker, VarianceAngular, float, 0.01, uFormat("Angular variance to set on marker detections. If %s is enabled, it is ignored with %s=1 (g2o) and it corresponds to bearing variance with %s=2 (GTSAM).", kMarkerVarianceOrientationIgnored().c_str(), kOptimizerStrategy().c_str(), kOptimizerStrategy().c_str())); RTABMAP_PARAM(Marker, VarianceAngular, float, 0.01, uFormat("Angular variance to set on marker detections. If %s is enabled, it is ignored with %s=1 (g2o) and it corresponds to bearing variance with %s=2 (GTSAM).", kMarkerVarianceOrientationIgnored().c_str(), kOptimizerStrategy().c_str(), kOptimizerStrategy().c_str()));
+248 -197
View File
@@ -171,6 +171,7 @@ void MarkerDetector::parseParameters(const ParametersMap & parameters)
#endif #endif
} }
// deprecated
std::map<int, Transform> MarkerDetector::detect(const cv::Mat & image, const CameraModel & model, const cv::Mat & depth, float * markerLengthOut, cv::Mat * imageWithDetections) std::map<int, Transform> MarkerDetector::detect(const cv::Mat & image, const CameraModel & model, const cv::Mat & depth, float * markerLengthOut, cv::Mat * imageWithDetections)
{ {
std::map<int, Transform> detections; std::map<int, Transform> detections;
@@ -199,88 +200,77 @@ std::map<int, MarkerInfo> MarkerDetector::detect(const cv::Mat & image,
UASSERT(int((image.cols/models.size())*models.size()) == image.cols); UASSERT(int((image.cols/models.size())*models.size()) == image.cols);
UASSERT(int((depth.cols/models.size())*models.size()) == depth.cols); UASSERT(int((depth.cols/models.size())*models.size()) == depth.cols);
int subRGBWidth = image.cols/models.size(); int subRGBWidth = image.cols/models.size();
int subDepthWidth = depth.cols/models.size();
std::map<int, MarkerInfo> allInfo; float rgbToDepthFactorX = 1.0f;
for(size_t i=0; i<models.size(); ++i) float rgbToDepthFactorY = 1.0f;
if(!depth.empty())
{ {
cv::Mat subImage(image, cv::Rect(subRGBWidth*i, 0, subRGBWidth, image.rows)); rgbToDepthFactorX = float(depth.cols) / float(image.cols);
cv::Mat subDepth; rgbToDepthFactorY = float(depth.rows) / float(image.rows);
if(!depth.empty()) }
subDepth = cv::Mat(depth, cv::Rect(subDepthWidth*i, 0, subDepthWidth, depth.rows)); else if(markerLength_ == 0)
CameraModel model = models[i]; {
cv::Mat subImageWithDetections; if(depth.empty())
std::map<int, MarkerInfo> subInfo = detect(subImage, model, subDepth, markerLengths, imageWithDetections?&subImageWithDetections:0);
if(ULogger::level() >= ULogger::kWarning)
{ {
for(std::map<int, MarkerInfo>::iterator iter=subInfo.begin(); iter!=subInfo.end(); ++iter) UERROR("Depth image is empty, please set %s parameter to non-null.", Parameters::kMarkerLength().c_str());
{ return std::map<int, MarkerInfo>();
std::pair<std::map<int, MarkerInfo>::iterator, bool> inserted = allInfo.insert(*iter);
if(!inserted.second)
{
UWARN("Marker %d already added by another camera, ignoring detection from camera %d", iter->first, i);
}
}
}
else
{
allInfo.insert(subInfo.begin(), subInfo.end());
}
if(imageWithDetections)
{
if(i==0)
{
*imageWithDetections = cv::Mat(image.size(), subImageWithDetections.type());
}
if(!subImageWithDetections.empty())
{
subImageWithDetections.copyTo(cv::Mat(*imageWithDetections, cv::Rect(subRGBWidth*i, 0, subRGBWidth, image.rows)));
}
} }
} }
return allInfo;
}
std::map<int, MarkerInfo> MarkerDetector::detect(const cv::Mat & image,
const CameraModel & model,
const cv::Mat & depth,
const std::map<int, float> & markerLengths,
cv::Mat * imageWithDetections)
{
if(!image.empty() && image.cols != model.imageWidth())
{
UERROR("This method cannot handle multi-camera marker detection, use the other function version supporting it.");
return std::map<int, MarkerInfo>();
}
std::map<int, MarkerInfo> detections;
std::vector< int > ids; std::vector< int > ids;
std::vector< std::vector< cv::Point2f > > corners, rejected; std::vector< int > cams;
std::vector< std::vector< cv::Point2f > > corners; // in stitched image
std::vector<Transform> poses; std::vector<Transform> poses;
// detect markers and estimate pose // detect markers and estimate pose
if(strategy_ == kStrategyApriltag) if(strategy_ == kStrategyApriltag)
{ {
#ifdef RTABMAP_APRILTAG #ifdef RTABMAP_APRILTAG
// Make an image_u8_t header for the Mat data
UASSERT(image.type() == CV_8UC1); UASSERT(image.type() == CV_8UC1);
image_u8_t im = {image.cols, image.rows, (int)image.step, image.data}; image_u8_t im = {image.cols, image.rows, (int)image.step, image.data};
zarray_t *apriltagDetections = apriltag_detector_detect(((apriltag_detector_t*)apriltagLibDetector_), &im); zarray_t *apriltagDetections = apriltag_detector_detect(((apriltag_detector_t*)apriltagLibDetector_), &im);
if (errno == EAGAIN) { if (errno == EAGAIN) {
UFATAL("Unable to create the %d threads requested.", ((apriltag_detector_t*)apriltagLibDetector_)->nthreads); UERROR("Unable to create the %d threads requested.", ((apriltag_detector_t*)apriltagLibDetector_)->nthreads);
exit(-1); if(apriltagDetections)
{
apriltag_detections_destroy(apriltagDetections);
}
return std::map<int, MarkerInfo>();
} }
for (int i = 0; i < zarray_size(apriltagDetections); i++) { std::set<int> idsAdded;
for (int i = 0; i < zarray_size(apriltagDetections); i++)
{
apriltag_detection_t *det; apriltag_detection_t *det;
zarray_get(apriltagDetections, i, &det); zarray_get(apriltagDetections, i, &det);
// Which camera?
int cameraIndex = int(det->c[0]) / subRGBWidth;
UASSERT(cameraIndex>=0 && cameraIndex<(int)models.size());
if(idsAdded.find(det->id)!=idsAdded.end())
{
UWARN("Marker %d already added by another camera, ignoring detection from camera %d", det->id, cameraIndex);
continue;
}
const CameraModel & model = models[cameraIndex];
float offsetX = cameraIndex*subRGBWidth;
// Convert detection in local camera
det->c[0] -= offsetX;
for(int i=0; i<4; ++i)
{
det->p[i][0] -= offsetX;
}
// FIXME, homography needs to be updated
apriltag_detection_info_t info; apriltag_detection_info_t info;
info.det = det; info.det = det;
info.tagsize = markerLength_<=0.0?1.0f:markerLength_; info.tagsize = 1.0f;
info.fx = model.fx(); info.fx = model.fx();
info.fy = model.fy(); info.fy = model.fy();
info.cx = model.cx(); info.cx = model.cx();
@@ -291,20 +281,22 @@ std::map<int, MarkerInfo> MarkerDetector::detect(const cv::Mat & image,
double err = estimate_tag_pose(&info, &pose); double err = estimate_tag_pose(&info, &pose);
if (pose.R && pose.t) if (pose.R && pose.t)
{ {
Transform t(MATD_EL(pose.R, 0, 0), MATD_EL(pose.R, 0, 1), MATD_EL(pose.R, 0, 2), MATD_EL(pose.t, 0, 0), Transform t(MATD_EL(pose.R, 0, 0), MATD_EL(pose.R, 1, 0), MATD_EL(pose.R, 2, 0), MATD_EL(pose.t, 0, 0),
MATD_EL(pose.R, 1, 0), MATD_EL(pose.R, 1, 1), MATD_EL(pose.R, 1, 2), MATD_EL(pose.t, 1, 0), MATD_EL(pose.R, 0, 1), MATD_EL(pose.R, 1, 1), MATD_EL(pose.R, 2, 1), MATD_EL(pose.t, 1, 0),
MATD_EL(pose.R, 2, 0), MATD_EL(pose.R, 2, 1), MATD_EL(pose.R, 2, 2), MATD_EL(pose.t, 2, 0)); MATD_EL(pose.R, 0, 2), MATD_EL(pose.R, 1, 2), MATD_EL(pose.R, 2, 2), MATD_EL(pose.t, 2, 0));
poses.push_back(t); poses.push_back(t);
corners.push_back(std::vector<cv::Point2f>(4)); corners.push_back(std::vector<cv::Point2f>(4));
for(int i=0; i<4; ++i) for(int i=0; i<4; ++i)
{ {
corners.back()[i].x = det->p[i][0]; // reconvert in original stitched image
corners.back()[i].x = det->p[i][0]+offsetX;
corners.back()[i].y = det->p[i][1]; corners.back()[i].y = det->p[i][1];
UDEBUG("Marker %d corner %d : %f %f", det->id, i, corners.back()[i].x, corners.back()[i].y);
} }
ids.push_back(det->id); ids.push_back(det->id);
cams.push_back(cameraIndex);
UDEBUG("Add marker %d (err = %f)", det->id, err); UDEBUG("Add marker %d (err = %f)", det->id, err);
idsAdded.insert(det->id);
} }
else else
{ {
@@ -323,168 +315,206 @@ std::map<int, MarkerInfo> MarkerDetector::detect(const cv::Mat & image,
} }
else // opencv else // opencv
{ {
std::vector< cv::Vec3d > rvecs, tvecs; std::vector< int > cvIds;
std::vector< std::vector< cv::Point2f > > cvCorners, cvRejected;
#ifdef HAVE_OPENCV_ARUCO #ifdef HAVE_OPENCV_ARUCO
#if CV_MAJOR_VERSION > 3 || (CV_MAJOR_VERSION == 3 && CV_MINOR_VERSION >=2) #if CV_MAJOR_VERSION > 3 || (CV_MAJOR_VERSION == 3 && CV_MINOR_VERSION >=2)
cv::aruco::detectMarkers(image, dictionary_, corners, ids, detectorParams_, rejected); cv::aruco::detectMarkers(image, dictionary_, cvCorners, cvIds, detectorParams_, cvRejected);
#else #else
cv::aruco::detectMarkers(image, *dictionary_, corners, ids, *detectorParams_, rejected); cv::aruco::detectMarkers(image, *dictionary_, cvCorners, cvIds, *detectorParams_, cvRejected);
#endif #endif
cv::aruco::estimatePoseSingleMarkers(corners, markerLength_<=0.0?1.0f:markerLength_, model.K(), model.D(), rvecs, tvecs); UDEBUG("Markers detected=%d rejected=%d", (int)cvIds.size(), (int)cvRejected.size());
for(size_t i=0; i<ids.size(); ++i) // split detections per camera
std::set<int> idsAdded;
std::vector< std::vector< std::vector< cv::Point2f > > > cvCornersPerCam(models.size());
std::vector< std::vector< int > > cvIdsPerCam(models.size());
for(size_t i=0; i<cvIds.size(); ++i)
{ {
cv::Mat R; int id = cvIds[i];
cv::Rodrigues(rvecs[i], R);
Transform t(R.at<double>(0,0), R.at<double>(0,1), R.at<double>(0,2), tvecs[i].val[0], // Which camera?
R.at<double>(1,0), R.at<double>(1,1), R.at<double>(1,2), tvecs[i].val[1], int cameraIndex = int(cvCorners[i][0].x) / subRGBWidth;
R.at<double>(2,0), R.at<double>(2,1), R.at<double>(2,2), tvecs[i].val[2]); UASSERT(cameraIndex>=0 && cameraIndex<(int)models.size());
poses.push_back(t);
if(idsAdded.find(id) != idsAdded.end())
{
UWARN("Marker %d already added by another camera, ignoring detection from camera %d", id, cameraIndex);
continue;
}
float offsetX = cameraIndex*subRGBWidth;
for(size_t j=0; j<cvCorners[i].size(); ++j)
{
cvCorners[i][j].x -= offsetX;
}
cvCornersPerCam[cameraIndex].push_back(cvCorners[i]);
cvIdsPerCam[cameraIndex].push_back(id);
idsAdded.insert(id);
UDEBUG("Detected marker %d on camera %d", id, cameraIndex);
} }
for(size_t cam=0; cam < cvCornersPerCam.size(); ++cam)
{
std::vector< cv::Vec3d > rvecs, tvecs;
const CameraModel & model = models[cam];
cv::aruco::estimatePoseSingleMarkers(cvCornersPerCam[cam], 1.0f, model.K(), model.D(), rvecs, tvecs);
float offsetX = cam*subRGBWidth;
for(size_t i=0; i<cvIdsPerCam[cam].size(); ++i)
{
cv::Mat R;
cv::Rodrigues(rvecs[i], R);
Transform t(R.at<double>(0,0), R.at<double>(0,1), R.at<double>(0,2), tvecs[i].val[0],
R.at<double>(1,0), R.at<double>(1,1), R.at<double>(1,2), tvecs[i].val[1],
R.at<double>(2,0), R.at<double>(2,1), R.at<double>(2,2), tvecs[i].val[2]);
poses.push_back(t);
ids.push_back(cvIdsPerCam[cam][i]);
cams.push_back(cam);
// reconvert in original stitched image
for(size_t j=0; j<cvCornersPerCam[cam][i].size(); ++j)
{
cvCornersPerCam[cam][i][j].x += offsetX;
}
corners.push_back(cvCornersPerCam[cam][i]);
}
}
#else #else
UERROR("RTAB-Map is not built with \"aruco\" module from OpenCV."); UERROR("RTAB-Map is not built with \"aruco\" module from OpenCV.");
#endif #endif
} }
UDEBUG("Markers detected=%d rejected=%d", (int)ids.size(), (int)rejected.size()); // Estimate scale and fill up output detections
if(ids.size() > 0) std::vector<float> scales;
std::map<int, MarkerInfo> detections;
for(size_t i=0; i<ids.size(); ++i)
{ {
float rgbToDepthFactorX = 1.0f; float length = 0.0f;
float rgbToDepthFactorY = 1.0f; std::map<int, float>::const_iterator findIter = markerLengths.find(ids[i]);
if(!depth.empty()) if(!depth.empty() && (markerLength_ == 0 || (markerLength_<0 && findIter==markerLengths.end())))
{ {
rgbToDepthFactorX = 1.0f/(model.imageWidth()>0?float(model.imageWidth())/float(depth.cols):1.0f); float d = util2d::getDepth(depth, (corners[i][0].x + (corners[i][2].x-corners[i][0].x)/2.0f)*rgbToDepthFactorX, (corners[i][0].y + (corners[i][2].y-corners[i][0].y)/2.0f)*rgbToDepthFactorY, true, 0.02f, true);
rgbToDepthFactorY = 1.0f/(model.imageHeight()>0?float(model.imageHeight())/float(depth.rows):1.0f); float d1 = util2d::getDepth(depth, corners[i][0].x*rgbToDepthFactorX, corners[i][0].y*rgbToDepthFactorY, true, 0.02f, true);
} float d2 = util2d::getDepth(depth, corners[i][1].x*rgbToDepthFactorX, corners[i][1].y*rgbToDepthFactorY, true, 0.02f, true);
else if(markerLength_ == 0) float d3 = util2d::getDepth(depth, corners[i][2].x*rgbToDepthFactorX, corners[i][2].y*rgbToDepthFactorY, true, 0.02f, true);
{ float d4 = util2d::getDepth(depth, corners[i][3].x*rgbToDepthFactorX, corners[i][3].y*rgbToDepthFactorY, true, 0.02f, true);
if(depth.empty()) // Accept measurement only if all 4 depth values are valid and
{ // they are at the same depth (camera should be perpendicular to marker for
UERROR("Depth image is empty, please set %s parameter to non-null.", Parameters::kMarkerLength().c_str()); // best depth estimation)
return detections; if(d>0 && d1>0 && d2>0 && d3>0 && d4>0)
}
}
std::vector<float> scales;
for(size_t i=0; i<ids.size(); ++i)
{
float length = 0.0f;
std::map<int, float>::const_iterator findIter = markerLengths.find(ids[i]);
if(!depth.empty() && (markerLength_ == 0 || (markerLength_<0 && findIter==markerLengths.end())))
{ {
float d = util2d::getDepth(depth, (corners[i][0].x + (corners[i][2].x-corners[i][0].x)/2.0f)*rgbToDepthFactorX, (corners[i][0].y + (corners[i][2].y-corners[i][0].y)/2.0f)*rgbToDepthFactorY, true, 0.02f, true); float scale = d / poses[i].z();
float d1 = util2d::getDepth(depth, corners[i][0].x*rgbToDepthFactorX, corners[i][0].y*rgbToDepthFactorY, true, 0.02f, true);
float d2 = util2d::getDepth(depth, corners[i][1].x*rgbToDepthFactorX, corners[i][1].y*rgbToDepthFactorY, true, 0.02f, true); if( fabs(d-d1) < maxDepthError_ &&
float d3 = util2d::getDepth(depth, corners[i][2].x*rgbToDepthFactorX, corners[i][2].y*rgbToDepthFactorY, true, 0.02f, true); fabs(d-d2) < maxDepthError_ &&
float d4 = util2d::getDepth(depth, corners[i][3].x*rgbToDepthFactorX, corners[i][3].y*rgbToDepthFactorY, true, 0.02f, true); fabs(d-d3) < maxDepthError_ &&
// Accept measurement only if all 4 depth values are valid and fabs(d-d4) < maxDepthError_)
// they are at the same depth (camera should be perpendicular to marker for
// best depth estimation)
if(d>0 && d1>0 && d2>0 && d3>0 && d4>0)
{ {
float scale = d / poses[i].z(); length = scale;
scales.push_back(length);
if( fabs(d-d1) < maxDepthError_ && UWARN("Automatic marker length estimation: id=%d depth=%fm length=%fm", ids[i], d, length);
fabs(d-d2) < maxDepthError_ &&
fabs(d-d3) < maxDepthError_ &&
fabs(d-d4) < maxDepthError_)
{
length = scale;
scales.push_back(length);
poses[i].x() *= length;
poses[i].y() *= length;
poses[i].z() *= length;
UWARN("Automatic marker length estimation: id=%d depth=%fm length=%fm", ids[i], d, length);
}
else
{
UWARN("The four marker's corners should be "
"perpendicular to camera to estimate correctly "
"the marker's length. Errors: %f, %f, %f > %fm (%s). Four corners: %f %f %f %f (middle=%f). "
"Parameter %s can be set to non-null to skip automatic "
"marker length estimation. Detections are ignored.",
fabs(d1-d2), fabs(d1-d3), fabs(d1-d4), maxDepthError_, Parameters::kMarkerMaxDepthError().c_str(),
d1, d2, d3, d4, d,
Parameters::kMarkerLength().c_str());
continue;
}
} }
else else
{ {
UWARN("Some depth values (%f,%f,%f,%f, middle=%f) cannot be detected on the " UWARN("The four marker's corners should be "
"marker's corners, cannot initialize marker length. " "perpendicular to camera to estimate correctly "
"Parameter %s can be set to non-null to skip automatic " "the marker's length. Errors: %f, %f, %f > %fm (%s). Four corners: %f %f %f %f (middle=%f). "
"marker length estimation. Detections are ignored.", "Parameter %s can be set to non-null to skip automatic "
d1,d2,d3,d4,d, "marker length estimation. Detections are ignored.",
Parameters::kMarkerLength().c_str()); fabs(d1-d2), fabs(d1-d3), fabs(d1-d4), maxDepthError_, Parameters::kMarkerMaxDepthError().c_str(),
continue; d1, d2, d3, d4, d,
Parameters::kMarkerLength().c_str());
continue;
} }
} }
else if(markerLength_ < 0) else
{
if(findIter!=markerLengths.end())
{
length = findIter->second;
poses[i].x() *= length;
poses[i].y() *= length;
poses[i].z() *= length;
}
else
{
UWARN("Cannot find marker length for marker %d, ignoring this marker (count=%d)", ids[i], (int)markerLengths.size());
continue;
}
}
else if(markerLength_ > 0)
{
length = markerLength_;
}
else
{
continue;
}
// Limit the detection range to be between the min / max range.
// If the ranges are -1, allow any detection within that direction.
if((maxRange_ <= 0 || poses[i].z() < maxRange_) &&
(minRange_ <= 0 || poses[i].z() > minRange_))
{ {
Transform pose = model.localTransform() * poses[i]; UWARN("Some depth values (%f,%f,%f,%f, middle=%f) cannot be detected on the "
detections.insert(std::make_pair(ids[i], MarkerInfo(ids[i], length, pose))); "marker's corners, cannot initialize marker length. "
UDEBUG("Marker %d detected in base_link: %s, optical_link=%s, local transform=%s", "Parameter %s can be set to non-null to skip automatic "
ids[i], pose.prettyPrint().c_str(), poses[i].prettyPrint().c_str(), model.localTransform().prettyPrint().c_str()); "marker length estimation. Detections are ignored.",
d1,d2,d3,d4,d,
Parameters::kMarkerLength().c_str());
continue;
} }
} }
if(markerLength_ == 0 && !scales.empty()) else if(markerLength_ < 0)
{ {
float sum = 0.0f; if(findIter!=markerLengths.end())
float maxError = 0.0f;
for(size_t i=0; i<scales.size(); ++i)
{ {
if(i>0) length = findIter->second;
{ }
float error = fabs(scales[i]-scales[0]); else
if(error > 0.001f) {
{ UWARN("Cannot find marker length for marker %d, ignoring this marker (count=%d)", ids[i], (int)markerLengths.size());
UWARN("The marker's length detected between 2 of the " continue;
"markers doesn't match (%fm vs %fm)."
"Parameter %s can be set to non-null to skip automatic "
"marker length estimation. Detections are ignored.",
scales[i], scales[0],
Parameters::kMarkerLength().c_str());
detections.clear();
return detections;
}
if(error > maxError)
{
maxError = error;
}
}
sum += scales[i];
} }
markerLength_ = sum/float(scales.size());
UWARN("Final marker length estimated = %fm, max error=%fm (used for subsequent detections)", markerLength_, maxError);
} }
else if(markerLength_ > 0)
{
length = markerLength_;
}
else
{
continue;
}
UASSERT(length > 0.0f);
poses[i].x() *= length;
poses[i].y() *= length;
poses[i].z() *= length;
// Limit the detection range to be between the min / max range.
// If the ranges are -1, allow any detection within that direction.
if((maxRange_ <= 0 || poses[i].z() < maxRange_) &&
(minRange_ <= 0 || poses[i].z() > minRange_))
{
Transform pose = models[cams[i]].localTransform() * poses[i];
detections.insert(std::make_pair(ids[i], MarkerInfo(ids[i], length, pose)));
UDEBUG("Marker %d detected in base_link: %s, optical_link=%s, local transform=%s",
ids[i],
pose.prettyPrint().c_str(),
poses[i].prettyPrint().c_str(),
models[cams[i]].localTransform().prettyPrint().c_str());
}
else
{
UDEBUG("Filtered marker %d by distance (min=%f max=%f value=%f) from the camera %d",
ids[i], minRange_, maxRange_, poses[i].z(), cams[i]);
}
}
if(markerLength_ == 0 && !scales.empty())
{
float sum = 0.0f;
float maxError = 0.0f;
for(size_t i=0; i<scales.size(); ++i)
{
if(i>0)
{
float error = fabs(scales[i]-scales[0]);
if(error > 0.001f)
{
UWARN("The marker's length detected between 2 of the "
"markers doesn't match (%fm vs %fm)."
"Parameter %s can be set to non-null to skip automatic "
"marker length estimation or set to a negative value to "
"estimate length for each marker. The current %ld detections "
"are ignored!",
scales[i], scales[0],
Parameters::kMarkerLength().c_str(),
detections.size());
detections.clear();
return detections;
}
if(error > maxError)
{
maxError = error;
}
}
sum += scales[i];
}
markerLength_ = sum/float(scales.size());
UWARN("Final marker length estimated = %fm, max error=%fm (used for subsequent detections)", markerLength_, maxError);
} }
if(imageWithDetections) if(imageWithDetections)
@@ -507,13 +537,16 @@ std::map<int, MarkerInfo> MarkerDetector::detect(const cv::Mat & image,
std::map<int, MarkerInfo>::iterator iter = detections.find(ids[i]); std::map<int, MarkerInfo>::iterator iter = detections.find(ids[i]);
if(iter!=detections.end()) if(iter!=detections.end())
{ {
int cam = cams[i];
const CameraModel & model = models[cam];
cv::Mat subImage(*imageWithDetections, cv::Rect(subRGBWidth*i, 0, subRGBWidth, imageWithDetections->rows));
cv::Vec3d rvec; cv::Vec3d rvec;
cv::Vec3d tvec(poses[i].x(), poses[i].y(), poses[i].z()); cv::Vec3d tvec(poses[i].x(), poses[i].y(), poses[i].z());
cv::Rodrigues(poses[i].rotationMatrix(), rvec); cv::Rodrigues(poses[i].rotationMatrix(), rvec);
#if CV_MAJOR_VERSION > 4 || (CV_MAJOR_VERSION == 4 && (CV_MINOR_VERSION >1 || (CV_MINOR_VERSION==1 && CV_PATCH_VERSION>=1))) #if CV_MAJOR_VERSION > 4 || (CV_MAJOR_VERSION == 4 && (CV_MINOR_VERSION >1 || (CV_MINOR_VERSION==1 && CV_PATCH_VERSION>=1)))
cv::drawFrameAxes(*imageWithDetections, model.K(), model.D(), rvec, tvec, iter->second.length() * 0.5f); cv::drawFrameAxes(subImage, model.K(), model.D(), rvec, tvec, iter->second.length() * 0.5f);
#else #else
cv::aruco::drawAxis(*imageWithDetections, model.K(), model.D(), rvec, tvec, iter->second.length() * 0.5f); cv::aruco::drawAxis(subImage, model.K(), model.D(), rvec, tvec, iter->second.length() * 0.5f);
#endif #endif
} }
} }
@@ -526,5 +559,23 @@ std::map<int, MarkerInfo> MarkerDetector::detect(const cv::Mat & image,
return detections; return detections;
} }
std::map<int, MarkerInfo> MarkerDetector::detect(const cv::Mat & image,
const CameraModel & model,
const cv::Mat & depth,
const std::map<int, float> & markerLengths,
cv::Mat * imageWithDetections)
{
if(!image.empty() && image.cols != model.imageWidth())
{
UERROR("This method cannot handle multi-camera marker detection, use the other function version supporting it.");
return std::map<int, MarkerInfo>();
}
std::vector<CameraModel> models;
models.push_back(model);
return detect(image, models, depth, markerLengths, imageWithDetections);
}
} /* namespace rtabmap */ } /* namespace rtabmap */
+2 -1
View File
@@ -1750,7 +1750,8 @@ PreferencesDialog::PreferencesDialog(QWidget * parent) :
_ui->stereosgbm_mode->setObjectName(Parameters::kStereoSGBMMode().c_str()); _ui->stereosgbm_mode->setObjectName(Parameters::kStereoSGBMMode().c_str());
// Aruco marker // Aruco marker
_ui->ArucoDictionary->setObjectName(Parameters::kMarkerDictionary().c_str()); _ui->MarkerStrategy->setObjectName(Parameters::kMarkerStrategy().c_str());
_ui->MarkerDictionary->setObjectName(Parameters::kMarkerDictionary().c_str());
_ui->ArucoMarkerLength->setObjectName(Parameters::kMarkerLength().c_str()); _ui->ArucoMarkerLength->setObjectName(Parameters::kMarkerLength().c_str());
_ui->ArucoMaxDepthError->setObjectName(Parameters::kMarkerMaxDepthError().c_str()); _ui->ArucoMaxDepthError->setObjectName(Parameters::kMarkerMaxDepthError().c_str());
_ui->ArucoVarianceLinear->setObjectName(Parameters::kMarkerVarianceLinear().c_str()); _ui->ArucoVarianceLinear->setObjectName(Parameters::kMarkerVarianceLinear().c_str());
+389 -337
View File
@@ -95,7 +95,7 @@
<enum>QFrame::Raised</enum> <enum>QFrame::Raised</enum>
</property> </property>
<property name="currentIndex"> <property name="currentIndex">
<number>14</number> <number>18</number>
</property> </property>
<widget class="QWidget" name="page_22"> <widget class="QWidget" name="page_22">
<layout class="QVBoxLayout" name="verticalLayout_29" stretch="0,0"> <layout class="QVBoxLayout" name="verticalLayout_29" stretch="0,0">
@@ -15494,73 +15494,10 @@ If set to false, classic RTAB-Map loop closure detection is done using only imag
<layout class="QVBoxLayout" name="verticalLayout_136"> <layout class="QVBoxLayout" name="verticalLayout_136">
<item> <item>
<layout class="QGridLayout" name="gridLayout_63" columnstretch="0,1"> <layout class="QGridLayout" name="gridLayout_63" columnstretch="0,1">
<item row="7" column="0"> <item row="9" column="1">
<widget class="QDoubleSpinBox" name="ArucoMarkerRangeMax"> <widget class="QLabel" name="label_space2_14">
<property name="suffix">
<string> m</string>
</property>
<property name="decimals">
<number>2</number>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>999.000000000000000</double>
</property>
<property name="singleStep">
<double>1.000000000000000</double>
</property>
<property name="value">
<double>0.000000000000000</double>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QDoubleSpinBox" name="ArucoVarianceAngular">
<property name="suffix">
<string/>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>0.000001000000000</double>
</property>
<property name="maximum">
<double>9999.000000000000000</double>
</property>
<property name="singleStep">
<double>0.001000000000000</double>
</property>
<property name="value">
<double>0.001000000000000</double>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QDoubleSpinBox" name="ArucoMarkerLength">
<property name="suffix">
<string> m</string>
</property>
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>-1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLabel" name="label_space2_15">
<property name="text"> <property name="text">
<string>World prior locations of the markers. The map will be transformed in marker's world frame when a tag is detected. Format is the marker's ID followed by its position (angles in rad), markers are separated by a vertical line (&quot;id1 x y z roll pitch yaw|id2 x y z roll pitch yaw&quot;). Example: &quot;1 0 0 1 0 0 0|2 1 0 1 0 0 1.57&quot; (marker 2 is 1 meter forward than marker 1 with 90 deg yaw rotation).</string> <string>Maximum detection range (0=unlimited).</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
@@ -15570,20 +15507,7 @@ If set to false, classic RTAB-Map loop closure detection is done using only imag
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="1"> <item row="12" column="0">
<widget class="QLabel" name="label_space2_13">
<property name="text">
<string>Minimum detection range (0=disabled).</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QDoubleSpinBox" name="ArucoPriorsVarianceAngular"> <widget class="QDoubleSpinBox" name="ArucoPriorsVarianceAngular">
<property name="suffix"> <property name="suffix">
<string/> <string/>
@@ -15605,10 +15529,10 @@ If set to false, classic RTAB-Map loop closure detection is done using only imag
</property> </property>
</widget> </widget>
</item> </item>
<item row="9" column="1"> <item row="10" column="1">
<widget class="QLabel" name="label_space2_16"> <widget class="QLabel" name="label_space2_15">
<property name="text"> <property name="text">
<string>Linear variance to set on marker priors.</string> <string>World prior locations of the markers. The map will be transformed in marker's world frame when a tag is detected. Format is the marker's ID followed by its position (angles in rad), markers are separated by a vertical line (&quot;id1 x y z roll pitch yaw|id2 x y z roll pitch yaw&quot;). Example: &quot;1 0 0 1 0 0 0|2 1 0 1 0 0 1.57&quot; (marker 2 is 1 meter forward than marker 1 with 90 deg yaw rotation).</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
@@ -15618,7 +15542,33 @@ If set to false, classic RTAB-Map loop closure detection is done using only imag
</property> </property>
</widget> </widget>
</item> </item>
<item row="10" column="1"> <item row="5" column="1">
<widget class="QLabel" name="label_space2_6">
<property name="text">
<string>Linear variance to set on marker detections. If variance is adjusted to ignore orientation (see below) and Optimizer/Strategy=2 (GTSAM): it is the variance of the range factor, with 9999 to disable range factor and to do only bearing.</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="QLabel" name="label_space2_13">
<property name="text">
<string>Minimum detection range (0=disabled).</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="QLabel" name="label_space2_17"> <widget class="QLabel" name="label_space2_17">
<property name="text"> <property name="text">
<string>Angular variance to set on marker priors.</string> <string>Angular variance to set on marker priors.</string>
@@ -15631,33 +15581,7 @@ If set to false, classic RTAB-Map loop closure detection is done using only imag
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0"> <item row="4" column="0">
<widget class="QCheckBox" name="RGBDMarkerDetection">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QDoubleSpinBox" name="ArucoVarianceLinear">
<property name="suffix">
<string/>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>0.000001000000000</double>
</property>
<property name="singleStep">
<double>0.001000000000000</double>
</property>
<property name="value">
<double>0.001000000000000</double>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QDoubleSpinBox" name="ArucoMaxDepthError"> <widget class="QDoubleSpinBox" name="ArucoMaxDepthError">
<property name="suffix"> <property name="suffix">
<string> m</string> <string> m</string>
@@ -15676,7 +15600,313 @@ If set to false, classic RTAB-Map loop closure detection is done using only imag
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1">
<widget class="QLabel" name="label_space2_10">
<property name="text">
<string>Dictionary to use.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLineEdit" name="ArucoMarkerPriors">
<property name="placeholderText">
<string/>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QComboBox" name="MarkerDictionary">
<item>
<property name="text">
<string>ARUCO_4X4_50</string>
</property>
</item>
<item>
<property name="text">
<string>ARUCO_4X4_100</string>
</property>
</item>
<item>
<property name="text">
<string>ARUCO_4X4_250</string>
</property>
</item>
<item>
<property name="text">
<string>ARUCO_4X4_1000</string>
</property>
</item>
<item>
<property name="text">
<string>ARUCO_5X5_50</string>
</property>
</item>
<item>
<property name="text">
<string>ARUCO_5X5_100</string>
</property>
</item>
<item>
<property name="text">
<string>ARUCO_5X5_250</string>
</property>
</item>
<item>
<property name="text">
<string>ARUCO_5X5_1000</string>
</property>
</item>
<item>
<property name="text">
<string>ARUCO_6X6_50</string>
</property>
</item>
<item>
<property name="text">
<string>ARUCO_6X6_100</string>
</property>
</item>
<item>
<property name="text">
<string>ARUCO_6X6_250</string>
</property>
</item>
<item>
<property name="text">
<string>ARUCO_6X6_1000</string>
</property>
</item>
<item>
<property name="text">
<string>ARUCO_7X7_50</string>
</property>
</item>
<item>
<property name="text">
<string>ARUCO_7X7_100</string>
</property>
</item>
<item>
<property name="text">
<string>ARUCO_7X7_250</string>
</property>
</item>
<item>
<property name="text">
<string>ARUCO_7X7_1000</string>
</property>
</item>
<item>
<property name="text">
<string>ARUCO_ORIGINAL</string>
</property>
</item>
<item>
<property name="text">
<string>APRILTAG_16h5</string>
</property>
</item>
<item>
<property name="text">
<string>APRILTAG_25h9</string>
</property>
</item>
<item>
<property name="text">
<string>APRILTAG_36h10</string>
</property>
</item>
<item>
<property name="text">
<string>APRILTAG_36h11</string>
</property>
</item>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label_space2_7">
<property name="text">
<string>Marker length. The length (m) of the markers' side. Value &lt;=0 means automatic marker length estimation using the depth image (the camera should look at the marker perpendicularly for initialization). If 0, the length is estimated only on the first marker detected, then re-used for all next detections (i.e., this assumes that markers have all the same length). With &lt;0, the length is estimated once for each unique marker, then re-used for next detections with the same marker ID.</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="QLabel" name="label_space2_9">
<property name="text">
<string>Angular variance to set on marker detections. If variance is adjusted to ignore orientation (see below), the angular variance is ignored with Optimizer/Strategy=1 (g2o) and it corresponds to bearing variance with Optimizer/Strategy=2 (GTSAM).</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="0">
<widget class="QCheckBox" name="RGBDMarkerDetection">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QLabel" name="label_space2_16">
<property name="text">
<string>Linear variance to set on marker priors.</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="0">
<widget class="QDoubleSpinBox" name="ArucoMarkerRangeMax">
<property name="suffix">
<string> m</string>
</property>
<property name="decimals">
<number>2</number>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>999.000000000000000</double>
</property>
<property name="singleStep">
<double>1.000000000000000</double>
</property>
<property name="value">
<double>0.000000000000000</double>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QDoubleSpinBox" name="ArucoPriorsVarianceLinear">
<property name="suffix">
<string/>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>0.000001000000000</double>
</property>
<property name="singleStep">
<double>0.001000000000000</double>
</property>
<property name="value">
<double>0.001000000000000</double>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QCheckBox" name="ArucoVarianceOrientationIgnored">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="label_space2_8">
<property name="text">
<string>Maximum depth error between all corners of a marker when estimating the marker length (when marker length above is 0). The smaller it is, the more perpendicular the camera should be toward the marker to initialize the length.</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="QLabel" name="label_space2_20">
<property name="toolTip">
<string>When this setting is false, the landmark's orientation is optimized during graph optimization. When this setting is true, only the position of the landmark is optimized. This can be useful when the landmark's orientation estimation is not reliable. Note that for Optimizer/Strategy=1 (g2o), only Marker/VarianceLinear needs be set if we ignore orientation. For Optimizer/Strategy=2 (GTSAM), instead of optimizing the landmark's position directly, a bearing/range factor is used, with Marker/VarianceLinear as the variance of the range factor (with 9999 to optimize the position with only a bearing factor) and Marker/VarianceAngular as the variance of the bearing factor (pitch/yaw).</string>
</property>
<property name="text">
<string>Adjust variance to ignore orientation during optimization. Mouseover for more details.</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"> <item row="6" column="0">
<widget class="QDoubleSpinBox" name="ArucoVarianceAngular">
<property name="suffix">
<string/>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>0.000001000000000</double>
</property>
<property name="maximum">
<double>9999.000000000000000</double>
</property>
<property name="singleStep">
<double>0.001000000000000</double>
</property>
<property name="value">
<double>0.001000000000000</double>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_markerDetection">
<property name="text">
<string>Detect static markers to be added as landmarks for graph optimization. If input data have already landmarks, this will be ignored.</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="0">
<widget class="QDoubleSpinBox" name="ArucoVarianceLinear">
<property name="suffix">
<string/>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>0.000001000000000</double>
</property>
<property name="singleStep">
<double>0.001000000000000</double>
</property>
<property name="value">
<double>0.001000000000000</double>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QDoubleSpinBox" name="ArucoMarkerRangeMin"> <widget class="QDoubleSpinBox" name="ArucoMarkerRangeMin">
<property name="suffix"> <property name="suffix">
<string> m</string> <string> m</string>
@@ -15698,88 +15928,43 @@ If set to false, classic RTAB-Map loop closure detection is done using only imag
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="3" column="0">
<widget class="QLabel" name="label_space2_8"> <widget class="QDoubleSpinBox" name="ArucoMarkerLength">
<property name="text">
<string>Maximum depth error between all corners of a marker when estimating the marker length (when marker length above is 0). The smaller it is, the more perpendicular the camera should be toward the marker to initialize the length.</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="QLabel" name="label_space2_6">
<property name="text">
<string>Linear variance to set on marker detections. If variance is adjusted to ignore orientation (see below) and Optimizer/Strategy=2 (GTSAM): it is the variance of the range factor, with 9999 to disable range factor and to do only bearing.</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="1">
<widget class="QLabel" name="label_space2_9">
<property name="text">
<string>Angular variance to set on marker detections. If variance is adjusted to ignore orientation (see below), the angular variance is ignored with Optimizer/Strategy=1 (g2o) and it corresponds to bearing variance with Optimizer/Strategy=2 (GTSAM).</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="0">
<widget class="QDoubleSpinBox" name="ArucoPriorsVarianceLinear">
<property name="suffix"> <property name="suffix">
<string/> <string> m</string>
</property> </property>
<property name="decimals"> <property name="decimals">
<number>6</number> <number>4</number>
</property> </property>
<property name="minimum"> <property name="minimum">
<double>0.000001000000000</double> <double>-1.000000000000000</double>
</property> </property>
<property name="singleStep"> <property name="singleStep">
<double>0.001000000000000</double> <double>0.010000000000000</double>
</property> </property>
<property name="value"> <property name="value">
<double>0.001000000000000</double> <double>0.100000000000000</double>
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="1"> <item row="1" column="0">
<widget class="QLabel" name="label_space2_14"> <widget class="QComboBox" name="MarkerStrategy">
<property name="text"> <item>
<string>Maximum detection range (0=unlimited).</string> <property name="text">
</property> <string>opencv-aruco</string>
<property name="wordWrap"> </property>
<bool>true</bool> </item>
</property> <item>
<property name="textInteractionFlags"> <property name="text">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set> <string>apriltag</string>
</property> </property>
</widget> </item>
</item>
<item row="8" column="0">
<widget class="QLineEdit" name="ArucoMarkerPriors">
<property name="placeholderText">
<string/>
</property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QLabel" name="label_space2_7"> <widget class="QLabel" name="label_space2_24">
<property name="text"> <property name="text">
<string>Marker length. The length (m) of the markers' side. 0 means automatic marker length estimation using the depth image (the camera should look at the marker perpendicularly for initialization).</string> <string>Detector implementation. Note that both aruco and apriltag dictionaries can be used with either strategy.</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
@@ -15789,172 +15974,14 @@ If set to false, classic RTAB-Map loop closure detection is done using only imag
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1">
<widget class="QLabel" name="label_markerDetection">
<property name="text">
<string>Detect static markers to be added as landmarks for graph optimization. If input data have already landmarks, this will be ignored.</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="1">
<widget class="QLabel" name="label_space2_20">
<property name="toolTip">
<string>When this setting is false, the landmark's orientation is optimized during graph optimization. When this setting is true, only the position of the landmark is optimized. This can be useful when the landmark's orientation estimation is not reliable. Note that for Optimizer/Strategy=1 (g2o), only Marker/VarianceLinear needs be set if we ignore orientation. For Optimizer/Strategy=2 (GTSAM), instead of optimizing the landmark's position directly, a bearing/range factor is used, with Marker/VarianceLinear as the variance of the range factor (with 9999 to optimize the position with only a bearing factor) and Marker/VarianceAngular as the variance of the bearing factor (pitch/yaw).</string>
</property>
<property name="text">
<string>Adjust variance to ignore orientation during optimization. Mouseover for more details.</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="0">
<widget class="QCheckBox" name="ArucoVarianceOrientationIgnored">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QGroupBox" name="groupBox_31"> <widget class="QGroupBox" name="groupBox_31">
<property name="title"> <property name="title">
<string>ArUco</string> <string>OpenCV</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_106" columnstretch="0,1"> <layout class="QGridLayout" name="gridLayout_106" columnstretch="0,1">
<item row="0" column="0">
<widget class="QComboBox" name="ArucoDictionary">
<item>
<property name="text">
<string>4X4_50</string>
</property>
</item>
<item>
<property name="text">
<string>4X4_100</string>
</property>
</item>
<item>
<property name="text">
<string>4X4_250</string>
</property>
</item>
<item>
<property name="text">
<string>4X4_1000</string>
</property>
</item>
<item>
<property name="text">
<string>5X5_50</string>
</property>
</item>
<item>
<property name="text">
<string>5X5_100</string>
</property>
</item>
<item>
<property name="text">
<string>5X5_250</string>
</property>
</item>
<item>
<property name="text">
<string>5X5_1000</string>
</property>
</item>
<item>
<property name="text">
<string>6X6_50</string>
</property>
</item>
<item>
<property name="text">
<string>6X6_100</string>
</property>
</item>
<item>
<property name="text">
<string>6X6_250</string>
</property>
</item>
<item>
<property name="text">
<string>6X6_1000</string>
</property>
</item>
<item>
<property name="text">
<string>7X7_50</string>
</property>
</item>
<item>
<property name="text">
<string>7X7_100</string>
</property>
</item>
<item>
<property name="text">
<string>7X7_250</string>
</property>
</item>
<item>
<property name="text">
<string>7X7_1000</string>
</property>
</item>
<item>
<property name="text">
<string>ARUCO_ORIGINAL</string>
</property>
</item>
<item>
<property name="text">
<string>APRILTAG_16h5</string>
</property>
</item>
<item>
<property name="text">
<string>APRILTAG_25h9</string>
</property>
</item>
<item>
<property name="text">
<string>APRILTAG_36h10</string>
</property>
</item>
<item>
<property name="text">
<string>APRILTAG_36h11</string>
</property>
</item>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_space2_10">
<property name="text">
<string>Dictionary to use.</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="0"> <item row="1" column="0">
<widget class="QComboBox" name="ArucoCornerRefinementMethod"> <widget class="QComboBox" name="ArucoCornerRefinementMethod">
<item> <item>
@@ -15995,6 +16022,31 @@ If set to false, classic RTAB-Map loop closure detection is done using only imag
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupBox_43">
<property name="title">
<string>AprilTag</string>
</property>
<layout class="QGridLayout" name="gridLayout_140" columnstretch="0,1">
<item row="0" column="1">
<widget class="QLabel" name="label_space2_22">
<property name="text">
<string>nthreads.</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="0">
<widget class="QSpinBox" name="apriltag_nthreads"/>
</item>
</layout>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>