/* * scan_red implementation * * Copyright (C) Dorit Borrmann, Razvan-George Mihalyi, Remus Dumitru * * Released under the GPL version 3. * */ /** * @file * @brief Main program for reducing 3D scans. * * Program to reduce scans for use with slam6d * Usage: bin/scan_red -r 'dir', * Use -r for octree based reduction (voxel size=) * and 'dir' the directory of a set of scans * Reduced scans will be written to 'dir/reduced' * * @author Dorit Borrmann. Automation Group, Jacobs University Bremen gGmbH, Germany. */ #ifdef _MSC_VER #if !defined _OPENMP && defined OPENMP #define _OPENMP #endif #endif #define WANT_STREAM ///< define the WANT stream :) #include using std::string; #include using std::cout; using std::cerr; using std::endl; #include using std::ofstream; #include #include "slam6d/metaScan.h" #include "slam6d/io_utils.h" #include "slam6d/scan.h" #include "slam6d/Boctree.h" #include "slam6d/fbr/fbr_global.h" #include "slam6d/fbr/panorama.h" #include "slam6d/fbr/scan_cv.h" #include "scanserver/clientInterface.h" #include "slam6d/globals.icc" #ifdef _OPENMP #include #endif #ifndef _MSC_VER #include #else #include "XGetopt.h" #endif #ifdef _MSC_VER #define strcasecmp _stricmp #define strncasecmp _strnicmp #include #include #else #include #include #include #include #endif using namespace fbr; #include namespace po = boost::program_options; enum reduction_method {OCTREE, RANGE, INTERPOLATE}; /* Function used to check that 'opt1' and 'opt2' are not specified at the same time. */ void conflicting_options(const po::variables_map & vm, const char *opt1, const char *opt2) { if (vm.count(opt1) && !vm[opt1].defaulted() && vm.count(opt2) && !vm[opt2].defaulted()) throw std::logic_error(string("Conflicting options '") + opt1 + "' and '" + opt2 + "'."); } /* Function used to check that if 'for_what' is specified, then 'required_option' is specified too. */ void option_dependency(const po::variables_map & vm, const char *for_what, const char *required_option) { if (vm.count(for_what) && !vm[for_what].defaulted()) if (vm.count(required_option) == 0 || vm[required_option].defaulted()) throw std::logic_error(string("Option '") + for_what + "' requires option '" + required_option + "'."); } /* * validates panorama method specification */ namespace fbr { void validate(boost::any& v, const std::vector& values, projection_method*, int) { if (values.size() == 0) throw std::runtime_error("Invalid model specification"); string arg = values.at(0); if(strcasecmp(arg.c_str(), "EQUIRECTANGULAR") == 0) v = EQUIRECTANGULAR; else if(strcasecmp(arg.c_str(), "CYLINDRICAL") == 0) v = CYLINDRICAL; else if(strcasecmp(arg.c_str(), "MERCATOR") == 0) v = MERCATOR; else if(strcasecmp(arg.c_str(), "CONIC") == 0) v = CONIC; else throw std::runtime_error(std::string("projection method ") + arg + std::string(" is unknown")); } } /* * validates input type specification */ void validate(boost::any& v, const std::vector& values, IOType*, int) { if (values.size() == 0) throw std::runtime_error("Invalid model specification"); string arg = values.at(0); try { v = formatname_to_io_type(arg.c_str()); } catch (...) { // runtime_error throw std::runtime_error("Format " + arg + " unknown."); } } void reduction_option_dependency(const po::variables_map & vm, reduction_method stype, const char *option) { if (vm.count("reduction") && vm["reduction"].as() == stype) { if (!vm.count(option)) { throw std::logic_error (string("this reduction option needs ")+option+" to be set"); } } } void reduction_option_conflict(const po::variables_map & vm, reduction_method stype, const char *option) { if (vm.count("reduction") && vm["reduction"].as() == stype) { if (vm.count(option)) { throw std::logic_error (string("this reduction option is incompatible with ")+option); } } } /* * validates reduction method specification */ void validate(boost::any& v, const std::vector& values, reduction_method*, int) { if (values.size() == 0) throw std::runtime_error("Invalid model specification"); string arg = values.at(0); if(strcasecmp(arg.c_str(), "OCTREE") == 0) v = OCTREE; else if(strcasecmp(arg.c_str(), "RANGE") == 0) v = RANGE; else if(strcasecmp(arg.c_str(), "INTERPOLATE") == 0) v = INTERPOLATE; else throw std::runtime_error(std::string("reduction method ") + arg + std::string(" is unknown")); } void parse_options(int argc, char **argv, int &start, int &end, bool &scanserver, int &width, int &height, fbr::projection_method &ptype, string &dir, IOType &iotype, int &maxDist, int &minDist, reduction_method &rtype, double &scale, double &voxel, int &octree, bool &use_reflectance) { po::options_description generic("Generic options"); generic.add_options() ("help,h", "output this help message"); po::options_description input("Input options"); input.add_options() ("start,s", po::value(&start)->default_value(0), "start at scan (i.e., neglects the first scans) " "[ATTENTION: counting naturally starts with 0]") ("end,e", po::value(&end)->default_value(-1), "end after scan ") ("format,f", po::value(&iotype)->default_value(UOS), "using shared library for input. (chose F from {uos, uos_map, " "uos_rgb, uos_frames, uos_map_frames, old, rts, rts_map, ifp, " "riegl_txt, riegl_rgb, riegl_bin, zahn, ply})") ("max,M", po::value(&maxDist)->default_value(-1), "neglegt all data points with a distance larger than 'units") ("min,m", po::value(&minDist)->default_value(-1), "neglegt all data points with a distance smaller than 'units") ("scanserver,S", po::bool_switch(&scanserver), "Use the scanserver as an input method and handling of scan data"); po::options_description reduction("Reduction options"); reduction.add_options() ("reduction,r", po::value(&rtype)->required(), "choose reduction method (OCTREE, RANGE, INTERPOLATE)") ("scale,S", po::value(&scale), "scaling factor") ("voxel,v", po::value(&voxel), "voxel size") ("projection,P", po::value(&ptype), "projection method or panorama image") ("octree,O", po::value(&octree), "0 -> center\n1 -> random\nN>1 -> random N") ("width,w", po::value(&width), "width of panorama") ("height,h", po::value(&height), "height of panorama"); po::options_description output("Output options"); output.add_options() ("reflectance,R", po::bool_switch(&use_reflectance), "Use reflectance when reducing points and save scan files in UOSR format"); po::options_description hidden("Hidden options"); hidden.add_options() ("input-dir", po::value(&dir), "input dir"); // all options po::options_description all; all.add(generic).add(input).add(reduction).add(output).add(hidden); // options visible with --help po::options_description cmdline_options; cmdline_options.add(generic).add(input).add(reduction).add(output); // positional argument po::positional_options_description pd; pd.add("input-dir", 1); // process options po::variables_map vm; po::store(po::command_line_parser(argc, argv). options(all).positional(pd).run(), vm); // display help if (vm.count("help")) { cout << cmdline_options; cout << endl << "Example usage:" << endl << "\t./bin/scan_red -s 0 -e 0 -f uos --reduction OCTREE --voxel 10 --octree 0 dat" << endl << "\t./bin/scan_red -s 0 -e 0 -f uos --reduction RANGE --scale 0.5 --projection EQUIRECTANGULAR --width 3600 --height 1000 dat" << endl << "\t./bin/scan_red -s 0 -e 0 -f uos --reduction INTERPOLATE --scale 0.2 --projection EQUIRECTANGULAR --width 3600 --height 1000 dat" << endl; exit(0); } po::notify(vm); reduction_option_dependency(vm, OCTREE, "voxel"); reduction_option_dependency(vm, OCTREE, "octree"); reduction_option_conflict(vm, OCTREE, "scale"); reduction_option_conflict(vm, OCTREE, "projection"); reduction_option_conflict(vm, OCTREE, "width"); reduction_option_conflict(vm, OCTREE, "height"); reduction_option_conflict(vm, RANGE, "voxel"); reduction_option_conflict(vm, RANGE, "octree"); reduction_option_dependency(vm, RANGE, "scale"); reduction_option_dependency(vm, RANGE, "projection"); reduction_option_dependency(vm, RANGE, "width"); reduction_option_dependency(vm, RANGE, "height"); reduction_option_conflict(vm, INTERPOLATE, "voxel"); reduction_option_conflict(vm, INTERPOLATE, "octree"); reduction_option_dependency(vm, INTERPOLATE, "scale"); reduction_option_dependency(vm, INTERPOLATE, "projection"); reduction_option_dependency(vm, INTERPOLATE, "width"); reduction_option_dependency(vm, INTERPOLATE, "height"); #ifndef _MSC_VER if (dir[dir.length()-1] != '/') dir = dir + "/"; #else if (dir[dir.length()-1] != '\\') dir = dir + "\\"; #endif } void createdirectory(string dir) { int success = mkdir(dir.c_str(), S_IRWXU|S_IRWXG|S_IRWXO); if (success == 0 || errno == EEXIST) { cout << "Writing to " << dir << endl; } else { cerr << "Creating directory " << dir << " failed" << endl; exit(1); } } void scan2mat(Scan *source, cv::Mat &mat) { DataXYZ xyz = source->get("xyz"); DataReflectance xyz_reflectance = (((DataReflectance)source->get("reflectance")).size() == 0) ? source->create("reflectance", sizeof(float)*xyz.size()) : source->get("reflectance"); if(((DataReflectance)source->get("reflectance")).size() == 0){ for(unsigned int i = 0; i < xyz.size(); i++) xyz_reflectance[i] = 255; } unsigned int nPoints = xyz.size(); mat.create(nPoints,1,CV_32FC(4)); mat = cv::Scalar::all(0); cv::MatIterator_ it = mat.begin(); for(unsigned int i = 0; i < nPoints; i++){ float reflectance = xyz_reflectance[i]; //normalize the reflectance reflectance += 32; reflectance /= 64; reflectance -= 0.2; reflectance /= 0.3; if (reflectance < 0) reflectance = 0; if (reflectance > 1) reflectance = 1; (*it)[0] = xyz[i][0]; (*it)[1] = xyz[i][1]; (*it)[2] = xyz[i][2]; (*it)[3] = reflectance; ++it; } } void reduce_octree(Scan *scan, vector &reduced_points, int octree, int red, bool use_reflectance) { if (use_reflectance) { unsigned int types = PointType::USE_REFLECTANCE; PointType pointtype(types); scan->setReductionParameter(red, octree, pointtype); scan->calcReducedPoints(); DataXYZ xyz_reduced(scan->get("xyz reduced")); DataReflectance reflectance_reduced(scan->get("reflectance reduced")); if (xyz_reduced.size() != reflectance_reduced.size()) { cerr << "xyz_reduced size different than reflectance_reduced size" << endl; return; } for(unsigned int j = 0; j < xyz_reduced.size(); j++) { reduced_points.push_back(cv::Vec4f(xyz_reduced[j][0], xyz_reduced[j][1], xyz_reduced[j][2], reflectance_reduced[j])); } } else { scan->setReductionParameter(red, octree); scan->calcReducedPoints(); DataXYZ xyz_reduced(scan->get("xyz reduced")); for(unsigned int j = 0; j < xyz_reduced.size(); j++) { reduced_points.push_back(cv::Vec4f(xyz_reduced[j][0], xyz_reduced[j][1], xyz_reduced[j][2], 0.0)); } } } void reduce_range(Scan *scan, vector &reduced_points, int width, int height, fbr::projection_method ptype, double scale, bool use_reflectance) { panorama image(width, height, ptype); cv::Mat mat; scan2mat(scan, mat); image.createPanorama(mat); image.getDescription(); cv::Mat range_image_resized; cv::Mat reflectance_image_resized; resize(image.getRangeImage(), range_image_resized, cv::Size(), scale, scale, cv::INTER_NEAREST); if (use_reflectance) { resize(image.getReflectanceImage(), reflectance_image_resized, cv::Size(), scale, scale, cv::INTER_NEAREST); } else { reflectance_image_resized.create(range_image_resized.size(), CV_8U); reflectance_image_resized = cv::Scalar::all(0); } image.recoverPointCloud(range_image_resized, reflectance_image_resized, reduced_points); } void reduce_interpolation(Scan *scan, vector &reduced_points, int width, int height, fbr::projection_method ptype, double scale, bool use_reflectance) { panorama image(width, height, ptype); cv::Mat mat; scan2mat(scan, mat); image.createPanorama(mat); image.getDescription(); cv::Mat range_image_resized; cv::Mat reflectance_image_resized; resize(image.getMap(), range_image_resized, cv::Size(), scale, scale, cv::INTER_NEAREST); if (use_reflectance) { resize(image.getReflectanceImage(), reflectance_image_resized, cv::Size(), scale, scale, cv::INTER_NEAREST); } for(int i = 0; i < range_image_resized.rows; i++) { for(int j = 0; j < range_image_resized.cols; j++) { cv::Vec3f vec = range_image_resized.at(i, j); if (use_reflectance) { reduced_points.push_back(cv::Vec4f( vec[0], vec[1], vec[2], reflectance_image_resized.at(i, j)/255.0)); } else { reduced_points.push_back(cv::Vec4f(vec[0], vec[1], vec[2], 0.0)); } } } } /* * given a vector of 3d points, write them out as uos files */ void write_uos(vector &points, string &dir, string id) { ofstream outfile(dir + "/scan" + id + ".3d"); outfile << "# header is ignored" << endl; for (vector::iterator it=points.begin(); it < points.end(); it++) { outfile << (*it)[0] << " " << (*it)[1] << " " << (*it)[2] << endl; } outfile.close(); } /* * given a vector of 3d points, write them out as uosr files */ void write_uosr(vector &points, string &dir, string id) { ofstream outfile(dir + "/scan" + id + ".3d"); outfile << "# header is ignored" << endl; for (vector::iterator it=points.begin(); it < points.end(); it++) { outfile << (*it)[0] << " " << (*it)[1] << " " << (*it)[2] << " " << (*it)[3] << endl; } outfile.close(); } // write .pose files // .frames files can later be generated from them using ./bin/pose2frames void writeposefile(string &dir, const double* rPos, const double* rPosTheta, string id) { ofstream posefile(dir + "/scan" + id + ".pose"); posefile << rPos[0] << " " << rPos[1] << " " << rPos[2] << endl; posefile << deg(rPosTheta[0]) << " " << deg(rPosTheta[1]) << " " << deg(rPosTheta[2]) << endl; posefile.close(); } /** * Main program for reducing scans. * Usage: bin/scan_red -r 'dir', * Use -r for octree based reduction (voxel size=) * and 'dir' the directory of a set of scans * Reduced scans will be written to 'dir/reduced' * */ int main(int argc, char **argv) { int start, end; bool scanserver; int width, height; int maxDist, minDist; fbr::projection_method ptype; string dir; IOType iotype; reduction_method rtype; double scale, voxel; int octree; bool use_reflectance; parse_options(argc, argv, start, end, scanserver, width, height, ptype, dir, iotype, maxDist, minDist, rtype, scale, voxel, octree, use_reflectance); for (int iter = start; iter <= end; iter++) { Scan::openDirectory(scanserver, dir, iotype, iter, iter); if(Scan::allScans.size() == 0) { cerr << "No scans found. Did you use the correct format?" << endl; exit(-1); } for(ScanVector::iterator it = Scan::allScans.begin(); it != Scan::allScans.end(); ++it) { Scan* scan = *it; scan->setRangeFilter(maxDist, minDist); vector reduced_points; string reddir = dir + "reduced"; createdirectory(reddir); switch (rtype) { case OCTREE: reduce_octree(scan, reduced_points, octree, voxel, use_reflectance); break; case RANGE: reduce_range(scan, reduced_points, width, height, ptype, scale, use_reflectance); break; case INTERPOLATE: reduce_interpolation(scan, reduced_points, width, height, ptype, scale, use_reflectance); break; default: cerr << "unknown method" << endl; return 1; break; } if (use_reflectance) write_uosr(reduced_points, reddir, scan->getIdentifier()); else write_uos(reduced_points, reddir, scan->getIdentifier()); writeposefile(reddir, scan->get_rPos(), scan->get_rPosTheta(), scan->getIdentifier()); } Scan::closeDirectory(); } }