Skip to content

Commit aa001fb

Browse files
committed
Copying Tom's ideas
£bla
1 parent 3b0a861 commit aa001fb

File tree

4 files changed

+100
-86
lines changed

4 files changed

+100
-86
lines changed

raycloudtools/rayextract/rayextract.cpp

+43-34
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
// Author: Thomas Lowe
66
#include "raylib/extraction/rayclusters.h"
77
#include "raylib/extraction/rayforest.h"
8+
#include "raylib/extraction/rayleaves.h"
89
#include "raylib/extraction/rayterrain.h"
910
#include "raylib/extraction/raytrees.h"
1011
#include "raylib/extraction/raytrunks.h"
11-
#include "raylib/extraction/rayleaves.h"
1212
#include "raylib/raycloud.h"
1313
#include "raylib/rayforestgen.h"
1414
#include "raylib/rayforeststructure.h"
@@ -25,7 +25,8 @@ static std::string extract_type;
2525

2626
void usage(int exit_code = 1)
2727
{
28-
const bool none = extract_type != "terrain" && extract_type != "trunks" && extract_type != "forest" && extract_type != "trees" && extract_type != "leaves";
28+
const bool none = extract_type != "terrain" && extract_type != "trunks" && extract_type != "forest" &&
29+
extract_type != "trees" && extract_type != "leaves";
2930
// clang-format off
3031
std::cout << "Extract natural features into a text file or mesh file" << std::endl;
3132
std::cout << "usage:" << std::endl;
@@ -54,21 +55,22 @@ void usage(int exit_code = 1)
5455
std::cout << " --max_diameter 0.9 - (-m) maximum trunk diameter in segmenting trees" << std::endl;
5556
std::cout << " --crop_length 1.0 - (-p) crops small branches to this distance from end" << std::endl;
5657
std::cout << " --distance_limit 1 - (-d) maximum distance between neighbour points in a tree" << std::endl;
57-
std::cout << " --height_min 2 - (-h) minimum height counted as a tree" << std::endl;
58-
std::cout << " --min_radius 0.01 - (-r) minimum tree radius to consider" << std::endl;
58+
std::cout << " --height_min 2 - (-h) minimum height tree to reconstruct" << std::endl;
59+
std::cout << " --radius_min 0 - (-r) minimum radius tree to reconstruct" << std::endl;
5960
std::cout << " --girth_height_ratio 0.12 - (-i) the amount up tree's height to estimate trunk girth" << std::endl;
6061
std::cout << " --global_taper 0.024 - (-a) force a taper value (diameter per length) for trees under global_taper_factor of max tree height. Use 0 to estimate global taper from the data" << std::endl;
6162
std::cout << " --global_taper_factor 0.3- (-o) 1 estimates same taper for whole scan, 0 is per-tree tapering. Like a soft cutoff at this amount of max tree height" << std::endl;
6263
std::cout << " --gravity_factor 0.3 - (-f) larger values preference vertical trees" << std::endl;
6364
std::cout << " --branch_segmentation- (-b) _segmented.ply is per branch segment" << std::endl;
6465
std::cout << " --grid_width 10 - (-w) crops results assuming cloud has been gridded with given width" << std::endl;
65-
std::cout << " --grid_origin 0,0,0 - location of origin within grid cell that overlaps it. Defaults to a cell-centre origin (at grid_width/2 in each axis) matching raysplit grid. 0,0,0 is for a vertex origin." << std::endl;
6666
std::cout << " --use_rays - (-u) use rays to reduce trunk radius overestimation in noisy cloud data" << std::endl;
67-
std::cout << " (for internal constants -c -g -s see source file rayextract)" << std::endl;
67+
std::cout << " (for internal constants -c -g -s -d see source file rayextract)" << std::endl;
6868
// These are the internal parameters that I don't expose as they are 'advanced' only, you shouldn't need to adjust them
6969
// std::cout << " --cylinder_length_to_width 4- (-c) how slender the cylinders are" << std::endl;
7070
// std::cout << " --gap_ratio 0.016 - (-g) will split for lateral gaps at this multiple of branch length" << std::endl;
7171
// std::cout << " --span_ratio 4.5 - (-s) will split when branch width spans this multiple of radius" << std::endl;
72+
// std::cout << " --grid_origin 0,0 - (-d) location of grid corner (any of them) when grid_width used, use 0,0 for grid with vertex at 0,0.
73+
// Default is -grid_width/2,-grid_width/2 to match the grid in raysplit grid" << std::endl;
7274
}
7375
if (extract_type == "leaves" || none)
7476
{
@@ -94,33 +96,33 @@ int rayExtract(int argc, char *argv[])
9496
}
9597
ray::FileArgument cloud_file, mesh_file, trunks_file, trees_file, leaf_file;
9698
ray::TextArgument forest("forest"), trees("trees"), trunks("trunks"), terrain("terrain"), leaves("leaves");
97-
98-
ray::Vector3dArgument grid_origin;
99-
ray::OptionalKeyValueArgument grid_origin_option("grid_origin", &grid_origin);
10099
ray::OptionalKeyValueArgument groundmesh_option("ground", 'g', &mesh_file);
101100
ray::OptionalKeyValueArgument trunks_option("trunks", 't', &trunks_file);
102101
ray::DoubleArgument gradient(0.001, 1000.0, 1.0), global_taper(0.0, 1.0), global_taper_factor(0.0, 1.0);
103102
ray::OptionalKeyValueArgument gradient_option("gradient", 'g', &gradient);
104-
ray::OptionalFlagArgument exclude_rays("exclude_rays", 'e'), segment_branches("branch_segmentation", 'b'), stalks("stalks", 's'), use_rays("use_rays", 'u');
103+
ray::OptionalFlagArgument exclude_rays("exclude_rays", 'e'), segment_branches("branch_segmentation", 'b'),
104+
stalks("stalks", 's'), use_rays("use_rays", 'u');
105105
ray::DoubleArgument width(0.01, 10.0, 0.25), drop(0.001, 1.0), max_gradient(0.01, 5.0), min_gradient(0.01, 5.0);
106106

