├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── doc ├── images │ ├── findstems.png │ ├── findstems_regions.png │ ├── getcrownvolume.png │ ├── getdtmslice.png │ ├── segmentcrown.png │ ├── segmentstem.png │ ├── segmentstem_regions.png │ └── treeseg_cover.png ├── tutorial_downsample.md ├── tutorial_findstems.md ├── tutorial_getcrownvolume.md ├── tutorial_getdtmslice.md ├── tutorial_overview.md ├── tutorial_preprocessing.md ├── tutorial_segmentcrown.md ├── tutorial_segmentstem.md └── tutorial_tldr.md ├── include ├── leafsep.h ├── riegl │ └── .keep_include ├── treeseg.h └── treeseg_pointtype.h ├── lib └── riegl │ └── .keep_lib └── src ├── downsample.cpp ├── findstems.cpp ├── getcrownvolume.cpp ├── getdtmslice.cpp ├── leafsep.cpp ├── nearestneighbour.cpp ├── pcdPointTreeseg2txt.cpp ├── pcdPointXYZRGB2txt.cpp ├── plotcoords.cpp ├── rxp2pcd.cpp ├── rxp2tree.cpp ├── segmentcrown.cpp ├── segmentstem.cpp ├── sepwoodleaf.cpp ├── thin.cpp ├── treeparams.cpp ├── treeseg.cpp └── txtPointTreeseg2pcd.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | #Directories 35 | /build/ 36 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(treeseg) 3 | 4 | find_package(PCL 1.10 REQUIRED) 5 | find_package(Armadillo 9.8 REQUIRED) 6 | 7 | include_directories(${PCL_INCLUDE_DIRS} ${ARMADILLO_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/include/riegl) 8 | link_directories(${CMAKE_SOURCE_DIR}/lib ${CMAKE_SOURCE_DIR}/lib/riegl) 9 | 10 | if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") 11 | if(EXISTS "${CMAKE_SOURCE_DIR}/lib/riegl/libscanifc-mt.so") 12 | add_executable(rxp2pcd ${CMAKE_SOURCE_DIR}/src/rxp2pcd.cpp) 13 | target_link_libraries(rxp2pcd treeseg scanifc-mt) 14 | endif() 15 | else() 16 | message(WARNING "rxp executables not building because either RIEGL libs/headers not found, or not a compatible OS.") 17 | endif() 18 | 19 | add_library(treeseg SHARED ${CMAKE_SOURCE_DIR}/src/treeseg.cpp) 20 | target_link_libraries(treeseg ${PCL_LIBRARIES}) 21 | 22 | add_library(leafsep SHARED ${CMAKE_SOURCE_DIR}/src/leafsep.cpp) 23 | target_link_libraries(leafsep treeseg ${PCL_LIBRARIES} ${ARMADILLO_LIBRARIES}) 24 | 25 | add_executable(plotcoords ${CMAKE_SOURCE_DIR}/src/plotcoords.cpp) 26 | target_link_libraries(plotcoords) 27 | 28 | add_executable(nearestneighbour ${CMAKE_SOURCE_DIR}/src/nearestneighbour.cpp) 29 | target_link_libraries(nearestneighbour treeseg ${PCL_LIBRARIES}) 30 | 31 | add_executable(downsample ${CMAKE_SOURCE_DIR}/src/downsample.cpp) 32 | target_link_libraries(downsample treeseg ${PCL_LIBRARIES}) 33 | 34 | add_executable(thin ${CMAKE_SOURCE_DIR}/src/thin.cpp) 35 | target_link_libraries(thin treeseg ${PCL_LIBRARIES}) 36 | 37 | add_executable(getdtmslice ${CMAKE_SOURCE_DIR}/src/getdtmslice.cpp) 38 | target_link_libraries(getdtmslice treeseg ${PCL_LIBRARIES}) 39 | 40 | add_executable(findstems ${CMAKE_SOURCE_DIR}/src/findstems.cpp) 41 | target_link_libraries(findstems treeseg ${PCL_LIBRARIES}) 42 | 43 | add_executable(segmentstem ${CMAKE_SOURCE_DIR}/src/segmentstem.cpp) 44 | target_link_libraries(segmentstem treeseg ${PCL_LIBRARIES}) 45 | 46 | add_executable(getcrownvolume ${CMAKE_SOURCE_DIR}/src/getcrownvolume.cpp) 47 | target_link_libraries(getcrownvolume treeseg ${PCL_LIBRARIES}) 48 | 49 | add_executable(segmentcrown ${CMAKE_SOURCE_DIR}/src/segmentcrown.cpp) 50 | target_link_libraries(segmentcrown treeseg ${PCL_LIBRARIES}) 51 | 52 | add_executable(sepwoodleaf ${CMAKE_SOURCE_DIR}/src/sepwoodleaf.cpp) 53 | target_link_libraries(sepwoodleaf treeseg leafsep ${PCL_LIBRARIES}) 54 | 55 | add_executable(pcdPointTreeseg2txt ${CMAKE_SOURCE_DIR}/src/pcdPointTreeseg2txt.cpp) 56 | target_link_libraries(pcdPointTreeseg2txt treeseg ${PCL_LIBRARIES}) 57 | 58 | add_executable(txtPointTreeseg2pcd ${CMAKE_SOURCE_DIR}/src/txtPointTreeseg2pcd.cpp) 59 | target_link_libraries(txtPointTreeseg2pcd treeseg ${PCL_LIBRARIES}) 60 | 61 | add_executable(pcdPointXYZRGB2txt ${CMAKE_SOURCE_DIR}/src/pcdPointXYZRGB2txt.cpp) 62 | target_link_libraries(pcdPointXYZRGB2txt ${PCL_LIBRARIES}) 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Andrew Burt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # treeseg 2 | 3 | Extract individual trees from lidar point clouds 4 | 5 | 6 | 7 | ## Table of contents 8 | 9 | - [Overview](#overview) 10 | - [Installation](#installation) 11 | - [Usage](#usage) 12 | - [Acknowledgements](#acknowledgements) 13 | - [Authors](#authors) 14 | - [Citing](#citing) 15 | - [License](#license) 16 | 17 | ## Overview 18 | 19 | treeseg has been developed to near-automatically segment individual tree point clouds from high-density larger-area lidar point clouds acquired in forests. A formal, albeit somewhat outdated description of the methods can be found in our [paper](https://doi.org/10.1111/2041-210X.13121). 20 | 21 | ## Installation 22 | 23 | treeseg has been developed and tested on Ubuntu 20.04 LTS only, and is dependent on the following packages: 24 | 25 | * Point Cloud Library (v1.10) 26 | * Armadillo (v9.8) 27 | 28 | These dependencies are installed via apt: 29 | 30 | ``` 31 | apt install libpcl-dev libarmadillo-dev 32 | ``` 33 | 34 | treeseg can then be installed using: 35 | 36 | ``` 37 | git clone https://github.com/apburt/treeseg.git; 38 | mkdir ./treeseg/build; cd ./treeseg/build; cmake ..; make; 39 | ``` 40 | 41 | Optionally, for users with RIEGL V-Line scan data, treeseg includes the executable `rxp2pcd`, to convert and preprocess data in RXP data stream format, to binary PCD format. This executable will automatically be built if the directories `./treeseg/include/reigl/` and `./treeseg/lib/riegl/` are populated with the RIEGL RiVLIB headers and libraries (as appropriate for the user's particular CPU architecture and gcc version), which can be downloaded from the Members Area of the RIEGL website (e.g., rivlib-2_5_10-x86_64-linux-gcc9.zip). 42 | 43 | Finally, the environment variable `PATH` can then be updated to include the directory containing the built treeseg executables, either temporarily, by calling the following, or permanently, by inserting it at the top of `~/.bashrc`: 44 | 45 | ``` 46 | export PATH="$PATH:/path/to/treeseg/build" 47 | ``` 48 | 49 | ## Usage 50 | 51 | A tutorial demonstrating the usage of treeseg is available [here](/doc/tutorial_overview.md). 52 | 53 | ## Acknowledgements 54 | 55 | treeseg makes extensive use of the Point Cloud Library ([PCL](http://pointclouds.org)). 56 | 57 | ## Authors 58 | 59 | * Andrew Burt 60 | * Mathias Disney 61 | * Kim Calders 62 | * Matheus Boni Vicari 63 | * Tony Peter 64 | 65 | ## Citing 66 | 67 | treeseg can be cited as: 68 | 69 | Burt, A., Disney, M., Calders, K. (2019). Extracting individual trees from lidar point clouds using *treeseg*. *Methods Ecol Evol* 10(3), 438–445. doi: 10.1111/2041-210X.13121 70 | 71 | A doi for the latest version is available in [releases](https://github.com/apburt/treeseg/releases). 72 | 73 | ## License 74 | 75 | This project is licensed under the terms of the MIT license - see the [LICENSE](LICENSE) file for details. 76 | -------------------------------------------------------------------------------- /doc/images/findstems.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apburt/treeseg/076ea0949a17102fcc6ce8d33cf1d915895bf180/doc/images/findstems.png -------------------------------------------------------------------------------- /doc/images/findstems_regions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apburt/treeseg/076ea0949a17102fcc6ce8d33cf1d915895bf180/doc/images/findstems_regions.png -------------------------------------------------------------------------------- /doc/images/getcrownvolume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apburt/treeseg/076ea0949a17102fcc6ce8d33cf1d915895bf180/doc/images/getcrownvolume.png -------------------------------------------------------------------------------- /doc/images/getdtmslice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apburt/treeseg/076ea0949a17102fcc6ce8d33cf1d915895bf180/doc/images/getdtmslice.png -------------------------------------------------------------------------------- /doc/images/segmentcrown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apburt/treeseg/076ea0949a17102fcc6ce8d33cf1d915895bf180/doc/images/segmentcrown.png -------------------------------------------------------------------------------- /doc/images/segmentstem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apburt/treeseg/076ea0949a17102fcc6ce8d33cf1d915895bf180/doc/images/segmentstem.png -------------------------------------------------------------------------------- /doc/images/segmentstem_regions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apburt/treeseg/076ea0949a17102fcc6ce8d33cf1d915895bf180/doc/images/segmentstem_regions.png -------------------------------------------------------------------------------- /doc/images/treeseg_cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apburt/treeseg/076ea0949a17102fcc6ce8d33cf1d915895bf180/doc/images/treeseg_cover.png -------------------------------------------------------------------------------- /doc/tutorial_downsample.md: -------------------------------------------------------------------------------- 1 | # Downsampling 2 | 3 | Downsampling the data is an optional step, but provides two potential benefits: i) some of the point cloud processing methods (e.g., Euclidean clustering) will provide more robust results, and ii) reduces computation. However, determining the appropriate level of downsampling is a complicated subject, requiring careful consideration (e.g., the downstream implications of downsampling, such as the impacts on above-ground biomass estimation), which is briefly discussed in the following subsection. 4 | 5 | treeseg implements two methods for downsampling, both employing octrees. The first, and the one used in this tutorial, `downsample`, replaces the points inside each occupied voxel (leaf-node), with a single aggregate point: 6 | 7 | ``` 8 | downsample 0.04 NOU11.tile.*.pcd 9 | ``` 10 | 11 | Where 0.04 is the edge length of each voxel, in meters. This will output a downsampled point cloud for each input point cloud, e.g., `NOU11.tile.downsample.342.pcd`, whose point count has reduced by approximately 92% when compared to its original counterpart. 12 | 13 | The alternative approach, `thin`, preserves the original data, rather than replacing it, by keeping the original point in each voxel that is closest to the would-be aggregate point. This might be useful when [additional point fields](tutorial_preprocessing.md#Parsing-additional-point-fields) are parsed (e.g., reflectance), and their aggregation would not make sense. `thin` can be similarly called as: 14 | 15 | ``` 16 | thin 0.04 NOU11.tile.*.pcd 17 | ``` 18 | 19 | Note: if the preprocessor macro [XYZRRDRS](../include/treeseg_pointtype.h#L29) is defined as true, the behaviour of `thin` is modified. Instead, the point with the smallest deviation value (i.e., a proxy for range estimate goodness) will be preserved (if a voxel contains multiple points with the same deviation, the point nearest to the would-be aggregate is selected). This behaviour can be changed in [treeseg.cpp](../src/treeseg.cpp#L343). 20 | 21 | ## Downsampling resolution 22 | 23 | A potential place to start when determining the downsampling resolution, is to consider the characteristics of the lidar instrument (i.e., beam divergence, diameter of the beam at emission, and ranging accuracy), and to compare them with the observed distances between individual points in the point clouds collected by that instrument. For the data considered in this tutorial, this exercise was undertaken in figure 2 of our [paper](https://doi.org/10.1111/2041-210X.13121), where it can be seen that a voxel edge length of 0.04 m provided some balance between removing redundant points close to the ground (e.g., returns from the stems whose beam overlapped), whilst conserving important data from the canopy. 24 | 25 | treeseg includes the executable `nearestneighbour`, which may help in making this decision, by outputting the mean vertically-resolved nearest neighbour distances through a point cloud (i.e., the cloud is partitioned into height bins, and the mean distance of each point to its N nearest neighbours is calculated and averaged for each bin): 26 | 27 | ``` 28 | nearestneighbour 2 6 NOU11.tile.342.pcd 29 | ``` 30 | 31 | Where 2 is the width of the bins, in meters, and 6 is the number of nearest neighbours to be considered. 32 | 33 | ## [Next: Digital terrain model](tutorial_getdtmslice.md) 34 | -------------------------------------------------------------------------------- /doc/tutorial_findstems.md: -------------------------------------------------------------------------------- 1 | # Identifying the stems 2 | 3 | Individual tree stems are then identified in this slice across the larger-area point cloud through four steps: 4 | 5 | 1. Euclidean clustering: the slice is organised into individual clusters, based on the spatial distances between points. 6 | 2. Region-based segmentation: these clusters are further reduced to regions, based on the properties of their underlying surfaces (themselves inferred from point normals). 7 | 3. Shape fitting: RANSAC methods are used to fit cylinders to each region, and fit diagnostics are used to determine the likelihood of the underlying surface being a stem. 8 | 4. Principal component analysis: the angle between the principal components of each region and the ground are calculated, with regions broadly perpendicular to the ground deemed to be stems. 9 | 10 | These steps are implemented in the `findstems` executable, which can be called: 11 | 12 | ``` 13 | cd ./clusters/; 14 | findstems 4 0.2 2 ../NOU11.coords.dat ../NOU11.slice.pcd; 15 | ``` 16 | 17 | Where the inputs are: i) smoothness (degrees, see the following subsection), ii) minimum stem diameter (m), iii) maximum stem diameter (m), iv) the coordinates of the plot boundaries, and v) the slice of point cloud segmented from the larger-area point cloud along the xy-plane. That is, regions passing steps 1-4 are considered stems if their fitted cylinders have a diameter between or equal to the minimum and maximum diameter, and their centre resides inside or on the bounding box specified by the plot boundaries. 18 | 19 | The output point clouds, `NOU11.cluster.0.pcd` to `NOU11.cluster.189.pcd`, contain the inliers of each cylinder fit (ordered: descending by diameter), and can be viewed using: 20 | 21 | ``` 22 | pcl_viewer NOU11.cluster.*.pcd 23 | ``` 24 | 25 | Which will look something similar to: 26 | 27 | 28 | 29 | Also output are intermediate files useful for determining the source of any errors. For example, the following visualises the slice after applying Euclidean clustering and region-based segmentation: 30 | 31 | ``` 32 | pcl_viewer NOU11.intermediate.slice.clusters.regions.pcd 33 | ``` 34 | 35 | 36 | 37 | ## Parameters 38 | 39 | The input parameter smoothness is not particularly interpretable: it is used in the region-based segmentation to define the maximum permitted angle between point normals, for points to be considered belonging to the same region or underlying surface. However, it is important to select an appropriate value of smoothness, which unfortunately, varies depending on factors including the lidar instrument, the sampling pattern, and the scene itself. Therefore, some experimentation is required: the idea is that each resulting region wholly represents an individual stem (as shown in the figure above). If the value is too large, stems and surrounding vegetation will be lumped into the same region, and if too small, each stem will comprise of multiple regions. 40 | 41 | In addition to smoothness, other important parameters have been hard-coded into `findstems`, which may require further experimentation when applying these methods to other lidar datasets: 42 | 43 | 1. Euclidean clustering: [nnearest](../src/findstems.cpp#L22) and [nmin](../src/findstems.cpp#L23). 44 | 2. Region-based segmentation: [nnearest](../src/findstems.cpp#L38), [nneighbours](../src/findstems.cpp#L39), [nmin](../src/findstems.cpp#L39), [nmax](../src/findstems.cpp#L39) and [curvature](../src/findstems.cpp#L39). 45 | 3. Shape fitting: [nnearest](../src/findstems.cpp#L50), [lmin](../src/findstems.cpp#L70), [stepcovmax](../src/findstems.cpp#L71) and [radratiomin](../src/findstems.cpp#L72). 46 | 4. Principal component analysis: [anglemax](../src/findstems.cpp#L104). 47 | 48 | ## Errors of omission and commission 49 | 50 | Scenarios will arise where `findstems` either incorrectly identifies a region as a stem, or not a stem. So it can be useful to undertake a manual check, by comparing the original slice with the extracted inliers: 51 | 52 | ``` 53 | pcdPointTreeseg2txt NOU11.intermediate.slice.clusters.regions.pcd NOU11.cluster.*.pcd; 54 | CloudCompare NOU11.intermediate.slice.clusters.regions.txt NOU11.cluster.*.txt; 55 | ``` 56 | 57 | Whereby the slice can be coloured different to the inliers for ready comparison. Errors of commission (i.e., a region incorrectly identified as a stem) can be deleted (e.g., here, by removing the file `NOU11.cluster.33.pcd`, which is the stem of a palm). Errors of omission (i.e., a region incorrectly identified as not being a stem) can be corrected by manually segmenting missed stems from this slice, and saving them as additional point clouds (e.g., using the naming convention: `NOU11.cluster.190.txt` onwards). Note: it is then necessary convert these newly created ASCII point clouds to PCD format, which can be undertaken using the `txtPointTreeseg2pcd` executable. 58 | 59 | ## [Next: Stem segmentation](tutorial_segmentstem.md) 60 | -------------------------------------------------------------------------------- /doc/tutorial_getcrownvolume.md: -------------------------------------------------------------------------------- 1 | # Crown segmentation - Part 1 2 | 3 | At this point, the stems of the 10 largest trees inside the 1 ha stand have been successfully segmented. It is now necessary to extract their crowns from the neighbouring vegetation. The first part of this two step process is to generate a point cloud for each tree, containing: i) the stem, ii) the crown, and iii) neighbouring crown vegetation. This is undertaken using the following methods: 4 | 5 | 1. Shape fitting: RANSAC methods are used to fit a cylinder to each of the stem point clouds, to estimate the stem diameter of each tree. 6 | 2. Spatial filtering: pass-through filters (closed cylinder) are applied to the larger-area point cloud, as driven by stem-diameter:tree-height and stem-diameter:crown-diameter allometries, to generate point clouds containing the crown of each tree. 7 | 3. Duplicate point removal: the stem and crown point clouds are concatenated, and duplicate points removed. 8 | 9 | These steps are implemented in the `getcrownvolume` executable, which can be called: 10 | 11 | ``` 12 | cd ../volumes/; 13 | getcrownvolume 0.66 ../stems/NOU11.stem.*.pcd ../NOU11.tile.downsample.*.pcd; 14 | ``` 15 | 16 | Where the inputs are: i) the height along each stem defining the bottom of the pass-through cylinder, ii) the stem point clouds output from `segmentstem`, and iii) the downsampled tiles constituting the larger-area point cloud. The output point clouds can be viewed using: 17 | 18 | ``` 19 | pcl_viewer NOU11.volume.0.pcd 20 | ``` 21 | 22 | Which will look something similar to: 23 | 24 | 25 | 26 | ## Allometrics 27 | 28 | The purpose of the [stem-diameter:tree-height](../src/getcrownvolume.cpp#L7) and [stem-diameter:crown-diameter](../src/getcrownvolume.cpp#L15) allometries, is to segment an appropriately sized section of the larger-area point cloud containing the entirety of the crown of the tree of interest. These allometries were constructed using terrestrial lidar data collected across the tropics via a non-linear power-law model. The parameters used in these models are the upper 95% confidence intervals, and also include a further relaxation term. When applied to other lidar datasets, it may be necessary to relax these allometries further, to avoid accidental cropping of crowns. 29 | 30 | ## [Next: Crown segmentation - Part 2](tutorial_segmentcrown.md) 31 | -------------------------------------------------------------------------------- /doc/tutorial_getdtmslice.md: -------------------------------------------------------------------------------- 1 | # Digital terrain model 2 | 3 | The first principal step of treeseg is to identify the individual trees in the larger-area point cloud. This identification is undertaken on a slice segmented from the larger-area point cloud along the xy-plane. To generate this slice at some defined height above-ground whilst adjusting for topography, it is necessary to construct an underlying digital terrain model (DTM). Both this DTM, and the slice can be generated using: 4 | 5 | ``` 6 | getdtmslice 2 2.5 3 6 NOU11.tile.downsample.*.pcd > NOU11.dtm.dat 7 | ``` 8 | 9 | Where the inputs are: 1) the resolution of the DTM (m), 2) the percentile of the z-coordinates of the points inside each DTM grid which is considered to constitute the ground (this is used, rather than the absolute minimum, to provide more robust results in noisy point clouds), 3) the minimum height of the slice above-ground (m), 4) the maximum height of the slice above-ground (m), and 5) the downsampled tiles constituting the larger-area point cloud. 10 | 11 | The resulting slice can be viewed using: 12 | 13 | ``` 14 | pcl_viewer NOU11.slice.pcd 15 | ``` 16 | 17 | Which will look something similar to: 18 | 19 | 20 | 21 | ## [Next: Identifying the stems](tutorial_findstems.md) 22 | -------------------------------------------------------------------------------- /doc/tutorial_overview.md: -------------------------------------------------------------------------------- 1 | # Tutorial 2 | 3 | The objective of this tutorial is to demonstrate the usage of treeseg, by extracting the 10 largest trees inside a complex forest scene. 4 | 5 | ## TL;DR 6 | 7 | See [here](tutorial_tldr.md). 8 | 9 | ## Primer 10 | 11 | If you plan to extensively use or develop treeseg, it might first be useful to familiarise yourself with the various point cloud processing methods that are used. A brief overview of these methods can be found in our [paper](https://doi.org/10.1111/2041-210X.13121). 12 | 13 | treeseg uses the Point Cloud Libray (PCL) to implement these methods, and the [PCL website](https://pointclouds.org/) provides good generic tutorials. The most relevant are: Basic Usage, Features, Filtering, KdTree, Octree, Sample Consensus and Segmentation. 14 | 15 | ## Prerequisites 16 | 17 | It is assumed treeseg has been successfully built. Several other packages are also required, which can be installed via apt: 18 | 19 | ``` 20 | apt install pcl-tools cloudcompare 21 | ``` 22 | 23 | ## Data 24 | 25 | The terrestrial lidar data that will be downloaded and used in this tutorial were collected from a one hectare stand of old-growth intact tropical rainforest in French Guiana. A more complete description of these data is available via [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.4497751.svg)](https://doi.org/10.5281/zenodo.4497751) 26 | 27 | ### Directory structure 28 | 29 | It is assumed the following folders have been created: 30 | 31 | ``` 32 | ./treeseg_tutorial/ 33 | ├───data/ 34 | └───processing/ 35 | ├───clusters/ 36 | ├───stems/ 37 | ├───volumes/ 38 | ├───trees/ 39 | ``` 40 | 41 | ### Option 1 (recommended): Raw lidar data 42 | 43 | If the `rxp2pcd` executable has been compiled (see [here](../README.md#installation)), then the raw lidar data should be downloaded: 44 | 45 | ``` 46 | cd ./treeseg_tutorial/data/; 47 | wget https://zenodo.org/record/4497751/files/2015-11-10.001.riproject.part1.zip; 48 | wget https://zenodo.org/record/4497751/files/2015-11-10.001.riproject.part2.zip; 49 | wget https://zenodo.org/record/4497751/files/2015-11-10.001.riproject.part3.zip; 50 | wget https://zenodo.org/record/4497751/files/2015-11-10.001.riproject.part4.zip; 51 | wget https://zenodo.org/record/4497751/files/2015-11-10.001.riproject.part5.zip; 52 | unzip '2015-11-10.001.riproject.part?.zip'; 53 | ``` 54 | 55 | ### Option 2: Preprocessed lidar data 56 | 57 | If the `rxp2pcd` executable could not been compiled (e.g., it was not possible to access and download the RIEGL RiVLIB headers and libraries), then the preprocessed data should be downloaded. It is also necessary to create a file containing the coordinates of plot boundaries (this is discussed further in the following section): 58 | 59 | ``` 60 | cd ./treeseg_tutorial/processing/; 61 | wget https://zenodo.org/record/4661301/files/2015-11-10.001.preprocessed.part1.zip; 62 | wget https://zenodo.org/record/4661301/files/2015-11-10.001.preprocessed.part2.zip; 63 | wget https://zenodo.org/record/4661301/files/2015-11-10.001.preprocessed.part3.zip; 64 | wget https://zenodo.org/record/4661301/files/2015-11-10.001.preprocessed.part4.zip; 65 | wget https://zenodo.org/record/4661301/files/2015-11-10.001.preprocessed.part5.zip; 66 | unzip '2015-11-10.001.preprocessed.part?.zip'; 67 | echo -101.261 4.48606 -104.789 0 > NOU11.coords.dat; 68 | ``` 69 | 70 | ## [Next: Preprocessing](tutorial_preprocessing.md) 71 | -------------------------------------------------------------------------------- /doc/tutorial_preprocessing.md: -------------------------------------------------------------------------------- 1 | # Preprocessing 2 | 3 | ## The larger-area point cloud 4 | 5 | The primary input to treeseg is some larger-area point cloud collected in a forest scene (e.g., in this tutorial, the point cloud representing the 1 ha tropical rainforest plot in French Guiana). It is expected that this point cloud is stored in binary or ASCII [PCD format](https://pcl.readthedocs.io/projects/tutorials/en/latest/pcd_file_format.html). By default, the expected point fields are the Cartesian coordinates (x, y, z) of each point, but additional fields can be included (as described in the following subsection). It is also expected that the larger-area point cloud is broken into N tiles (Nmin = 1), using the following strict naming convention: 6 | 7 | ``` 8 | mycloud.tile.0.pcd 9 | mycloud.tile.1.pcd 10 | ... 11 | mycloud.tile.N.pcd 12 | ``` 13 | 14 | Where 'mycloud' can be replaced with some meaningful identifier of the larger-area point cloud. Depending on computing environment and resources, it will often be necessary for each tile to have a maximum size of a few gigabytes, to circumvent issues that potentially arise from requiring large contiguous blocks of unoccupied RAM to be available when loading large point clouds. 15 | 16 | ## Parsing additional point fields 17 | 18 | treeseg implements a custom point type, `PointTreeseg`, to provide the user with flexibility over the point fields. By default PointTreeseg = [pcl::PointXYZ](https://pointclouds.org/documentation/structpcl_1_1_point_x_y_z.html). However, the definition of [PointTreeseg](../include/treeseg_pointtype.h#L33) can be modified if the user wishes to include other fields (e.g., reflectance). 19 | 20 | An example of how this can be implemented is provided by the preprocessor macro [XYZRRDRS](../include/treeseg_pointtype.h#L29). If this macro is defined as true (default = false) and treeseg recompiled, then the `PointTreeseg` point fields become: x (m), y (m), z (m), range (m), reflectance (dB), deviation (#), return number (#) and scan number (#). These additional fields will then be preserved throughout the various subsequent processing steps. 21 | 22 | ## RXP to PCD conversion 23 | 24 | In this tutorial we only consider preprocessing lidar data stored in RIEGL's RXP data stream format, to the abovedescribed expected tiled PCD format. However, various open-source tools are available for converting lidar data in other formats (e.g., LAS). 25 | 26 | The raw lidar data downloaded during the previous step (i.e., [option 1](tutorial_overview.md#Data)) are preprocessed using the `rxp2pcd` executable. The overall x-y limits of the larger-area point cloud are defined by the coordinates of a bounding box, which here, we want dictated by the plot boundaries, which can be approximated from the scan locations via the transformation matrices in `./treeseg_tutorial/data/matrix/` using `plotcoords`: 27 | 28 | ``` 29 | cd ../processing/; 30 | plotcoords ../data/matrix/ > NOU11.coords.dat; 31 | ``` 32 | 33 | Whereby the resulting ASCII file `NOU11.coords.dat` contains: 34 | 35 | ``` 36 | -101.261 4.48606 -104.789 0 37 | ``` 38 | 39 | I.e., the minimum and maximum coordinates of the 121 scan locations (xmin, xmax, ymin, ymax), in meters. 40 | 41 | `rxp2pcd` can then be called as: 42 | 43 | ``` 44 | rxp2pcd ../data/ NOU11.coords.dat 25 15 NOU11 45 | ``` 46 | 47 | Where the inputs are: 1) the top directory containing the lidar data; 2) the coordinates of the plot boundaries (note: the dimensions of the bounding box are increased by 20 m in each direction to ensure data relating to the trees of interest are not truncated (e.g., the crowns of large trees at the edges of the plot); 3) the area of each output tile (m2); 4) the maximum permitted value of point deviation (a proxy for range estimate goodness that is used to remove noisy returns); and 5) the larger-area point cloud identifier. 48 | 49 | This command will output the larger-area point cloud into 870 binary PCD tiles: `NOU11.tile.0.pcd` to `NOU11.tile.869.pcd`. These tiles can then be viewed, e.g., using `pcl_viewer`: 50 | 51 | ``` 52 | pcl_viewer NOU11.tile.342.pcd 53 | ``` 54 | 55 | ## [Next: Downsampling](tutorial_downsample.md) 56 | -------------------------------------------------------------------------------- /doc/tutorial_segmentcrown.md: -------------------------------------------------------------------------------- 1 | # Crown segmentation - Part 2 2 | 3 | The second step in the process of extracting the crowns uses the following methods: 4 | 5 | 1. Region-based segmentation: the point clouds output from `getcrownvolume` are organised into regions, based on the properties of their underlying surfaces. 6 | 2. Connectivity testing: the tree is built from the stem up, by adding nearby regions considered belonging to the tree. 7 | 8 | These methods are implemented in the `segmentcrown` executable, which can be called: 9 | 10 | ``` 11 | cd ../trees/; 12 | segmentcrown 4 ../volumes/NOU11.volume.*.pcd; 13 | ``` 14 | 15 | Where the input parameters are: i) smoothness (degrees), and ii) the point clouds output from `getcrownvolume`. Depending on point cloud size and computing resources, this currently single-threaded process can take between 12-24 hours to complete. It might therefore be worthwhile running multiple instances in parallel. 16 | 17 | The output tree-level point clouds can then be viewed using: 18 | 19 | ``` 20 | pcl_viewer NOU11_1.pcd 21 | ``` 22 | 23 | Which will look something similar to: 24 | 25 | 26 | 27 | # The buildtree function 28 | 29 | The connectivity testing is implemented in the [buildtree](../src/treeseg.cpp#L963) function, and called by `segmentcrown` as: 30 | 31 | ``` 32 | buildTree(regions,20,1,0.2,5,1.0,tree); 33 | ``` 34 | 35 | Where the input parameters are: i) the vector of point clouds derived from the region-based segmentation, ii) cyclecount (integer), iii) firstcount (integer), iv) firstdist (m), v) nnearest (integer), vi) seconddist (m), and vii) the resulting tree-level point cloud. That is, first, the region representing the stem is designated the seed. For `firstcount` iteration(s), regions within the distance `firstdist` to the seed(s) are added to the tree and designated the new seeds. For the remaining `cyclecount` iterations, the `nnearest` regions closest to each seed and within the distance `seconddist` are added to the tree and designated the new seeds. These parameters may require further experimentation when applied to other lidar datasets. For example, for very high-quality lidar data, or for more sparsely populated forest scenes, it might be possible to significantly reduce `cyclecount`. This will substantially reduce compute time. 36 | 37 | In addition to this function, other important parameters have been hard-coded into `segmentcrown`: 38 | 39 | 1. Region-based segmentation: [nnearest](../src/segmentcrown.cpp#L24), [nneighbours](../src/segmentcrown.cpp#L25), [nmin](../src/segmentcrown.cpp#L25), [nmax](../src/segmentcrown.cpp#L25) and [curvature](../src/segmentcrown.cpp#L25). 40 | 41 | # Errors of commission 42 | 43 | Errors of commission in the final tree-level point clouds can then be manually segmented in CloudCompare, e.g.,: 44 | 45 | ``` 46 | pcdPointTreeseg2txt NOU11_1.pcd; 47 | CloudCompare NOU11_1.txt; 48 | ``` 49 | -------------------------------------------------------------------------------- /doc/tutorial_segmentstem.md: -------------------------------------------------------------------------------- 1 | # Stem segmentation 2 | 3 | Next, the full stem of each identified tree is segmented from the larger-area point cloud as follows: 4 | 5 | 1. Shape fitting: RANSAC methods are used to fit a cylinder to each set of inliers output from `findstems`, to describe the orientation and radius of each stem. 6 | 2. Spatial filtering: pass-through filters (infinite cylinder) are applied to the larger-area point cloud, as driven by expanded versions of these cylinders, to generate sections of point cloud containing each stem and neighbouring vegetation. 7 | 3. Plane fitting: points are classified as ground returns and segmented, if they are inliers of the plane fitted to the bottom of each of these sections via [SACMODEL_NORMAL_PARALLEL_PLANE](https://pointclouds.org/documentation/classpcl_1_1_sample_consensus_model_normal_parallel_plane.html) (n.b., this model imposes conditions on both the inliers, and the plane itself). 8 | 4. Region-based segmentation: the remaining sections are organised into regions, based on the properties of their underlying surfaces, in order to classify each element of vegetation. 9 | 10 | These steps are implemented in the `segmentstem` executable, which can be called: 11 | 12 | ``` 13 | cd ../stems/; 14 | segmentstem 4 ../clusters/NOU11.cluster.?.pcd ../NOU11.tile.downsample.*.pcd; 15 | ``` 16 | 17 | Where the inputs are: i) smoothness (degrees), ii) the inliers of the 10 largest stems output from `findstems`, and iii) the downsampled tiles constituting the larger-area point cloud. The output stem point clouds can be viewed using: 18 | 19 | ``` 20 | pcl_viewer NOU11.stem.?.pcd 21 | ``` 22 | 23 | Which will look something similar to: 24 | 25 | 26 | 27 | Also output are intermediate diagnostic files. For example, the following visualises the section of point cloud generated for `NOU11.cluster.1.pcd`, after the spatial filtering, plane fitting and region-based segmentation have been applied: 28 | 29 | ``` 30 | pcl_viewer NOU11.c.ng.r.1.pcd 31 | ``` 32 | 33 | 34 | 35 | ## Parameters 36 | 37 | In addition to smoothness (described [here](tutorial_findstems.md#Parameters)), other important parameters have been hard-coded into `segmentstem`, which may require further experimentation when applying these methods to other lidar datasets: 38 | 39 | 1. Shape fitting: [nnearest](../src/segmentstem.cpp#L27). 40 | 2. Spatial filtering: [expansionfactor](../src/segmentstem.cpp#L35). 41 | 3. Plane fitting: [groundidx](../src/segmentstem.cpp#L50), [ground](../src/segmentstem.cpp#L54), [nnearest](../src/segmentstem.cpp#L56), [dthreshold](../src/segmentstem.cpp#L57), [nweight](../src/segmentstem.cpp#L57), [angle](../src/segmentstem.cpp#L57). 42 | 4. Region-based segmentation: [nnearest](../src/segmentstem.cpp#L68), [nneighbours](../src/segmentstem.cpp#L70), [nmin](../src/segmentstem.cpp#L70), [nmax](../src/segmentstem.cpp#L70) and [curvature](../src/segmentstem.cpp#L70). 43 | 44 | ## Errors of commission 45 | 46 | Scenarios will arise where the outputs from `segmentstem` erroneously include neighbouring vegetation (e.g., `NOU.stem.0.pcd`). A potential approach to correcting this is to lower the value of smoothness (e.g., 3.5). However this can lead to errors of omission, which are more difficult to correct. At this point is is unnecessary to manually correct errors of commission, as this will be undertaken once the crown of each tree has been segmented in the final step. 47 | 48 | ## [Next: Crown segmentation - Part 1](tutorial_getcrownvolume.md) 49 | -------------------------------------------------------------------------------- /doc/tutorial_tldr.md: -------------------------------------------------------------------------------- 1 | # TL;DR 2 | 3 | ``` 4 | mkdir treeseg_tutorial; cd treeseg_tutorial/; mkdir data processing; cd data/; 5 | wget https://zenodo.org/record/4497751/files/2015-11-10.001.riproject.part1.zip; 6 | wget https://zenodo.org/record/4497751/files/2015-11-10.001.riproject.part2.zip; 7 | wget https://zenodo.org/record/4497751/files/2015-11-10.001.riproject.part3.zip; 8 | wget https://zenodo.org/record/4497751/files/2015-11-10.001.riproject.part4.zip; 9 | wget https://zenodo.org/record/4497751/files/2015-11-10.001.riproject.part5.zip; 10 | unzip '2015-11-10.001.riproject.part?.zip'; 11 | ``` 12 | 13 | ``` 14 | cd ../processing/; 15 | mkdir clusters stems volumes trees; 16 | plotcoords ../data/matrix/ > NOU11.coords.dat; 17 | rxp2pcd ../data/ NOU11.coords.dat 25 15 NOU11; 18 | downsample 0.04 NOU11.tile.*.pcd; 19 | getdtmslice 2 2.5 3 6 NOU11.tile.downsample.*.pcd > NOU11.dtm.dat; 20 | ``` 21 | 22 | ``` 23 | cd ./clusters/; 24 | findstems 4 0.2 2 ../NOU11.coords.dat ../NOU11.slice.pcd; 25 | cd ../stems/; 26 | segmentstem 4 ../clusters/NOU11.cluster.?.pcd ../NOU11.tile.downsample.*.pcd; 27 | cd ../volumes/; 28 | getcrownvolume 0.66 ../stems/NOU11.stem.*.pcd ../NOU11.tile.downsample.*.pcd; 29 | cd ../trees/; 30 | segmentcrown 4 ../volumes/NOU11.volume.*.pcd; 31 | ``` 32 | -------------------------------------------------------------------------------- /include/leafsep.h: -------------------------------------------------------------------------------- 1 | /* 2 | * leafsep.h 3 | * 4 | * MIT License 5 | * 6 | * Copyright 2018 Andrew Burt and Matheus Boni Vicari 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to 10 | * deal in the Software without restriction, including without limitation the 11 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | * sell copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 24 | * IN THE SOFTWARE. 25 | */ 26 | 27 | // leafsep has been developed using: 28 | // TLSeparation (https://github.com/TLSeparation) 29 | // Point Cloud Library (http://www.pointclouds.org) 30 | // Armadillo (http://arma.sourceforge.net) 31 | 32 | #include "treeseg.h" 33 | 34 | #include 35 | 36 | void gmmByPoint(const pcl::PointCloud::Ptr &cloud, int knn, arma::uword n_gaus, int dist_mode, int seed_mode, int km_iter, int em_iter, arma::mat &featmatrix, arma::gmm_diag &model); 37 | void gmmByCluster(const std::vector::Ptr> &clouds, arma::uword n_gaus, int dist_mode, int seed_mode, int km_iter, int em_iter, arma::mat &featmatrix, arma::gmm_full &model); 38 | std::vector classifyGmmPointModel(const pcl::PointCloud::Ptr &cloud, int n_gaus, arma::mat &featmatrix, arma::gmm_diag &model); 39 | std::vector classifyGmmClusterModel(const std::vector::Ptr> &clouds, int n_gaus, arma::mat &featmatrix, arma::gmm_full &model); 40 | 41 | void separateCloudsClassifiedByPoint(const pcl::PointCloud::Ptr &cloud, const std::vector &classifications, std::vector::Ptr> &classifiedclouds); 42 | void separateCloudsClassifiedByCluster(const std::vector::Ptr> &clouds, const std::vector &classifications, std::vector::Ptr> &classifiedclouds); 43 | 44 | void writeCloudClassifiedByPoint(const pcl::PointCloud::Ptr &cloud, const std::vector &classifications, std::string fname); 45 | void writeCloudClassifiedByCluster(const std::vector::Ptr> &clouds, const std::vector &classifications, std::string fname); 46 | -------------------------------------------------------------------------------- /include/riegl/.keep_include: -------------------------------------------------------------------------------- 1 | abc 2 | -------------------------------------------------------------------------------- /include/treeseg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * treeseg.h 3 | * 4 | * MIT License 5 | * 6 | * Copyright 2017 Andrew Burt 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to 10 | * deal in the Software without restriction, including without limitation the 11 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | * sell copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 24 | * IN THE SOFTWARE. 25 | */ 26 | 27 | // treeseg has been developed using: 28 | // Point Cloud Library (http://www.pointclouds.org) 29 | 30 | #include "treeseg_pointtype.h" 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | struct treeparams 37 | { 38 | float x,y; 39 | float d; 40 | float h; 41 | float c; 42 | }; 43 | 44 | struct cylinder 45 | { 46 | bool ismodel; 47 | float x,y,z; 48 | float dx,dy,dz; 49 | float rad; 50 | float len; 51 | float steprad; 52 | float stepcov; 53 | float radratio; 54 | pcl::PointCloud::Ptr cloud; 55 | pcl::PointCloud::Ptr inliers; 56 | }; 57 | 58 | struct cloudmetrics 59 | { 60 | int count; 61 | Eigen::Vector4f min3D; 62 | Eigen::Vector4f max3D; 63 | Eigen::Vector4f centroid; 64 | Eigen::Matrix3f covariancematrix; 65 | Eigen::Matrix3f eigenvectors; 66 | Eigen::Vector3f eigenvalues; 67 | Eigen::Vector3f vector3D; 68 | float length; 69 | }; 70 | 71 | struct basiccloudmetrics 72 | { 73 | int count; 74 | Eigen::Vector4f min3D; 75 | Eigen::Vector4f max3D; 76 | }; 77 | 78 | //File IO 79 | 80 | std::vector getFileID(std::string filename); 81 | void readTiles(const std::vector &args, pcl::PointCloud::Ptr &cloud); 82 | int getTilesStartIdx(const std::vector &args); 83 | void writeClouds(const std::vector::Ptr> &clusters, std::string fname, bool doPCA); 84 | 85 | //Nearest neighbour analysis 86 | 87 | std::vector dNN(const pcl::PointCloud::Ptr &cloud, int nnearest); 88 | std::vector> dNNz(const pcl::PointCloud::Ptr &cloud, int nnearest, float zstep); 89 | float interpolatedNNZ(float x, const std::vector> &nndata, bool extrapolate); 90 | 91 | //Cloud metrics 92 | 93 | void getCloudMetrics(const pcl::PointCloud::Ptr &cloud, cloudmetrics metrics); 94 | float getCloudLength(const pcl::PointCloud::Ptr &cloud, const Eigen::Vector4f ¢roid, const Eigen::Matrix3f &eigenvectors); 95 | void getBasicCloudMetrics(const pcl::PointCloud::Ptr &cloud, basiccloudmetrics metrics); 96 | 97 | //Downsampling 98 | 99 | void downsample(const pcl::PointCloud::Ptr &original, float edgelength, pcl::PointCloud::Ptr &filtered, bool octree=true); 100 | void thin(const pcl::PointCloud::Ptr &original, float edgelength, pcl::PointCloud::Ptr &filtered, bool preservePointClosestToVoxelCentroid=false); 101 | 102 | //Spatial filters 103 | 104 | void spatial1DFilter(const pcl::PointCloud::Ptr &original, std::string dimension, float min, float max, pcl::PointCloud::Ptr &filtered); 105 | void spatial3DCylinderFilter(const pcl::PointCloud::Ptr &original, cylinder cyl, pcl::PointCloud::Ptr &filtered); 106 | 107 | //Clustering 108 | 109 | void euclideanClustering(const pcl::PointCloud::Ptr &cloud, float dmax, int nmin, std::vector::Ptr> &clusters); 110 | 111 | //Principal component analysis 112 | 113 | void computePCA(const pcl::PointCloud::Ptr &cloud, Eigen::Vector4f ¢roid, Eigen::Matrix3f &covariancematrix, Eigen::Matrix3f &eigenvectors, Eigen::Vector3f &eigenvalues); 114 | 115 | //Surface normals 116 | 117 | void estimateNormals(const pcl::PointCloud::Ptr &cloud, int nnearest, pcl::PointCloud::Ptr &normals); 118 | 119 | //Segmentation 120 | 121 | void regionSegmentation(const pcl::PointCloud::Ptr &cloud, const pcl::PointCloud::Ptr &normals, int nneighbours, int nmin, int nmax, float smoothness, float curvature, std::vector::Ptr> ®ions); 122 | 123 | //Shape fitting 124 | 125 | void fitPlane(const pcl::PointCloud::Ptr &cloud, const pcl::PointCloud::Ptr &normals, float dthreshold, pcl::PointIndices::Ptr &inliers, float nweight=0, float angle=0, Eigen::Vector3f axis={0,0,1}); 126 | std::vector fitCircle(const pcl::PointCloud::Ptr &cloud, int nnearest); 127 | void fitCylinder(const pcl::PointCloud::Ptr &cloud, int nnearest, bool finite, bool diagnostics, cylinder &cyl); 128 | void cylinderDiagnostics(cylinder &cyl, int nnearest); 129 | 130 | //Generic 131 | 132 | bool sortCloudByX(const PointTreeseg &p1, const PointTreeseg &p2); 133 | bool sortCloudByY(const PointTreeseg &p1, const PointTreeseg &p2); 134 | bool sortCloudByZ(const PointTreeseg &p1, const PointTreeseg &p2); 135 | bool sortCloudByDescEuclidDist(const PointTreeseg &p1, const PointTreeseg &p2); 136 | #if XYZRRDRS == true 137 | bool sortCloudByRange(const PointTreeseg &p1, const PointTreeseg &p2); 138 | bool sortCloudByReflectance(const PointTreeseg &p1, const PointTreeseg &p2); 139 | bool sortCloudByDeviation(const PointTreeseg &p1, const PointTreeseg &p2); 140 | bool sortCloudByReturnNumber(const PointTreeseg &p1, const PointTreeseg &p2); 141 | bool sortCloudByScanNumber(const PointTreeseg &p1, const PointTreeseg &p2); 142 | #endif 143 | bool equalPoint(const PointTreeseg &p1, const PointTreeseg &p2); 144 | bool sort2DFloatVectorByCol1(const std::vector &v1, const std::vector &v2); 145 | bool sort2DFloatVectorByCol2(const std::vector &v1, const std::vector &v2); 146 | int findClosestIdx(const pcl::PointCloud::Ptr &cloud, const std::vector::Ptr> &clouds, bool biggest); 147 | int findPrincipalCloudIdx(const std::vector::Ptr> &clouds); 148 | void extractIndices(const pcl::PointCloud::Ptr &cloud, const pcl::PointIndices::Ptr &inliers, bool invert, pcl::PointCloud::Ptr &filtered); 149 | float minDistBetweenClouds(const pcl::PointCloud::Ptr &a, const pcl::PointCloud::Ptr &b); 150 | float minDistBetweenClouds(const pcl::PointCloud::Ptr &a, const pcl::PointCloud::Ptr &b, const pcl::KdTreeFLANN &kdtree); 151 | bool intersectionTest3DBox(const Eigen::Vector4f &amin, const Eigen::Vector4f &amax, const Eigen::Vector4f &bmin, const Eigen::Vector4f &bmax); 152 | void catIntersectingClouds(std::vector::Ptr> &clouds); 153 | void removeDuplicatePoints(pcl::PointCloud::Ptr &cloud); 154 | 155 | //treeseg specific 156 | 157 | std::vector> getDtmAndSlice(const pcl::PointCloud::Ptr &plot, float resolution, float percentile, float zmin, float zmax, pcl::PointCloud::Ptr &slice); 158 | void correctStem(const pcl::PointCloud::Ptr &stem, float nnearest, float zstart, float zstep, float stepcovmax, float radchangemin, pcl::PointCloud::Ptr &corrected); 159 | void removeFarRegions(float dmin, std::vector::Ptr> ®ions); 160 | void precalculateIntersections(const std::vector::Ptr> ®ions, std::vector> &intersections, float expansion); 161 | void buildTree(const std::vector::Ptr> ®ions, int cyclecount, int firstcount, float firstdistance, int nnearest, float seconddist, pcl::PointCloud::Ptr &tree); 162 | treeparams getTreeParams(const pcl::PointCloud::Ptr &cloud, int nnearest, float zstep, float diffmax); 163 | -------------------------------------------------------------------------------- /include/treeseg_pointtype.h: -------------------------------------------------------------------------------- 1 | /* 2 | * treeseg_pointtype.h 3 | * 4 | * MIT License 5 | * 6 | * Copyright 2020 Andrew Burt 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to 10 | * deal in the Software without restriction, including without limitation the 11 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | * sell copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 24 | * IN THE SOFTWARE. 25 | */ 26 | 27 | #define PCL_NO_PRECOMPILE 28 | 29 | #define XYZRRDRS false 30 | 31 | #include 32 | 33 | struct PointTreeseg 34 | { 35 | PCL_ADD_POINT4D; 36 | #if XYZRRDRS == true 37 | union 38 | { 39 | struct 40 | { 41 | float range; 42 | float reflectance; 43 | }; 44 | float data_rr[4]; // 16bytes 45 | }; 46 | union 47 | { 48 | struct 49 | { 50 | std::uint16_t deviation; 51 | std::uint16_t return_number; 52 | std::uint16_t scan_number; 53 | }; 54 | std::uint16_t data_drs[4]; // 8bytes 55 | }; 56 | //this may be poorly aligned (i.e., padding) 57 | #endif 58 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 59 | }; 60 | POINT_CLOUD_REGISTER_POINT_STRUCT 61 | ( 62 | PointTreeseg, 63 | (float,x,x) 64 | (float,y,y) 65 | (float,z,z) 66 | #if XYZRRDRS == true 67 | (float,range,range) 68 | (float,reflectance,reflectance) 69 | (std::uint16_t,deviation,deviation) 70 | (std::uint16_t,return_number,return_number) 71 | (std::uint16_t,scan_number,scan_number) 72 | #endif 73 | ) 74 | -------------------------------------------------------------------------------- /lib/riegl/.keep_lib: -------------------------------------------------------------------------------- 1 | abc 2 | -------------------------------------------------------------------------------- /src/downsample.cpp: -------------------------------------------------------------------------------- 1 | #include "treeseg.h" 2 | 3 | #include 4 | 5 | int main(int argc, char **argv) 6 | { 7 | std::vector args(argv+1,argv+argc); 8 | float edgelength = std::stof(args[0]); 9 | bool otree = true; 10 | pcl::PCDReader reader; 11 | pcl::PCDWriter writer; 12 | pcl::PointCloud::Ptr original(new pcl::PointCloud); 13 | pcl::PointCloud::Ptr downsampled(new pcl::PointCloud); 14 | for(int i=1;i id = getFileID(args[i]); 17 | std::stringstream ss; 18 | ss << id[0] << ".tile.downsample." << id[1] << ".pcd"; 19 | reader.read(args[i],*original); 20 | downsample(original,edgelength,downsampled,otree); 21 | writer.write(ss.str(),*downsampled,true); 22 | original->clear(); 23 | downsampled->clear(); 24 | } 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /src/findstems.cpp: -------------------------------------------------------------------------------- 1 | #include "treeseg.h" 2 | 3 | #include 4 | #include 5 | 6 | int main(int argc, char **argv) 7 | { 8 | std::vector args(argv+1,argv+argc); 9 | // 10 | pcl::PCDReader reader; 11 | pcl::PCDWriter writer; 12 | std::stringstream ss; 13 | // 14 | std::cout << "Reading slice: " << std::flush; 15 | std::vector id = getFileID(args[4]); 16 | pcl::PointCloud::Ptr slice(new pcl::PointCloud); 17 | reader.read(args[4],*slice); 18 | std::cout << "complete" << std::endl; 19 | // 20 | std::cout << "Cluster extraction: " << std::flush; 21 | std::vector::Ptr> clusters; 22 | int nnearest = 18; 23 | int nmin = 100; 24 | std::vector nndata = dNN(slice,nnearest); 25 | euclideanClustering(slice,nndata[0],nmin,clusters); 26 | ss.str(""); 27 | ss << id[0] << ".intermediate.slice.clusters.pcd"; 28 | writeClouds(clusters,ss.str(),false); 29 | std::cout << ss.str() << " | " << clusters.size() << std::endl; 30 | // 31 | std::cout << "Region-based segmentation: " << std::flush; 32 | std::vector::Ptr> regions; 33 | pcl::PointCloud::Ptr normals(new pcl::PointCloud); 34 | float smoothness = std::stof(args[0]); 35 | for(int i=0;i::Ptr> tmpregions; 38 | estimateNormals(clusters[i],50,normals); 39 | regionSegmentation(clusters[i],normals,250,100,std::numeric_limits::max(),smoothness,2,tmpregions); 40 | for(int j=0;jclear(); 42 | } 43 | ss.str(""); 44 | ss << id[0] << ".intermediate.slice.clusters.regions.pcd"; 45 | writeClouds(regions,ss.str(),false); 46 | std::cout << ss.str() << " | " << regions.size() << std::endl; 47 | // 48 | std::cout << "RANSAC cylinder fits: " << std::flush; 49 | std::vector::Ptr>> cylinders; 50 | nnearest = 60; 51 | float dmin = std::stof(args[1]); 52 | float dmax = std::stof(args[2]); 53 | std::ifstream coordfile; 54 | coordfile.open(args[3]); 55 | float coords[4]; 56 | int n = 0; 57 | if(coordfile.is_open()) 58 | { 59 | while(!coordfile.eof()) 60 | { 61 | coordfile >> coords[n]; 62 | n++; 63 | } 64 | } 65 | coordfile.close(); 66 | float xmin = coords[0]; 67 | float xmax = coords[1]; 68 | float ymin = coords[2]; 69 | float ymax = coords[3]; 70 | float lmin = 2.5; //assumes 3m slice 71 | float stepcovmax = 0.1; 72 | float radratiomin = 0.9; 73 | for(int i=0;i= dmin && cyl.rad*2 <= dmax && cyl.len >= lmin) 81 | { 82 | if(cyl.stepcov <= stepcovmax && cyl.radratio > radratiomin) 83 | { 84 | if(cyl.x >= xmin && cyl.x <= xmax) 85 | { 86 | if(cyl.y >= ymin && cyl.y <= ymax) 87 | { 88 | cylinders.push_back(std::make_pair(cyl.rad,cyl.inliers)); 89 | } 90 | } 91 | } 92 | } 93 | } 94 | } 95 | std::sort(cylinders.rbegin(),cylinders.rend()); 96 | std::vector::Ptr> cyls; 97 | for(int i=0;i idx; 106 | for(int j=0;j= (90 - anglemax) && angle <= (90 + anglemax)) idx.push_back(j); 117 | } 118 | std::vector::Ptr> pca; 119 | for(int k=0;k::Ptr> stems; 128 | stems = pca; 129 | catIntersectingClouds(stems); 130 | ss.str(""); 131 | ss << id[0] << ".intermediate.slice.clusters.regions.cylinders.principal.cat.pcd"; 132 | writeClouds(stems,ss.str(),false); 133 | for(int m=0;m 4 | #include 5 | #include 6 | 7 | float maxheight(float dbh) 8 | { 9 | //m -> 41.22 * dbh ^ 0.3406 10 | //ci_u -> 42.30 * dbh ^ 0.3697 11 | float height = 42.30 * pow(dbh,0.3697) + 50; 12 | return height; 13 | } 14 | 15 | float maxcrown(float dbh) 16 | { 17 | //m -> 29.40 * dbh ^ 0.6524 18 | //ci_u -> 30.36 * dbh ^ 0.6931 19 | float extent = 30.36 * pow(dbh,0.6931) + 30; 20 | return extent; 21 | } 22 | 23 | int main(int argc, char **argv) 24 | { 25 | std::vector args(argv+1,argv+argc); 26 | pcl::PCDReader reader; 27 | pcl::PCDWriter writer; 28 | std::cout << "Reading plot-level cloud: " << std::flush; 29 | pcl::PointCloud::Ptr plot(new pcl::PointCloud); 30 | readTiles(args,plot); 31 | std::cout << "complete" << std::endl; 32 | // 33 | for(int i=1;i id = getFileID(args[i]); 38 | pcl::PointCloud::Ptr stem(new pcl::PointCloud); 39 | reader.read(args[i],*stem); 40 | // 41 | std::cout << "Estimating DBH: " << std::flush; 42 | int nnearest = 90; 43 | float zstep = 0.75; 44 | float diffmax = 0.1; 45 | treeparams params = getTreeParams(stem,nnearest,zstep,diffmax); 46 | std::cout << params.d << std::endl; 47 | // 48 | std::cout << "Volume dimensions: " << std::flush; 49 | float h = maxheight(params.d); 50 | float r = maxcrown(params.d)/2; 51 | std::cout << h << "m x " << r << "m (height + radius)" << std::endl; 52 | // 53 | std::cout << "Segmenting volume: " << std::flush; 54 | pcl::PointCloud::Ptr volume(new pcl::PointCloud); 55 | pcl::PointCloud::Ptr tmp1(new pcl::PointCloud); 56 | pcl::PointCloud::Ptr tmp2(new pcl::PointCloud); 57 | Eigen::Vector4f min,max,centroid; 58 | cylinder cyl; 59 | pcl::getMinMax3D(*stem,min,max); 60 | pcl::compute3DCentroid(*stem,centroid); 61 | cyl.x = centroid[0]; 62 | cyl.y = centroid[1]; 63 | cyl.z = centroid[2]; 64 | cyl.rad = r; 65 | cyl.dx = 0; 66 | cyl.dy = 0; 67 | cyl.dz = 1; 68 | spatial3DCylinderFilter(plot,cyl,tmp1); 69 | float stempos = std::stof(args[0]); 70 | spatial1DFilter(tmp1,"z",max[2]*stempos,min[2]+h,tmp2); 71 | *volume += *stem; 72 | *volume += *tmp2; 73 | std::stringstream ss; 74 | ss << id[0] << ".v." << id[1] << ".pcd"; 75 | writer.write(ss.str(),*volume,true); 76 | std::cout << ss.str() << std::endl; 77 | // 78 | std::cout << "Removing duplicate points: " << std::flush; 79 | removeDuplicatePoints(volume); 80 | ss.str(""); 81 | ss << id[0] << ".volume." << id[1] << ".pcd"; 82 | writer.write(ss.str(),*volume,true); 83 | std::cout << ss.str() << std::endl; 84 | } 85 | return 0; 86 | } 87 | -------------------------------------------------------------------------------- /src/getdtmslice.cpp: -------------------------------------------------------------------------------- 1 | #include "treeseg.h" 2 | 3 | #include 4 | 5 | int main(int argc, char **argv) 6 | { 7 | std::vector args(argv+1,argv+argc); 8 | float resolution = std::stof(args[0]); 9 | float percentile = std::stof(args[1]); 10 | float zmin = std::stof(args[2]); 11 | float zmax = std::stof(args[3]); 12 | pcl::PointCloud::Ptr plotcloud(new pcl::PointCloud); 13 | pcl::PCDWriter writer; 14 | std::vector id = getFileID(args[4]); 15 | readTiles(args,plotcloud); 16 | std::stringstream ss; 17 | ss.str(""); 18 | ss << id[0] << ".slice.pcd"; 19 | std::vector> dem; 20 | pcl::PointCloud::Ptr slice(new pcl::PointCloud); 21 | dem = getDtmAndSlice(plotcloud,resolution,percentile,zmin,zmax,slice); 22 | for(int j=0;j 35 | 36 | #include 37 | #include 38 | #include 39 | 40 | void gmmByPoint(const pcl::PointCloud::Ptr &cloud, int knn, arma::uword n_gaus, int dist_mode, int seed_mode, int km_iter, int em_iter, arma::mat &featmatrix, arma::gmm_diag &model) 41 | { 42 | pcl::PointCloud::Ptr kcloud(new pcl::PointCloud); 43 | Eigen::Vector4f centroid; 44 | Eigen::Matrix3f covariancematrix,eigenvectors; 45 | Eigen::Vector3f eigenvalues; 46 | float esum,e1,e2,e3; 47 | float features[6]; 48 | featmatrix.resize(6,cloud->points.size()); 49 | featmatrix.zeros(); 50 | pcl::KdTreeFLANN tree; 51 | tree.setInputCloud(cloud); 52 | int k = knn+1; 53 | std::vector pointIdxNKNSearch(k); 54 | std::vector pointNKNSquaredDistance(k); 55 | for(int i=0;ipoints.size();i++) 56 | { 57 | PointTreeseg searchPoint = cloud->points[i]; 58 | tree.nearestKSearch(searchPoint,k,pointIdxNKNSearch,pointNKNSquaredDistance); 59 | for(int j=0;jinsert(kcloud->end(),cloud->points[pointIdxNKNSearch[j]]); 60 | computePCA(kcloud,centroid,covariancematrix,eigenvectors,eigenvalues); 61 | esum = eigenvalues(0) + eigenvalues(1) + eigenvalues(2); 62 | e1 = eigenvalues(2) / esum; 63 | e2 = eigenvalues(1) / esum; 64 | e3 = eigenvalues(0) / esum; 65 | features[0] = e3; 66 | features[1] = e1 - e2; 67 | features[2] = e2 - e3; 68 | features[3] = (e2 - e3) / e1; 69 | features[4] = (e1 * log(e1)) + (e2 * log(e2)) + (e3 * log(e3)); 70 | features[5] = (e1 - e2) / e1; 71 | for(int k=0;kclear(); 73 | } 74 | bool status = model.learn(featmatrix,n_gaus,arma::gmm_dist_mode(dist_mode),arma::gmm_seed_mode(seed_mode),km_iter,em_iter,1e-10,false); 75 | } 76 | 77 | void gmmByCluster(const std::vector::Ptr> &clouds, arma::uword n_gaus, int dist_mode, int seed_mode, int km_iter, int em_iter, arma::mat &featmatrix, arma::gmm_full &model) 78 | { 79 | Eigen::Vector4f centroid; 80 | Eigen::Matrix3f covariancematrix,eigenvectors; 81 | Eigen::Vector3f eigenvalues; 82 | float esum,e1,e2,e3; 83 | float features[6]; 84 | featmatrix.resize(6,clouds.size()); 85 | featmatrix.zeros(); 86 | for(int i=0;i classifyGmmPointModel(const pcl::PointCloud::Ptr &cloud, int n_gaus, arma::mat &featmatrix, arma::gmm_diag &model) 113 | { 114 | int features =6; 115 | Eigen::MatrixXf means(features,n_gaus); 116 | for(int i=0;i woodlist(n_gaus,0); 131 | for(int i=0;ipoints.size()-1),arma::eucl_dist); 136 | std::vector classifications; 137 | for(int i=0;ipoints.size();i++) 138 | { 139 | if(woodlist[ids[i]] == 1) classifications.push_back(0); 140 | else if(woodlist[ids[i]] == 0) classifications.push_back(1); 141 | } 142 | return classifications; 143 | } 144 | 145 | std::vector classifyGmmClusterModel(const std::vector::Ptr> &clouds, int n_gaus, arma::mat &featmatrix, arma::gmm_full &model) 146 | { 147 | int features = 6; 148 | Eigen::MatrixXf means(features,n_gaus); 149 | for(int i=0;i woodlist(n_gaus,0); 164 | for(int i=0;i classifications; 169 | arma::urowvec ids = model.assign(featmatrix.cols(0,clouds.size()-1),arma::eucl_dist); 170 | for(int i=0;i::Ptr &cloud, const std::vector &classifications, std::vector::Ptr> &classifiedclouds) 179 | { 180 | auto minmax = minmax_element(classifications.begin(),classifications.end()); 181 | int min = *(minmax.first); 182 | int max = *(minmax.second); 183 | for(int i=0;i<=max;i++) 184 | { 185 | pcl::PointCloud::Ptr ccloud(new pcl::PointCloud); 186 | classifiedclouds.push_back(ccloud); 187 | } 188 | for(int j=0;jpoints.size();j++) classifiedclouds[classifications[j]]->insert(classifiedclouds[classifications[j]]->end(),cloud->points[j]); 189 | } 190 | 191 | void separateCloudsClassifiedByCluster(const std::vector::Ptr> &clouds, const std::vector &classifications, std::vector::Ptr> &classifiedclouds) 192 | { 193 | auto minmax = minmax_element(classifications.begin(),classifications.end()); 194 | int min = *(minmax.first); 195 | int max = *(minmax.second); 196 | for(int i=0;i<=max;i++) 197 | { 198 | pcl::PointCloud::Ptr ccloud(new pcl::PointCloud); 199 | classifiedclouds.push_back(ccloud); 200 | } 201 | for(int j=0;jpoints.size();k++) classifiedclouds[classifications[j]]->insert(classifiedclouds[classifications[j]]->end(),clouds[j]->points[k]); 204 | } 205 | } 206 | 207 | void writeCloudClassifiedByPoint(const pcl::PointCloud::Ptr &cloud, const std::vector &classifications, std::string fname) 208 | { 209 | pcl::PCDWriter writer; 210 | auto minmax = minmax_element(classifications.begin(),classifications.end()); 211 | int min = *(minmax.first); 212 | int max = *(minmax.second); 213 | int colours[max+1][3]; 214 | for(int i=0;i::Ptr out(new pcl::PointCloud); 221 | //srand(time(NULL)); 222 | for(int j=0;jpoints.size();j++) 223 | { 224 | pcl::PointXYZRGB point; 225 | point.x = cloud->points[j].x; 226 | point.y = cloud->points[j].y; 227 | point.z = cloud->points[j].z; 228 | point.r = colours[classifications[j]][0]; 229 | point.g = colours[classifications[j]][1]; 230 | point.b = colours[classifications[j]][2]; 231 | out->insert(out->end(),point); 232 | } 233 | writer.write(fname,*out,true); 234 | } 235 | 236 | void writeCloudClassifiedByCluster(const std::vector::Ptr> &clouds, const std::vector &classifications, std::string fname) 237 | { 238 | pcl::PCDWriter writer; 239 | auto minmax = minmax_element(classifications.begin(),classifications.end()); 240 | int min = *(minmax.first); 241 | int max = *(minmax.second); 242 | int colours[max+1][3]; 243 | for(int i=0;i::Ptr out(new pcl::PointCloud); 250 | for(int j=0;jpoints.size();k++) 253 | { 254 | pcl::PointXYZRGB point; 255 | point.x = clouds[j]->points[k].x; 256 | point.y = clouds[j]->points[k].y; 257 | point.z = clouds[j]->points[k].z; 258 | point.r = colours[classifications[j]][0]; 259 | point.g = colours[classifications[j]][1]; 260 | point.b = colours[classifications[j]][2]; 261 | out->insert(out->end(),point); 262 | } 263 | } 264 | writer.write(fname,*out,true); 265 | } 266 | -------------------------------------------------------------------------------- /src/nearestneighbour.cpp: -------------------------------------------------------------------------------- 1 | #include "treeseg.h" 2 | 3 | #include 4 | 5 | int main(int argc, char **argv) 6 | { 7 | std::vector args(argv+1,argv+argc); 8 | pcl::PCDReader reader; 9 | pcl::PointCloud::Ptr cloud(new pcl::PointCloud); 10 | float zstep = std::stof(args[0]); 11 | int nnearest = std::stoi(args[1]); 12 | reader.read(args[2],*cloud); 13 | std::vector> nndata; 14 | nndata = dNNz(cloud,nnearest,zstep); 15 | for(int i=0;i 4 | 5 | int main (int argc, char **argv) 6 | { 7 | std::vector args(argv+1,argv+argc); 8 | pcl::PCDReader reader; 9 | pcl::PCDWriter writer; 10 | for(int i=0;i tmp1,tmp2; 14 | boost::split(tmp1,args[i],boost::is_any_of("/")); 15 | boost::split(tmp2,tmp1[tmp1.size()-1],boost::is_any_of(".")); 16 | for(int j=0;j::Ptr cloud(new pcl::PointCloud); 19 | reader.read(args[i],*cloud); 20 | std::ofstream outfile(ss.str()); 21 | for(int i=0;ipoints.size();i++) 22 | { 23 | #if XYZRRDRS == false 24 | outfile << cloud->points[i].x << " " << cloud->points[i].y << " " << cloud->points[i].z << "\n"; 25 | #else 26 | outfile << cloud->points[i].x << " " << cloud->points[i].y << " " << cloud->points[i].z << " " << cloud->points[i].range << " " << cloud->points[i].reflectance << " " << cloud->points[i].deviation << " " << cloud->points[i].return_number << " " << cloud->points[i].scan_number<< "\n"; 27 | #endif 28 | } 29 | outfile.close(); 30 | } 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /src/pcdPointXYZRGB2txt.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main (int argc, char **argv) 4 | { 5 | std::vector args(argv+1,argv+argc); 6 | pcl::PCDReader reader; 7 | pcl::PCDWriter writer; 8 | for(int i=0;i tmp1,tmp2; 12 | boost::split(tmp1,args[i],boost::is_any_of("/")); 13 | boost::split(tmp2,tmp1[tmp1.size()-1],boost::is_any_of(".")); 14 | for(int j=0;j::Ptr cloud(new pcl::PointCloud); 17 | reader.read(args[i],*cloud); 18 | std::ofstream outfile(ss.str()); 19 | for(int i=0;ipoints.size();i++) 20 | { 21 | outfile << cloud->points[i].x << " " << cloud->points[i].y << " " << cloud->points[i].z << " " << (int)cloud->points[i].r << " " << (int)cloud->points[i].g << " " << (int)cloud->points[i].b << "\n"; 22 | } 23 | outfile.close(); 24 | } 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /src/plotcoords.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | int main(int argc, char **argv) 10 | { 11 | std::vector args(argv+1,argv+argc); 12 | std::string matrix_dir = args[0]; 13 | if(matrix_dir[matrix_dir.length()-1] != '/') matrix_dir = matrix_dir + '/'; 14 | std::vector fnames; 15 | char buf[PATH_MAX + 1]; 16 | DIR *dir = NULL; 17 | struct dirent *drnt = NULL; 18 | dir = opendir(matrix_dir.c_str()); 19 | while(drnt = readdir(dir)) fnames.push_back(drnt->d_name); 20 | closedir(dir); 21 | std::vector x,y; 22 | for(int i=0;i> matrix[no_count]; 38 | no_count++; 39 | } 40 | } 41 | x.push_back(matrix[3]); 42 | y.push_back(matrix[7]); 43 | } 44 | } 45 | float x_sum = 0; 46 | float y_sum = 0; 47 | for(int j=0;j 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | #include "treeseg.h" 10 | 11 | struct pcloud 12 | { 13 | std::vector x,y,z; 14 | std::vector range; 15 | std::vector amplitude; 16 | std::vector reflectance; 17 | std::vector deviation; 18 | std::vector return_number; 19 | std::uint16_t scan_number; 20 | std::vector time; 21 | float matrix[16]; 22 | }; 23 | 24 | class importer : public scanlib::pointcloud 25 | { 26 | pcloud &pc; 27 | public: 28 | importer(pcloud &pc) : scanlib::pointcloud(false), pc(pc){} 29 | protected: 30 | //void on_echo_transformed(echo_type echo) 31 | void on_shot_end() 32 | { 33 | for(int i=0;i args(argv+1,argv+argc); 55 | std::string top_dir = args[0]; 56 | if(top_dir[top_dir.length()-1] != '/') top_dir = top_dir + "/"; 57 | std::ifstream cfile; 58 | cfile.open(args[1]); 59 | float coordfile[4]; 60 | int no_count = 0; 61 | if(cfile.is_open()) 62 | { 63 | while(!cfile.eof()) 64 | { 65 | cfile >> coordfile[no_count]; 66 | no_count++; 67 | } 68 | } 69 | cfile.close(); 70 | float plot_xmin = coordfile[0] - 20; 71 | float plot_xmax = coordfile[1] + 20; 72 | float plot_ymin = coordfile[2] - 20; 73 | float plot_ymax = coordfile[3] + 20; 74 | float tile_area = std::stof(args[2]); 75 | float tile_length = sqrt(tile_area); 76 | int tile_count = 0; 77 | for(float x=plot_xmin;x positions; 112 | DIR *tdir = NULL; 113 | tdir = opendir (top_dir.c_str()); 114 | struct dirent *tent = NULL; 115 | while(tent = readdir(tdir)) positions.push_back(tent->d_name); 116 | closedir(tdir); 117 | for(int k=0;k position_contents; 128 | DIR *pdir = NULL; 129 | pdir = opendir(c_position); 130 | struct dirent *pent = NULL; 131 | while(pent = readdir(pdir)) position_contents.push_back(pent->d_name); 132 | closedir(pdir); 133 | std::string rxpname; 134 | for(int l=0;l rc; 153 | rc = scanlib::basic_rconnection::create(rxpname); 154 | rc->open(); 155 | scanlib::decoder_rxpmarker dec(rc); 156 | importer imp(pc); 157 | scanlib::buffer buf; 158 | for(dec.get(buf);!dec.eoi();dec.get(buf)) 159 | { 160 | imp.dispatch(buf.begin(), buf.end()); 161 | } 162 | rc->close(); 163 | } 164 | catch(...) 165 | { 166 | continue; 167 | } 168 | ss.str(""); 169 | if(positions[k][7] == '0' && positions[k][8] == '0') ss << positions[k][9]; 170 | else if(positions[k][7] == '0') ss << positions[k][8] << positions[k][9]; 171 | else ss << positions[k][7] << positions[k][8] << positions[k][9]; 172 | std::string scan_number = ss.str(); 173 | pc.scan_number = (std::uint16_t)atoi(scan_number.c_str()); 174 | std::ifstream mfile; 175 | mfile.open(matrixname); 176 | int no_count = 0; 177 | if(mfile.is_open()) 178 | { 179 | while(!mfile.eof()) 180 | { 181 | mfile >> pc.matrix[no_count]; 182 | no_count++; 183 | } 184 | } 185 | for(int m=0;m= tile_coords[n][0] && X < tile_coords[n][1]) 193 | { 194 | if(Y >= tile_coords[n][2] && Y < tile_coords[n][3]) 195 | { 196 | if(pc.deviation[m] <= deviation_max) 197 | { 198 | xyzfiles[n].write(reinterpret_cast(&X),sizeof(X)); 199 | xyzfiles[n].write(reinterpret_cast(&Y),sizeof(Y)); 200 | xyzfiles[n].write(reinterpret_cast(&Z),sizeof(Z)); 201 | #if XYZRRDRS == true 202 | xyzfiles[n].write(reinterpret_cast(&pc.range[m]),sizeof(pc.range[m])); 203 | xyzfiles[n].write(reinterpret_cast(&pc.reflectance[m]),sizeof(pc.reflectance[m])); 204 | xyzfiles[n].write(reinterpret_cast(&pc.deviation[m]),sizeof(pc.deviation[m])); 205 | xyzfiles[n].write(reinterpret_cast(&pc.return_number[m]),sizeof(pc.return_number[m])); 206 | xyzfiles[n].write(reinterpret_cast(&pc.scan_number),sizeof(pc.scan_number)); 207 | #endif 208 | tile_pointcount[n] += 1; 209 | } 210 | } 211 | } 212 | } 213 | } 214 | } 215 | } 216 | for(int p=0;p " << pcdnames[q] << "; rm header.tmp " << xyznames[q]; 231 | std::string string; 232 | const char* cc; 233 | string = ss.str(); 234 | cc = string.c_str(); 235 | system(cc); 236 | } 237 | return 0; 238 | } 239 | -------------------------------------------------------------------------------- /src/rxp2tree.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | struct pcloud 12 | { 13 | std::vector x,y,z; 14 | std::vector range; 15 | std::vector amplitude; 16 | std::vector reflectance; 17 | std::vector deviation; 18 | std::vector return_number; 19 | float scan_number; 20 | std::vector time; 21 | float matrix[16]; 22 | }; 23 | 24 | class importer : public scanlib::pointcloud 25 | { 26 | pcloud &pc; 27 | public: 28 | importer(pcloud &pc) : scanlib::pointcloud(false), pc(pc){} 29 | protected: 30 | void on_shot_end() 31 | { 32 | for(int i=0;i fnames; 54 | std::vector count; 55 | std::vector x,y,z; 56 | for(int i=3;i name1; 60 | std::vector name2; 61 | std::vector name3; 62 | boost::split(name1,argv[i],boost::is_any_of(".")); 63 | boost::split(name2,name1[name1.size()-2],boost::is_any_of("/")); 64 | boost::split(name3,name2[name2.size()-1],boost::is_any_of("_")); 65 | fname = name3[name3.size()-2]+"_"+name3[name3.size()-1]+".txt"; 66 | pcl::PointCloud::Ptr tree(new pcl::PointCloud); 67 | reader.read(argv[i],*tree); 68 | Eigen::Vector4f min,max; 69 | pcl::getMinMax3D(*tree,min,max); 70 | pcl::PassThrough pass; 71 | int c=0; 72 | for(float xt=min[0]; xt::Ptr tmpx(new pcl::PointCloud); 75 | pass.setInputCloud(tree); 76 | pass.setFilterFieldName("x"); 77 | pass.setFilterLimits(xt,xt+resolution); 78 | pass.filter(*tmpx); 79 | for(float yt=min[1]; yt::Ptr tmpy(new pcl::PointCloud); 82 | pass.setInputCloud(tmpx); 83 | pass.setFilterFieldName("y"); 84 | pass.setFilterLimits(yt,yt+resolution); 85 | pass.filter(*tmpy); 86 | for(float zt=min[2]; zt::Ptr tmpz(new pcl::PointCloud); 89 | pass.setInputCloud(tmpy); 90 | pass.setFilterFieldName("z"); 91 | pass.setFilterLimits(zt,zt+resolution); 92 | pass.filter(*tmpz); 93 | if(tmpz->points.size() != 0) 94 | { 95 | x.push_back(xt); 96 | y.push_back(yt); 97 | z.push_back(zt); 98 | c++; 99 | } 100 | } 101 | } 102 | } 103 | count.push_back(c); 104 | fnames.push_back(fname); 105 | } 106 | std::ofstream cloudfiles[fnames.size()]; 107 | for(int j=0;j positions; 115 | DIR *tdir = NULL; 116 | tdir = opendir(top_dir.c_str()); 117 | struct dirent *tent = NULL; 118 | while(tent = readdir(tdir)) positions.push_back(tent->d_name); 119 | closedir(tdir); 120 | for(int k=0;k position_contents; 131 | DIR *pdir = NULL; 132 | pdir = opendir(c_position); 133 | struct dirent *pent = NULL; 134 | while(pent = readdir(pdir)) position_contents.push_back(pent->d_name); 135 | closedir(pdir); 136 | std::string rxpname; 137 | for(int l=0;l rc; 151 | rc = scanlib::basic_rconnection::create(rxpname); 152 | rc->open(); 153 | scanlib::decoder_rxpmarker dec(rc); 154 | pcloud pc; 155 | importer imp(pc); 156 | scanlib::buffer buf; 157 | for(dec.get(buf);!dec.eoi();dec.get(buf)) 158 | { 159 | imp.dispatch(buf.begin(), buf.end()); 160 | } 161 | rc->close(); 162 | ss.str(""); 163 | if(positions[k][7] == '0' && positions[k][8] == '0') ss << positions[k][9]; 164 | else if(positions[k][7] == '0') ss << positions[k][8] << positions[k][9]; 165 | else ss << positions[k][7] << positions[k][8] << positions[k][9]; 166 | std::string scan_number = ss.str(); 167 | pc.scan_number = atof(scan_number.c_str()); 168 | std::ifstream mfile; 169 | mfile.open(matrixname); 170 | int no_count = 0; 171 | if(mfile.is_open()) 172 | { 173 | while(!mfile.eof()) 174 | { 175 | mfile >> pc.matrix[no_count]; 176 | no_count++; 177 | } 178 | } 179 | for(int m=0;m= x[p] && X <= x[p]+resolution) 193 | { 194 | if(Y >= y[p] && Y <= y[p]+resolution) 195 | { 196 | if(Z >= z[p] && Z <= z[p]+resolution) 197 | { 198 | cloudfiles[n] << X << " " << Y << " " << Z << " " << pc.range[m] << " " << pc.reflectance[m] << " " << pc.deviation[m] << " " << pc.return_number[m] << " " << pc.scan_number << std::endl; 199 | } 200 | } 201 | } 202 | } 203 | } 204 | } 205 | } 206 | } 207 | for(int q=0;q 4 | 5 | int main(int argc, char **argv) 6 | { 7 | std::vector args(argv+1,argv+argc); 8 | pcl::PCDReader reader; 9 | pcl::PCDWriter writer; 10 | std::stringstream ss; 11 | float smoothness = std::stof(args[0]); 12 | for(int i=1;i id = getFileID(args[i]); 17 | pcl::PointCloud::Ptr volume(new pcl::PointCloud); 18 | reader.read(args[i],*volume); 19 | std::cout << "complete" << std::endl; 20 | // 21 | std::cout << "Region-based segmentation: " << std::flush; 22 | std::vector::Ptr> regions; 23 | pcl::PointCloud::Ptr normals(new pcl::PointCloud); 24 | estimateNormals(volume,50,normals); 25 | regionSegmentation(volume,normals,250,3,std::numeric_limits::max(),smoothness,1,regions); 26 | volume->clear(); 27 | normals->clear(); 28 | ss.str(""); 29 | ss << id[0] << ".rg." << id[1] << ".pcd"; 30 | writeClouds(regions,ss.str(),false); 31 | std::cout << ss.str() << std::endl; 32 | // 33 | std::cout << "Optimising regions: " << std::flush; 34 | removeFarRegions(0.5,regions); 35 | ss.str(""); 36 | ss << id[0] << ".rg.o." << id[1] << ".pcd"; 37 | writeClouds(regions,ss.str(),false); 38 | std::cout << ss.str() << std::endl; 39 | // 40 | std::cout << "Building tree: " << std::flush; 41 | pcl::PointCloud::Ptr tree(new pcl::PointCloud); 42 | buildTree(regions,15,1,0.2,3,1.0,tree); 43 | ss.str(""); 44 | ss << id[0] << "_" << id[1] << ".pcd"; 45 | writer.write(ss.str(),*tree,true); 46 | std::cout << " " << ss.str() << std::endl; 47 | } 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /src/segmentstem.cpp: -------------------------------------------------------------------------------- 1 | #include "treeseg.h" 2 | 3 | #include 4 | #include 5 | 6 | int main(int argc, char **argv) 7 | { 8 | std::vector args(argv+1,argv+argc); 9 | // 10 | pcl::PCDReader reader; 11 | pcl::PCDWriter writer; 12 | std::stringstream ss; 13 | // 14 | std::cout << "Reading plot-level cloud: " << std::flush; 15 | pcl::PointCloud::Ptr plot(new pcl::PointCloud); 16 | readTiles(args,plot); 17 | std::cout << "complete" << std::endl; 18 | // 19 | for(int i=1;i id = getFileID(args[i]); 23 | pcl::PointCloud::Ptr foundstem(new pcl::PointCloud); 24 | reader.read(args[i],*foundstem); 25 | // 26 | std::cout << "RANSAC cylinder fit: " << std::flush; 27 | int nnearest = 60; 28 | cylinder cyl; 29 | fitCylinder(foundstem,nnearest,false,false,cyl); 30 | if(cyl.rad < 0.2) cyl.rad = 0.2; 31 | std::cout << cyl.rad << std::endl; 32 | // 33 | std::cout << "Segmenting extended cylinder: " << std::flush; 34 | pcl::PointCloud::Ptr volume(new pcl::PointCloud); 35 | float expansionfactor = 12; 36 | cyl.rad = cyl.rad*expansionfactor; 37 | spatial3DCylinderFilter(plot,cyl,volume); 38 | ss.str(""); 39 | ss << id[0] << ".c." << id[1] << ".pcd"; 40 | writer.write(ss.str(),*volume,true); 41 | std::cout << ss.str() << std::endl; 42 | // 43 | std::cout << "Segmenting ground returns: " << std::flush; 44 | pcl::PointCloud::Ptr bottom(new pcl::PointCloud); 45 | pcl::PointCloud::Ptr top(new pcl::PointCloud); 46 | pcl::PointCloud::Ptr vnoground(new pcl::PointCloud); 47 | pcl::PointCloud::Ptr normals(new pcl::PointCloud); 48 | pcl::PointIndices::Ptr inliers(new pcl::PointIndices); 49 | std::sort(volume->points.begin(),volume->points.end(),sortCloudByZ); 50 | int groundidx = (2.5/100) * volume->points.size(); 51 | float ground = volume->points[groundidx].z; 52 | Eigen::Vector4f min, max; 53 | pcl::getMinMax3D(*volume,min,max); 54 | spatial1DFilter(volume,"z",ground-1,ground+3.5,bottom); 55 | spatial1DFilter(volume,"z",ground+3.5,max[2],top); 56 | estimateNormals(bottom,250,normals); 57 | fitPlane(bottom,normals,0.5,inliers,0.75,30); 58 | extractIndices(bottom,inliers,true,vnoground); 59 | *vnoground += *top; 60 | ss.str(""); 61 | ss << id[0] << ".c.ng." << id[1] << ".pcd"; 62 | writer.write(ss.str(),*vnoground,true); 63 | std::cout << ss.str() << std::endl; 64 | // 65 | std::cout << "Region-based segmentation: " << std::flush; 66 | std::vector::Ptr> regions; 67 | normals->clear(); 68 | estimateNormals(vnoground,50,normals); 69 | float smoothness = std::stof(args[0]); 70 | regionSegmentation(vnoground,normals,250,3,std::numeric_limits::max(),smoothness,1,regions); 71 | ss.str(""); 72 | ss << id[0] << ".c.ng.r." << id[1] << ".pcd"; 73 | writeClouds(regions,ss.str(),false); 74 | std::cout << ss.str() << std::endl; 75 | // 76 | std::cout << "Correcting stem: " << std::flush; 77 | pcl::PointCloud::Ptr stem(new pcl::PointCloud); 78 | int idx = findClosestIdx(foundstem,regions,true); 79 | *stem += *regions[idx]; 80 | //nnearest = 60; 81 | //float zdelta = 0.75; 82 | //float zstart = 5; 83 | //float stepcovmax = 0.05; 84 | //float radchangemin = 0.9; 85 | //correctStem(regions[idx],nnearest,zstart,zdelta,stepcovmax,radchangemin,stem); 86 | ss.str(""); 87 | ss << id[0] << ".stem." << id[1] << ".pcd"; 88 | writer.write(ss.str(),*stem,true); 89 | std::cout << ss.str() << std::endl; 90 | } 91 | return 0; 92 | } 93 | -------------------------------------------------------------------------------- /src/sepwoodleaf.cpp: -------------------------------------------------------------------------------- 1 | #include "leafsep.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | int main (int argc, char **argv) 8 | { 9 | std::vector args(argv+1,argv+argc); 10 | pcl::PCDReader reader; 11 | pcl::PCDWriter writer; 12 | int kn = std::stoi(args[0]); 13 | int n_gaus = std::stoi(args[1]); 14 | int dist_mode = std::stoi(args[2]); 15 | int seed_mode = std::stoi(args[3]); 16 | int km_iter = std::stoi(args[4]); 17 | int em_iter = std::stoi(args[5]); 18 | float smoothness = std::stof(args[6]); 19 | for(int i=7;i id = getFileID(args[i]); 24 | pcl::PointCloud::Ptr cloud(new pcl::PointCloud); 25 | reader.read(args[i],*cloud); 26 | std::cout << "complete" << std::endl; 27 | // 28 | std::cout << "Region-based segmentation: " << std::flush; 29 | pcl::PointCloud::Ptr normals(new pcl::PointCloud); 30 | std::vector::Ptr> regions; 31 | estimateNormals(cloud,250,normals); 32 | regionSegmentation(cloud,normals,30,3,std::numeric_limits::max(),smoothness,1,regions); 33 | std::stringstream ss; 34 | ss << "regions_" << id[0] << ".pcd"; 35 | writeClouds(regions,ss.str(),false); 36 | std::cout << ss.str() << std::endl; 37 | // 38 | std::cout << "Classifying cloud by region: " << std::flush; 39 | arma::mat featmatrix; 40 | arma::gmm_full model; 41 | gmmByCluster(regions,n_gaus,dist_mode,seed_mode,km_iter,em_iter,featmatrix,model); 42 | std::vector classifications; 43 | classifications = classifyGmmClusterModel(regions,n_gaus,featmatrix,model); 44 | std::vector::Ptr> clouds; 45 | separateCloudsClassifiedByCluster(regions,classifications,clouds); 46 | ss.str(""); 47 | ss << "regions_rclass_" << id[0] << ".pcd"; 48 | writeCloudClassifiedByCluster(regions,classifications,ss.str()); 49 | std::cout << ss.str() << std::endl; 50 | // 51 | std::cout << "Classifying remaining cloud by point: " << std::flush; 52 | arma::mat pfeatmat; 53 | arma::gmm_diag pmodel; 54 | gmmByPoint(clouds[1],kn,n_gaus,dist_mode,seed_mode,km_iter,em_iter,pfeatmat,pmodel); 55 | std::vector pclassifications; 56 | pclassifications = classifyGmmPointModel(clouds[1],n_gaus,pfeatmat,pmodel); 57 | std::vector::Ptr> pclouds; 58 | separateCloudsClassifiedByPoint(clouds[1],pclassifications,pclouds); 59 | ss.str(""); 60 | ss << "regions_rclass_pclass_" << id[0] << ".pcd"; 61 | writeCloudClassifiedByPoint(clouds[1],pclassifications,ss.str()); 62 | std::cout << ss.str() << std::endl; 63 | // 64 | std::cout << "Writing wood/leaf clouds: " << std::flush; 65 | pcl::PointCloud::Ptr wood(new pcl::PointCloud); 66 | *wood += *clouds[0] + *pclouds[0]; 67 | ss.str(""); 68 | ss << id[1] << "_" << id[0] << "w" << ".pcd"; 69 | writer.write(ss.str(),*wood,true); 70 | std::cout << ss.str() << "," << std::flush; 71 | pcl::PointCloud::Ptr leaf(new pcl::PointCloud); 72 | *leaf = *pclouds[1]; 73 | ss.str(""); 74 | ss << id[1] << "_" << id[0] << "l" << ".pcd"; 75 | writer.write(ss.str(),*leaf,true); 76 | std::cout << ss.str() << std::endl; 77 | } 78 | return 0; 79 | } 80 | -------------------------------------------------------------------------------- /src/thin.cpp: -------------------------------------------------------------------------------- 1 | #include "treeseg.h" 2 | 3 | #include 4 | 5 | int main(int argc, char **argv) 6 | { 7 | std::vector args(argv+1,argv+argc); 8 | float edgelength = std::stof(args[0]); 9 | pcl::PCDReader reader; 10 | pcl::PCDWriter writer; 11 | pcl::PointCloud::Ptr original(new pcl::PointCloud); 12 | pcl::PointCloud::Ptr downsampled(new pcl::PointCloud); 13 | for(int i=1;i id = getFileID(args[i]); 16 | std::stringstream ss; 17 | ss << id[0] << ".tile.thin." << id[1] << ".pcd"; 18 | reader.read(args[i],*original); 19 | thin(original,edgelength,downsampled); 20 | writer.write(ss.str(),*downsampled,true); 21 | original->clear(); 22 | downsampled->clear(); 23 | } 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /src/treeparams.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "treeseg.h" 6 | 7 | int main (int argc, char **argv) 8 | { 9 | int nnearest = atof(argv[1]); 10 | float zstep = atof(argv[2]); 11 | float diffmax = atof(argv[3]); 12 | float density = atof(argv[4]); 13 | pcl::PCDReader reader; 14 | for(int i=5;i id = getFileID(argv[i]); 17 | pcl::PointCloud::Ptr tree(new pcl::PointCloud); 18 | reader.read(argv[i],*tree); 19 | treeparams params = getTreeParams(tree,nnearest,zstep,diffmax); 20 | std::cout << id[1] << "_" << id[0] << " " << params.x << " " << params.y << " " << params.d << " " << params.h << " " << density << std::endl; 21 | } 22 | return 0; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/treeseg.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * treeseg.cpp 3 | * 4 | * MIT License 5 | * 6 | * Copyright 2017 Andrew Burt 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to 10 | * deal in the Software without restriction, including without limitation the 11 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | * sell copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 24 | * IN THE SOFTWARE. 25 | */ 26 | 27 | // treeseg has been developed using: 28 | // Point Cloud Library (http://www.pointclouds.org) 29 | 30 | #include "treeseg.h" 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | //File IO 44 | 45 | std::vector getFileID(std::string filename) 46 | { 47 | //Inflexible naming convention: 48 | //From rxp2pcd: ./DIR/PLOT.tile.number.pcd (plot can include hyphen e.g., FGC01 or FGC-01) 49 | //From downsample/thin: ./DIR/PLOT.tile.downsample(thin).number.pcd 50 | //Onwards: ./DIR/PLOT.X.number.pcd 51 | std::string pid,tid; 52 | std::vector info; 53 | std::vector tmp1,tmp2,tmp3; 54 | boost::split(tmp1,filename,boost::is_any_of("/")); 55 | boost::split(tmp2,tmp1[tmp1.size()-1],boost::is_any_of(".")); 56 | if(tmp2[1] == "tile") 57 | { 58 | pid = tmp2[0]; 59 | if(tmp2[2] == "downsample" || tmp2[2] == "thin") tid = tmp2[3]; 60 | else tid = tmp2[2]; 61 | info.push_back(pid); 62 | info.push_back(tid); 63 | return info; 64 | } 65 | else 66 | { 67 | pid = tmp2[0]; 68 | tid = tmp2[tmp2.size()-2]; 69 | info.push_back(pid); 70 | info.push_back(tid); 71 | return info; 72 | } 73 | } 74 | 75 | void readTiles(const std::vector &args, pcl::PointCloud::Ptr &cloud) 76 | { 77 | pcl::PCDReader reader; 78 | pcl::PointCloud::Ptr tmp(new pcl::PointCloud); 79 | for(int i=0;i tmp1,tmp2; 83 | boost::split(tmp1,filename,boost::is_any_of("/")); 84 | boost::split(tmp2,tmp1[tmp1.size()-1],boost::is_any_of(".")); 85 | if(tmp2.size() > 1) 86 | { 87 | if(tmp2[1] == "tile") 88 | { 89 | reader.read(args[i],*tmp); 90 | *cloud += *tmp; 91 | tmp->clear(); 92 | } 93 | } 94 | } 95 | } 96 | 97 | int getTilesStartIdx(const std::vector &args) 98 | { 99 | int start_tiles; 100 | for(int i=0;i tmp1,tmp2; 104 | boost::split(tmp1,filename,boost::is_any_of("/")); 105 | boost::split(tmp2,tmp1[tmp1.size()-1],boost::is_any_of(".")); 106 | if(tmp2.size() > 1) 107 | { 108 | if(tmp2[1] == "tile") 109 | { 110 | start_tiles = i; 111 | break; 112 | } 113 | 114 | } 115 | } 116 | return start_tiles; 117 | } 118 | 119 | void writeClouds(const std::vector::Ptr> &clouds, std::string fname, bool doPCA) 120 | { 121 | pcl::PCDWriter writer; 122 | pcl::PointCloud::Ptr out(new pcl::PointCloud); 123 | for(int i=0;ipoints.size();j++) 129 | { 130 | pcl::PointXYZRGB point; 131 | point.x = clouds[i]->points[j].x; 132 | point.y = clouds[i]->points[j].y; 133 | point.z = clouds[i]->points[j].z; 134 | point.r = r; 135 | point.g = g; 136 | point.b = b; 137 | out->insert(out->end(),point); 138 | } 139 | } 140 | writer.write(fname,*out,true); 141 | } 142 | 143 | //Nearest neighbour analysis 144 | 145 | std::vector dNN(const pcl::PointCloud::Ptr &cloud, int nnearest) 146 | { 147 | std::vector dist; 148 | pcl::KdTreeFLANN tree; 149 | tree.setInputCloud(cloud); 150 | int k = nnearest + 1; 151 | for(pcl::PointCloud::iterator it=cloud->begin();it!=cloud->end();it++) 152 | { 153 | std::vector p_dist; 154 | PointTreeseg searchPoint; 155 | searchPoint.x = it->x; 156 | searchPoint.y = it->y; 157 | searchPoint.z = it->z; 158 | std::vector pointIdxNKNSearch(k); 159 | std::vector pointNKNSquaredDistance(k); 160 | tree.nearestKSearch(searchPoint,k,pointIdxNKNSearch,pointNKNSquaredDistance); 161 | for(int i=1;i results; 174 | results.push_back(dist_mean); 175 | results.push_back(stddev); 176 | return results; 177 | } 178 | 179 | std::vector> dNNz(const pcl::PointCloud::Ptr &cloud, int nnearest, float zstep) 180 | { 181 | std::vector> results; 182 | Eigen::Vector4f min,max; 183 | pcl::getMinMax3D(*cloud,min,max); 184 | pcl::PointCloud::Ptr tmp(new pcl::PointCloud); 185 | for(float z=min[2];zpoints.size() > nnearest) 189 | { 190 | std::vector nn = dNN(tmp,nnearest); 191 | //float pos = z - min[2]; 192 | float pos = z + zstep; 193 | std::vector r; 194 | r.push_back(pos); 195 | r.push_back(nn[0]); 196 | results.push_back(r); 197 | } 198 | tmp->clear(); 199 | } 200 | return results; 201 | } 202 | 203 | float interpolatedNNZ(float x, const std::vector> &nndata, bool extrapolate) 204 | { 205 | std::vector xData; 206 | std::vector yData; 207 | for(int m=0;m= xData[size-2]) i = size - 2; 215 | else 216 | { 217 | while ( x > xData[i+1] ) i++; 218 | } 219 | double xL = xData[i], yL = yData[i], xR = xData[i+1], yR = yData[i+1]; 220 | if(!extrapolate) 221 | { 222 | if ( x < xL ) yR = yL; 223 | if ( x > xR ) yL = yR; 224 | } 225 | double dydx = ( yR - yL ) / ( xR - xL ); 226 | return yL + dydx * ( x - xL ); 227 | } 228 | 229 | //Cloud metrics 230 | 231 | void getCloudMetrics(const pcl::PointCloud::Ptr &cloud, cloudmetrics metrics) 232 | { 233 | Eigen::Vector4f min3D; 234 | Eigen::Vector4f max3D; 235 | Eigen::Vector4f centroid; 236 | Eigen::Matrix3f covariancematrix; 237 | Eigen::Matrix3f eigenvectors; 238 | Eigen::Vector3f eigenvalues; 239 | pcl::getMinMax3D(*cloud,min3D,max3D); 240 | computePCA(cloud,centroid,covariancematrix,eigenvectors,eigenvalues); 241 | float length = getCloudLength(cloud,centroid,eigenvectors); 242 | Eigen::Vector3f vector3D(eigenvectors(0,2),eigenvectors(1,2),eigenvectors(2,2)); 243 | metrics.count = cloud->points.size(); 244 | metrics.min3D = min3D; 245 | metrics.max3D = max3D; 246 | metrics.centroid = centroid; 247 | metrics.covariancematrix = covariancematrix; 248 | metrics.eigenvectors = eigenvectors; 249 | metrics.eigenvalues = eigenvalues; 250 | metrics.vector3D = vector3D; 251 | metrics.length = length; 252 | } 253 | 254 | float getCloudLength(const pcl::PointCloud::Ptr &cloud, const Eigen::Vector4f ¢roid, const Eigen::Matrix3f &eigenvectors) 255 | { 256 | Eigen::Vector3f point(centroid[0],centroid[1],centroid[2]); 257 | Eigen::Vector3f direction(eigenvectors(0,2),eigenvectors(1,2),eigenvectors(2,2)); 258 | Eigen::Affine3f transform; 259 | Eigen::Vector3f world(0,direction[2],-direction[1]); 260 | direction.normalize(); 261 | pcl::getTransformationFromTwoUnitVectorsAndOrigin(world,direction,point,transform); 262 | pcl::PointCloud::Ptr transformedcloud(new pcl::PointCloud); 263 | pcl::transformPointCloud(*cloud,*transformedcloud,transform); 264 | Eigen::Vector4f min,max; 265 | pcl::getMinMax3D(*transformedcloud,min,max); 266 | float length = max[2]-min[2]; 267 | return length; 268 | } 269 | 270 | void getBasicCloudMetrics(const pcl::PointCloud::Ptr &cloud, basiccloudmetrics metrics) 271 | { 272 | Eigen::Vector4f min3D; 273 | Eigen::Vector4f max3D; 274 | pcl::getMinMax3D(*cloud,min3D,max3D); 275 | metrics.count = cloud->points.size(); 276 | metrics.min3D = min3D; 277 | metrics.max3D = max3D; 278 | } 279 | 280 | //Downsampling 281 | 282 | void downsample(const pcl::PointCloud::Ptr &original, float edgelength, pcl::PointCloud::Ptr &filtered, bool octree) 283 | { 284 | if(octree == false) 285 | { 286 | pcl::VoxelGrid downsample; 287 | downsample.setInputCloud(original); 288 | downsample.setLeafSize(edgelength,edgelength,edgelength); 289 | downsample.filter(*filtered); 290 | } 291 | else 292 | { 293 | pcl::octree::OctreePointCloudSearch octree(edgelength); 294 | octree.setInputCloud(original); 295 | octree.defineBoundingBox(); 296 | octree.addPointsFromInputCloud(); 297 | pcl::PointCloud::VectorType voxelcentres; 298 | octree.getOccupiedVoxelCenters(voxelcentres); 299 | for(int i=0;i voxelinliersidx; 302 | octree.voxelSearch(voxelcentres[i],voxelinliersidx); 303 | pcl::PointCloud::Ptr voxelinliers(new pcl::PointCloud); 304 | for(int j=0;jinsert(voxelinliers->end(),original->points[voxelinliersidx[j]]); 305 | PointTreeseg centroid; 306 | pcl::computeCentroid(*voxelinliers,centroid); 307 | filtered->insert(filtered->end(),centroid); 308 | } 309 | } 310 | } 311 | 312 | void thin(const pcl::PointCloud::Ptr &original, float edgelength, pcl::PointCloud::Ptr &filtered, bool preservePointClosestToVoxelCentroid) 313 | { 314 | pcl::octree::OctreePointCloudSearch octree(edgelength); 315 | octree.setInputCloud(original); 316 | octree.defineBoundingBox(); 317 | octree.addPointsFromInputCloud(); 318 | pcl::PointCloud::VectorType voxelcentres; 319 | octree.getOccupiedVoxelCenters(voxelcentres); 320 | for(int i=0;i voxelinliersidx; 323 | octree.voxelSearch(voxelcentres[i],voxelinliersidx); 324 | pcl::PointCloud::Ptr voxelinliers(new pcl::PointCloud); 325 | for(int j=0;jinsert(voxelinliers->end(),original->points[voxelinliersidx[j]]); 326 | PointTreeseg centroid; 327 | pcl::computeCentroid(*voxelinliers,centroid); 328 | pcl::KdTreeFLANN kdtree; 329 | kdtree.setInputCloud(voxelinliers); 330 | int K = voxelinliers->points.size(); 331 | std::vector pointIdx(K); 332 | std::vector pointDist(K); 333 | kdtree.nearestKSearch(centroid,K,pointIdx,pointDist); 334 | #if XYZRRDRS == true 335 | if(preservePointClosestToVoxelCentroid == true) 336 | { 337 | filtered->insert(filtered->end(),voxelinliers->points[pointIdx[0]]); 338 | } 339 | else 340 | { 341 | pcl::PointCloud::Ptr sortedvoxelinliers(new pcl::PointCloud); 342 | for(int k=0;kinsert(sortedvoxelinliers->end(),voxelinliers->points[pointIdx[k]]); 343 | std::sort(sortedvoxelinliers->points.begin(),sortedvoxelinliers->points.end(),sortCloudByDeviation); //change this for any of RRDRS 344 | filtered->insert(filtered->end(),sortedvoxelinliers->points[0]); 345 | } 346 | #else 347 | filtered->insert(filtered->end(),voxelinliers->points[pointIdx[0]]); 348 | #endif 349 | } 350 | } 351 | 352 | //Spatial Filters 353 | 354 | void spatial1DFilter(const pcl::PointCloud::Ptr &original, std::string dimension, float min, float max, pcl::PointCloud::Ptr &filtered) 355 | { 356 | pcl::PassThrough pass; 357 | pass.setInputCloud(original); 358 | pass.setFilterFieldName(dimension); 359 | pass.setFilterLimits(min,max); 360 | pass.filter(*filtered); 361 | } 362 | 363 | void spatial3DCylinderFilter(const pcl::PointCloud::Ptr &original, cylinder cyl, pcl::PointCloud::Ptr &filtered) 364 | { 365 | float len = 1000; 366 | Eigen::Vector3f cp1(cyl.x+len*cyl.dx,cyl.y+len*cyl.dy,cyl.z+len*cyl.dz); 367 | Eigen::Vector3f cp2(cyl.x-len*cyl.dx,cyl.y-len*cyl.dy,cyl.z-len*cyl.dz); 368 | Eigen::Vector3f cn1 = cp2 - cp1; 369 | cn1.normalize(); 370 | Eigen::Vector3f cn2 = -cn1; 371 | for(int i=0;ipoints.size();i++) 372 | { 373 | Eigen::Vector3f p(original->points[i].x,original->points[i].y,original->points[i].z); 374 | float dot1 = cn1.dot(p-cp1); 375 | if(dot1 > 0) 376 | { 377 | float dot2 = cn2.dot(p-cp2); 378 | if(dot2 > 0) 379 | { 380 | Eigen::Vector3f mp = p - (cn1 * cn1.dot(p-cp1)); 381 | float dist = sqrt(pow(mp(0)-cp1(0),2)+pow(mp(1)-cp1(1),2)+pow(mp(2)-cp1(2),2)); 382 | if(dist <= cyl.rad) 383 | { 384 | filtered->insert(filtered->end(),original->points[i]); 385 | } 386 | } 387 | } 388 | } 389 | } 390 | 391 | //Clustering 392 | 393 | void euclideanClustering(const pcl::PointCloud::Ptr &cloud, float dmax, int nmin, std::vector::Ptr> &clusters) 394 | { 395 | std::vector indices; 396 | pcl::search::KdTree::Ptr tree(new pcl::search::KdTree); 397 | tree->setInputCloud(cloud); 398 | pcl::EuclideanClusterExtraction ec; 399 | ec.setClusterTolerance(dmax); 400 | ec.setMinClusterSize(nmin); 401 | ec.setMaxClusterSize(std::numeric_limits().max()); 402 | ec.setSearchMethod(tree); 403 | ec.setInputCloud(cloud); 404 | ec.extract(indices); 405 | for(std::vector::iterator it=indices.begin();it!=indices.end();it++) 406 | { 407 | pcl::PointCloud::Ptr tmpcloud(new pcl::PointCloud); 408 | for(std::vector::iterator pit=it->indices.begin();pit!=it->indices.end();pit++) 409 | { 410 | tmpcloud->insert(tmpcloud->end(),cloud->points[*pit]); 411 | } 412 | clusters.push_back(tmpcloud); 413 | } 414 | } 415 | 416 | //Principal component analysis 417 | 418 | void computePCA(const pcl::PointCloud::Ptr &cloud, Eigen::Vector4f ¢roid, Eigen::Matrix3f &covariancematrix, Eigen::Matrix3f &eigenvectors, Eigen::Vector3f &eigenvalues) 419 | { 420 | pcl::compute3DCentroid(*cloud,centroid); 421 | pcl::computeCovarianceMatrix(*cloud,centroid,covariancematrix); 422 | pcl::eigen33(covariancematrix,eigenvectors,eigenvalues); 423 | } 424 | 425 | //Surface normals 426 | 427 | void estimateNormals(const pcl::PointCloud::Ptr &cloud, int nnearest, pcl::PointCloud::Ptr &normals) 428 | { 429 | pcl::search::KdTree::Ptr tree(new pcl::search::KdTree()); 430 | tree->setInputCloud(cloud); 431 | pcl::NormalEstimation ne; 432 | ne.setSearchMethod(tree); 433 | ne.setInputCloud(cloud); 434 | ne.setKSearch(nnearest); 435 | //ne.setRadiusSearch(0.03); 436 | ne.compute(*normals); 437 | } 438 | 439 | //Segmentation 440 | 441 | void regionSegmentation(const pcl::PointCloud::Ptr &cloud, const pcl::PointCloud::Ptr &normals, int nneighbours, int nmin, int nmax, float smoothness, float curvature, std::vector::Ptr> ®ions) 442 | { 443 | std::vector indices; 444 | pcl::search::KdTree::Ptr tree(new pcl::search::KdTree ()); 445 | tree->setInputCloud(cloud); 446 | pcl::RegionGrowing reg; 447 | reg.setInputCloud(cloud); 448 | reg.setInputNormals(normals); 449 | reg.setSearchMethod(tree); 450 | reg.setNumberOfNeighbours(nneighbours); 451 | reg.setMinClusterSize(nmin); 452 | reg.setMaxClusterSize(nmax); 453 | reg.setSmoothnessThreshold(smoothness * (M_PI / 180.0)); 454 | reg.setCurvatureThreshold(curvature); 455 | reg.extract(indices); 456 | for(std::vector::iterator it=indices.begin();it!=indices.end();it++) 457 | { 458 | pcl::PointCloud::Ptr tmpcloud(new pcl::PointCloud); 459 | for(std::vector::iterator pit=it->indices.begin();pit!=it->indices.end();pit++) 460 | { 461 | tmpcloud->insert(tmpcloud->end(),cloud->points[*pit]); 462 | } 463 | regions.push_back(tmpcloud); 464 | } 465 | } 466 | 467 | //Shape fitting 468 | 469 | void fitPlane(const pcl::PointCloud::Ptr &cloud, const pcl::PointCloud::Ptr &normals, float dthreshold, pcl::PointIndices::Ptr &inliers, float nweight, float angle, Eigen::Vector3f axis) 470 | { 471 | pcl::SACSegmentationFromNormals seg; 472 | seg.setModelType(pcl::SACMODEL_NORMAL_PARALLEL_PLANE); 473 | seg.setMethodType(pcl::SAC_RANSAC); 474 | seg.setInputCloud(cloud); 475 | seg.setInputNormals(normals); 476 | seg.setOptimizeCoefficients(true); 477 | seg.setMaxIterations(1000); 478 | seg.setDistanceThreshold(dthreshold); 479 | seg.setNormalDistanceWeight(nweight); 480 | seg.setAxis(axis); 481 | seg.setEpsAngle(angle * (M_PI / 180.0)); 482 | pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients); 483 | seg.segment(*inliers,*coefficients); 484 | } 485 | 486 | std::vector fitCircle(const pcl::PointCloud::Ptr &cloud, int nnearest) 487 | { 488 | std::vector nndata; 489 | nndata = dNN(cloud,nnearest); 490 | float nndist = nndata[0]; 491 | pcl::PointIndices inliers; 492 | pcl::ModelCoefficients coefficients; 493 | pcl::SACSegmentation seg; 494 | seg.setOptimizeCoefficients(true); 495 | seg.setMethodType(pcl::SAC_RANSAC); 496 | seg.setMaxIterations(1000000); 497 | seg.setModelType(pcl::SACMODEL_CIRCLE2D); 498 | seg.setDistanceThreshold(nndist); 499 | seg.setInputCloud(cloud); 500 | seg.segment(inliers,coefficients); 501 | std::vector results; 502 | results.push_back(coefficients.values[0]); 503 | results.push_back(coefficients.values[1]); 504 | results.push_back(coefficients.values[2]); 505 | return results; 506 | } 507 | 508 | void fitCylinder(const pcl::PointCloud::Ptr &cloud, int nnearest, bool finite, bool diagnostics, cylinder &cyl) 509 | { 510 | cyl.ismodel = false; 511 | if(cloud->points.size() >= 10) 512 | { 513 | std::vector nndata; 514 | nndata = dNN(cloud,nnearest); 515 | float nndist = nndata[0]; 516 | pcl::PointCloud::Ptr normals(new pcl::PointCloud); 517 | estimateNormals(cloud,nnearest,normals); 518 | pcl::search::KdTree::Ptr tree(new pcl::search::KdTree()); 519 | tree->setInputCloud(cloud); 520 | pcl::PointIndices indices; 521 | pcl::ModelCoefficients coeff; 522 | pcl::SACSegmentationFromNormals seg; 523 | seg.setOptimizeCoefficients(true); 524 | seg.setModelType(pcl::SACMODEL_CYLINDER); 525 | seg.setNormalDistanceWeight(0.1); 526 | seg.setMethodType(pcl::SAC_RANSAC); 527 | seg.setMaxIterations(1000000); 528 | seg.setDistanceThreshold(nndist); 529 | seg.setInputCloud(cloud); 530 | seg.setInputNormals(normals); 531 | seg.segment(indices,coeff); 532 | if(indices.indices.size() > 0) 533 | { 534 | pcl::PointCloud::Ptr inliers(new pcl::PointCloud); 535 | for(std::vector::iterator pit=indices.indices.begin();pit!=indices.indices.end();pit++) 536 | { 537 | inliers->insert(inliers->end(),cloud->points[*pit]); 538 | } 539 | cyl.x = coeff.values[0]; 540 | cyl.y = coeff.values[1]; 541 | cyl.z = coeff.values[2]; 542 | cyl.dx = coeff.values[3]; 543 | cyl.dy = coeff.values[4]; 544 | cyl.dz = coeff.values[5]; 545 | cyl.rad = coeff.values[6]; 546 | 547 | cyl.steprad = 0;//init these to zero as we are not always setting in these in cylinderDiagnostics 548 | cyl.stepcov = 0; 549 | cyl.radratio = 0; 550 | 551 | cyl.cloud = cloud; 552 | cyl.inliers = inliers; 553 | if(cyl.rad > 0 && finite == false && diagnostics == false) cyl.ismodel = true; 554 | if(cyl.rad > 0 && finite == true) 555 | { 556 | pcl::PointCloud::Ptr inliers_transformed(new pcl::PointCloud); 557 | Eigen::Vector3f point(coeff.values[0],coeff.values[1],coeff.values[2]); 558 | Eigen::Vector3f direction(coeff.values[3],coeff.values[4],coeff.values[5]); 559 | Eigen::Affine3f transform; 560 | Eigen::Vector3f world(0,direction[2],-direction[1]); 561 | direction.normalize(); 562 | pcl::getTransformationFromTwoUnitVectorsAndOrigin(world,direction,point,transform); 563 | pcl::transformPointCloud(*inliers,*inliers_transformed,transform); 564 | Eigen::Vector4f min,max; 565 | pcl::getMinMax3D(*inliers_transformed,min,max); 566 | cyl.len = max[2]-min[2]; 567 | if(cyl.len > 0) cyl.ismodel = true; 568 | } 569 | if(cyl.rad > 0 && diagnostics == true) 570 | { 571 | cylinderDiagnostics(cyl,nnearest); 572 | if(cyl.steprad > 0) cyl.ismodel = true; 573 | } 574 | } 575 | } 576 | } 577 | 578 | void cylinderDiagnostics(cylinder &cyl, int nnearest) 579 | { 580 | int NSTEP = 6; 581 | pcl::PointCloud::Ptr inliers_transformed(new pcl::PointCloud); 582 | Eigen::Vector3f point(cyl.x,cyl.y,cyl.z); 583 | Eigen::Vector3f direction(cyl.dx,cyl.dy,cyl.dz); 584 | Eigen::Affine3f transform; 585 | Eigen::Vector3f world(0,direction[2],-direction[1]); 586 | direction.normalize(); 587 | pcl::getTransformationFromTwoUnitVectorsAndOrigin(world,direction,point,transform); 588 | pcl::transformPointCloud(*cyl.inliers,*inliers_transformed,transform); 589 | Eigen::Vector4f min,max; 590 | pcl::getMinMax3D(*inliers_transformed,min,max); 591 | float zstep = (max[2]-min[2]) / NSTEP; 592 | std::vector zrads; 593 | for(int i=0;i::Ptr slice(new pcl::PointCloud); 598 | spatial1DFilter(inliers_transformed,"z",zmin,zmax,slice); 599 | cylinder zcyl; 600 | fitCylinder(slice,nnearest,false,false,zcyl); 601 | if(zcyl.ismodel == true) zrads.push_back(zcyl.rad); 602 | } 603 | if(zrads.size() >= NSTEP - 2) 604 | { 605 | float sum = std::accumulate(zrads.begin(),zrads.end(),0.0); 606 | float mean = sum/zrads.size(); 607 | std::vector diff(zrads.size()); 608 | std::transform(zrads.begin(),zrads.end(),diff.begin(),std::bind2nd(std::minus(),mean)); 609 | float stddev = std::sqrt(std::inner_product(diff.begin(),diff.end(),diff.begin(),0.0) / zrads.size()); 610 | cyl.steprad = mean; 611 | cyl.stepcov = stddev/mean; 612 | cyl.radratio = std::min(cyl.rad,cyl.steprad)/std::max(cyl.rad,cyl.steprad); 613 | } 614 | } 615 | 616 | //Generic 617 | 618 | bool sortCloudByX(const PointTreeseg &p1, const PointTreeseg &p2) {return p1.x < p2.x;} 619 | bool sortCloudByY(const PointTreeseg &p1, const PointTreeseg &p2) {return p1.y < p2.y;} 620 | bool sortCloudByZ(const PointTreeseg &p1, const PointTreeseg &p2) {return p1.z < p2.z;} 621 | bool sortCloudByDescEuclidDist(const PointTreeseg &p1, const PointTreeseg &p2) 622 | { 623 | if (p1.x != p2.x) return p1.x > p2.x; 624 | else if (p1.y != p2.y) return p1.y > p2.y; 625 | else return p1.z > p2.z; 626 | } 627 | #if XYZRRDRS == true 628 | bool sortCloudByRange(const PointTreeseg &p1, const PointTreeseg &p2) {return p1.range < p2.range;} 629 | bool sortCloudByReflectance(const PointTreeseg &p1, const PointTreeseg &p2) {return p1.reflectance < p2.reflectance;} 630 | bool sortCloudByDeviation(const PointTreeseg &p1, const PointTreeseg &p2) {return p1.deviation < p2.deviation;} 631 | bool sortCloudByReturnNumber(const PointTreeseg &p1, const PointTreeseg &p2) {return p1.return_number < p2.return_number;} 632 | bool sortCloudByScanNumber(const PointTreeseg &p1, const PointTreeseg &p2) {return p1.scan_number < p2.scan_number;} 633 | #endif 634 | 635 | bool equalPoint(const PointTreeseg &p1, const PointTreeseg &p2) 636 | { 637 | if (p1.x == p2.x && p1.y == p2.y && p1.z == p2.z) return true; 638 | return false; 639 | } 640 | 641 | bool sort2DFloatVectorByCol1(const std::vector &v1, const std::vector &v2) {return v1[1] < v2[1];} 642 | bool sort2DFloatVectorByCol2(const std::vector &v1, const std::vector &v2) {return v1[2] < v2[2];} 643 | 644 | int findClosestIdx(const pcl::PointCloud::Ptr &cloud, const std::vector::Ptr> &clouds, bool biggest) 645 | { 646 | int idx = 0; 647 | std::vector> data; 648 | for(int i=0;ipoints.size() > clouds[i]->points.size()) d = minDistBetweenClouds(cloud,clouds[i]); 652 | else d = minDistBetweenClouds(clouds[i],cloud); 653 | std::vector tmp; 654 | tmp.push_back(d); 655 | tmp.push_back(clouds[i]->points.size()); 656 | data.push_back(tmp); 657 | } 658 | float mindist = std::numeric_limits::infinity(); 659 | for(int j=0;j size) 675 | { 676 | size = int(data[k][1]); 677 | idx = k; 678 | } 679 | } 680 | } 681 | return idx; 682 | } 683 | 684 | int findPrincipalCloudIdx(const std::vector::Ptr> &clouds) 685 | { 686 | std::vector> info; 687 | for(int i=0;i tmp; 692 | tmp.push_back(i); 693 | tmp.push_back(clouds[i]->points.size()); 694 | tmp.push_back(min[2]); 695 | info.push_back(tmp); 696 | } 697 | std::sort(info.begin(),info.end(),sort2DFloatVectorByCol2); 698 | float zrange = abs(info[info.size()-1][2]-info[0][2]); 699 | float zpercentile = (5.0/100.0)*zrange; 700 | float zmax = info[0][2]+zpercentile; 701 | int idx = 0; 702 | int pcount = 0; 703 | for(int i=0;i pcount) 706 | { 707 | if(info[i][2] < zmax) 708 | { 709 | idx = info[i][0]; 710 | pcount = info[i][1]; 711 | } 712 | } 713 | } 714 | return idx; 715 | } 716 | 717 | void extractIndices(const pcl::PointCloud::Ptr &cloud, const pcl::PointIndices::Ptr &inliers, bool invert, pcl::PointCloud::Ptr &filtered) 718 | { 719 | pcl::ExtractIndices extract; 720 | extract.setInputCloud(cloud); 721 | extract.setIndices(inliers); 722 | extract.setNegative(invert); 723 | extract.filter(*filtered); 724 | } 725 | 726 | float minDistBetweenClouds(const pcl::PointCloud::Ptr &a, const pcl::PointCloud::Ptr &b) 727 | { 728 | pcl::KdTreeFLANN kdtree; 729 | kdtree.setInputCloud(a); 730 | return minDistBetweenClouds(a,b,kdtree); 731 | } 732 | 733 | float minDistBetweenClouds(const pcl::PointCloud::Ptr &a, const pcl::PointCloud::Ptr &b, const pcl::KdTreeFLANN &kdtree) 734 | { 735 | //assuming a is the larger of the two clouds 736 | float distance = std::numeric_limits::infinity(); 737 | int K = 1; 738 | for(pcl::PointCloud::iterator it=b->begin();it!=b->end();it++) 739 | { 740 | std::vector p_dist; 741 | PointTreeseg searchPoint; 742 | searchPoint.x = it->x; 743 | searchPoint.y = it->y; 744 | searchPoint.z = it->z; 745 | std::vector pointIdxNKNSearch(K); 746 | std::vector pointNKNSquaredDistance(K); 747 | kdtree.nearestKSearch(searchPoint,K,pointIdxNKNSearch,pointNKNSquaredDistance); 748 | float d = sqrt(pointNKNSquaredDistance[0]); 749 | if(d < distance) distance = d; 750 | } 751 | return distance; 752 | } 753 | 754 | bool intersectionTest3DBox(const Eigen::Vector4f &amin, const Eigen::Vector4f &amax, const Eigen::Vector4f &bmin, const Eigen::Vector4f &bmax) 755 | { 756 | bool intersection = false; 757 | if(amin[0] <= bmax[0] && amax[0] >= bmin[0]) 758 | { 759 | if(amin[1] <= bmax[1] && amax[1] >= bmin[1]) 760 | { 761 | if(amin[2] <= bmax[2] && amax[2] >= bmin[2]) 762 | { 763 | intersection = true; 764 | } 765 | } 766 | } 767 | return intersection; 768 | } 769 | 770 | void catIntersectingClouds(std::vector::Ptr> &clouds) 771 | { 772 | //this is poorly optimised and currently the primary bottleneck of findstems. 773 | bool donesomething = true; 774 | while(donesomething == true) 775 | { 776 | int idx; 777 | std::vector duplicates; 778 | for(int i=0;i 0) 793 | { 794 | idx = i; 795 | break; 796 | } 797 | } 798 | if(duplicates.size() > 0) 799 | { 800 | std::sort(duplicates.begin(),duplicates.end(),std::greater()); 801 | for(int k=0;k::Ptr &cloud) 812 | { 813 | std::sort(cloud->points.begin(),cloud->points.end(),sortCloudByDescEuclidDist); 814 | auto unique = std::unique(cloud->points.begin(),cloud->points.end(),equalPoint); 815 | cloud->erase(unique,cloud->end()); 816 | } 817 | 818 | //treeseg specific 819 | 820 | std::vector> getDtmAndSlice(const pcl::PointCloud::Ptr &plot, float resolution, float percentile, float zmin, float zmax, pcl::PointCloud::Ptr &slice) 821 | { 822 | std::vector> dem; 823 | std::vector result; 824 | pcl::PointCloud::Ptr tmpcloud(new pcl::PointCloud); 825 | pcl::PointCloud::Ptr tile(new pcl::PointCloud); 826 | pcl::PointCloud::Ptr tileslice(new pcl::PointCloud); 827 | Eigen::Vector4f plotmin,plotmax; 828 | pcl::getMinMax3D(*plot,plotmin,plotmax); 829 | for(float x=plotmin[0];xpoints.begin(),tile->points.end(),sortCloudByZ); 836 | int idx = (percentile / 100) * tile->points.size(); 837 | float ground = tile->points[idx].z; 838 | result.push_back(x); 839 | result.push_back(y); 840 | result.push_back(ground); 841 | dem.push_back(result); 842 | spatial1DFilter(tile,"z",ground+zmin,ground+zmax,tileslice); 843 | *slice += *tileslice; 844 | result.clear(); 845 | tile->clear(); 846 | tileslice->clear(); 847 | } 848 | tmpcloud->clear(); 849 | } 850 | return dem; 851 | } 852 | 853 | void correctStem(const pcl::PointCloud::Ptr &stem, float nnearest, float zstart, float zstep, float stepcovmax, float radchangemin, pcl::PointCloud::Ptr &corrected) 854 | { 855 | Eigen::Vector4f min, max; 856 | pcl::getMinMax3D(*stem,min,max); 857 | float zstop; 858 | bool broken = false; 859 | for(float z=min[2]+zstart;z::Ptr slice(new pcl::PointCloud); 862 | spatial1DFilter(stem,"z",z,z+zstep,slice); 863 | std::vector circle = fitCircle(slice,nnearest); 864 | Eigen::Vector4f zmin,zmax; 865 | pcl::getMinMax3D(*slice,zmin,zmax); 866 | int steps = 5; 867 | float dz = (zmax[2]-zmin[2])/float(steps); 868 | std::vector zrad; 869 | for(int i=0;i::Ptr zslice(new pcl::PointCloud); 872 | spatial1DFilter(slice,"z",z+i*dz,z+(i+1)*dz,zslice); 873 | if(zslice->points.size() > 10) 874 | { 875 | std::vector zcircle = fitCircle(zslice,nnearest); 876 | zrad.push_back(zcircle[2]); 877 | } 878 | } 879 | float sum = std::accumulate(zrad.begin(),zrad.end(),0.0); 880 | float mean = sum/zrad.size(); 881 | std::vector diff(zrad.size()); 882 | std::transform(zrad.begin(),zrad.end(),diff.begin(),std::bind2nd(std::minus(),mean)); 883 | float stdev = std::sqrt(std::inner_product(diff.begin(),diff.end(),diff.begin(),0.0) / zrad.size()); 884 | float cov = stdev / mean; 885 | float radchange = std::min(circle[2],mean) / std::max(circle[2],mean); 886 | //std::cout << circle[2] << " " << mean << " " << cov << " " << radchange << std::endl; 887 | if(cov > stepcovmax || radchange < radchangemin) 888 | { 889 | zstop = z-zstep*1.5; 890 | broken = true; 891 | break; 892 | } 893 | } 894 | if(broken == true) spatial1DFilter(stem,"z",min[2],zstop,corrected); 895 | else spatial1DFilter(stem,"z",min[2],max[2]-zstep,corrected); 896 | } 897 | 898 | void removeFarRegions(float dmin, std::vector::Ptr> ®ions) 899 | { 900 | std::vector> kdtrees; 901 | for(int i=0;i tree; 904 | tree.setInputCloud(regions[i]); 905 | kdtrees.push_back(tree); 906 | } 907 | int principalidx = findPrincipalCloudIdx(regions); 908 | Eigen::Vector4f pmin,pmax; 909 | pcl::getMinMax3D(*regions[principalidx],pmin,pmax); 910 | int mincount = static_cast(static_cast(regions[principalidx]->points.size() * 0.25)); 911 | std::vector remove_list; 912 | for(int i=0;ipoints.size() > mincount) 915 | { 916 | Eigen::Vector4f cmin,cmax; 917 | pcl::getMinMax3D(*regions[i],cmin,cmax); 918 | if(cmin[2] < pmax[2]) 919 | { 920 | float d = 0; 921 | if(regions[principalidx]->points.size() >= regions[i]->points.size()) d = minDistBetweenClouds(regions[principalidx],regions[i],kdtrees[principalidx]); 922 | else d = minDistBetweenClouds(regions[i],regions[principalidx],kdtrees[i]); 923 | if(d > dmin) remove_list.push_back(i); 924 | } 925 | } 926 | 927 | } 928 | std::sort(remove_list.begin(),remove_list.end(),std::greater()); 929 | for(int k=0;k::Ptr> ®ions, std::vector> &intersections, float expansion) 936 | { 937 | std::vector bbmin,bbmax; 938 | for(int i=0;i intersects; 954 | for(int j=0;j::Ptr> ®ions, int cyclecount, int firstcount, float firstdist, int nnearest, float seconddist, pcl::PointCloud::Ptr &tree) 964 | { 965 | std::vector unallocated; 966 | std::vector allocated; 967 | std::vector previouslyallocated; 968 | std::vector newlyallocated; 969 | std::vector> kdtrees; 970 | for(int i=0;i tree; 974 | tree.setInputCloud(regions[i]); 975 | kdtrees.push_back(tree); 976 | } 977 | std::vector> intersections; 978 | precalculateIntersections(regions,intersections,seconddist/2); 979 | std::cout << "." << std::flush; 980 | int idx = findPrincipalCloudIdx(regions); 981 | allocated.push_back(unallocated[idx]); 982 | previouslyallocated.push_back(allocated[0]); 983 | unallocated.erase(std::remove(unallocated.begin(),unallocated.end(),allocated[0]),unallocated.end()); 984 | bool donesomething = true; 985 | int count = 0; 986 | while(donesomething == true && count < cyclecount) 987 | { 988 | for(int i=0;i> dinfo; 991 | for(int j=0;j::max(); 994 | if(intersections[previouslyallocated[i]][unallocated[j]] == true) 995 | { 996 | if(regions[previouslyallocated[i]]->points.size() >= regions[unallocated[j]]->points.size()) 997 | { 998 | d = minDistBetweenClouds(regions[previouslyallocated[i]],regions[unallocated[j]],kdtrees[previouslyallocated[i]]); 999 | } 1000 | else 1001 | { 1002 | d = minDistBetweenClouds(regions[unallocated[j]],regions[previouslyallocated[i]],kdtrees[unallocated[j]]); 1003 | } 1004 | } 1005 | std::vector tmp; 1006 | tmp.push_back(unallocated[j]); 1007 | tmp.push_back(d); 1008 | dinfo.push_back(tmp); 1009 | } 1010 | if(dinfo.size() > 0) 1011 | { 1012 | std::sort(dinfo.begin(),dinfo.end(),sort2DFloatVectorByCol1); 1013 | if(count < firstcount) 1014 | { 1015 | for(int j=0;j(dinfo[j][0]); 1020 | newlyallocated.push_back(tmpidx); 1021 | } 1022 | } 1023 | } 1024 | else 1025 | { 1026 | for(int j=0;j(dinfo[j][0]); 1034 | newlyallocated.push_back(tmpidx); 1035 | } 1036 | } 1037 | } 1038 | } 1039 | } 1040 | } 1041 | previouslyallocated.clear(); 1042 | if(newlyallocated.size() > 0) 1043 | { 1044 | std::sort(newlyallocated.begin(),newlyallocated.end()); 1045 | newlyallocated.erase(std::unique(newlyallocated.begin(),newlyallocated.end()),newlyallocated.end()); 1046 | for(int i=0;i::Ptr &cloud, int nnearest, float zstep, float diffmax) 1062 | { 1063 | treeparams params; 1064 | Eigen::Vector4f min,max; 1065 | pcl::getMinMax3D(*cloud,min,max); 1066 | float z = min[2] + 1.3; 1067 | int i=0; 1068 | bool stable = false; 1069 | while(stable == false) 1070 | { 1071 | if(z >= max[2]-zstep*1.5) break; 1072 | else 1073 | { 1074 | pcl::PointCloud::Ptr slice(new pcl::PointCloud); 1075 | pcl::PointCloud::Ptr bslice(new pcl::PointCloud); 1076 | pcl::PointCloud::Ptr fslice(new pcl::PointCloud); 1077 | spatial1DFilter(cloud,"z",z-zstep/2,z+zstep/2,slice); 1078 | spatial1DFilter(cloud,"z",z-zstep*1.5,z-zstep/2,bslice); 1079 | spatial1DFilter(cloud,"z",z+zstep/2,z+zstep*1.5,fslice); 1080 | if(i == 0) 1081 | { 1082 | Eigen::Vector4f smin,smax; 1083 | pcl::getMinMax3D(*slice,smin,smax); 1084 | params.x = (smax[0] + smin[0]) / 2; 1085 | params.y = (smax[1] + smin[1]) / 2; 1086 | params.h = max[2]-min[2]; 1087 | params.c = sqrt(pow(max[0]-min[0],2)+pow(max[1]-min[1],2)); 1088 | } 1089 | cylinder cyl,bcyl,fcyl; 1090 | fitCylinder(slice,nnearest,false,true,cyl); 1091 | fitCylinder(bslice,nnearest,false,false,bcyl); 1092 | fitCylinder(fslice,nnearest,false,false,fcyl); 1093 | float d = (cyl.rad + bcyl.rad + fcyl.rad) / 3 * 2; 1094 | float bdiff = fabs(cyl.rad - bcyl.rad) / cyl.rad; 1095 | float fdiff = fabs(cyl.rad - fcyl.rad) / cyl.rad; 1096 | float diff = (bdiff + fdiff) / 2; 1097 | if(cyl.ismodel == true && diff <= diffmax) 1098 | { 1099 | params.d = d; 1100 | stable = true; 1101 | } 1102 | } 1103 | z += 0.1; 1104 | i++; 1105 | } 1106 | return params; 1107 | } 1108 | -------------------------------------------------------------------------------- /src/txtPointTreeseg2pcd.cpp: -------------------------------------------------------------------------------- 1 | #include "treeseg.h" 2 | 3 | #include 4 | 5 | int main (int argc, char **argv) 6 | { 7 | std::vector args(argv+1,argv+argc); 8 | pcl::PCDReader reader; 9 | pcl::PCDWriter writer; 10 | for(int i=0;i tmp1,tmp2; 14 | boost::split(tmp1,args[i],boost::is_any_of("/")); 15 | boost::split(tmp2,tmp1[tmp1.size()-1],boost::is_any_of(".")); 16 | for(int j=0;j::Ptr cloud(new pcl::PointCloud); 20 | #if XYZRRDRS == false 21 | float x,y,z; 22 | while(infile >> x >> y >> z) 23 | { 24 | PointTreeseg point; 25 | point.x = x; 26 | point.y = y; 27 | point.z = z; 28 | cloud->insert(cloud->end(),point); 29 | } 30 | #else 31 | float x,y,z,range,reflectance,deviation,return_number,scan_number; 32 | while(infile >> x >> y >> z >> range >> reflectance >> deviation >> return_number >> scan_number) 33 | { 34 | PointTreeseg point; 35 | point.x = x; 36 | point.y = y; 37 | point.z = z; 38 | point.range = range; 39 | point.reflectance = reflectance; 40 | point.deviation = std::uint16_t(deviation); 41 | point.return_number = std::uint16_t(return_number); 42 | point.scan_number = std::uint16_t(scan_number); 43 | cloud->insert(cloud->end(),point); 44 | } 45 | #endif 46 | infile.close(); 47 | writer.write(ss.str(),*cloud,true); 48 | } 49 | return 0; 50 | } 51 | --------------------------------------------------------------------------------