/** * * Copyright (C) Jacobs University Bremen * * @author Vaibhav Kumar Mehta * @file normals.cc */ #include #include #include #include #include #include #include #include #include "slam6d/fbr/panorama.h" #include #include #include "newmat/newmat.h" #include "newmat/newmatap.h" using namespace NEWMAT; #ifdef _MSC_VER #define strcasecmp _stricmp #define strncasecmp _strnicmp #else #include #endif namespace po = boost::program_options; using namespace std; enum normal_method {AKNN, ADAPTIVE_AKNN, PANORAMA, PANORAMA_FAST}; /* * validates normal calculation method specification */ void validate(boost::any& v, const std::vector& values, normal_method*, int) { if (values.size() == 0) throw std::runtime_error("Invalid model specification"); string arg = values.at(0); if(strcasecmp(arg.c_str(), "AKNN") == 0) v = AKNN; else if(strcasecmp(arg.c_str(), "ADAPTIVE_AKNN") == 0) v = ADAPTIVE_AKNN; else if(strcasecmp(arg.c_str(), "PANORAMA") == 0) v = PANORAMA; else if(strcasecmp(arg.c_str(), "PANORAMA_FAST") == 0) v = PANORAMA_FAST; else throw std::runtime_error(std::string("normal calculation method ") + arg + std::string(" is unknown")); } /// validate IO types 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."); } } /// Parse commandline options void parse_options(int argc, char **argv, int &start, int &end, bool &scanserver, int &max_dist, int &min_dist, string &dir, IOType &iotype, int &k1, int &k2, normal_method &ntype,int &width,int &height) { /// ---------------------------------- /// set up program commandline options /// ---------------------------------- po::options_description cmd_options("Usage: calculateNormals where options are (default values in brackets)"); cmd_options.add_options() ("help,?", "Display this help message") ("start,s", po::value(&start)->default_value(0), "Start at scan number ") ("end,e", po::value(&end)->default_value(-1), "Stop at scan number ") ("scanserver,S", po::value(&scanserver)->default_value(false), "Use the scanserver as an input method") ("format,f", po::value(&iotype)->default_value(UOS), "using shared library for input. (chose format from [uos|uosr|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(&max_dist)->default_value(-1),"neglegt all data points with a distance larger than 'units") ("min,m", po::value(&min_dist)->default_value(-1),"neglegt all data points with a distance smaller than 'units") ("normal,g", po::value(&ntype)->default_value(AKNN), "normal calculation method " "(AKNN, ADAPTIVE_AKNN, PANORAMA, PANORAMA_FAST)") ("K1,k", po::value(&k1)->default_value(20), " value of K value used in the nearest neighbor search of ANN or" "kmin for k-adaptation") ("K2,K", po::value(&k2)->default_value(20), " value of Kmax for k-adaptation") ("width,w", po::value(&width)->default_value(1280),"width of panorama image") ("height,h", po::value(&height)->default_value(960),"height of panorama image") ; po::options_description hidden("Hidden options"); hidden.add_options() ("input-dir", po::value(&dir), "input dir"); po::positional_options_description pd; pd.add("input-dir", 1); po::options_description all; all.add(cmd_options).add(hidden); po::variables_map vmap; po::store(po::command_line_parser(argc, argv).options(all).positional(pd).run(), vmap); po::notify(vmap); if (vmap.count("help")) { cout << cmd_options << endl << endl; cout << "SAMPLE COMMAND FOR CALCULATING NORMALS" << endl; cout << " bin/normals -s 0 -e 0 -f UOS -g AKNN -k 20 dat/" < &normals,vector &points, int k, const double _rPos[3] ) { cout<<"Total number of points: "< &normals,vector &points, int kmin, int kmax, const double _rPos[3]) { ColumnVector rPos(3); for (int i = 0; i < 3; ++i) rPos(i+1) = _rPos[i]; cout<<"Total number of points: "< 0.25 * e2) && (fabs(1.0 - (double)e2/(double)e3) < 0.25)) break; } //normal = eigenvector corresponding to lowest //eigen value that is the 1rd column of matrix U ColumnVector n(3); n(1) = U(1,1); n(2) = U(2,1); n(3) = U(3,1); ColumnVector point_vector(3); point_vector(1) = p[0] - rPos(1); point_vector(2) = p[1] - rPos(2); point_vector(3) = p[2] - rPos(3); point_vector = point_vector / point_vector.NormFrobenius(); Real angle = (n.t() * point_vector).AsScalar(); if (angle < 0) { n *= -1.0; } n = n / n.NormFrobenius(); normals.push_back(Point(n(1), n(2), n(3))); } annDeallocPts(pa); } /////////////////////////////////////////////////////// /////////////NORMALS USING IMAGE NEIGHBORS //////////// /////////////////////////////////////////////////////// void calculateNormalsPANORAMA(vector &normals, vector &points, vector< vector< vector< cv::Vec3f > > > extendedMap, const double _rPos[3]) { ColumnVector rPos(3); for (int i = 0; i < 3; ++i) rPos(i+1) = _rPos[i]; cout<<"Total number of points: "< neighbors; for (size_t i=0; i< extendedMap.size(); i++) { for (size_t j=0; j= 0 && x < (int)extendedMap.size() && y >= 0 && y < (int)extendedMap[x].size()) { for (unsigned int k = 0; k < extendedMap[x][y].size(); k++) { neighbors.push_back(extendedMap[x][y][k]); } } } nr_neighbors = neighbors.size(); cv::Vec3f p = extendedMap[i][j][0]; //if no neighbor point is found in the 4-neighboring pixels then normal is set to zero if (nr_neighbors < 3) { points.push_back(Point(p[0], p[1], p[2])); normals.push_back(Point(0.0,0.0,0.0)); continue; } //calculate mean for all the points Matrix X(nr_neighbors,3); SymmetricMatrix A(3); Matrix U(3,3); DiagonalMatrix D(3); //calculate mean for all the points for(int k = 0; k < nr_neighbors; k++) { cv::Vec3f pp = neighbors[k]; mean.x += pp[0]; mean.y += pp[1]; mean.z += pp[2]; } mean.x /= nr_neighbors; mean.y /= nr_neighbors; mean.z /= nr_neighbors; //calculate covariance = A for all the points for (int i = 0; i < nr_neighbors; ++i) { cv::Vec3f pp = neighbors[i]; X(i+1, 1) = pp[0] - mean.x; X(i+1, 2) = pp[1] - mean.y; X(i+1, 3) = pp[2] - mean.z; } A << 1.0/nr_neighbors * X.t() * X; EigenValues(A, D, U); //normal = eigenvector corresponding to lowest //eigen value that is the 1st column of matrix U ColumnVector n(3); n(1) = U(1,1); n(2) = U(2,1); n(3) = U(3,1); ColumnVector point_vector(3); point_vector(1) = p[0] - rPos(1); point_vector(2) = p[1] - rPos(2); point_vector(3) = p[2] - rPos(3); point_vector = point_vector / point_vector.NormFrobenius(); Real angle = (n.t() * point_vector).AsScalar(); if (angle < 0) { n *= -1.0; } n = n / n.NormFrobenius(); for (unsigned int k = 0; k < extendedMap[i][j].size(); k++) { cv::Vec3f p = extendedMap[i][j][k]; points.push_back(Point(p[0], p[1], p[2])); normals.push_back(Point(n(1), n(2), n(3))); } } } } ////////////////////////////////////////////////////////////////////////////////////////////// ///////////FAST NORMALS USING PANORAMA EQUIRECTANGULAR RANGE IMAGE ////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////// /* void calculateNormalsFAST(vector &normals,vector &points,cv::Mat &img,vector>> extendedMap) { cout<<"Total number of points: "<(i-1,j); dRdPhi += 3 *img.at(i-1,j-1); dRdPhi += 3 *img.at(i-1,j+1); dRdPhi -= 10*img.at(i+1,j); dRdPhi -= 3 *img.at(i+1,j-1); dRdPhi -= 3 *img.at(i+1,j+1); dRdTheta += 10*img.at(i,j-1); dRdTheta += 3 *img.at(i-1,j-1); dRdTheta += 3 *img.at(i+1,j-1); dRdTheta -= 10*img.at(i,j+1); dRdTheta -= 3 *img.at(i-1,j+1); dRdTheta -= 3 *img.at(i+1,j+1); n[0] = cos(theta) * sin(phi) - sin(theta) * dRdTheta / rho / sin(phi) + cos(theta) * cos(phi) * dRdPhi / rho; n[1] = sin(theta) * sin(phi) + cos(theta) * dRdTheta / rho / sin(phi) + sin(theta) * cos(phi) * dRdPhi / rho; n[2] = cos(phi) - sin(phi) * dRdPhi / rho; //n[2] = -n[2]; m = sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]); n[0] /= m; n[1] /= m; n[2] /= m; points.push_back(Point(x, y, z)); normals.push_back(Point(n[0],n[1],n[2])); } } } } */ /* * retrieve a cv::Mat with x,y,z,r from a scan object * functionality borrowed from scan_cv::convertScanToMat but this function * does not allow a scanserver to be used, prints to stdout and can only * handle a single scan */ cv::Mat scan2mat(Scan *source) { DataXYZ xyz = source->get("xyz"); DataReflectance xyz_reflectance = source->get("reflectance"); unsigned int nPoints = xyz.size(); cv::Mat scan(nPoints,1,CV_32FC(4)); scan = cv::Scalar::all(0); cv::MatIterator_ it; it = scan.begin(); for(unsigned int i = 0; i < nPoints; i++){ float x, y, z, reflectance; x = xyz[i][0]; y = xyz[i][1]; z = xyz[i][2]; if(xyz_reflectance.size() != 0) { 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] = x; (*it)[1] = y; (*it)[2] = z; if(xyz_reflectance.size() != 0) (*it)[3] = reflectance; else (*it)[3] = 0; ++it; } return scan; } /* * convert a matrix of float values (range image) to a matrix of unsigned * eight bit characters using different techniques */ cv::Mat float2uchar(cv::Mat &source, bool logarithm, float cutoff) { cv::Mat result(source.size(), CV_8U, cv::Scalar::all(0)); float max = 0; // find maximum value if (cutoff == 0.0) { // without cutoff, just iterate through all values to find the largest for (cv::MatIterator_ it = source.begin(); it != source.end(); ++it) { float val = *it; if (val > max) { max = val; } } } else { // when a cutoff is specified, sort all the points by value and then // specify the max so that values are larger than it vector sorted(source.cols*source.rows); int i = 0; for (cv::MatIterator_ it = source.begin(); it != source.end(); ++it, ++i) { sorted[i] = *it; } std::sort(sorted.begin(), sorted.end()); max = sorted[(int)(source.cols*source.rows*(1.0-cutoff))]; cout << "A cutoff of " << cutoff << " resulted in a max value of " << max << endl; } cv::MatIterator_ src = source.begin(); cv::MatIterator_ dst = result.begin(); cv::MatIterator_ end = source.end(); if (logarithm) { // stretch values from 0 to max logarithmically over 0 to 255 // using the logarithm allows to represent smaller values with more // precision and larger values with less max = log(max+1); for (; src != end; ++src, ++dst) { float val = (log(*src+1)*255.0)/max; if (val > 255) *dst = 255; else *dst = (uchar)val; } } else { // stretch values from 0 to max linearly over 0 to 255 for (; src != end; ++src, ++dst) { float val = (*src*255.0)/max; if (val > 255) *dst = 255; else *dst = (uchar)val; } } return result; } /// Write a pose file with the specofied name void writePoseFiles(string dir, const double* rPos, const double* rPosTheta,int scanNumber) { string poseFileName = dir + "/scan" + to_string(scanNumber, 3) + ".pose"; ofstream posout(poseFileName.c_str()); posout << rPos[0] << " " << rPos[1] << " " << rPos[2] << endl << deg(rPosTheta[0]) << " " << deg(rPosTheta[1]) << " " << deg(rPosTheta[2]) << endl; posout.clear(); posout.close(); } /// write scan files for all segments void writeScanFiles(string dir, vector &points, vector &normals, int scanNumber) { string ofilename = dir + "/scan" + to_string(scanNumber, 3) + ".3d"; ofstream normptsout(ofilename.c_str()); for (size_t i=0; i::iterator it = Scan::allScans.begin(); int scanNumber = 0; for( ; it != Scan::allScans.end(); ++it) { Scan* scan = *it; // apply optional filtering scan->setRangeFilter(max_dist, min_dist); const double* rPos = scan->get_rPos(); const double* rPosTheta = scan->get_rPosTheta(); /// read scan into points DataXYZ xyz(scan->get("xyz")); vector points; points.reserve(xyz.size()); vector normals; normals.reserve(xyz.size()); for(unsigned int j = 0; j < xyz.size(); j++) { points.push_back(Point(xyz[j][0], xyz[j][1], xyz[j][2])); } if(ntype == AKNN) calculateNormalsAKNN(normals,points, k1, rPos); else if(ntype == ADAPTIVE_AKNN) calculateNormalsAdaptiveAKNN(normals,points, k1, k2, rPos); else { // create panorama fbr::panorama fPanorama(width, height, fbr::EQUIRECTANGULAR, 1, 0, fbr::EXTENDED); fPanorama.createPanorama(scan2mat(scan)); // the range image has to be converted from float to uchar img = fPanorama.getRangeImage(); img = float2uchar(img, 0, 0.0); if(ntype == PANORAMA) calculateNormalsPANORAMA(normals,points,fPanorama.getExtendedMap(), rPos); else if(ntype == PANORAMA_FAST) cout << "PANORAMA_FAST is not working yet" << endl; // calculateNormalsFAST(normals,points,img,fPanorama.getExtendedMap()); } // pose file (repeated for the number of segments writePoseFiles(normdir, rPos, rPosTheta, scanNumber); // scan files for all segments writeScanFiles(normdir, points,normals,scanNumber); scanNumber++; } // shutdown everything if (scanserver) ClientInterface::destroy(); else Scan::closeDirectory(); cout << "Normal program end" << endl; return 0; }