107107
ray::DoubleArgument max_diameter(0.01, 100.0), distance_limit(0.01, 10.0), height_min(0.01, 1000.0),
108-
min_diameter(0.01, 100.0), leaf_area(0.00001, 1.0, 0.002), leaf_droop(0.0, 10.0, 0.1), crop_length(0.01, 100.0);;
109-
ray::DoubleArgument girth_height_ratio(0.001, 0.5), length_to_radius(0.01, 10000.0), cylinder_length_to_width(0.1, 20.0), gap_ratio(0.01, 10.0),
110-
span_ratio(0.01, 10.0), min_radius(0.01, 100.0);
111-
ray::DoubleArgument gravity_factor(0.0, 100.0), grid_width(1.0, 100000.0),
112-
grid_overlap(0.0, 0.9);
108+
radius_min(0.0, 1000.0), min_diameter(0.01, 100.0), leaf_area(0.00001, 1.0, 0.002), leaf_droop(0.0, 10.0, 0.1),
109+
crop_length(0.01, 100.0);
110+
;
111+
ray::DoubleArgument girth_height_ratio(0.001, 0.5), length_to_radius(0.01, 10000.0),
112+
cylinder_length_to_width(0.1, 20.0), gap_ratio(0.01, 10.0), span_ratio(0.01, 10.0);
113+
ray::DoubleArgument gravity_factor(0.0, 100.0), grid_width(1.0, 100000.0), grid_overlap(0.0, 0.9);
114+
ray::Vector2dArgument grid_origin(-1e10, 1e10);
113115
ray::OptionalKeyValueArgument max_diameter_option("max_diameter", 'm', &max_diameter);
114116
ray::OptionalKeyValueArgument crop_length_option("crop_length", 'n', &crop_length);
115117
ray::OptionalKeyValueArgument distance_limit_option("distance_limit", 'd', &distance_limit);
116118
ray::OptionalKeyValueArgument height_min_option("height_min", 'h', &height_min);
119+
ray::OptionalKeyValueArgument radius_min_option("radius_min", 'r', &radius_min);
117120
ray::OptionalKeyValueArgument girth_height_ratio_option("girth_height_ratio", 'i', &girth_height_ratio);
118121
ray::OptionalKeyValueArgument cylinder_length_to_width_option("cylinder_length_to_width", 'c',
119122
&cylinder_length_to_width);
120-
121-
ray::OptionalKeyValueArgument min_radius_option("min_radius", 'r', &min_radius);
122123
ray::OptionalKeyValueArgument gap_ratio_option("gap_ratio", 'g', &gap_ratio);
123124
ray::OptionalKeyValueArgument span_ratio_option("span_ratio", 's', &span_ratio);
125+
ray::OptionalKeyValueArgument grid_origin_option("grid_origin", 'd', &grid_origin);
124126
ray::OptionalKeyValueArgument gravity_factor_option("gravity_factor", 'f', &gravity_factor);
125127
ray::OptionalKeyValueArgument grid_width_option("grid_width", 'w', &grid_width);
126128
ray::OptionalKeyValueArgument global_taper_option("global_taper", 'a', &global_taper);
@@ -142,11 +144,12 @@ int rayExtract(int argc, char *argv[])
142144
{ &groundmesh_option, &trunks_option, &width_option, &smooth_option, &drop_option, &verbose });
143145
bool extract_trees = ray::parseCommandLine(
144146
argc, argv, { &trees, &cloud_file, &mesh_file },
145-
{ &max_diameter_option, &distance_limit_option, &height_min_option, &crop_length_option, &girth_height_ratio_option,
146-
&cylinder_length_to_width_option, &gap_ratio_option, &span_ratio_option, &gravity_factor_option,
147-
&segment_branches, &grid_width_option, &global_taper_option, &global_taper_factor_option, &use_rays, &verbose,
148-
&grid_origin_option, &min_radius_option });
149-
bool extract_leaves = ray::parseCommandLine(argc, argv, { &leaves, &cloud_file, &trees_file }, { &leaf_option, &leaf_area_option, &leaf_droop_option, &stalks });
147+
{ &max_diameter_option, &distance_limit_option, &height_min_option, &radius_min_option, &crop_length_option,
148+
&girth_height_ratio_option, &cylinder_length_to_width_option, &gap_ratio_option, &span_ratio_option,
149+
&grid_origin_option, &gravity_factor_option, &segment_branches, &grid_width_option, &global_taper_option,
150+
&global_taper_factor_option, &use_rays, &verbose });
151+
bool extract_leaves = ray::parseCommandLine(argc, argv, { &leaves, &cloud_file, &trees_file },
152+
{ &leaf_option, &leaf_area_option, &leaf_droop_option, &stalks });
150153

