├── .gitignore ├── test └── noisy_sphere100.pcd ├── CMakeLists.txt ├── README.md ├── main.cpp └── geometric_distortion.h /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /test/noisy_sphere100.pcd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mauriceqch/geo_dist/HEAD/test/noisy_sphere100.pcd -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8 FATAL_ERROR) 2 | 3 | set(PROJECT_OUTPUT_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}/../test") 4 | set(CMAKE_CONFIGURATION_TYPES "Release;Debug" CACHE STRING "Release;Debug") 5 | 6 | project(pc_error) 7 | 8 | find_package(PCL 1.8 COMPONENTS common octree io search features REQUIRED) 9 | 10 | find_package(OpenMP) 11 | if (OPENMP_FOUND) 12 | message(STATUS "OpenMP found.") 13 | set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") 14 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") 15 | endif() 16 | 17 | set(Boost_USE_STATIC_LIBS OFF) 18 | set(Boost_USE_MULTITHREADED ON) 19 | set(Boost_USE_STATIC_RUNTIME OFF) 20 | find_package(Boost 1.61.0 COMPONENTS program_options) 21 | 22 | if(Boost_FOUND) 23 | include_directories(${Boost_INCLUDE_DIRS}) 24 | # target_link_libraries(progname ${Boost_LIBRARIES}) 25 | endif() 26 | 27 | set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c++11") 28 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 29 | 30 | file(GLOB_RECURSE PROJ_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/*.h*) 31 | file(GLOB SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) 32 | 33 | include_directories("${CMAKE_CURRENT_SOURCE_DIR}/./") 34 | include_directories(${PCL_INCLUDE_DIRS}) 35 | link_directories(${PCL_LIBRARY_DIRS}) 36 | add_definitions(${PCL_DEFINITIONS}) 37 | 38 | list(REMOVE_ITEM PCL_LIBRARIES ${VTK_LIBRARIES} ${QHULL_LIBRARIES} ${OPENNI_LIBRARIES} ${OPENNI2_LIBRARIES}) # no need for vtk, qhull, openni/openni2 39 | 40 | message(STATUS "PCL_LIBRARIES=${PCL_LIBRARIES}") 41 | 42 | if (MSVC) 43 | message(STATUS "TO BE TESTED") 44 | # add_definitions("/D\"_CRT_SECURE_NO_WARNINGS\"") 45 | # add_definitions("/D\"_SCL_SECURE_NO_WARNINGS\"") 46 | add_executable(pc_error ${SOURCE}) 47 | # add_executable(pc_error main.cpp.cpp stdafx.cpp ${PROJ_HEADERS}) 48 | else() 49 | list(REMOVE_ITEM SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/stdafx.cpp") 50 | add_executable(pc_error ${SOURCE}) 51 | endif() 52 | 53 | target_link_libraries(pc_error ${PCL_LIBRARIES} ${Boost_PROGRAM_OPTIONS_LIBRARY}) 54 | install(TARGETS pc_error DESTINATION ".") 55 | 56 | set_target_properties(pc_error PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_OUTPUT_FOLDER}) 57 | set_target_properties(pc_error PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_OUTPUT_FOLDER}) 58 | set_target_properties(pc_error PROPERTIES DEBUG_POSTFIX "_d") 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Software 2 | * Written by Dong Tian @ MERL 3 | * Modified by Maurice Quach @ L2S, CNRS, CentraleSupélec, Université Paris-Saclay 4 | 5 | This software is written to compute geometric distortions in a point 6 | cloud, including point-to-point and point-to-plane. 7 | 8 | If you use this code for research, please cite: 9 | 10 | @inproceedings{Tian_2017, 11 | doi = {10.1109/icip.2017.8296925}, 12 | url = {https://doi.org/10.1109%2Ficip.2017.8296925}, 13 | year = 2017, 14 | month = {sep}, 15 | publisher = {{IEEE}}, 16 | author = {Dong Tian and Hideaki Ochimizu and Chen Feng and Robert Cohen and Anthony Vetro}, 17 | title = {Geometric distortion metrics for point cloud compression}, 18 | booktitle = {2017 {IEEE} International Conference on Image Processing ({ICIP})} 19 | } 20 | 21 | # Modifications 22 | * Changed Readme to Markdown format 23 | * Changes to output format to facilitate parsing 24 | 25 | # Compiling notes 26 | ## PCL required 27 | Tested with PCL 1.8. May work with some older versions, but not tested yet. 28 | ## Compile under Linux, follow the steps: 29 | mkdir build 30 | cd build 31 | ### Simple compile 32 | The executable pc_error would be produced under ./build folder. 33 | 34 | cmake .. 35 | make 36 | ### Eclipse IDE 37 | The executable pc_error_d would be produced under ./run folder. 38 | 39 | cmake -G "Eclipse CDT4 - Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_ECLIPSE_GENERATE_SOURCE_PROJECT=TRUE -DCMAKE_ECLIPSE_MAKE_ARGUMENTS=-j12 .. 40 | ## Compile under Window, not tested yet 41 | # Calling examples 42 | * ./pc_error -a cloudA.ply 43 | 44 | Just load the single point cloud, check its intrinsic resolutions. 45 | * ./pc_error -a cloudA.ply -A cloudNormalsA.ply 46 | 47 | Estimate normals for cloudA.ply and save the point cloud with normals to 48 | cloudNormalsA.ply. 49 | * ./pc_error -a cloudOrg.ply -b cloudDec.ply 50 | 51 | Compute the point-to-point and point-to-plane metrics. Only report RMS 52 | and corresponding PSNR's. 53 | * ./pc_error -a cloudOrg.ply -b cloudDec.ply -d 54 | 55 | Compute the point-to-point and point-to-plane metrics. Report RMS, 56 | Hausdorff and corresponding PSNR's. 57 | * ./pc_error -a cloudOrg.ply -b cloudDec.ply -f 58 | 59 | Compute the point-to-point and point-to-plane metrics. Force normal 60 | estimation even normals are provided in the input cloud. This feature is 61 | for experimental purposes. 62 | ## More parameters for experimental purposes 63 | -k [ --knn ] arg (=12) Set KNN number of neighbor points for normal 64 | estimation. Default normal estimation method 65 | -t [ --rtimes ] arg (=0) Guide to set radius for normal estimation, 66 | times to NN points distances 67 | -s [ --singlePass ] Force running a single pass, where the loop 68 | is over the original point cloud. Non-symmetric 69 | reports 70 | -1 [ --point2point ] Force to report point-to-point metric only 71 | ## Screen snapshot example 72 | $ ./build/pc_error -a test/sphere100.pcd -b test/noisy_sphere100.pcd 73 | infile1: test/sphere100.pcd 74 | infile2: test/noisy_sphere100.pcd 75 | knn = 12 76 | force normal estimation: 0 77 | 78 | Failed to find match for field 'rgb'. 79 | Failed to find match for field 'normal_x'. 80 | Failed to find match for field 'normal_y'. 81 | Failed to find match for field 'normal_z'. 82 | Failed to find match for field 'curvature'. 83 | Reading file 1 done. 84 | Failed to find match for field 'rgb'. 85 | Failed to find match for field 'normal_x'. 86 | Failed to find match for field 'normal_y'. 87 | Failed to find match for field 'normal_z'. 88 | Failed to find match for field 'curvature'. 89 | Reading file 2 done. 90 | Minimum and maximum NN distances (intrinsic resolutions): 0.00999999, 0.0217456 91 | Point cloud sizes for org version, dec version, and the scaling ratio: 10000, 10000, 1 92 | 93 | 0. Preparing normals. 94 | KNN in use: 12 95 | Normal estimation begin.. 96 | Normal estimation on original point cloud DONE! It takes 0 seconds (in CPU time). 97 | Converting normal vector DONE. It takes 0 seconds (in CPU time). 98 | 99 | 1. Use infile1 (A) as reference, loop over A, use normals on B. (A->B). 100 | Error computing takes 0 seconds (in CPU time). 101 | ### A->B,rms1,p2point,0.000847707 102 | ### A->B,rms1PSNR,p2point,28.1825 103 | ### A->B,rms1,p2plane,0.000471006 104 | ### A->B,rms1PSNR,p2plane,33.2869 105 | 2. Use infile2 (B) as reference, loop over B, use normals on A. (B->A). 106 | Error computing takes 0 seconds (in CPU time). 107 | ### B->A,rms2,p2point,0.000847707 108 | ### B->A,rms2PSNR,p2point,28.1825 109 | ### B->A,rms2,p2plane, 0.000471006 110 | ### B->A,rms2PSNR,p2plane,33.2869 111 | 3. Final (symmetric). 112 | ### Symmetric,rmsF,p2point,0.000847707 113 | ### Symmetric,rmsFPSNR,p2point,28.1825 114 | ### Symmetric,rmsF,p2plane,0.000471006 115 | ### Symmetric,rmsFPSNR,p2plane,33.2869 116 | Job done! 0 seconds elapsed (excluding the time to load the point clouds). 117 | 118 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement 3 | * 4 | * Point to plane metric for point cloud distortion measurement 5 | * Copyright (c) 2016, MERL 6 | * 7 | * All rights reserved. 8 | * 9 | * Contributors: 10 | * Dong Tian 11 | * 12 | * Redistribution and use in source and binary forms, with or without 13 | * modification, are permitted provided that the following conditions 14 | * are met: 15 | * 16 | * * Redistributions of source code must retain the above copyright 17 | * notice, this list of conditions and the following disclaimer. 18 | * * Redistributions in binary form must reproduce the above 19 | * copyright notice, this list of conditions and the following 20 | * disclaimer in the documentation and/or other materials provided 21 | * with the distribution. 22 | * * Neither the name of the copyright holder(s) nor the names of its 23 | * contributors may be used to endorse or promote products derived 24 | * from this software without specific prior written permission. 25 | * 26 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 29 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 30 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 31 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 32 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 33 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 35 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 36 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 37 | * POSSIBILITY OF SUCH DAMAGE. 38 | * 39 | */ 40 | 41 | #ifdef _WIN32 42 | #include "stdafx.h" 43 | #endif 44 | 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | 53 | #include "geometric_distortion.h" 54 | 55 | using namespace boost::program_options; 56 | using namespace pcl::geometric_quality; 57 | 58 | void printusage() 59 | { 60 | cout << "pc_psnr cloud_a cloud_b [radiusTimes]" << endl; 61 | cout << " default radiusTimes is 10" << endl; 62 | } 63 | 64 | int parseCommand( int ac, char * av[], commandPar &cPar ) 65 | { 66 | try { 67 | options_description desc("Allowed options"); 68 | desc.add_options() 69 | ("help,h", "produce help message") 70 | ("fileA,a", value(&cPar.file1)->required(), "Input file 1") // value< string > 71 | ("fileB,b", value(&cPar.file2)->default_value(""), "Input file 2") 72 | ("saveNorm,A", value(&cPar.normFile)->default_value(""), "File name to output the normals of original point cloud") 73 | ("knn,k", value(&cPar.knn)->default_value(12), "Set KNN number of neighbor points for normal estimation. Default normal estimation method") 74 | ("rtimes,t", value(&cPar.rtimes)->default_value(0), "Guide to set radius for normal estimation, times to NN points distances") 75 | ("forceNormalEstimation,f", bool_switch(&cPar.force), "Force normal estimation even normals are provided") 76 | ("singlePass,s", bool_switch(&cPar.singlePass)->default_value(false), "Force running a single pass, where the loop is over the original point cloud") 77 | ("hausdorff,d", bool_switch(&cPar.hausdorff)->default_value(false), "Send the Haursdorff metric as well") 78 | ("point2point,1", bool_switch(&cPar.c2c_only)->default_value(false), "Force to report point-to-point metric only") 79 | ; 80 | 81 | // positional_options_description p; 82 | // p.add("rtimes", -1); 83 | variables_map vm; 84 | store(parse_command_line(ac, av, desc), vm); 85 | 86 | if (ac == 1 || vm.count("help")) { // @DT: Important too add ac == 1 87 | cout << "Usage: " << av[0] << " [options]\n"; 88 | cout << desc; 89 | return 0; 90 | } 91 | 92 | notify(vm); // @DT: Report any missing parameters 93 | 94 | // It is wierd the variables were not set. Force the job 95 | cPar.file1 = vm["fileA"].as< string >(); 96 | cPar.file2 = vm["fileB"].as< string >(); 97 | cPar.normFile = vm["saveNorm"].as< string >(); 98 | cPar.rtimes = vm["rtimes"].as< float >(); 99 | if (cPar.rtimes == 0) 100 | cPar.knn = vm["knn"].as< int >(); 101 | else 102 | cPar.knn = 0; 103 | cPar.singlePass = vm["singlePass"].as< bool >(); 104 | cPar.c2c_only = vm["point2point"].as< bool >(); 105 | 106 | // Safety check 107 | if (cPar.knn < 0) 108 | { 109 | cout << "Error: knn must be non-negative" << endl; 110 | return 0; 111 | } 112 | 113 | return 1; 114 | } 115 | 116 | catch(std::exception& e) 117 | { 118 | cout << e.what() << "\n"; 119 | return 0; 120 | } 121 | 122 | // Confict check 123 | 124 | } 125 | 126 | void printCommand( commandPar &cPar ) 127 | { 128 | cout << "infile1: " << cPar.file1 << endl; 129 | cout << "infile2: " << cPar.file2 << endl; 130 | 131 | if ( cPar.knn > 0 ) 132 | cout << "knn = " << cPar.knn << endl; 133 | else 134 | cout << "rtimes = " << cPar.rtimes << endl; 135 | 136 | if (cPar.normFile != "") 137 | cout << "save normals of original point cloud to: " << cPar.normFile << endl; 138 | cout << "force normal estimation: " << cPar.force << endl; 139 | if (cPar.singlePass) 140 | cout << "force running a single pass" << endl; 141 | 142 | cout << endl; 143 | } 144 | 145 | int main (int argc, char *argv[]) 146 | { 147 | commandPar cPar; 148 | if ( parseCommand( argc, argv, cPar ) == 0 ) 149 | return 0; 150 | 151 | printCommand( cPar ); 152 | 153 | pcl::PointCloud::Ptr incloud1(new pcl::PointCloud); 154 | pcl::PointCloud::Ptr incloud2(new pcl::PointCloud); 155 | 156 | 157 | if (readcloud(cPar.file1, *incloud1)) 158 | { 159 | cout << "Error reading " << cPar.file1 << endl; 160 | return -1; 161 | } 162 | cout << "Reading file 1 done." << endl; 163 | 164 | if (cPar.file2 != "") 165 | { 166 | if (readcloud(cPar.file2, *incloud2)) 167 | { 168 | cout << "Error reading " << cPar.file2 << endl; 169 | return -1; 170 | } 171 | cout << "Reading file 2 done." << endl; 172 | } 173 | 174 | // compute the point to plane distances, as well as point to point distances 175 | struct timeval t1, t2; 176 | gettimeofday(&t1, NULL); 177 | pcl::geometric_quality::qMetric qm; 178 | pcl::geometric_quality::computeGeometricQualityMetric(*incloud1, *incloud2, cPar, qm); 179 | gettimeofday(&t2, NULL); 180 | 181 | cout << "Job done! " << (t2.tv_sec - t1.tv_sec) << " seconds elapsed (excluding the time to load the point clouds)." << endl; 182 | return 0; 183 | } 184 | -------------------------------------------------------------------------------- /geometric_distortion.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Software License Agreement 3 | * 4 | * Point to plane metric for point cloud distortion measurement 5 | * Copyright (c) 2016, MERL 6 | * 7 | * All rights reserved. 8 | * 9 | * Contributors: 10 | * Dong Tian 11 | * 12 | * Redistribution and use in source and binary forms, with or without 13 | * modification, are permitted provided that the following conditions 14 | * are met: 15 | * 16 | * * Redistributions of source code must retain the above copyright 17 | * notice, this list of conditions and the following disclaimer. 18 | * * Redistributions in binary form must reproduce the above 19 | * copyright notice, this list of conditions and the following 20 | * disclaimer in the documentation and/or other materials provided 21 | * with the distribution. 22 | * * Neither the name of the copyright holder(s) nor the names of its 23 | * contributors may be used to endorse or promote products derived 24 | * from this software without specific prior written permission. 25 | * 26 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 29 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 30 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 31 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 32 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 33 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 35 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 36 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 37 | * POSSIBILITY OF SUCH DAMAGE. 38 | * 39 | */ 40 | 41 | #ifndef GEOMETRIC_DISTORTION_HPP 42 | #define GEOMETRIC_DISTORTION_HPP 43 | 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | 50 | using namespace std; 51 | using namespace pcl; 52 | using namespace pcl::io; 53 | using namespace pcl::console; 54 | using namespace pcl::search; 55 | 56 | namespace pcl { 57 | 58 | namespace geometric_quality { 59 | 60 | class commandPar 61 | { 62 | public: 63 | string file1; 64 | string file2; 65 | 66 | string normFile; //! output the normals to this file 67 | 68 | float rtimes; //! \times the minimum distance of nearest neighbor 69 | float radius; //! radius to estimate normals. to be derived based on rtimes 70 | int knn; //! knn method to do normal estimation 71 | 72 | bool force; //! Force to do normal estimation, even if the normals provided in the input 73 | bool singlePass; //! Force to run a single pass algorithm. where the loop is over the original point cloud 74 | 75 | bool hausdorff; //! true: output hausdorff metric as well 76 | 77 | bool c2c_only; //! skip point-to-plane metric 78 | commandPar() 79 | { 80 | file1 = ""; file2 = ""; 81 | normFile = ""; 82 | rtimes = -1.0; 83 | radius = 1.0; 84 | knn = 0; 85 | force = false; 86 | singlePass = false; 87 | hausdorff = false; 88 | c2c_only = false; 89 | } 90 | }; 91 | 92 | /**! 93 | * \brief 94 | * Store the quality metric for point to plane measurements 95 | */ 96 | class qMetric { 97 | 98 | public: 99 | // point-2-point ( cloud 2 cloud ), benchmark metric 100 | float c2c_rms; //! store symm rms metric 101 | float c2c_hausdorff; //! store symm haussdorf 102 | float c2c_psnr; 103 | float c2c_hausdorff_psnr; //! store symm haussdorf 104 | 105 | // point-2-plane ( cloud 2 plane ), proposed metric 106 | float c2p_rms; //! store symm rms metric 107 | float c2p_hausdorff; //! store symm haussdorf 108 | float c2p_psnr; 109 | float c2p_hausdorff_psnr; //! store symm haussdorf 110 | 111 | // point 2 plane ( cloud 2 plane ), proposed metric 112 | float maxDist; //! maximum distnace between NN points in reference point cloud 113 | 114 | qMetric() 115 | { 116 | c2c_rms = 0; c2c_hausdorff = 0; 117 | c2p_rms = 0; c2p_hausdorff = 0; 118 | } 119 | }; 120 | 121 | /* 122 | * \brief load a point cloud 123 | * \param[in] file_name: the name of the file to load 124 | * \param[out] cloud: the resultant templated point cloud 125 | */ 126 | template int readcloud(const string &file_name, PointCloud &cloud) 127 | { 128 | int(*readfunc)( const string &, PointCloud & ); 129 | std::string suffix; 130 | suffix = file_name.substr(file_name.find_last_of(".") + 1); 131 | if (suffix == "pcd") 132 | readfunc = io::loadPCDFile; 133 | else if (suffix == "ply") 134 | readfunc = io::loadPLYFile; 135 | else 136 | { 137 | cerr << "Error: File " << file_name << " doesn't have a valid suffix" << endl; 138 | return -1; 139 | } 140 | 141 | return readfunc(file_name.c_str(), cloud); 142 | } 143 | 144 | /* 145 | * \brief write a point cloud into file 146 | * \param[in] file_name: the name of the file to load 147 | * \param[out] cloud: the resultant templated point cloud 148 | */ 149 | template int writecloud(const string &file_name, PointCloud &cloud) 150 | { 151 | int(*savefunc)( const string &, const pcl::PointCloud &, bool ); 152 | string suffix; 153 | suffix = file_name.substr(file_name.find_last_of(".") + 1); 154 | if (suffix == "pcd") 155 | { 156 | savefunc = pcl::io::savePCDFile; 157 | } 158 | else if (suffix == "ply") 159 | { 160 | savefunc = pcl::io::savePLYFile; 161 | } 162 | else 163 | { 164 | cout << "Error: File " << file_name.c_str() << " doesn't have a valid suffix" << endl; 165 | return -1; 166 | } 167 | 168 | return savefunc(file_name.c_str(), cloud, false); 169 | } 170 | 171 | /**! 172 | * \function 173 | * Compute the minimum and maximum NN distances, find out the 174 | * intrinsic resolutions 175 | * \parameters 176 | * @param cloudA: point cloud 177 | * @param minDist: output 178 | * @param maxDist: output 179 | * \note 180 | * PointT typename of point used in point cloud 181 | * \author 182 | * Dong Tian, MERL 183 | */ 184 | template void 185 | findNNdistances(PointCloud &cloudA, float &minDist, float &maxDist) 186 | { 187 | maxDist = numeric_limits::min(); 188 | minDist = numeric_limits::max(); 189 | double distTmp = 0; 190 | mutex myMutex; 191 | search::KdTree treeA; 192 | treeA.setInputCloud(cloudA.makeShared()); 193 | 194 | #pragma omp parallel for 195 | for (size_t i = 0; i < cloudA.points.size(); ++i) 196 | { 197 | std::vector indices; 198 | std::vector sqrDist; 199 | 200 | int nFound = treeA.nearestKSearch(cloudA.points[i], 2, indices, sqrDist); 201 | if ( nFound <= 0) 202 | cerr << "Error! No NN found!" << endl; 203 | 204 | if (indices[0] != i || sqrDist[1] <= 0.0000000001) 205 | { 206 | // Maybe print some warnings 207 | // cerr << "Error! nFound = " << nFound << ", i, iFound = " << i << ", " << indices[0] << ", " << indices[1] << endl; 208 | // cerr << " Distances = " << sqrDist[0] << ", " << sqrDist[1] << endl; 209 | // cerr << " Some points are repeated!" << endl; 210 | } 211 | 212 | else 213 | { 214 | // Use the second one. assume the first one is the current point 215 | myMutex.lock(); 216 | distTmp = sqrt( sqrDist[1] ); 217 | if (distTmp > maxDist) 218 | maxDist = distTmp; 219 | if (distTmp < minDist) 220 | minDist = distTmp; 221 | myMutex.unlock(); 222 | } 223 | } 224 | } 225 | 226 | /**! 227 | * \function 228 | * Convert the MSE error to PSNR numbers 229 | * \parameters 230 | * @param cloudA: the original point cloud 231 | * @param dist: the distortion 232 | * @param p: the peak value for conversion 233 | * \return 234 | * psnr value 235 | * \note 236 | * PointT typename of point used in point cloud 237 | * \author 238 | * Dong Tian, MERL 239 | */ 240 | template float 241 | getPSNR(PointCloud &cloudA, float dist, float p) 242 | { 243 | // @DT: If bounding box is wanted for the peak value 244 | // PointT pMinA, pMaxA; 245 | // getMinMax3D( cloudA, pMinA, pMaxA ); 246 | // metric.maxDist = pMaxA; 247 | 248 | float max_energy = p * p; 249 | float psnr = 10 * log10( max_energy / (dist*dist) ); 250 | 251 | return psnr; 252 | } 253 | 254 | /**! 255 | * \function 256 | * Check if meaningful normals exist. 257 | * \parameters 258 | * @param cloudA: the original point cloud 259 | * \return 260 | * true: normals are available 261 | * false: otherwise 262 | * \note 263 | * PointT typename of point used in point cloud 264 | * \author 265 | * Dong Tian, MERL 266 | */ 267 | template bool 268 | checkNormalsAvailability(PointCloud &cloudA) 269 | { 270 | size_t sz = cloudA.points.size(); 271 | size_t i = 0; 272 | if (cloudA.at(i).normal_x != 0 && cloudA.at(i).normal_x != 0 && cloudA.at(i).normal_x != 0 ) 273 | return true; 274 | i = sz - 1; 275 | if (cloudA.at(i).normal_x != 0 && cloudA.at(i).normal_x != 0 && cloudA.at(i).normal_x != 0 ) 276 | return true; 277 | i = sz / 2 - 1; 278 | if (cloudA.at(i).normal_x != 0 && cloudA.at(i).normal_x != 0 && cloudA.at(i).normal_x != 0 ) 279 | return true; 280 | return false; 281 | } 282 | 283 | /**! 284 | * \function 285 | * Derive the normals for the decoded point cloud based on the 286 | * normals in the original point cloud 287 | * \parameters 288 | * @param cloudA: the original point cloud 289 | * @param cloudNormalsA: the normals in the original point cloud 290 | * @param cloudB: the decoded point cloud 291 | * @param cloudNormalsB: the normals in the original point 292 | * cloud. Output parameter 293 | * \note 294 | * PointT typename of point used in point cloud 295 | * \author 296 | * Dong Tian, MERL 297 | */ 298 | template void 299 | scaleNormals(PointCloud &cloudA, PointCloud::Ptr &cloudNormalsA, PointCloud &cloudB, vector< vector > &cloudNormalsB) 300 | { 301 | // Prepare the buffer to compute the average normals 302 | clock_t t1 = clock(); 303 | 304 | vector< vector > vecMap( cloudB.points.size() ); 305 | 306 | for (size_t i = 0; i < cloudB.points.size(); i++) 307 | { 308 | cloudNormalsB[i].push_back(0.0); // x 309 | cloudNormalsB[i].push_back(0.0); // y 310 | cloudNormalsB[i].push_back(0.0); // z 311 | vecMap[i].clear(); 312 | } 313 | 314 | // sum up 315 | search::KdTree treeA; 316 | treeA.setInputCloud (cloudA.makeShared()); 317 | search::KdTree treeB; 318 | treeB.setInputCloud (cloudB.makeShared()); 319 | 320 | for (size_t i = 0; i < cloudA.points.size(); i++) 321 | { 322 | // Find the NNs in cloudA 323 | vector indices; 324 | vector sqrDist; 325 | float nX, nY, nZ; 326 | int nCount; 327 | 328 | treeB.nearestKSearch(cloudA.points[i], 1, indices, sqrDist); 329 | nX = nY = nZ = 0.0; 330 | nCount = 0; 331 | 332 | if ( !isnan(cloudNormalsA->at(i).normal_x) && !isnan(cloudNormalsA->at(i).normal_y) && !isnan(cloudNormalsA->at(i).normal_z) ) 333 | { 334 | cloudNormalsB[indices[0]][0] += cloudNormalsA->at( i ).normal_x; 335 | cloudNormalsB[indices[0]][1] += cloudNormalsA->at( i ).normal_y; 336 | cloudNormalsB[indices[0]][2] += cloudNormalsA->at( i ).normal_z; 337 | vecMap[ indices[0] ].push_back( i ); 338 | } 339 | } 340 | 341 | // average now 342 | for (size_t i = 0; i < cloudB.points.size(); i++) 343 | { 344 | int nCount = vecMap[i].size(); 345 | if (nCount > 0) // main branch 346 | { 347 | cloudNormalsB[i][0] = cloudNormalsB[i][0] / nCount; 348 | cloudNormalsB[i][1] = cloudNormalsB[i][1] / nCount; 349 | cloudNormalsB[i][2] = cloudNormalsB[i][2] / nCount; 350 | } 351 | else 352 | { 353 | vector indices; 354 | vector sqrDist; 355 | treeA.nearestKSearch(cloudB.points[i], 1, indices, sqrDist); 356 | 357 | if ( !isnan(cloudNormalsA->at(indices[0]).normal_x) && !isnan(cloudNormalsA->at(indices[0]).normal_y) && !isnan(cloudNormalsA->at(indices[0]).normal_z) ) 358 | { 359 | cloudNormalsB[i][0] = cloudNormalsA->at( indices[0] ).normal_x; 360 | cloudNormalsB[i][1] = cloudNormalsA->at( indices[0] ).normal_y; 361 | cloudNormalsB[i][2] = cloudNormalsA->at( indices[0] ).normal_z; 362 | } 363 | else 364 | { // Should never comes here. The code just for completeness 365 | cloudNormalsB[i][0] = 0; 366 | cloudNormalsB[i][1] = 0; 367 | cloudNormalsB[i][2] = 0; 368 | } 369 | } 370 | } 371 | 372 | clock_t t2 = clock(); 373 | cout << " Converting normal vector DONE. It takes " << (t2-t1)/CLOCKS_PER_SEC << " seconds (in CPU time)." << endl; 374 | } 375 | 376 | /**! 377 | * \function 378 | * Get the normals for the original point cloud, either by importing 379 | * or estimation 380 | * \parameters 381 | * @param cloudA: the original point cloud 382 | * @param normFile: the file name to store the normals 383 | * @param cPar: input parameter from command line 384 | * @param cloudNormals: output paraemter for the normals 385 | * \note 386 | * PointT typename of point used in point cloud 387 | * \author 388 | * Dong Tian, MERL 389 | */ 390 | template void 391 | getNormals(PointCloud &cloudA, string &normFile, commandPar &cPar, PointCloud::Ptr cloudNormals) 392 | { 393 | if (!cPar.force && checkNormalsAvailability( cloudA ) ) 394 | { 395 | cout << " Import existing normal from input" << endl; 396 | copyPointCloud( cloudA, *cloudNormals ); 397 | cout << " Normal importing on original point cloud DONE!" << endl; 398 | return; 399 | } 400 | 401 | clock_t t1; 402 | t1=clock(); 403 | 404 | if (cPar.knn == 0) 405 | { 406 | // Step 0 ------------------ 407 | float minDist; 408 | float maxDist; 409 | findNNdistances(cloudA, minDist, maxDist); 410 | cout << " Point cloud, to estimate normals: minDist, maxDist = " << minDist << ", " << maxDist << endl; 411 | 412 | cPar.radius = cPar.rtimes * minDist; 413 | cout << " Radius in use: " << cPar.radius << endl; 414 | } 415 | else 416 | { 417 | cout << " KNN in use: " << cPar.knn << endl; 418 | } 419 | cout << " Normal estimation begin.." << endl; 420 | 421 | // Step 1 ------------------ 422 | // @DT: Compute the normals of A, the reference point cloud 423 | // Create the normal estimation class, and pass the input dataset to it 424 | NormalEstimationOMP ne; 425 | ne.setInputCloud(cloudA.makeShared()); 426 | 427 | // Create an empty kdtree representation, and pass it to the normal estimation object. 428 | // Its content will be filled inside the object, based on the given input dataset (as no other search surface is given). 429 | search::KdTree::Ptr tree (new search::KdTree()); 430 | ne.setSearchMethod(tree); 431 | 432 | // // Output datasets 433 | // PointCloud::Ptr cloudNormals (new PointCloud); 434 | 435 | if (cPar.knn == 0) 436 | // Use all neighbors in a sphere of radius 437 | ne.setRadiusSearch(cPar.radius); 438 | else 439 | // Use a fixed number of neighbors 440 | ne.setKSearch( cPar.knn ); 441 | 442 | // Set view points 443 | Eigen::Vector4f centroid; 444 | centroid.setZero(); 445 | compute3DCentroid( cloudA, centroid ); 446 | PointT pMin, pMax; 447 | getMinMax3D( cloudA, pMin, pMax ); 448 | ne.setViewPoint( centroid[0], centroid[1], pMax.z+1.0 ); 449 | // cout << "Centroid: " << centroid[0] << " " << centroid[1] << " " << centroid[2] << " " << centroid[3] << " size " << cloudA.size() << endl; 450 | // cout << "Min: " << pMin.x << " " << pMin.y << " " << pMin.z << endl; 451 | // cout << "Max: " << pMax.x << " " << pMax.y << " " << pMax.z << endl; 452 | 453 | // Compute the features 454 | ne.compute(*cloudNormals); 455 | 456 | if ( normFile != "" ) 457 | { 458 | PointCloud::Ptr cloudWithNormals(new PointCloud); 459 | copyPointCloud( cloudA, *cloudWithNormals ); 460 | copyPointCloud( *cloudNormals, *cloudWithNormals ); 461 | writecloud(normFile, *cloudWithNormals); 462 | } 463 | 464 | // Check if any nan normals 465 | size_t nanNormal = 0; 466 | for (size_t i = 0; i < cloudA.points.size(); i++) 467 | { 468 | if ( isnan(cloudNormals->at(i).normal_x) || isnan(cloudNormals->at(i).normal_y) || isnan(cloudNormals->at(i).normal_z) ) 469 | nanNormal++; 470 | } 471 | if (nanNormal > 0) 472 | { 473 | if (cPar.knn == 0) 474 | cerr << " ** Warning: nan normals found: " << nanNormal << "! Increase the radius!" << endl; 475 | else 476 | cerr << " ** Warning: nan normals found: " << nanNormal << "! Increase the knn!" << endl; 477 | // cout << "The points with nan normals would be excluded from metric calculation." << endl; 478 | } 479 | 480 | cout << " Normal estimation on original point cloud DONE! It takes " << (clock() - t1) / CLOCKS_PER_SEC << " seconds (in CPU time)." << endl; 481 | } 482 | 483 | /**! 484 | * function to compute the symmetric quality metric: Point-to-Point and Point-to-Plane 485 | * @param cloudA: point cloud, original version 486 | * @param cloudB: point cloud, decoded/reconstructed version 487 | * @param cPar: input parameters 488 | * @param qual_metric: quality metric, to be returned 489 | * \note 490 | * PointT typename of point used in point cloud 491 | * \author 492 | * Dong Tian, MERL 493 | */ 494 | template void 495 | computeGeometricQualityMetric(PointCloud &cloudA, PointCloud &cloudB, commandPar &cPar, qMetric &qual_metric) 496 | { 497 | float minDist; 498 | float maxDist; 499 | findNNdistances(cloudA, minDist, maxDist); 500 | qual_metric.maxDist = maxDist; 501 | cout << "Minimum and maximum NN distances (intrinsic resolutions): " << minDist << ", " << maxDist << endl; 502 | 503 | // Check cloud size 504 | size_t orgSize = max( cloudA.points.size(), cloudB.points.size() ); 505 | size_t newSize = min( cloudA.points.size(), cloudB.points.size() ); 506 | float ratio = 1.0 * newSize / orgSize; 507 | cout << "Point cloud sizes for org version, dec version, and the scaling ratio: " << orgSize << ", " << newSize << ", " << ratio << endl; 508 | 509 | if (cPar.file2 == "" && cPar.normFile == "" ) // If no file2 & no normFile provided, return just after checking the NN 510 | return; 511 | 512 | // Estimate or import normals, only on original point cloud 513 | PointCloud::Ptr cloudNormalsA (new PointCloud); 514 | if (!cPar.c2c_only) 515 | { 516 | cout << endl; 517 | cout << "0. Preparing normals.\n"; 518 | getNormals(cloudA, cPar.normFile, cPar, cloudNormalsA); 519 | } 520 | 521 | if (cPar.file2 == "") // If no file2 provided, return just after normal estimations. 522 | return; 523 | 524 | // Based on normals on original point cloud, derive normals on reconstructed point cloud 525 | vector< vector > cloudNormalsB( cloudB.points.size() ); 526 | if (!cPar.c2c_only) 527 | scaleNormals( cloudA, cloudNormalsA, cloudB, cloudNormalsB ); 528 | cout << endl; 529 | 530 | // Use "a" as reference 531 | cout << "1. Use infile1 (A) as reference, loop over A, use normals on B. (A->B).\n"; 532 | qMetric metricA; 533 | metricA.maxDist = maxDist; 534 | findMetricA( cloudA, cloudB, cPar, cloudNormalsB, metricA ); 535 | 536 | cout << " ### A->B,rms1,p2point," << metricA.c2c_rms << endl; 537 | cout << " ### A->B,rms1PSNR,p2point," << metricA.c2c_psnr << endl; 538 | if (!cPar.c2c_only) 539 | { 540 | cout << " ### A->B,rms1,p2plane," << metricA.c2p_rms << endl; 541 | cout << " ### A->B,rms1PSNR,p2plane," << metricA.c2p_psnr << endl; 542 | } 543 | if ( cPar.hausdorff ) 544 | { 545 | cout << " ### A->B,h1,p2point," << metricA.c2c_hausdorff << endl; 546 | cout << " ### A->B,hPSNR1,p2point," << metricA.c2c_hausdorff_psnr << endl; 547 | if (!cPar.c2c_only) 548 | { 549 | cout << " ### A->B,h1,p2plane," << metricA.c2p_hausdorff << endl; 550 | cout << " ### A->B,hPSNR1,p2plane," << metricA.c2p_hausdorff_psnr << endl; 551 | } 552 | } 553 | 554 | if (!cPar.singlePass) 555 | { 556 | // Use "b" as reference 557 | cout << "2. Use infile2 (B) as reference, loop over B, use normals on A. (B->A).\n"; 558 | qMetric metricB; 559 | metricB.maxDist = maxDist; 560 | findMetricB( cloudA, cloudB, cPar, cloudNormalsA, metricB ); 561 | 562 | cout << " ### B->A,rms2,p2point," << metricB.c2c_rms << endl; 563 | cout << " ### B->A,rms2PSNR,p2point," << metricB.c2c_psnr << endl; 564 | if (!cPar.c2c_only) 565 | { 566 | cout << " ### B->A,rms2,p2plane, " << metricB.c2p_rms << endl; 567 | cout << " ### B->A,rms2PSNR,p2plane," << metricB.c2p_psnr << endl; 568 | } 569 | if ( cPar.hausdorff ) 570 | { 571 | cout << " ### B->A,h2,p2point," << metricB.c2c_hausdorff << endl; 572 | cout << " ### B->A,hPSNR2,p2point," << metricB.c2c_hausdorff_psnr << endl; 573 | if (!cPar.c2c_only) 574 | { 575 | cout << " ### B->A,h2,p2plane," << metricB.c2p_hausdorff << endl; 576 | cout << " ### B->A,hPSNR2,p2plane," << metricB.c2p_hausdorff_psnr << endl; 577 | } 578 | } 579 | 580 | // Derive the final symmetric metric 581 | qual_metric.c2c_rms = max( metricA.c2c_rms, metricB.c2c_rms ); 582 | qual_metric.c2p_rms = max( metricA.c2p_rms, metricB.c2p_rms ); 583 | qual_metric.c2c_psnr = min( metricA.c2c_psnr, metricB.c2c_psnr ); 584 | qual_metric.c2p_psnr = min( metricA.c2p_psnr, metricB.c2p_psnr ); 585 | 586 | qual_metric.c2c_hausdorff = max( metricA.c2c_hausdorff, metricB.c2c_hausdorff ); 587 | qual_metric.c2p_hausdorff = max( metricA.c2p_hausdorff, metricB.c2p_hausdorff ); 588 | qual_metric.c2c_hausdorff_psnr = min( metricA.c2c_hausdorff_psnr, metricB.c2c_hausdorff_psnr ); 589 | qual_metric.c2p_hausdorff_psnr = min( metricA.c2p_hausdorff_psnr, metricB.c2p_hausdorff_psnr ); 590 | 591 | cout << "3. Final (symmetric).\n"; 592 | cout << " ### Symmetric,rmsF,p2point," << qual_metric.c2c_rms << endl; 593 | cout << " ### Symmetric,rmsFPSNR,p2point," << qual_metric.c2c_psnr << endl; 594 | if (!cPar.c2c_only) 595 | { 596 | cout << " ### Symmetric,rmsF,p2plane," << qual_metric.c2p_rms << endl; 597 | cout << " ### Symmetric,rmsFPSNR,p2plane," << qual_metric.c2p_psnr << endl; 598 | } 599 | if ( cPar.hausdorff ) 600 | { 601 | cout << " ### Symmetric,hF,p2point," << qual_metric.c2c_hausdorff << endl; 602 | cout << " ### Symmetric,hPSNRF,p2point," << qual_metric.c2c_hausdorff_psnr << endl; 603 | if (!cPar.c2c_only) 604 | { 605 | cout << " ### Symmetric,hF,p2plane," << qual_metric.c2p_hausdorff << endl; 606 | cout << " ### Symmetric,hPSNRF,p2plane," << qual_metric.c2p_hausdorff_psnr << endl; 607 | } 608 | } 609 | } 610 | } 611 | 612 | /**! 613 | * \function 614 | * To compute "one-way" quality metric: Point-to-Point and 615 | * Point-to-Plane. Loop over each point in A. Normals in B to be used 616 | * 617 | * 1) For each point in A, find a corresponding point in B. 618 | * 2) Form an error vector between the point pair. 619 | * 3) Use the length of the error vector as point-to-point measure 620 | * 4) Project the error vector along the normals in B, use the length 621 | * of the projected error vector as point-to-plane measure 622 | * 623 | * @param cloudA: Reference point cloud. e.g. the original cloud, on 624 | * which normals would be estimated. It is the full set of point 625 | * cloud. Multiple points in count 626 | * @param cloudB: Processed point cloud. e.g. the decoded cloud 627 | * @param cPar: Command line parameters 628 | * @param cloudNormalsB: Normals for cloudB 629 | * @param metric: updated quality metric, to be returned 630 | * \note 631 | * PointT typename of point used in point cloud 632 | * \author 633 | * Dong Tian, MERL 634 | */ 635 | template void 636 | findMetricA(PointCloud &cloudA, PointCloud &cloudB, commandPar &cPar, vector< vector > &cloudNormalsB, qMetric &metric) 637 | { 638 | mutex myMutex; 639 | 640 | // @DT: Compute the projected distance along the normal direction (cloud 2 plane) 641 | clock_t t2 = clock(); 642 | float max_dist_b_c2p = -std::numeric_limits::max(); 643 | double rms_dist_b_c2p = 0; 644 | float max_dist_b_c2c = -std::numeric_limits::max(); 645 | double rms_dist_b_c2c = 0; 646 | size_t num = 0; 647 | 648 | search::KdTree treeB; 649 | treeB.setInputCloud (cloudB.makeShared()); 650 | #pragma omp parallel for 651 | for (size_t i = 0; i < cloudA.points.size(); i++) 652 | { 653 | // Find the nearest neighbor in B. store it in 'j' 654 | vector indices(1); 655 | vector sqrDist(1); 656 | treeB.nearestKSearch(cloudA.points[i], 1, indices, sqrDist); 657 | int j = indices[0]; 658 | 659 | // Compute the error vector 660 | vector errVector(3); 661 | errVector[0] = cloudA.points[i].x - cloudB.points[j].x; 662 | errVector[1] = cloudA.points[i].y - cloudB.points[j].y; 663 | errVector[2] = cloudA.points[i].z - cloudB.points[j].z; 664 | 665 | // Compute point-to-point, which should be equal to sqrt( sqrDist[0] ) 666 | float distProj_c2c = sqrt( errVector[0] * errVector[0] + 667 | errVector[1] * errVector[1] + 668 | errVector[2] * errVector[2] ); 669 | 670 | // Compute point-to-plane 671 | // Normals in B will be used for point-to-plane 672 | float distProj = 0.0; 673 | if (!cPar.c2c_only) 674 | { 675 | distProj = fabs( errVector[0] * cloudNormalsB[j][0] + 676 | errVector[1] * cloudNormalsB[j][1] + 677 | errVector[2] * cloudNormalsB[j][2] ); 678 | } 679 | 680 | myMutex.lock(); 681 | 682 | num++; 683 | // mean square distance 684 | rms_dist_b_c2c += distProj_c2c; 685 | if (distProj_c2c > max_dist_b_c2c) 686 | max_dist_b_c2c = distProj_c2c; 687 | if (!cPar.c2c_only) 688 | { 689 | rms_dist_b_c2p += distProj; 690 | if (distProj > max_dist_b_c2p) 691 | max_dist_b_c2p = distProj; 692 | } 693 | 694 | myMutex.unlock(); 695 | } 696 | 697 | rms_dist_b_c2p = rms_dist_b_c2p / num; 698 | rms_dist_b_c2c = rms_dist_b_c2c / num; 699 | 700 | metric.c2p_rms = rms_dist_b_c2p; 701 | metric.c2c_rms = rms_dist_b_c2c; 702 | metric.c2p_hausdorff = max_dist_b_c2p; 703 | metric.c2c_hausdorff = max_dist_b_c2c; 704 | 705 | // from distance to PSNR. cloudA always the original 706 | metric.c2c_psnr = getPSNR( cloudA, metric.c2c_rms, metric.maxDist ); 707 | metric.c2p_psnr = getPSNR( cloudA, metric.c2p_rms, metric.maxDist ); 708 | 709 | metric.c2c_hausdorff_psnr = getPSNR( cloudA, metric.c2c_hausdorff, metric.maxDist ); 710 | metric.c2p_hausdorff_psnr = getPSNR( cloudA, metric.c2p_hausdorff, metric.maxDist ); 711 | 712 | clock_t t3 = clock(); 713 | cerr << " Error computing takes " << (t3-t2)/CLOCKS_PER_SEC << " seconds (in CPU time)." << endl; 714 | } 715 | 716 | /**! 717 | * \function 718 | * To compute "one-way" quality metric: Point-to-Point and 719 | * Point-to-Plane. Loop over each point in B. Normals in A to be used 720 | * 721 | * 1) For each point in B, find a corresponding point in A. 722 | * 2) Form an error vector between the point pair. 723 | * 3) Use the length of the error vector as point-to-point measure 724 | * 4) Project the error vector along the normals in A, use the length 725 | * of the projected error vector as point-to-plane measure 726 | * 727 | * @param cloudA: Reference point cloud. e.g. the original cloud, on 728 | * which normals would be estimated. It is the full set of point 729 | * cloud. Multiple points in count 730 | * @param cloudB: Processed point cloud. e.g. the decoded cloud 731 | * @param cPar: Command line parameters 732 | * @param cloudNormalsA: Normals for cloudA 733 | * @param metric: updated quality metric, to be returned 734 | * \note 735 | * PointT typename of point used in point cloud 736 | * \author 737 | * Dong Tian, MERL 738 | */ 739 | template void 740 | findMetricB(PointCloud &cloudA, PointCloud &cloudB, commandPar &cPar, PointCloud::Ptr &cloudNormalsA, qMetric &metric) 741 | { 742 | mutex myMutex; 743 | 744 | clock_t t2 = clock(); 745 | float max_dist_b_c2p = -std::numeric_limits::max(); 746 | double rms_dist_b_c2p = 0; 747 | float max_dist_b_c2c = -std::numeric_limits::max(); 748 | double rms_dist_b_c2c = 0; 749 | size_t num = 0; 750 | 751 | search::KdTree treeA; 752 | treeA.setInputCloud (cloudA.makeShared()); 753 | #pragma omp parallel for 754 | for (size_t i = 0; i < cloudB.points.size(); i++) 755 | { 756 | // Find the nearest neighbor in A. store it in 'j' 757 | vector indices(1); 758 | vector sqrDist(1); 759 | treeA.nearestKSearch(cloudB.points[i], 1, indices, sqrDist); 760 | int j = indices[0]; 761 | 762 | // Compute the error vector 763 | vector errVector(3); 764 | errVector[0] = cloudB.points[i].x - cloudA.points[j].x; 765 | errVector[1] = cloudB.points[i].y - cloudA.points[j].y; 766 | errVector[2] = cloudB.points[i].z - cloudA.points[j].z; 767 | 768 | // Compute point-to-point, which should be equal to sqrt( sqrDist[0] ) 769 | float distProj_c2c = sqrt( errVector[0] * errVector[0] + 770 | errVector[1] * errVector[1] + 771 | errVector[2] * errVector[2] ); 772 | 773 | // Compute point-to-plane 774 | // Normals in A will be used for point-to-plane 775 | float distProj; 776 | if (!cPar.c2c_only) 777 | { 778 | distProj = fabs( errVector[0] * cloudNormalsA->at(j).normal_x + 779 | errVector[1] * cloudNormalsA->at(j).normal_y + 780 | errVector[2] * cloudNormalsA->at(j).normal_z ); 781 | } 782 | 783 | myMutex.lock(); 784 | 785 | num++; 786 | // mean square distance 787 | rms_dist_b_c2c += distProj_c2c; 788 | if (distProj_c2c > max_dist_b_c2c) 789 | max_dist_b_c2c = distProj_c2c; 790 | if (!cPar.c2c_only) 791 | { 792 | rms_dist_b_c2p += distProj; 793 | if (distProj > max_dist_b_c2p) 794 | max_dist_b_c2p = distProj; 795 | } 796 | 797 | myMutex.unlock(); 798 | } 799 | 800 | rms_dist_b_c2p = rms_dist_b_c2p / num; 801 | rms_dist_b_c2c = rms_dist_b_c2c / num; 802 | 803 | metric.c2p_rms = rms_dist_b_c2p; 804 | metric.c2c_rms = rms_dist_b_c2c; 805 | metric.c2p_hausdorff = max_dist_b_c2p; 806 | metric.c2c_hausdorff = max_dist_b_c2c; 807 | 808 | // from distance to PSNR. cloudA always the original 809 | metric.c2c_psnr = getPSNR( cloudA, metric.c2c_rms, metric.maxDist ); 810 | metric.c2p_psnr = getPSNR( cloudA, metric.c2p_rms, metric.maxDist ); 811 | metric.c2c_hausdorff_psnr = getPSNR( cloudA, metric.c2c_hausdorff, metric.maxDist ); 812 | metric.c2p_hausdorff_psnr = getPSNR( cloudA, metric.c2p_hausdorff, metric.maxDist ); 813 | 814 | clock_t t3 = clock(); 815 | cerr << " Error computing takes " << (t3-t2)/CLOCKS_PER_SEC << " seconds (in CPU time)." << endl; 816 | } 817 | 818 | }; 819 | 820 | } //~ namespace pcl 821 | 822 | #endif 823 | --------------------------------------------------------------------------------