41 #include <pcl/filters/filter.h>
42 #include <pcl/io/ply_io.h>
43 #include <pcl/io/obj_io.h>
44 #include <pcl/common/common.h>
45 #include <pcl/surface/poisson.h>
57 "rtabmap-export [options] database.db\n"
59 " --output \"\" Output name (default: name of the database is used).\n"
60 " --output_dir \"\" Output directory (default: same directory than the database).\n"
61 " --ascii Export PLY in ascii format.\n"
62 " --las Export cloud in LAS instead of PLY (PDAL dependency required).\n"
63 " --mesh Create a mesh.\n"
64 " --texture Create a mesh with texture.\n"
65 " --texture_size # Texture size 1024, 2048, 4096, 8192, 16384 (default 8192).\n"
66 " --texture_count # Maximum textures generated (default 1). Ignored by --multiband option (adjust --multiband_contrib instead).\n"
67 " --texture_range # Maximum camera range for texturing a polygon (default 0 meters: no limit).\n"
68 " --texture_angle # Maximum camera angle for texturing a polygon (default 0 deg: no limit).\n"
69 " --texture_depth_error # Maximum depth error between reprojected mesh and depth image to texture a face (-1=disabled, 0=edge length is used, default=0).\n"
70 " --texture_roi_ratios \"# # # #\" Region of interest from images to texture or to color scans. Format is \"left right top bottom\" (e.g. \"0 0 0 0.1\" means 10%% of the image bottom not used).\n"
71 " --texture_d2c Distance to camera policy.\n"
72 " --texture_blur # Motion blur threshold (default 0: disabled). Below this threshold, the image is considered blurred. 0 means disabled. 50 can be good default.\n"
73 " --cam_projection Camera projection on assembled cloud and export node ID on each point (in PointSourceId field).\n"
74 " --cam_projection_keep_all Keep not colored points from cameras (node ID will be 0 and color will be red).\n"
75 " --cam_projection_decimation Decimate images before projecting the points.\n"
76 " --cam_projection_mask \"\" File path for a mask. Format should be 8-bits grayscale. The mask should cover all cameras in case multi-camera is used and have the same resolution.\n"
77 " --poses Export optimized poses of the robot frame (e.g., base_link).\n"
78 " --poses_camera Export optimized poses of the camera frame (e.g., optical frame).\n"
79 " --poses_scan Export optimized poses of the scan frame.\n"
80 " --poses_format # Format used for exported poses (default is 11):\n"
81 " 0=Raw 3x4 transformation matrix (r11 r12 r13 tx r21 r22 r23 ty r31 r32 r33 tz)\n"
82 " 1=RGBD-SLAM (in motion capture coordinate frame)\n"
83 " 2=KITTI (same as raw but in optical frame)\n"
86 " 10=RGBD-SLAM in ROS coordinate frame (stamp x y z qx qy qz qw)\n"
87 " 11=RGBD-SLAM in ROS coordinate frame + ID (stamp x y z qx qy qz qw id)\n"
88 " --images Export images with stamp as file name.\n"
89 " --images_id Export images with node id as file name.\n"
90 " --ba Do global bundle adjustment before assembling the clouds.\n"
91 " --gain # Gain compensation value (default 1, set 0 to disable).\n"
92 " --gain_gray Do gain estimation compensation on gray channel only (default RGB channels).\n"
93 " --no_blending Disable blending when texturing.\n"
94 " --no_clean Disable cleaning colorless polygons.\n"
95 " --min_cluster # When meshing, filter clusters of polygons with size less than this threshold (default 200, -1 means keep only biggest contiguous surface).\n"
96 " --low_gain # Low brightness gain 0-100 (default 0).\n"
97 " --high_gain # High brightness gain 0-100 (default 10).\n"
98 " --multiband Enable multiband texturing (AliceVision dependency required).\n"
99 " --multiband_downscale # Downscaling reduce the texture quality but speed up the computation time (default 2).\n"
100 " --multiband_contrib \"# # # # \" Number of contributions per frequency band for the multi-band blending, should be 4 values! (default \"1 5 10 0\").\n"
101 " --multiband_unwrap # Method to unwrap input mesh: 0=basic (default, >600k faces, fast), 1=ABF (<=300k faces, generate 1 atlas), 2=LSCM (<=600k faces, optimize space).\n"
102 " --multiband_fillholes Fill Texture holes with plausible values.\n"
103 " --multiband_padding # Texture edge padding size in pixel (0-100) (default 5).\n"
104 " --multiband_scorethr # 0 to disable filtering based on threshold to relative best score (0.0-1.0). (default 0.1).\n"
105 " --multiband_anglethr # 0 to disable angle hard threshold filtering (0.0, 180.0) (default 90.0).\n"
106 " --multiband_forcevisible Triangle visibility is based on the union of vertices visibility.\n"
107 " --poisson_depth # Set Poisson depth for mesh reconstruction.\n"
108 " --poisson_size # Set target polygon size when computing Poisson's depth for mesh reconstruction (default 0.03 m).\n"
109 " --max_polygons # Maximum polygons when creating a mesh (default 300000, set 0 for no limit).\n"
110 " --min_range # Minimum range of the created clouds (default 0 m).\n"
111 " --max_range # Maximum range of the created clouds (default 4 m, 0 m with --scan).\n"
112 " --decimation # Depth image decimation before creating the clouds (default 4, 1 with --scan).\n"
113 " --voxel # Voxel size of the created clouds (default 0.01 m, 0 m with --scan).\n"
114 " --ground_normals_up # Flip ground normals up if close to -z axis (default 0, 0=disabled, value should be >0 and <1, typical 0.9).\n"
115 " --noise_radius # Noise filtering search radius (default 0, 0=disabled).\n"
116 " --noise_k # Noise filtering minimum neighbors in search radius (default 5, 0=disabled).\n"
117 " --prop_radius_factor # Proportional radius filter factor (default 0, 0=disabled). Start tuning from 0.01.\n"
118 " --prop_radius_scale # Proportional radius filter neighbor scale (default 2).\n"
119 " --random_samples # Number of output samples using a random filter (default 0, 0=disabled).\n"
120 " --color_radius # Radius used to colorize polygons (default 0.05 m, 0 m with --scan). Set 0 for nearest color.\n"
121 " --scan Use laser scan for the point cloud.\n"
122 " --save_in_db Save resulting assembled point cloud or mesh in the database.\n"
123 " --xmin # Minimum range on X axis to keep nodes to export.\n"
124 " --xmax # Maximum range on X axis to keep nodes to export.\n"
125 " --ymin # Minimum range on Y axis to keep nodes to export.\n"
126 " --ymax # Maximum range on Y axis to keep nodes to export.\n"
127 " --zmin # Minimum range on Z axis to keep nodes to export.\n"
128 " --zmax # Maximum range on Z axis to keep nodes to export.\n"
129 " --filter_ceiling # Filter points over a custom height (default 0 m, 0=disabled).\n"
130 " --filter_floor # Filter points below a custom height (default 0 m, 0=disabled).\n"
139 virtual bool callback(
const std::string & msg)
const
142 printf(
"%s\n",
msg.c_str());
160 bool texture =
false;
162 bool doGainCompensationRGB =
true;
164 bool doBlending =
true;
166 int minCluster = 200;
167 int poissonDepth = 0;
168 float poissonSize = 0.03;
169 int maxPolygons = 300000;
171 float minRange = 0.0f;
172 float maxRange = -1.0f;
173 float voxelSize = -1.0f;
174 float groundNormalsUp = 0.0f;
175 float noiseRadius = 0.0f;
176 int noiseMinNeighbors = 5;
177 float proportionalRadiusFactor = 0.0f;
178 float proportionalRadiusScale = 2.0f;
179 int randomSamples = 0;
180 int textureSize = 8192;
181 int textureCount = 1;
182 float textureRange = 0;
183 float textureAngle = 0;
184 float textureDepthError = 0;
185 std::vector<float> textureRoiRatios;
186 bool distanceToCamPolicy =
false;
187 int laplacianThr = 0;
188 bool multiband =
false;
189 int multibandDownScale = 2;
190 std::string multibandNbContrib =
"1 5 10 0";
191 int multibandUnwrap = 0;
192 bool multibandFillHoles =
false;
193 int multibandPadding = 5;
194 double multibandBestScoreThr = 0.1;
195 double multibandAngleHardthr = 90;
196 bool multibandForceVisible =
false;
197 float colorRadius = -1.0f;
198 bool cloudFromScan =
false;
199 bool saveInDb =
false;
200 int lowBrightnessGain = 0;
201 int highBrightnessGain = 10;
202 bool camProjection =
false;
203 bool camProjectionKeepAll =
false;
204 int cameraProjDecimation = 1;
205 std::string cameraProjMask;
207 bool exportPosesCamera =
false;
208 bool exportPosesScan =
false;
209 int exportPosesFormat = 11;
210 bool exportImages =
false;
211 bool exportImagesId =
false;
212 std::string outputName;
213 std::string outputDir;
215 float filter_ceiling = 0.0f;
216 float filter_floor = 0.0f;
217 for(
int i=1;
i<argc; ++
i)
219 if(std::strcmp(
argv[
i],
"--help") == 0)
223 else if(std::strcmp(
argv[
i],
"--output") == 0)
228 outputName =
argv[
i];
235 else if(std::strcmp(
argv[
i],
"--output_dir") == 0)
247 else if(std::strcmp(
argv[
i],
"--bin") == 0)
249 printf(
"No need to set --bin anymore, ply are now automatically exported in binary by default. Set --ascii to export as text.\n");
251 else if(std::strcmp(
argv[
i],
"--ascii") == 0)
255 else if(std::strcmp(
argv[
i],
"--las") == 0)
259 #elif defined(RTABMAP_LIBLAS)
260 printf(
"\"--las\" option cannot be used with libLAS because the cloud has normals, build RTAB-Map with PDAL support to export in las with normals. Will export in PLY...\n");
262 printf(
"\"--las\" option cannot be used because RTAB-Map is not built with PDAL support. Will export in PLY...\n");
266 else if(std::strcmp(
argv[
i],
"--mesh") == 0)
270 else if(std::strcmp(
argv[
i],
"--texture") == 0)
274 else if(std::strcmp(
argv[
i],
"--texture_size") == 0)
287 else if(std::strcmp(
argv[
i],
"--texture_count") == 0)
299 else if(std::strcmp(
argv[
i],
"--texture_range") == 0)
311 else if(std::strcmp(
argv[
i],
"--texture_angle") == 0)
323 else if(std::strcmp(
argv[
i],
"--texture_depth_error") == 0)
335 else if(std::strcmp(
argv[
i],
"--texture_roi_ratios") == 0)
340 std::list<std::string> strValues =
uSplit(
argv[
i],
' ');
341 if(strValues.size() != 4)
343 printf(
"The number of values must be 4 (roi=\"%s\")\n",
argv[
i]);
348 std::vector<float> tmpValues(4);
350 for(std::list<std::string>::iterator jter = strValues.begin(); jter!=strValues.end(); ++jter)
356 if(tmpValues[0] >= 0 && tmpValues[0] < 1 && tmpValues[0] < 1.0
f-tmpValues[1] &&
357 tmpValues[1] >= 0 && tmpValues[1] < 1 && tmpValues[1] < 1.0
f-tmpValues[0] &&
358 tmpValues[2] >= 0 && tmpValues[2] < 1 && tmpValues[2] < 1.0
f-tmpValues[3] &&
359 tmpValues[3] >= 0 && tmpValues[3] < 1 && tmpValues[3] < 1.0
f-tmpValues[2])
361 textureRoiRatios = tmpValues;
365 printf(
"The roi ratios are not valid (roi=\"%s\")\n",
argv[
i]);
375 else if(std::strcmp(
argv[
i],
"--texture_d2c") == 0)
377 distanceToCamPolicy =
true;
379 else if(std::strcmp(
argv[
i],
"--texture_blur") == 0)
391 else if(std::strcmp(
argv[
i],
"--cam_projection") == 0)
393 camProjection =
true;
395 else if(std::strcmp(
argv[
i],
"--cam_projection_keep_all") == 0)
397 camProjectionKeepAll =
true;
399 else if(std::strcmp(
argv[
i],
"--cam_projection_decimation") == 0)
405 if(cameraProjDecimation<1)
407 printf(
"--cam_projection_decimation cannot be <1! value=\"%s\"\n",
argv[
i]);
416 else if(std::strcmp(
argv[
i],
"--cam_projection_mask") == 0)
421 cameraProjMask =
argv[
i];
424 printf(
"--cam_projection_mask is set with a file not existing or don't have permissions to open it. Path=\"%s\"\n",
argv[
i]);
433 else if(std::strcmp(
argv[
i],
"--poses") == 0)
437 else if(std::strcmp(
argv[
i],
"--poses_camera") == 0)
439 exportPosesCamera =
true;
441 else if(std::strcmp(
argv[
i],
"--poses_scan") == 0)
443 exportPosesScan =
true;
445 else if(std::strcmp(
argv[
i],
"--poses_format") == 0)
457 else if(std::strcmp(
argv[
i],
"--images") == 0)
461 else if(std::strcmp(
argv[
i],
"--images_id") == 0)
464 exportImagesId =
true;
466 else if(std::strcmp(
argv[
i],
"--ba") == 0)
470 else if(std::strcmp(
argv[
i],
"--gain_gray") == 0)
472 doGainCompensationRGB =
false;
474 else if(std::strcmp(
argv[
i],
"--gain") == 0)
487 else if(std::strcmp(
argv[
i],
"--no_blending") == 0)
491 else if(std::strcmp(
argv[
i],
"--no_clean") == 0)
495 else if(std::strcmp(
argv[
i],
"--min_cluster") == 0)
507 else if(std::strcmp(
argv[
i],
"--multiband") == 0)
509 #ifdef RTABMAP_ALICE_VISION
512 printf(
"\"--multiband\" option cannot be used because RTAB-Map is not built with AliceVision support. Ignoring multiband...\n");
515 else if(std::strcmp(
argv[
i],
"--multiband_fillholes") == 0)
517 multibandFillHoles =
true;
519 else if(std::strcmp(
argv[
i],
"--multiband_downscale") == 0)
531 else if(std::strcmp(
argv[
i],
"--multiband_contrib") == 0)
538 printf(
"--multiband_contrib has wrong format! value=\"%s\"\n",
argv[
i]);
541 multibandNbContrib =
argv[
i];
548 else if(std::strcmp(
argv[
i],
"--multiband_unwrap") == 0)
560 else if(std::strcmp(
argv[
i],
"--multiband_padding") == 0)
572 else if(std::strcmp(
argv[
i],
"--multiband_forcevisible") == 0)
574 multibandForceVisible =
true;
576 else if(std::strcmp(
argv[
i],
"--multiband_scorethr") == 0)
588 else if(std::strcmp(
argv[
i],
"--multiband_anglethr") == 0)
600 else if(std::strcmp(
argv[
i],
"--poisson_depth") == 0)
612 else if(std::strcmp(
argv[
i],
"--poisson_size") == 0)
624 else if(std::strcmp(
argv[
i],
"--max_polygons") == 0)
636 else if(std::strcmp(
argv[
i],
"--min_range") == 0)
648 else if(std::strcmp(
argv[
i],
"--max_range") == 0)
660 else if(std::strcmp(
argv[
i],
"--decimation") == 0)
672 else if(std::strcmp(
argv[
i],
"--voxel") == 0)
684 else if(std::strcmp(
argv[
i],
"--ground_normals_up") == 0)
696 else if(std::strcmp(
argv[
i],
"--noise_radius") == 0)
708 else if(std::strcmp(
argv[
i],
"--noise_k") == 0)
720 else if(std::strcmp(
argv[
i],
"--prop_radius_factor") == 0)
732 else if(std::strcmp(
argv[
i],
"--prop_radius_scale") == 0)
738 UASSERT_MSG(proportionalRadiusScale>=1.0
f,
"--prop_radius_scale should be >= 1.0");
745 else if(std::strcmp(
argv[
i],
"--random_samples") == 0)
757 else if(std::strcmp(
argv[
i],
"--color_radius") == 0)
769 else if(std::strcmp(
argv[
i],
"--scan") == 0)
771 cloudFromScan =
true;
773 else if(std::strcmp(
argv[
i],
"--save_in_db") == 0)
777 else if(std::strcmp(
argv[
i],
"--low_gain") == 0)
789 else if(std::strcmp(
argv[
i],
"--high_gain") == 0)
801 else if(std::strcmp(
argv[
i],
"--xmin") == 0)
813 else if(std::strcmp(
argv[
i],
"--xmax") == 0)
825 else if(std::strcmp(
argv[
i],
"--ymin") == 0)
837 else if(std::strcmp(
argv[
i],
"--ymax") == 0)
849 else if(std::strcmp(
argv[
i],
"--zmin") == 0)
861 else if(std::strcmp(
argv[
i],
"--zmax") == 0)
873 else if(std::strcmp(
argv[
i],
"--filter_ceiling") == 0)
879 if(filter_floor!=0.0
f && filter_ceiling != 0.0
f && filter_ceiling<filter_floor)
881 printf(
"Option --filter_ceiling (%f) should be higher than --filter_floor option (%f)!\n", filter_ceiling, filter_floor);
890 else if(std::strcmp(
argv[
i],
"--filter_floor") == 0)
896 if(filter_floor!=0.0
f && filter_ceiling != 0.0
f && filter_ceiling<filter_floor)
898 printf(
"Option --filter_ceiling (%f) should be higher than --filter_floor option (%f)!\n", filter_ceiling, filter_floor);
912 decimation = cloudFromScan?1:4;
916 maxRange = cloudFromScan?0:4;
920 voxelSize = cloudFromScan?0:0.01f;
922 if(colorRadius < 0.0
f)
924 colorRadius = cloudFromScan?0:0.05f;
931 printf(
"Option --multiband is not supported with --save_in_db option, disabling multiband...\n");
936 printf(
"Option --texture_count > 1 is not supported with --save_in_db option, setting texture_count to 1...\n");
943 std::string dbPath =
argv[argc-1];
947 UERROR(
"File \"%s\" doesn't exist!", dbPath.c_str());
961 UERROR(
"Cannot open database %s!", dbPath.c_str());
969 printf(
"Added custom parameter %s=%s\n",
iter->first.c_str(),
iter->second.c_str());
974 printf(
"Loading database \"%s\"...\n", dbPath.c_str());
978 rtabmap.init(parameters, dbPath);
979 printf(
"Loading database \"%s\"... done (%fs).\n", dbPath.c_str(),
timer.ticks());
981 std::map<int, Signature>
nodes;
982 std::map<int, Transform> optimizedPoses;
983 std::multimap<int, Link> links;
984 printf(
"Optimizing the map...\n");
985 rtabmap.getGraph(optimizedPoses, links,
true,
true, &
nodes,
true,
true,
true,
true);
986 printf(
"Optimizing the map... done (%fs, poses=%d).\n",
timer.ticks(), (
int)optimizedPoses.size());
988 if(optimizedPoses.empty())
990 printf(
"The optimized graph is empty!? Aborting...\n");
998 printf(
"Filtering poses (range: x=%f<->%f, y=%f<->%f, z=%f<->%f, map size=%f x %f x %f)...\n",
1000 maxP[0]-minP[0],maxP[1]-minP[1],maxP[2]-minP[2]);
1001 std::map<int, Transform> posesFiltered;
1002 for(std::map<int, Transform>::const_iterator
iter=optimizedPoses.begin();
iter!=optimizedPoses.end(); ++
iter)
1004 bool ignore =
false;
1019 posesFiltered.insert(*
iter);
1023 printf(
"Filtering poses... done! %d/%d remaining (new map size=%f x %f x %f).\n", (
int)posesFiltered.size(), (
int)optimizedPoses.size(), maxP[0]-minP[0],maxP[1]-minP[1],maxP[2]-minP[2]);
1024 optimizedPoses = posesFiltered;
1025 if(optimizedPoses.empty())
1031 std::string outputDirectory = outputDir.empty()?
UDirectory::getDir(dbPath):outputDir;
1036 std::string baseName = outputName.empty()?
uSplit(
UFile::getName(dbPath),
'.').front():outputName;
1040 printf(
"Global bundle adjustment...\n");
1042 optimizedPoses = ((
Optimizer*)&
g2o)->optimizeBA(optimizedPoses.lower_bound(1)->first, optimizedPoses, links,
nodes,
true);
1043 printf(
"Global bundle adjustment... done (%fs).\n",
timer.ticks());
1047 printf(
"Create and assemble the clouds...\n");
1048 pcl::PointCloud<pcl::PointXYZRGB>::Ptr assembledCloud(
new pcl::PointCloud<pcl::PointXYZRGB>);
1049 pcl::PointCloud<pcl::PointXYZI>::Ptr assembledCloudI(
new pcl::PointCloud<pcl::PointXYZI>);
1050 std::map<int, rtabmap::Transform> robotPoses;
1051 std::vector<std::map<int, rtabmap::Transform> >
cameraPoses;
1052 std::map<int, rtabmap::Transform> scanPoses;
1053 std::map<int, double> cameraStamps;
1054 std::map<int, std::vector<rtabmap::CameraModel> > cameraModels;
1055 std::map<int, cv::Mat> cameraDepths;
1056 int imagesExported = 0;
1057 std::vector<int> rawViewpointIndices;
1058 std::map<int, Transform> rawViewpoints;
1059 for(std::map<int, Transform>::iterator
iter=optimizedPoses.lower_bound(1);
iter!=optimizedPoses.end(); ++
iter)
1069 pcl::IndicesPtr
indices(
new std::vector<int>);
1070 pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud;
1071 pcl::PointCloud<pcl::PointXYZI>::Ptr cloudI;
1081 printf(
"Node %d doesn't have scan data, empty cloud is created.\n",
iter->first);
1083 if(decimation>1 || minRange>0.0
f || maxRange)
1090 if(noiseRadius>0.0
f && noiseMinNeighbors>0)
1098 if(noiseRadius>0.0
f && noiseMinNeighbors>0)
1109 printf(
"Node %d doesn't have depth or stereo data, empty cloud is "
1110 "created (if you want to create point cloud from scan, use --scan option).\n",
iter->first);
1118 if(noiseRadius>0.0
f && noiseMinNeighbors>0)
1125 if(exportImages && !rgb.empty())
1127 std::string dirSuffix = (depth.type() != CV_16UC1 && depth.type() != CV_32FC1 && !depth.empty())?
"left":
"rgb";
1128 std::string dir = outputDirectory+
"/"+baseName+
"_"+dirSuffix;
1133 cv::imwrite(outputPath, rgb);
1138 cv::Mat depthExported = depth;
1139 if(depth.type() != CV_16UC1 && depth.type() != CV_32FC1)
1142 dir = outputDirectory+
"/"+baseName+
"_right";
1147 dir = outputDirectory+
"/"+baseName+
"_depth";
1148 if(depth.type() == CV_32FC1)
1158 cv::imwrite(outputPath, depthExported);
1162 for(
size_t i=0;
i<models.size(); ++
i)
1166 if(models.size() > 1) {
1169 model.setName(modelName);
1170 std::string dir = outputDirectory+
"/"+baseName+
"_calib";
1176 for(
size_t i=0;
i<stereoModels.size(); ++
i)
1180 if(stereoModels.size() > 1) {
1183 model.setName(modelName,
"left",
"right");
1184 std::string dir = outputDirectory+
"/"+baseName+
"_calib";
1194 if(cloud.get() && !cloud->empty())
1196 else if(cloudI.get() && !cloudI->empty())
1199 if(cloud.get() && !cloud->empty())
1201 else if(cloudI.get() && !cloudI->empty())
1204 if(filter_ceiling != 0.0 || filter_floor != 0.0
f)
1206 if(cloud.get() && !cloud->empty())
1210 if(cloudI.get() && !cloudI->empty())
1219 rawViewpoints.insert(std::make_pair(
iter->first, lidarViewpoint));
1224 rawViewpoints.insert(std::make_pair(
iter->first, cameraViewpoint));
1229 rawViewpoints.insert(std::make_pair(
iter->first, cameraViewpoint));
1233 rawViewpoints.insert(*
iter);
1236 if(cloud.get() && !cloud->empty())
1238 if(assembledCloud->empty())
1240 *assembledCloud = *cloud;
1244 *assembledCloud += *cloud;
1246 rawViewpointIndices.resize(assembledCloud->size(),
iter->first);
1248 else if(cloudI.get() && !cloudI->empty())
1250 if(assembledCloudI->empty())
1252 *assembledCloudI = *cloudI;
1256 *assembledCloudI += *cloudI;
1258 rawViewpointIndices.resize(assembledCloudI->size(),
iter->first);
1269 robotPoses.insert(std::make_pair(
iter->first,
iter->second));
1270 cameraStamps.insert(std::make_pair(
iter->first, node.
getStamp()));
1271 if(models.empty() && node.
getWeight() == -1 && !cameraModels.empty())
1274 models = cameraModels.rbegin()->second;
1280 cameraModels.insert(std::make_pair(
iter->first, models));
1282 if(exportPosesCamera)
1288 UASSERT_MSG(models.size() ==
cameraPoses.size(),
"Not all nodes have same number of cameras to export camera poses.");
1289 for(
size_t i=0;
i<models.size(); ++
i)
1295 if(!depth.empty() && (depth.type() == CV_16UC1 || depth.type() == CV_32FC1))
1297 cameraDepths.insert(std::make_pair(
iter->first, depth));
1304 printf(
"Create and assemble the clouds... done (%fs, %d points).\n",
timer.ticks(), !assembledCloud->empty()?(
int)assembledCloud->size():(
int)assembledCloudI->size());
1306 if(imagesExported>0)
1307 printf(
"%d images exported!\n", imagesExported);
1311 if(!assembledCloud->empty() || !assembledCloudI->empty())
1325 std::string posesExt = (exportPosesFormat==3?
"toro":exportPosesFormat==4?
"g2o":
"txt");
1328 std::string outputPath=outputDirectory+
"/"+baseName+
"_poses." + posesExt;
1330 printf(
"Poses exported to \"%s\".\n", outputPath.c_str());
1332 if(exportPosesCamera)
1336 std::string outputPath;
1338 outputPath = outputDirectory+
"/"+baseName+
"_camera_poses." + posesExt;
1340 outputPath = outputDirectory+
"/"+baseName+
"_camera_poses_"+
uNumber2Str((
int)
i)+
"." + posesExt;
1342 printf(
"Camera poses exported to \"%s\".\n", outputPath.c_str());
1347 std::string outputPath=outputDirectory+
"/"+baseName+
"_scan_poses." + posesExt;
1349 printf(
"Scan poses exported to \"%s\".\n", outputPath.c_str());
1353 if(proportionalRadiusFactor>0.0
f && proportionalRadiusScale>=1.0
f)
1355 printf(
"Proportional radius filtering of the assembled cloud... (factor=%f scale=%f, %d points)\n", proportionalRadiusFactor, proportionalRadiusScale, !assembledCloud->empty()?(
int)assembledCloud->size():(
int)assembledCloudI->size());
1357 if(!assembledCloud->empty())
1360 pcl::PointCloud<pcl::PointXYZRGB> tmp;
1361 pcl::copyPointCloud(*assembledCloud, *
indices, tmp);
1362 *assembledCloud = tmp;
1364 else if(!assembledCloudI->empty())
1367 pcl::PointCloud<pcl::PointXYZI> tmp;
1368 pcl::copyPointCloud(*assembledCloudI, *
indices, tmp);
1369 *assembledCloudI = tmp;
1373 std::vector<int> rawCameraIndicesTmp(
indices->size());
1374 for (std::size_t
i = 0;
i <
indices->size(); ++
i)
1375 rawCameraIndicesTmp[
i] = rawViewpointIndices[
indices->at(
i)];
1376 rawViewpointIndices = rawCameraIndicesTmp;
1378 printf(
"Proportional radius filtering of the assembled cloud.... done! (%fs, %d points)\n",
timer.ticks(), !assembledCloud->empty()?(
int)assembledCloud->size():(
int)assembledCloudI->size());
1381 pcl::PointCloud<pcl::PointXYZ>::Ptr rawAssembledCloud(
new pcl::PointCloud<pcl::PointXYZ>);
1382 if(!assembledCloud->empty())
1383 pcl::copyPointCloud(*assembledCloud, *rawAssembledCloud);
1384 else if(!assembledCloudI->empty())
1385 pcl::copyPointCloud(*assembledCloudI, *rawAssembledCloud);
1387 pcl::PointCloud<pcl::PointXYZ>::Ptr cloudWithoutNormals = rawAssembledCloud;
1391 printf(
"Voxel grid filtering of the assembled cloud... (voxel=%f, %d points)\n", voxelSize, !assembledCloud->empty()?(
int)assembledCloud->size():(
int)assembledCloudI->size());
1392 if(!assembledCloud->empty())
1395 cloudWithoutNormals.reset(
new pcl::PointCloud<pcl::PointXYZ>);
1396 pcl::copyPointCloud(*assembledCloud, *cloudWithoutNormals);
1398 else if(!assembledCloudI->empty())
1401 cloudWithoutNormals.reset(
new pcl::PointCloud<pcl::PointXYZ>);
1402 pcl::copyPointCloud(*assembledCloudI, *cloudWithoutNormals);
1404 printf(
"Voxel grid filtering of the assembled cloud.... done! (%fs, %d points)\n",
timer.ticks(), !assembledCloud->empty()?(
int)assembledCloud->size():(
int)assembledCloudI->size());
1407 printf(
"Computing normals of the assembled cloud... (k=20, %d points)\n", !assembledCloud->empty()?(
int)assembledCloud->size():(
int)assembledCloudI->size());
1410 pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloudToExport(
new pcl::PointCloud<pcl::PointXYZRGBNormal>);
1411 pcl::PointCloud<pcl::PointXYZINormal>::Ptr cloudIToExport(
new pcl::PointCloud<pcl::PointXYZINormal>);
1412 if(!assembledCloud->empty())
1414 UASSERT(assembledCloud->size() == normals->size());
1415 pcl::concatenateFields(*assembledCloud, *normals, *cloudToExport);
1416 printf(
"Computing normals of the assembled cloud... done! (%fs, %d points)\n",
timer.ticks(), (
int)assembledCloud->size());
1417 assembledCloud->clear();
1420 printf(
"Adjust normals to viewpoints of the assembled cloud... (%d points)\n", (
int)cloudToExport->size());
1424 rawViewpointIndices,
1427 printf(
"Adjust normals to viewpoints of the assembled cloud... (%fs, %d points)\n",
timer.ticks(), (
int)cloudToExport->size());
1429 else if(!assembledCloudI->empty())
1431 UASSERT(assembledCloudI->size() == normals->size());
1432 pcl::concatenateFields(*assembledCloudI, *normals, *cloudIToExport);
1433 printf(
"Computing normals of the assembled cloud... done! (%fs, %d points)\n",
timer.ticks(), (
int)assembledCloudI->size());
1434 assembledCloudI->clear();
1437 printf(
"Adjust normals to viewpoints of the assembled cloud... (%d points)\n", (
int)cloudIToExport->size());
1441 rawViewpointIndices,
1444 printf(
"Adjust normals to viewpoints of the assembled cloud... (%fs, %d points)\n",
timer.ticks(), (
int)cloudIToExport->size());
1446 cloudWithoutNormals->clear();
1447 rawAssembledCloud->clear();
1451 printf(
"Random samples filtering of the assembled cloud... (samples=%d, %d points)\n", randomSamples, !cloudToExport->empty()?(
int)cloudToExport->size():(
int)cloudIToExport->size());
1452 if(!cloudToExport->empty())
1456 else if(!cloudIToExport->empty())
1460 printf(
"Random samples filtering of the assembled cloud.... done! (%fs, %d points)\n",
timer.ticks(), !cloudToExport->empty()?(
int)cloudToExport->size():(
int)cloudIToExport->size());
1463 std::vector<int> pointToCamId;
1464 std::vector<float> pointToCamIntensity;
1465 if(camProjection && !robotPoses.empty())
1467 printf(
"Camera projection...\n");
1468 std::map<int, std::vector<rtabmap::CameraModel> > cameraModelsProj;
1469 if(cameraProjDecimation>1)
1471 for(std::map<
int, std::vector<rtabmap::CameraModel> >::
iterator iter=cameraModels.begin();
1472 iter!=cameraModels.end();
1475 std::vector<rtabmap::CameraModel> models;
1476 for(
size_t i=0;
i<
iter->second.size(); ++
i)
1478 models.push_back(
iter->second[
i].scaled(1.0/
double(cameraProjDecimation)));
1480 cameraModelsProj.insert(std::make_pair(
iter->first, models));
1485 cameraModelsProj = cameraModels;
1490 printf(
"Camera projection... projecting cloud to individual cameras (--images option)\n");
1492 pcl::PCLPointCloud2::Ptr cloud2(
new pcl::PCLPointCloud2);
1493 if(!cloudToExport->empty())
1495 pcl::toPCLPointCloud2(*cloudToExport, *cloud2);
1497 else if(!cloudIToExport->empty())
1499 pcl::toPCLPointCloud2(*cloudIToExport, *cloud2);
1502 std::string dir = outputDirectory+
"/"+baseName+
"_depth_from_scan";
1507 for(std::map<
int, std::vector<rtabmap::CameraModel> >::
iterator iter=cameraModelsProj.begin();
1508 iter!=cameraModelsProj.end();
1511 cv::Mat depth(
iter->second.front().imageHeight(),
iter->second.front().imageWidth()*
iter->second.size(), CV_32FC1);
1512 for(
size_t i=0;
i<
iter->second.size(); ++
i)
1515 iter->second.at(
i).imageSize(),
1516 iter->second.at(
i).K(),
1518 robotPoses.at(
iter->first) *
iter->second.at(
i).localTransform());
1519 subDepth.copyTo(depth(
cv::Range::all(), cv::Range(
i*
iter->second.front().imageWidth(), (
i+1)*
iter->second.front().imageWidth())));
1524 cv::imwrite(outputPath, depth);
1529 if(!cameraProjMask.empty())
1531 projMask = cv::imread(cameraProjMask, cv::IMREAD_GRAYSCALE);
1532 if(cameraProjDecimation>1)
1534 cv::Mat
out = projMask;
1535 cv::resize(projMask,
out, cv::Size(), 1.0
f/
float(cameraProjDecimation), 1.0
f/
float(cameraProjDecimation), cv::INTER_NEAREST);
1540 printf(
"Camera projection... projecting cloud to all cameras\n");
1541 pointToCamId.resize(!cloudToExport->empty()?cloudToExport->size():cloudIToExport->size());
1542 std::vector<std::pair< std::pair<int, int>, pcl::PointXY> > pointToPixel;
1543 if(!cloudToExport->empty())
1553 distanceToCamPolicy,
1556 else if(!cloudIToExport->empty())
1566 distanceToCamPolicy,
1568 pointToCamIntensity.resize(pointToPixel.size());
1571 printf(
"Camera projection... coloring the cloud\n");
1573 UASSERT(pointToPixel.empty() || pointToPixel.size() == pointToCamId.size());
1574 pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr assembledCloudValidPoints(
new pcl::PointCloud<pcl::PointXYZRGBNormal>());
1575 assembledCloudValidPoints->resize(pointToCamId.size());
1578 for(std::map<int, rtabmap::Transform>::iterator
iter=robotPoses.begin();
iter!=robotPoses.end(); ++
iter)
1580 int nodeID =
iter->first;
1584 nodes.at(nodeID).sensorData().uncompressDataConst(&image, 0);
1588 if(cameraProjDecimation>1)
1592 UASSERT(cameraModelsProj.find(nodeID) != cameraModelsProj.end());
1593 int modelsSize = cameraModelsProj.at(nodeID).size();
1594 for(
size_t i=0;
i<pointToPixel.size(); ++
i)
1596 int cameraIndex = pointToPixel[
i].first.second;
1597 if(nodeID == pointToPixel[
i].
first.first && cameraIndex>=0)
1599 pcl::PointXYZRGBNormal pt;
1600 float intensity = 0;
1601 if(!cloudToExport->empty())
1603 pt = cloudToExport->at(
i);
1605 else if(!cloudIToExport->empty())
1607 pt.x = cloudIToExport->at(
i).x;
1608 pt.y = cloudIToExport->at(
i).y;
1609 pt.z = cloudIToExport->at(
i).z;
1610 pt.normal_x = cloudIToExport->at(
i).normal_x;
1611 pt.normal_y = cloudIToExport->at(
i).normal_y;
1612 pt.normal_z = cloudIToExport->at(
i).normal_z;
1613 intensity = cloudIToExport->at(
i).intensity;
1616 int subImageWidth = image.cols / modelsSize;
1617 cv::Mat subImage = image(
cv::Range::all(), cv::Range(cameraIndex*subImageWidth, (cameraIndex+1)*subImageWidth));
1619 int x = pointToPixel[
i].second.x * (
float)subImage.cols;
1620 int y = pointToPixel[
i].second.y * (
float)subImage.rows;
1624 if(subImage.type()==CV_8UC3)
1626 cv::Vec3b bgr = subImage.at<cv::Vec3b>(
y,
x);
1633 UASSERT(subImage.type()==CV_8UC1);
1634 pt.r = pt.g = pt.b = subImage.at<
unsigned char>(pointToPixel[
i].second.y * subImage.rows, pointToPixel[
i].second.x * subImage.cols);
1637 int exportedId = nodeID;
1638 pointToCamId[
i] = exportedId;
1639 if(!pointToCamIntensity.empty())
1641 pointToCamIntensity[
i] = intensity;
1643 assembledCloudValidPoints->at(
i) = pt;
1647 UINFO(
"Processed %d/%d images", imagesDone++, (
int)robotPoses.size());
1650 pcl::IndicesPtr validIndices(
new std::vector<int>(pointToPixel.size()));
1652 for(
size_t i=0;
i<pointToPixel.size(); ++
i)
1654 if(pointToPixel[
i].
first.first <=0)
1656 if(camProjectionKeepAll)
1658 pcl::PointXYZRGBNormal pt;
1659 float intensity = 0;
1660 if(!cloudToExport->empty())
1662 pt = cloudToExport->at(
i);
1664 else if(!cloudIToExport->empty())
1666 pt.x = cloudIToExport->at(
i).x;
1667 pt.y = cloudIToExport->at(
i).y;
1668 pt.z = cloudIToExport->at(
i).z;
1669 pt.normal_x = cloudIToExport->at(
i).normal_x;
1670 pt.normal_y = cloudIToExport->at(
i).normal_y;
1671 pt.normal_z = cloudIToExport->at(
i).normal_z;
1672 intensity = cloudIToExport->at(
i).intensity;
1675 pointToCamId[
i] = 0;
1679 if(!pointToCamIntensity.empty())
1681 pointToCamIntensity[
i] = intensity;
1683 assembledCloudValidPoints->at(
i) = pt;
1684 validIndices->at(oi++) =
i;
1689 validIndices->at(oi++) =
i;
1693 if(oi != validIndices->size())
1695 validIndices->resize(oi);
1696 assembledCloudValidPoints =
util3d::extractIndices(assembledCloudValidPoints, validIndices,
false,
false);
1697 std::vector<int> pointToCamIdTmp(validIndices->size());
1698 std::vector<float> pointToCamIntensityTmp(validIndices->size());
1699 for(
size_t i=0;
i<validIndices->size(); ++
i)
1701 pointToCamIdTmp[
i] = pointToCamId[validIndices->at(
i)];
1702 pointToCamIntensityTmp[
i] = pointToCamIntensity[validIndices->at(
i)];
1704 pointToCamId = pointToCamIdTmp;
1705 pointToCamIntensity = pointToCamIntensityTmp;
1706 pointToCamIdTmp.clear();
1707 pointToCamIntensityTmp.clear();
1710 cloudToExport = assembledCloudValidPoints;
1711 cloudIToExport->clear();
1713 printf(
"Camera projection... done! (%fs)\n",
timer.ticks());
1716 if(!(mesh || texture))
1720 printf(
"Saving in db... (%d points)\n", !cloudToExport->empty()?(
int)cloudToExport->size():(
int)cloudIToExport->size());
1721 if(!cloudToExport->empty())
1723 else if(!cloudIToExport->empty())
1725 printf(
"Saving in db... done!\n");
1729 std::string ext = las?
"las":
"ply";
1730 std::string outputPath=outputDirectory+
"/"+baseName+
"_cloud."+ext;
1731 printf(
"Saving %s... (%d points)\n", outputPath.c_str(), !cloudToExport->empty()?(
int)cloudToExport->size():(
int)cloudIToExport->size());
1733 if(las || !pointToCamId.empty() || !pointToCamIntensity.empty())
1735 if(!cloudToExport->empty())
1737 if(!pointToCamIntensity.empty())
1746 else if(!cloudIToExport->empty())
1752 if(!pointToCamId.empty())
1754 if(!pointToCamIntensity.empty())
1756 printf(
"Option --cam_projection is enabled but rtabmap is not built "
1757 "with PDAL support, so camera IDs and lidar intensities won't be exported in the output cloud.\n");
1761 printf(
"Option --cam_projection is enabled but rtabmap is not built "
1762 "with PDAL support, so camera IDs won't be exported in the output cloud.\n");
1765 if(!cloudToExport->empty())
1766 pcl::io::savePLYFile(outputPath, *cloudToExport,
binary);
1767 else if(!cloudIToExport->empty())
1768 pcl::io::savePLYFile(outputPath, *cloudIToExport,
binary);
1770 printf(
"Saving %s... done!\n", outputPath.c_str());
1777 if(!cloudIToExport->empty())
1779 pcl::copyPointCloud(*cloudIToExport, *cloudToExport);
1780 cloudIToExport->clear();
1786 int optimizedDepth = 12;
1787 for(
int i=6;
i<12; ++
i)
1789 if(mapLength/
float(1<<
i) < poissonSize)
1797 optimizedDepth = poissonDepth;
1801 printf(
"Mesh reconstruction... depth=%d\n", optimizedDepth);
1802 pcl::PolygonMesh::Ptr mesh(
new pcl::PolygonMesh);
1803 pcl::Poisson<pcl::PointXYZRGBNormal> poisson;
1804 poisson.setDepth(optimizedDepth);
1805 poisson.setInputCloud(cloudToExport);
1806 poisson.reconstruct(*mesh);
1807 printf(
"Mesh reconstruction... done (%fs, %d polygons).\n",
timer.ticks(), (
int)mesh->polygons.size());
1809 if(mesh->polygons.size())
1811 printf(
"Mesh color transfer (max polygons=%d, color radius=%f, clean=%s)...\n",
1814 doClean?
"true":
"false");
1815 rtabmap::util3d::denseMeshPostProcessing<pcl::PointXYZRGBNormal>(
1824 printf(
"Mesh color transfer... done (%fs).\n",
timer.ticks());
1830 printf(
"Saving mesh in db...\n");
1831 std::vector<std::vector<std::vector<RTABMAP_PCL_INDEX> > > polygons;
1836 printf(
"Saving mesh in db... done!\n");
1840 std::string outputPath=outputDirectory+
"/"+baseName+
"_mesh.ply";
1841 printf(
"Saving %s...\n", outputPath.c_str());
1843 pcl::io::savePLYFileBinary(outputPath, *mesh);
1845 pcl::io::savePLYFile(outputPath, *mesh);
1846 printf(
"Saving %s... done!\n", outputPath.c_str());
1852 std::map<int, rtabmap::Transform> robotPosesFiltered;
1855 printf(
"Filtering %ld images from texturing...\n", robotPoses.size());
1856 for(std::map<int, rtabmap::Transform>::iterator
iter=robotPoses.begin();
iter!=robotPoses.end(); ++
iter)
1860 nodes.find(
iter->first)->second.sensorData().uncompressDataConst(&
img, 0);
1863 cv::Mat imgLaplacian;
1864 cv::Laplacian(
img, imgLaplacian, CV_16S);
1866 cv::meanStdDev(imgLaplacian,
m,
s);
1867 double stddev_pxl =
s.at<
double>(0);
1868 double var = stddev_pxl*stddev_pxl;
1869 if(var < (
double)laplacianThr)
1871 printf(
"Camera's image %d is detected as blurry (var=%f < thr=%d), camera is ignored for texturing.\n",
iter->first, var, laplacianThr);
1875 robotPosesFiltered.insert(*
iter);
1879 printf(
"Filtered %ld/%ld images from texturing", robotPosesFiltered.size(), robotPoses.size());
1883 robotPosesFiltered = robotPoses;
1886 printf(
"Texturing %d polygons... robotPoses=%d, cameraModels=%d, cameraDepths=%d\n", (
int)mesh->polygons.size(), (
int)robotPosesFiltered.size(), (
int)cameraModels.size(), (
int)cameraDepths.size());
1887 std::vector<std::map<int, pcl::PointXY> > vertexToPixels;
1900 distanceToCamPolicy);
1901 printf(
"Texturing... done (%fs).\n",
timer.ticks());
1904 if(doClean && textureMesh->tex_coordinates.size())
1906 printf(
"Cleanup mesh...\n");
1908 printf(
"Cleanup mesh... done (%fs).\n",
timer.ticks());
1911 if(textureMesh->tex_materials.size())
1915 printf(
"Merging %d texture(s) to single one (multiband enabled)...\n", (
int)textureMesh->tex_materials.size());
1919 printf(
"Merging %d texture(s)... (%d max textures)\n", (
int)textureMesh->tex_materials.size(), textureCount);
1921 std::map<int, std::map<int, cv::Vec4d> > gains;
1922 std::map<int, std::map<int, cv::Mat> > blendingGains;
1923 std::pair<float, float> contrastValues(0,0);
1926 std::map<int, cv::Mat>(),
1927 std::map<
int, std::vector<rtabmap::CameraModel> >(),
1931 multiband?1:textureCount,
1933 gainValue>0.0f, gainValue, doGainCompensationRGB,
1935 lowBrightnessGain, highBrightnessGain,
1942 printf(
"Merging to %d texture(s)... done (%fs).\n", (
int)textureMesh->tex_materials.size(),
timer.ticks());
1946 printf(
"Saving texture mesh in db...\n");
1950 textureMesh->tex_coordinates,
1952 printf(
"Saving texture mesh in db... done!\n");
1960 std::string outputPath=outputDirectory+
"/"+baseName+
"_mesh_multiband.obj";
1961 printf(
"MultiBand texturing (size=%d, downscale=%d, unwrap method=%s, fill holes=%s, padding=%d, best score thr=%f, angle thr=%f, force visible=%s)... \"%s\"\n",
1964 multibandUnwrap==1?
"ABF":multibandUnwrap==2?
"LSCM":
"Basic",
1965 multibandFillHoles?
"true":
"false",
1967 multibandBestScoreThr,
1968 multibandAngleHardthr,
1969 multibandForceVisible?
"false":
"true",
1970 outputPath.c_str());
1973 textureMesh->tex_polygons[0],
1976 std::map<int, cv::Mat >(),
1977 std::map<
int, std::vector<CameraModel> >(),
1987 doGainCompensationRGB,
1991 multibandBestScoreThr,
1992 multibandAngleHardthr,
1993 multibandForceVisible))
1995 printf(
"MultiBand texturing...done (%fs).\n",
timer.ticks());
1999 printf(
"MultiBand texturing...failed! (%fs)\n",
timer.ticks());
2004 bool success =
false;
2006 for(
size_t i=0;
i<textureMesh->tex_materials.size(); ++
i)
2008 textureMesh->tex_materials[
i].tex_file +=
".jpg";
2009 printf(
"Saving texture to %s.\n", textureMesh->tex_materials[
i].tex_file.c_str());
2010 UASSERT(textures.cols % textures.rows == 0);
2011 success = cv::imwrite(outputDirectory+
"/"+textureMesh->tex_materials[
i].tex_file, cv::Mat(textures,
cv::Range::all(), cv::Range(textures.rows*
i, textures.rows*(
i+1))));
2014 UERROR(
"Failed saving %s!", textureMesh->tex_materials[
i].tex_file.c_str());
2018 printf(
"Saved %s.\n", textureMesh->tex_materials[
i].tex_file.c_str());
2023 std::string outputPath=outputDirectory+
"/"+baseName+
"_mesh.obj";
2024 printf(
"Saving obj (%d vertices) to %s.\n", (
int)textureMesh->cloud.data.size()/textureMesh->cloud.point_step, outputPath.c_str());
2025 #if PCL_VERSION_COMPARE(>=, 1, 13, 0)
2026 textureMesh->tex_coord_indices = std::vector<std::vector<pcl::Vertices>>();
2027 auto nr_meshes =
static_cast<unsigned>(textureMesh->tex_polygons.size());
2029 for (
unsigned m = 0;
m < nr_meshes;
m++) {
2030 std::vector<pcl::Vertices> ci = textureMesh->tex_polygons[
m];
2031 for(std::size_t
i = 0;
i < ci.size();
i++) {
2032 for (std::size_t
j = 0;
j < ci[
i].vertices.size();
j++) {
2033 ci[
i].vertices[
j] = ci[
i].vertices.size() * (
i + f_idx) +
j;
2036 textureMesh->tex_coord_indices.push_back(ci);
2037 f_idx +=
static_cast<unsigned>(textureMesh->tex_polygons[
m].size());
2040 success = pcl::io::saveOBJFile(outputPath, *textureMesh) == 0;
2044 printf(
"Saved obj to %s!\n", outputPath.c_str());
2048 UERROR(
"Failed saving obj to %s!", outputPath.c_str());
2059 printf(
"Export failed! The cloud is empty.\n");