151154

152155
if (!extract_trunks && !extract_forest && !extract_terrain && !extract_trees && !extract_leaves)
@@ -199,6 +202,10 @@ int rayExtract(int argc, char *argv[])
199202
{
200203
params.height_min = height_min.value();
201204
}
205+
if (radius_min_option.isSet())
206+
{
207+
params.radius_min = radius_min.value();
208+
}
202209
if (crop_length_option.isSet())
203210
{
204211
params.crop_length = crop_length.value();
@@ -226,6 +233,12 @@ int rayExtract(int argc, char *argv[])
226233
if (grid_width_option.isSet())
227234
{
228235
params.grid_width = grid_width.value();
236+
params.grid_origin =
237+
-Eigen::Vector2d(grid_width.value(), grid_width.value()) / 2.0; // the centred grid used by raysplit grid
238+
}
239+
if (grid_origin_option.isSet())
240+
{
241+
params.grid_origin = grid_origin.value();
229242
}
230243
if (global_taper_option.isSet())
231244
{
@@ -235,14 +248,6 @@ int rayExtract(int argc, char *argv[])
235248
{
236249
params.global_taper_factor = global_taper_factor.value();
237250
}
238-
if (grid_origin_option.isSet())
239-
{
240-
params.grid_origin = grid_origin.value();
241-
}
242-
if (min_radius_option.isSet())
243-
{
244-
params.min_radius = min_radius.value();
245-
}
246251
params.use_rays = use_rays.isSet();
247252
params.segment_branches = segment_branches.isSet();
248253

@@ -259,11 +264,15 @@ int rayExtract(int argc, char *argv[])
259264
ray::ForestStructure forest;
260265
if (!forest.load(cloud_file.nameStub() + "_trees.txt"))
261266
{
262-
usage();
267+
std::cerr << "Unable to load "
268+
<< cloud_file.nameStub() +
269+
"_trees.txt to generate tree mesh file, this could mean that there were no trees output"
270+
<< std::endl;
271+
exit(true);
263272
}
264273
ray::Mesh tree_mesh;
265274
forest.generateSmoothMesh(tree_mesh, -1, 1, 1, 1);
266-
ray::writePlyMesh(cloud_file.nameStub() + "_trees_mesh.ply", tree_mesh, true);
275+
ray::writePlyMesh(cloud_file.nameStub() + "_trees_mesh.ply", tree_mesh, true);
267276
}
268277
// extract the tree locations from a larger, aerial view of a forest
269278
else if (extract_forest)
@@ -321,8 +330,8 @@ int rayExtract(int argc, char *argv[])
321330
}
322331
else if (extract_leaves)
323332
{
324-
ray::generateLeaves(cloud_file.nameStub(), trees_file.name(), leaf_file.name(),
325-
leaf_area.value(), leaf_droop.value(), stalks.isSet());
333+
ray::generateLeaves(cloud_file.nameStub(), trees_file.name(), leaf_file.name(), leaf_area.value(),
334+
leaf_droop.value(), stalks.isSet());
326335
}
327336
else
328337
{

raylib/extraction/raytrees.cpp

+50-22
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
#include <nabo/nabo.h>
88
#include "rayclusters.h"
99

10+
#include <unordered_set>
11+
1012
namespace ray
1113
{
1214
TreesParams::TreesParams()
1315
: max_diameter(0.9)
1416
, crop_length(1.0)
1517
, distance_limit(1.0)
1618
, height_min(2.0)
19+
, radius_min(0.0)
1720
, girth_height_ratio(0.12)
1821
, cylinder_length_to_width(4.0)
1922
, gap_ratio(0.016)
@@ -23,7 +26,7 @@ TreesParams::TreesParams()
2326
, segment_branches(false)
2427
, global_taper(0.012)
2528
, global_taper_factor(0.3)
26-
, grid_origin(0, 0, 0)
29+
, grid_origin(0, 0)
2730
{}
2831

2932
/// The main reconstruction algorithm
@@ -148,7 +151,7 @@ Trees::Trees(Cloud &cloud, const Eigen::Vector3d &offset, const Mesh &mesh, cons
148151
continue;
149152
}
150153

151-
if (params_->min_radius && estimated_radius < params_->min_radius)
154+
if (params_->radius_min && estimated_radius < params_->radius_min)
152155
{
153156
sections_[sec_].tip = base + Eigen::Vector3d(0, 0, 0.01);
154157
sections_[sec_].total_weight = 1e-10;
@@ -300,9 +303,9 @@ Trees::Trees(Cloud &cloud, const Eigen::Vector3d &offset, const Mesh &mesh, cons
300303
removeOutOfBoundSections(cloud, min_bound, max_bound, offset);
301304
}
302305

303-
if (params_->min_radius)
306+
if (params_->radius_min)
304307
{
305-
removeSmallRadiusTrees();
308+
removeSmallRadiusTrees(verbose, offset, children);
306309
}
307310

308311
std::vector<int> root_segs(cloud.ends.size(), -1);
@@ -1082,14 +1085,19 @@ void Trees::removeOutOfBoundSections(const Cloud &cloud, Eigen::Vector3d &min_bo
10821085
{
10831086
const double width = params_->grid_width;
10841087
cloud.calcBounds(&min_bound, &max_bound);
1085-
const Eigen::Vector3d mid = (min_bound + max_bound) / 2.0 + offset + params_->grid_origin;
1086-
const Eigen::Vector2d inds(std::round(mid[0] / width), std::round(mid[1] / width));
1087-
min_bound[0] = width * (inds[0] - 0.5) - offset[0];
1088-
min_bound[1] = width * (inds[1] - 0.5) - offset[1];
1089-
max_bound[0] = width * (inds[0] + 0.5) - offset[0];
1090-
max_bound[1] = width * (inds[1] + 0.5) - offset[1];
1091-
std::cerr << "min bound: " << (min_bound + offset).transpose() << ", max bound: " << (max_bound + offset).transpose()
1092-
<< " offset " << offset << std::endl;
1088+
const Eigen::Vector3d mid = (min_bound + max_bound) / 2.0 + offset;
1089+
Eigen::Vector2d mid_local(mid[0], mid[1]);
1090+
mid_local -= params_->grid_origin;
1091+
const Eigen::Vector2d inds(std::floor(mid_local[0] / width), std::floor(mid_local[1] / width));
1092+
min_bound[0] = width * (inds[0]) + params_->grid_origin[0] - offset[0];
1093+
min_bound[1] = width * (inds[1]) + params_->grid_origin[1] - offset[1];
1094+
max_bound[0] = width * (inds[0] + 1.0) + params_->grid_origin[0] - offset[0];
1095+
max_bound[1] = width * (inds[1] + 1.0) + params_->grid_origin[1] - offset[1];
1096+
1097+
std::cout << "min bound: " << (min_bound + offset).transpose() << ", max bound: " << (max_bound + offset).transpose()
1098+
<< std::endl;
1099+
1100+
int number_of_trees_outside = 0;
10931101

10941102
// disable trees out of bounds
10951103
for (auto &section : sections_)
@@ -1102,8 +1110,10 @@ void Trees::removeOutOfBoundSections(const Cloud &cloud, Eigen::Vector3d &min_bo
11021110
if (pos[0] < min_bound[0] || pos[0] > max_bound[0] || pos[1] < min_bound[1] || pos[1] > max_bound[1])
11031111
{
11041112
section.children.clear(); // make it a non-tree
1113+
number_of_trees_outside++;
11051114
}
11061115
}
1116+
std::cerr << "Number of trees outside of section: " << number_of_trees_outside << std::endl;
11071117
}
11081118

11091119
// colour the cloud by tree id, or by branch segment id
@@ -1269,11 +1279,19 @@ bool Trees::save(const std::string &filename, const Eigen::Vector3d &offset, boo
12691279
return true;
12701280
}
12711281

1272-
void Trees::removeSmallRadiusTrees()
1282+
void Trees::removeSmallRadiusTrees(bool verbose, const Eigen::Vector3d &offset,
1283+
const std::vector<std::vector<int>> &children)
12731284
{
1285+
std::cerr << "Number of section " << sections_.size() << std::endl;
1286+
ray::Cloud debug_cloud;
12741287
for (sec_ = 0; sec_ < (int)sections_.size(); sec_++)
12751288
{
1276-
std::cerr << "------- processing " << sec_ << "th section" << std::endl;
1289+
if (sections_[sec_].parent >= 0 || sections_[sec_].children.empty())
1290+
{
1291+
continue;
1292+
}
1293+
if (verbose)
1294+
std::cerr << "------- processing " << sec_ << "th section" << std::endl;
12771295
double best_accuracy = -1.0;
12781296
std::vector<int> nodes; // used
12791297
Eigen::Vector3d base = getRootPosition(); // used
@@ -1301,7 +1319,7 @@ void Trees::removeSmallRadiusTrees()
13011319
if (verbose)
13021320
{
13031321
// choose random color for the section
1304-
1322+
ray::RGBA section_color(0, 0, 0, 255);
13051323
for (auto &node : nodes)
13061324
{
13071325
debug_cloud.addRay(Eigen::Vector3d(0, 0, 0), points_[node].pos, 0.0, section_color);
@@ -1324,9 +1342,11 @@ void Trees::removeSmallRadiusTrees()
13241342
double circle_coverage = 0;
13251343
Eigen::Vector3d circle_centre;
13261344
double radius = estimateCylinderRadiusUsingRANSAC(nodes, up, accuracy, circle_coverage, circle_centre);
1327-
1328-
std::cerr << sec_ << "Estimated radius " << radius << " and accuracy " << accuracy << " and coverage "
1329-
<< circle_coverage << " j " << j << std::endl;
1345+
if (verbose)
1346+
{
1347+
std::cerr << sec_ << "Estimated radius " << radius << " and accuracy " << accuracy << " and coverage "
1348+
<< circle_coverage << " j " << j << std::endl;
1349+
}
13301350
if (accuracy > 0.4 && circle_coverage > 0.5)
13311351
{
13321352
radius_estimates.push_back(radius);
@@ -1351,20 +1371,27 @@ void Trees::removeSmallRadiusTrees()
13511371
std::accumulate(radius_estimates.begin(), radius_estimates.end(), 0.0) / radius_estimates.size();
13521372
double accuracy_average =
13531373
std::accumulate(accuracy_estimates.begin(), accuracy_estimates.end(), 0.0) / accuracy_estimates.size();
1354-
1355-
std::cerr << sec_ << "radius_average " << radius_average << std::endl;
1374+
if (verbose)
1375+
std::cerr << sec_ << "radius_average " << radius_average << std::endl;
13561376
if (radius_average < 0.05 && accuracy_average > 0.4)
13571377
{
1358-
std::cerr << "Removing tree with average radius " << radius_average << " sec " << sec_ << std::endl;
1378+
if (verbose)
1379+
std::cerr << "Removing tree with average radius " << radius_average << " sec " << sec_ << std::endl;
13591380
sections_[sec_].children.clear();
13601381
continue;
13611382
}
13621383
}
13631384
}
1385+
if (verbose)
1386+
{
1387+
debug_cloud.translate(offset);
1388+
debug_cloud.save("../data/debug.ply");
1389+
}
13641390
}
13651391

13661392
double Trees::estimateCylinderRadiusUsingRANSAC(const std::vector<int> &nodes, const Eigen::Vector3d &dir,
1367-
double &accuracy, double &circle_coverage)
1393+
double &accuracy, double &circle_coverage,
1394+
Eigen::Vector3d &circle_centre)
13681395
{
13691396
double best_rad = 0.0;
13701397
double best_accuracy = 0.0;
@@ -1456,6 +1483,7 @@ double Trees::estimateCylinderRadiusUsingRANSAC(const std::vector<int> &nodes, c
14561483
best_rad = avg_radius;
14571484
best_accuracy = static_cast<double>(inliers.size()) / nodes.size();
14581485
circle_coverage = coverage;
1486+
circle_centre = centroid;
14591487
}
14601488
}
14611489

0 commit comments

Comments
 (0)