diff --git a/CMakeLists.txt b/CMakeLists.txt index 24cc12db0fe8cc6fd480995877afc40ecf7b4118..895a37d7b144faa8266e10e54f157cd2b038a5d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,25 +11,25 @@ if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) endif() #Set compiler according C++11 support -include(CheckCXXCompilerFlag) -CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) -CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) -if(COMPILER_SUPPORTS_CXX11) - message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has C++11 support.") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") -elseif(COMPILER_SUPPORTS_CXX0X) - message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has C++0x support.") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") -else() - message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.") -endif() +#include(CheckCXXCompilerFlag) +#CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) +#CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) +#if(COMPILER_SUPPORTS_CXX11) +# message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has C++11 support.") +# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +#elseif(COMPILER_SUPPORTS_CXX0X) +# message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has C++0x support.") +# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") +#else() +# message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.") +#endif() ## Find catkin macros and libraries find_package(catkin REQUIRED) # ******************************************************************** # Add catkin additional components here # ******************************************************************** -find_package(catkin REQUIRED COMPONENTS iri_base_algorithm message_generation) +find_package(catkin REQUIRED COMPONENTS iri_base_algorithm message_generation cv_bridge) ## System dependencies are found with CMake's conventions # find_package(Boost REQUIRED COMPONENTS system) @@ -88,7 +88,7 @@ catkin_package( # ******************************************************************** # Add ROS and IRI ROS run time dependencies # ******************************************************************** - CATKIN_DEPENDS iri_base_algorithm std_msgs message_runtime + CATKIN_DEPENDS iri_base_algorithm std_msgs message_runtime # ******************************************************************** # Add system and labrobotica run time dependencies here # ******************************************************************** diff --git a/cfg/GroundSegmentation.cfg b/cfg/GroundSegmentation.cfg index 54fa04ee05b9ab35998e02ae792bc5c97828341b..6505d99ad9a14d68729b4a94ddec1e317fb56a22 100644 --- a/cfg/GroundSegmentation.cfg +++ b/cfg/GroundSegmentation.cfg @@ -46,8 +46,9 @@ gen.add("robot_height", double_t, 0, "All obstacle points above this value (w.r. # Parameters affecting the pointcloud exploration gen.add("ROI_delta_x_and_y", double_t, 0, "ROI area = ( 2 * ROI_delta_x_and_y ) ^ 2", 2.0, 0.0, 10.0); +gen.add("ROI_shadow_area", double_t, 0, "ROI for root vertex in shadow area = ( 2 * ROI_delta_x_and_y ) ^ 2", 6.0, 0.0, 10.0); gen.add("ground_reference_search_resolution_deg", double_t, 0, "It is the angular resolution when generating new ground references (graph nodes) in degrees", 20.0, 1.0, 100.0); -gen.add("elevation_grid_resolution", double_t, 0, "define the coarseness of the elevation points", 0.5, 0.01, 10.0); +gen.add("elevation_grid_resolution", double_t, 0, "define the coarseness of the elevation points", 0.5, 0.2, 10.0); # Kalman filter noise parameters ## initial uncertainties @@ -62,11 +63,20 @@ gen.add("z_observation_std_dev", double_t, 0, "Measurement std_dev", 0.2, 0.0, 1 # threshold for outlier rejection and classification gen.add("mahalanobis_threshold", double_t, 0, "Max mahalanobis distance for ground points", 3.0, 0.01, 6.0); +# Neural Network related parameters +gen.add("use_neural_network", bool_t, 0, "uses neural network to segmentate ground points attending to intensity related features", False); +gen.add("extract_data_to_external_training_of_the_network", bool_t, 0, "Extract features to external file to train with Matlab", False); +gen.add("dataset_filename_with_global_path", str_t, 0, "name of the file where dump the features for neural net training in matlab", ""); +gen.add("neural_net_filename_with_global_path", str_t, 0, "name of the file containing the neural net weights", ""); +gen.add("neural_net_number_of_features", int_t, 0, "Size of the input layer", 9, 1, 100); +gen.add("neural_net_number_of_neurons_in_hidden_layer", int_t, 0, "Size of the hidden layer", 25, 1, 100); +gen.add("neural_net_number_of_neurons_in_output_layer", int_t, 0, "Size of the output layer", 2, 1, 100); + # labeling parameters -gen.add("number_of_references_used_for_labelling", int_t, 0, "used to evaluate each elevation point cloud from different POVs", 4, 1, 100); gen.add("max_pred_std_dev_for_labelling", double_t, 0, "To give up trying to label ground points if we don't have enough confidence in our predictions", 0.3, 0.01, 1.0); +gen.add("score_threshold", double_t, 0, "for assigning ground class label: one means maha. dist. equal to zero, zero means mahalanobis dist equal to maha. thres", 0.8, 0.0, 1.0); gen.add("classify_not_labeled_points_as_obstacles", bool_t, 0, "when a point has no reference satisfying the max_pred_std_dev_for_labelling threshold we can leave as unknown or label it as obstacle", True); -gen.add("discard_not_connected_references_for_labelling", bool_t, 0, "to exclude not connected vertices in the roadmap from the labelling step", True); +gen.add("ground_threshold_in_not_analyzed_areas", double_t, 0, "when it is not possible to make a local analysis of the data, we will use the lowest point (the one in elevation_cloud) as ground height, and all the points above it and below this threshold will be classified as ground", 0.3, 0.01, 1.0); # visualization and debug parameters gen.add("measure_performance", bool_t, 0, "to measure times of inner functions", False); diff --git a/include/aos.hpp b/include/aos.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d34f9ec9800af6b0ccdbec8357d04f126812b6ad --- /dev/null +++ b/include/aos.hpp @@ -0,0 +1,701 @@ +#ifndef TRAVEL_OSEG_H +#define TRAVEL_OSEG_H +// +// Created by Minho Oh & Euigon Jung on 1/31/22. +// We really appreciate Hyungtae Lim and Prof. Hyun Myung! :) +// +#pragma once +#include <vector> +#include <algorithm> +#include <iostream> +#include <list> +#include <numeric> +#include <random> +#include <chrono> +#include <forward_list> +#include <boost/optional.hpp> + +#include <ros/ros.h> +#include <pcl/point_cloud.h> +#include <pcl/point_types.h> +#include <pcl/common/io.h> + +using namespace std; + +namespace travel { + + class AOSNode { + public: + uint start; + uint end; + int label; + + AOSNode() : label(-1) {} + }; + + class Point { + public: + float x,y,z; + uint idx; + bool valid = false; + int label; + }; + + template <typename T> + class ObjectCluster { + private: + uint16_t max_label_; + vector<forward_list<Point*>> clusters_; + forward_list<Point*> empty_; + vector<int> valid_cnt_; + vector<vector<AOSNode>> nodes_; + vector<float> vert_angles_; + vector<vector<Point>> range_mat_; + vector<vector<Point>> emptyrange_mat_; + + // parameters + int VERT_SCAN; + int HORZ_SCAN; + float MIN_RANGE; + float MAX_RANGE; + float HORZ_MERGE_THRES; + float VERT_MERGE_THRES; + int VERT_SCAN_SIZE; + int HORZ_SCAN_SIZE; + int HORZ_SKIP_SIZE; + int HORZ_EXTENSION_SIZE; + int DOWNSAMPLE; + float MIN_VERT_ANGLE; + float MAX_VERT_ANGLE; + boost::optional<int> MIN_CLUSTER_SIZE; + boost::optional<int> MAX_CLUSTER_SIZE; + + // test + // vector<vector<vector<std::pair<float, float>>>> point_angle_; + + public: + ObjectCluster() { + empty_ = {}; + } + + ~ObjectCluster() {} + + void setParams(int _vert_scan, int _horz_scan, float _min_range, float _max_range, + float _min_vert_angle, float _max_vert_angle, + float _horz_merge_thres, float _vert_merge_thres, int _vert_scan_size, + int _horz_scan_size, int _horz_extension_size, int _horz_skip_size, + int _downsample, + boost::optional<int> _min_cluster_size=boost::none, + boost::optional<int> _max_cluster_size=boost::none) { + + std::cout<<""<<std::endl; + ROS_WARN("Set ObjectSeg Parameters"); + + VERT_SCAN = _vert_scan; + ROS_WARN("vert scan: %d", VERT_SCAN); + + HORZ_SCAN = _horz_scan; + ROS_WARN("horz scan: %d", HORZ_SCAN); + + MIN_RANGE = _min_range; + ROS_WARN("min range: %f", MIN_RANGE); + + MAX_RANGE = _max_range; + ROS_WARN("max range: %f", MAX_RANGE); + + HORZ_MERGE_THRES = _horz_merge_thres; + ROS_WARN("horz merge thres: %f", HORZ_MERGE_THRES); + + VERT_MERGE_THRES = _vert_merge_thres; + ROS_WARN("vert merge thres: %f", VERT_MERGE_THRES); + + VERT_SCAN_SIZE = _vert_scan_size; + ROS_WARN("vert scan size: %d", VERT_SCAN_SIZE); + + HORZ_SKIP_SIZE = _horz_skip_size; + ROS_WARN("horz skip size: %d", HORZ_SKIP_SIZE); + + HORZ_SCAN_SIZE = _horz_scan_size; + ROS_WARN("horz scan size: %d", HORZ_SCAN_SIZE); + + HORZ_EXTENSION_SIZE = _horz_extension_size; + ROS_WARN("horz extension size: %d", HORZ_EXTENSION_SIZE); + + DOWNSAMPLE = _downsample; + ROS_WARN("downsample: %d", DOWNSAMPLE); + + MAX_VERT_ANGLE = _max_vert_angle; + MIN_VERT_ANGLE = _min_vert_angle; + ROS_WARN("Max vertical angle : %f", MAX_VERT_ANGLE); + ROS_WARN("Min vertical angle : %f", MIN_VERT_ANGLE); + + float resolution = (float) (MAX_VERT_ANGLE - MIN_VERT_ANGLE) / (float)(VERT_SCAN-1); + ROS_WARN("Vertical resolution: %f", resolution); + + + for(int i = 0; i < _vert_scan; i++) + vert_angles_.push_back(_min_vert_angle + i*resolution); + + range_mat_.resize(VERT_SCAN, vector<Point>(HORZ_SCAN)); + + valid_cnt_.resize(VERT_SCAN, 0); + nodes_.resize(VERT_SCAN, vector<AOSNode>()); + + if (_min_cluster_size) + MIN_CLUSTER_SIZE = _min_cluster_size; + + if (_max_cluster_size) + MAX_CLUSTER_SIZE = _max_cluster_size; + } + + void segmentObjects(boost::shared_ptr<pcl::PointCloud<T>> cloud_in, + boost::shared_ptr<pcl::PointCloud<T>> cloud_out, + vector<float> *vert_angles=nullptr) { + // 0. reset + max_label_ = 1; + clusters_.clear(); + clusters_.push_back(empty_); + clusters_.push_back(empty_); + std::fill(valid_cnt_.begin(), valid_cnt_.end(), 0); + std::for_each(range_mat_.begin(), range_mat_.end(), [](vector<Point>& inner_vec) { + std::fill(inner_vec.begin(), inner_vec.end(), Point()); + }); + // 1. do spherical projection + auto start = chrono::steady_clock::now(); + sphericalProjection(cloud_in); + auto end = chrono::steady_clock::now(); + // cloud_out->points.resize(cloud_in->points.size()); + printf("Creating range map: %ld ms\n", chrono::duration_cast<chrono::milliseconds>(end-start).count()); + + // 2. begin clustering + start = chrono::steady_clock::now(); + for (int channel = 0; channel < VERT_SCAN; channel++) { + auto horz_start = chrono::steady_clock::now(); + horizontalUpdate(channel); + auto horz_end = chrono::steady_clock::now(); + auto vert_start = chrono::steady_clock::now(); + verticalUpdate(channel); + auto vert_end = chrono::steady_clock::now(); + // printf("Channel %d: pt count: %d, node count: %d, horz: %ld ms, vert: %ld ms\n", channel, + // valid_cnt_[channel], nodes_[channel].size(), + // chrono::duration_cast<chrono::milliseconds>(horz_end-horz_start).count(), + // chrono::duration_cast<chrono::milliseconds>(vert_end-vert_start).count()); + } + end = chrono::steady_clock::now(); + printf("Processing clustering: %ld ms\n", chrono::duration_cast<chrono::milliseconds>(end-start).count()); + + // 3. post-processing (clusters -> labels) + start = chrono::steady_clock::now(); + labelPointcloud(cloud_in, cloud_out); + end = chrono::steady_clock::now(); + printf("Post-processing: %ld ms\n", chrono::duration_cast<chrono::milliseconds>(end-start).count()); + } + + void sphericalProjection(boost::shared_ptr<pcl::PointCloud<T>> cloud_in) { + boost::shared_ptr<pcl::PointCloud<T>> valid_cloud = boost::make_shared<pcl::PointCloud<T>>(); + valid_cloud->points.reserve(cloud_in->points.size()); + int ring_idx = -1, row_idx = -1, col_idx = -1; + float range; + Point point; + int range_chcker = 0; + int valid_checker = 0; + int count_range, count_downsample, count_out_of_row, count_out_of_col, count_out_of_mat; + count_range = count_downsample = count_out_of_row = count_out_of_col = count_out_of_mat = 0; + int count_pass = 0; + for (size_t i = 0; i < cloud_in->points.size(); i++) { + + range = getRange(cloud_in->points[i]); + if (range < MIN_RANGE || range > MAX_RANGE){ + count_range++; + continue; + } + row_idx = getRowIdx(cloud_in->points[i]); + if (row_idx % DOWNSAMPLE != 0) { + count_downsample++; + continue; + } + if (row_idx < 0 || row_idx >= VERT_SCAN) { + count_out_of_row++; + continue; + } + col_idx = getColIdx(cloud_in->points[i]); + if (col_idx < 0 || col_idx >= HORZ_SCAN) { + count_out_of_col++; + continue; + } + if (range_mat_[row_idx][col_idx].valid){ + count_out_of_mat ++; + continue; + } else { + count_pass++; + point.x = cloud_in->points[i].x; + point.y = cloud_in->points[i].y; + point.z = cloud_in->points[i].z; + point.valid = true; + point.idx = valid_cloud->points.size(); + valid_cloud->points.push_back(cloud_in->points[i]); + range_mat_[row_idx][col_idx] = point; + assert(range_mat_[row_idx][col_idx].valid == true); + valid_cnt_[row_idx]++; + } + } + printf("Input cloud size: %d\n", (int)cloud_in->points.size()); + printf("Projected cloud size: %d\n", (int)valid_cloud->points.size()); + *cloud_in = *valid_cloud; + } + + void labelPointcloud(boost::shared_ptr<pcl::PointCloud<T>> cloud_in, + boost::shared_ptr<pcl::PointCloud<T>> cloud_out) { + uint cnt = 0; + uint pt_cnt = 0; + uint max = 2000; + cloud_out->points.reserve(cloud_in->points.size()); + + // generate random number + std::vector<size_t> valid_indices; + for (size_t i = 0; i < clusters_.size(); i++) { + if (std::distance(clusters_[i].begin(), clusters_[i].end()) > 0) { + valid_indices.push_back(i); + cnt++; + } + } + std::list<uint16_t> l(cnt); + std::iota(l.begin(), l.end(), 1); + std::vector<std::list<uint16_t>::iterator> v(l.size()); + std::iota(v.begin(), v.end(), l.begin()); + std::shuffle(v.begin(), v.end(), std::mt19937{std::random_device{}()}); + + for (size_t i = 0; i < valid_indices.size(); i++) { + uint16_t label = *v[i]; + size_t idx = valid_indices[i]; + int point_size = std::distance(clusters_[idx].begin(), clusters_[idx].end()); + if (MIN_CLUSTER_SIZE && MAX_CLUSTER_SIZE) { + if (point_size < MIN_CLUSTER_SIZE || point_size > MAX_CLUSTER_SIZE) + continue; + } + + for (auto &p : clusters_[idx]) { + if (p->valid) { + assert(cloud_in->points[p->idx].x == p->x); + assert(cloud_in->points[p->idx].y == p->y); + assert(cloud_in->points[p->idx].z == p->z); + T point = cloud_in->points[p->idx]; + point.intensity = label; + cloud_out->points.push_back(point); + pt_cnt++; + } else { + ROS_ERROR("point invalid"); + } + } + } + // assert(cloud_in->points.size() == cloud_out->points.size()); + printf("# of cluster: %d, point: %d\n", cnt, pt_cnt); + } + + void horizontalUpdate(int channel) { + nodes_[channel].clear(); + // no point in the channel + if (valid_cnt_[channel] <= 0){ + return; + } + + int first_valid_idx = -1; + int last_valid_idx = -1; + int start_pos = -1; + int pre_pos = -1; + int end_pos = -1; + + AOSNode node; + bool first_node = false; + for(int j = 0; j < HORZ_SCAN; j++) { + if (!range_mat_[channel][j].valid) + continue; + if (!first_node) { + first_node = true; + // update index + first_valid_idx = j; + start_pos = j; + pre_pos = j; + // update label + max_label_++; + clusters_.push_back(empty_); + range_mat_[channel][j].label = max_label_; + // push a new node + node.start = start_pos; + node.end = j; + node.label = range_mat_[channel][j].label; + nodes_[channel].push_back(node); + clusters_[node.label].insert_after(clusters_[node.label].cbefore_begin(), &range_mat_[channel][j]); + } else { + auto &cur_pt = range_mat_[channel][j]; + auto &pre_pt = range_mat_[channel][pre_pos]; + if (pointDistance(cur_pt, pre_pt) < HORZ_MERGE_THRES) { + // update existing node + pre_pos = j; + cur_pt.label = pre_pt.label; + nodes_[channel].back().end = j; + } else { + // update index + start_pos = j; + pre_pos = j; + // update label + max_label_++; + clusters_.push_back(empty_); + cur_pt.label = max_label_; + // push new node + node.start = start_pos; + node.end = j; + node.label = range_mat_[channel][j].label; + nodes_[channel].push_back(node); + assert(range_mat_[channel][j].label == cur_pt.label); + } + clusters_[cur_pt.label].insert_after(clusters_[cur_pt.label].cbefore_begin(), &range_mat_[channel][j]); + } + } + last_valid_idx = pre_pos; + + // merge last and first points + if (nodes_[channel].size() > 2) { + auto &p_0 = range_mat_[channel][first_valid_idx]; + auto &p_l = range_mat_[channel][last_valid_idx]; + if (pointDistance(p_0, p_l) < HORZ_MERGE_THRES) { + if (p_0.label == 0) + printf("Ring merge to label 0\n"); + if (p_0.label != p_l.label) { + nodes_[channel].back().label = p_0.label; + mergeClusters(p_l.label, p_0.label); + } + } + } + + // merge skipped nodes due to occlusion + if (nodes_[channel].size() > 2) { + uint16_t cur_label = 0, target_label = 0; + for (size_t i = 0; i < nodes_[channel].size()-1; i++){ + for (size_t j = i+1; j < nodes_[channel].size(); j++) { + auto &node_i = nodes_[channel][i]; + auto &node_j = nodes_[channel][j]; + + if (node_i.label == node_j.label) + continue; + + int end_idx = node_i.end; + int start_idx = node_j.start; + float dist = pointDistance(range_mat_[channel][end_idx], range_mat_[channel][start_idx]); + if (dist < HORZ_MERGE_THRES) { + if (node_i.label > node_j.label) { + target_label = node_j.label; + cur_label = node_i.label; + node_i.label = target_label; + } else { + target_label = node_i.label; + cur_label = node_j.label; + node_j.label = target_label; + } + mergeClusters(cur_label, target_label); + // if (DEBUG) + // printf("merge two labels despite occlusion: %d %d\n", cur_label, target_label); + } + if (j-i >= HORZ_SKIP_SIZE) + break; + } + } + } + } + + void verticalUpdate(int channel) { + // Iterate each point of this channel to update the labels. + int point_size = valid_cnt_[channel]; + // Current scan line is emtpy, do nothing. + if(point_size == 0) return; + // Skip first scan line + if(channel == 0) return; + + int prev_channel = channel - 1; + + for(int n = 0; n < nodes_[channel].size(); n++) { + + auto &cur_node = nodes_[channel][n]; + + for (int l = prev_channel; l >= channel - VERT_SCAN_SIZE; l -= 1) { + + if (l < 0) // out of range + break; + + if (valid_cnt_[l] == 0) { + // if (DEBUG) + // printf("No valid points in the channel\n"); + continue; + } + + // binary search lower bound + // lower_bnd inclusive + int N = nodes_[l].size(); + int first = 0; + int last = N - 1; + int lower_bnd = 0; + int mid = 0; + + while (first <= last) { + mid = (first + last) / 2; + auto &prev_node = nodes_[l][mid]; + if (overlap(cur_node, prev_node) || cur_node.end < prev_node.start){ + lower_bnd = mid; + last = mid - 1; + } else { + first = mid + 1; + } + } + + // binary search upper bound + // exclusive but gives -1 if end of list + first = 0; + last = N - 1; + mid = 0; + int upper_bnd = 0; + + while (first <= last) { + mid = (first + last) / 2; + auto &prev_node = nodes_[l][mid]; + if (overlap(cur_node, prev_node) || prev_node.end < cur_node.start) { + upper_bnd = mid; + first = mid + 1; + } else { + last = mid - 1; + } + } + upper_bnd = upper_bnd + 1; + + // loop through overlapped nodes + for (size_t idx = lower_bnd; idx < upper_bnd; idx++) { + + auto &ovl_node = nodes_[l][idx]; + + if (ovl_node.label == cur_node.label) { + // if (DEBUG) + // printf("same label: %d, %d\n", ovl_node.label, cur_node.label); + continue; + } + + // if (DEBUG) + // printf("overlapped: %d, %d\n", ovl_node.start, ovl_node.end); + + int iter_start_idx = -1; + int iter_end_idx = -1; + + if (ovl_node.start <= cur_node.start && cur_node.end <= ovl_node.end) { + // cur_node inside prev_node + iter_start_idx = cur_node.start; + iter_end_idx = cur_node.end; + } else if (cur_node.start <= ovl_node.start && ovl_node.end <= cur_node.end) { + // prev_node inside cur_node + iter_start_idx = ovl_node.start; + iter_end_idx = ovl_node.end; + } else if (cur_node.start < ovl_node.start && cur_node.end >= ovl_node.start && cur_node.end <= ovl_node.end) { + // tail of cur_node with head of prev_node + iter_start_idx = ovl_node.start; + iter_end_idx = cur_node.end; + } else if (ovl_node.start <= cur_node.start && cur_node.start <= ovl_node.end && cur_node.end > ovl_node.end) { + // head of cur_node with tail of prev_node + iter_start_idx = cur_node.start; + iter_end_idx = ovl_node.end; + } else { + // overlapped within search window size, use euclidean distance directly + if (ovl_node.end < cur_node.start) { + if (nodeDistance(cur_node, ovl_node, range_mat_[channel][cur_node.start], range_mat_[l][ovl_node.end])) { + // if (DEBUG) { + // printf("Merge by euclidean distance: cur: %f;%f;%f, prev: %f;%f;%f\n", + // range_mat_[channel][cur_node.start].x, range_mat_[channel][cur_node.start].y, range_mat_[channel][cur_node.start].z, + // range_mat_[l][ovl_node.end].x, range_mat_[l][ovl_node.end].y, range_mat_[l][ovl_node.end].z); + // } + } + } else if (cur_node.end < ovl_node.start) { + if (nodeDistance(cur_node, ovl_node, range_mat_[channel][cur_node.end], range_mat_[l][ovl_node.start])) { + // if (DEBUG) { + // printf("Merge by euclidean distance: cur: %f;%f;%f, prev: %f;%f;%f\n", + // range_mat_[channel][cur_node.end].x, range_mat_[channel][cur_node.end].y, range_mat_[channel][cur_node.end].z, + // range_mat_[l][ovl_node.start].x, range_mat_[l][ovl_node.start].y, range_mat_[l][ovl_node.start].z); + // } + } + } + continue; + } + + // if (DEBUG) + // printf("overlapping: %d %d\n", iter_start_idx, iter_end_idx); + + // iterate through overlapping indices + uint16_t cur_label = 0, target_label = 0; + bool merged = false; + int cur_start_left = iter_start_idx; + int cur_start_right = iter_start_idx; + int cur_end_left = iter_end_idx; + int cur_end_right = iter_end_idx; + + while (1) { + if (cur_start_right > cur_end_left && cur_start_left < iter_start_idx - HORZ_SCAN_SIZE && cur_end_right > iter_end_idx + HORZ_SCAN_SIZE) // end of search + break; + if (mergeNodes(cur_node, ovl_node, channel, l, cur_start_left)) + break; + if (cur_start_left != cur_end_left) { // more than one overlapping cur_node point + if (mergeNodes(cur_node, ovl_node, channel, l, cur_end_left)) + break; + } + if (cur_start_left != cur_start_right) { // not the first iteration + if (mergeNodes(cur_node, ovl_node, channel, l, cur_start_right)) + break; + } + if (cur_end_left != cur_end_right) { // not the first iteration + if (mergeNodes(cur_node, ovl_node, channel, l, cur_end_right)) + break; + } + cur_start_left--; + cur_start_right++; + cur_end_left--; + cur_end_right++; + } + } + } + } + } + + bool overlap(AOSNode &first_node, AOSNode &second_node) { + int new_start = first_node.start - HORZ_EXTENSION_SIZE; + int new_end = first_node.end + HORZ_EXTENSION_SIZE; + if (new_start <= second_node.start && second_node.start <= new_end) + return true; + else if (new_start <= second_node.end && second_node.end <= new_end) + return true; + else if (second_node.start <= new_start && new_end <= second_node.end) + return true; + return false; + } + + bool mergeNodes(AOSNode &first_node, AOSNode &second_node, int cur_channel, int prev_channel, int query_idx) { + if (query_idx >= first_node.start && query_idx <= first_node.end) { + if (range_mat_[cur_channel][query_idx].valid) { + int left_idx = query_idx; + int right_idx = query_idx; + while (1) { + if (left_idx <= query_idx - HORZ_SCAN_SIZE && right_idx >= query_idx + HORZ_SCAN_SIZE) + break; + + if (left_idx >= second_node.start && left_idx <= second_node.end && range_mat_[prev_channel][left_idx].valid) { + if (nodeDistance(first_node, second_node, range_mat_[cur_channel][query_idx], range_mat_[prev_channel][left_idx])) { + // if (DEBUG) + // printf("query: %d, left_idx: %d\n", query_idx, left_idx); + return true; + } + } + + if (right_idx <= second_node.end && right_idx >= second_node.start && range_mat_[prev_channel][right_idx].valid) { + if (nodeDistance(first_node, second_node, range_mat_[cur_channel][query_idx], range_mat_[prev_channel][right_idx])) { + // if (DEBUG) + // printf("query: %d, right_idx: %d\n", query_idx, right_idx); + return true; + } + } + left_idx--; + right_idx++; + } + } + } + return false; + } + + float pointDistance(Point pt1, Point pt2) { + return sqrt((pt1.x - pt2.x)*(pt1.x-pt2.x) + (pt1.y-pt2.y)*(pt1.y-pt2.y)); + } + + bool nodeDistance(AOSNode &first_node, AOSNode &second_node, Point &first_point, Point &second_point) { + uint16_t cur_label, target_label = 0; + + if (first_node.label == second_node.label) + return false; + + if (pointDistance(first_point, second_point) < VERT_MERGE_THRES) { + // if (DEBUG) { + // printf("cur: %f;%f;%f, prev: %f;%f;%f distance: %f\n", first_point.x, first_point.y, first_point.z, second_point.x, second_point.y, second_point.z, pointDistance(first_point, second_point)); + // printf("Two nodes merged: %d, %d\n", first_node.label, second_node.label); + // } + + if (first_node.label > second_node.label) { + cur_label = first_node.label; + target_label = second_node.label; + first_node.label = target_label; + } else { + cur_label = second_node.label; + target_label = first_node.label; + second_node.label = target_label; + } + mergeClusters(cur_label, target_label); + return true; + } + return false; + } + + float getRange(T pt) { + return sqrt(pt.x*pt.x+pt.y*pt.y+pt.z*pt.z); + } + + int getRowIdx(T pt) { + float angle = atan2(pt.z, sqrt(pt.x*pt.x+pt.y*pt.y))*180/M_PI; + auto iter_geq = std::lower_bound(vert_angles_.begin(), vert_angles_.end(), angle); + int row_idx; + + if (iter_geq == vert_angles_.begin()) { + row_idx = 0; + } else { + float a = *(iter_geq - 1); + float b = *(iter_geq); + if (fabs(angle-a) < fabs(angle-b)){ + row_idx = iter_geq - vert_angles_.begin() - 1; + } else { + row_idx = iter_geq - vert_angles_.begin(); + } + } + return row_idx; + } + + int getRowIdx(T pt, vector<float> vert_angles) { + float angle = atan2(pt.z, sqrt(pt.x*pt.x+pt.y*pt.y))*180/M_PI; + auto iter_geq = std::lower_bound(vert_angles.begin(), vert_angles.end(), angle); + int row_idx; + + if (iter_geq == vert_angles.begin()) { + row_idx = 0; + } else { + float a = *(iter_geq - 1); + float b = *(iter_geq); + if (fabs(angle-a) < fabs(angle-b)){ + row_idx = iter_geq - vert_angles.begin() - 1; + } else { + row_idx = iter_geq - vert_angles.begin(); + } + } + return row_idx; + } + + int getColIdx(T pt) { + float horizonAngle = atan2(pt.x, pt.y) * 180 / M_PI; + static float ang_res_x = 360.0/float(HORZ_SCAN); + int col_idx = -round((horizonAngle-90.0)/ang_res_x) + HORZ_SCAN/2; + if (col_idx >= HORZ_SCAN) + col_idx -= HORZ_SCAN; + return col_idx; + } + + void mergeClusters(uint16_t cur_label, uint16_t target_label) { + if(cur_label == 0 || target_label == 0){ + printf("Error merging runs cur_label:%u target_label:%u", cur_label, target_label); + } + for(auto &p : clusters_[cur_label]){ + p->label = target_label; + } + clusters_[target_label].insert_after(clusters_[target_label].cbefore_begin(), clusters_[cur_label].begin(),clusters_[cur_label].end() ); + clusters_[cur_label].clear(); + } + }; +} + +#endif \ No newline at end of file diff --git a/include/tgs.hpp b/include/tgs.hpp new file mode 100644 index 0000000000000000000000000000000000000000..99746f90981556222b38c2e78d52b3fe4ecae89f --- /dev/null +++ b/include/tgs.hpp @@ -0,0 +1,1643 @@ +#ifndef TRAVEL_GSEG_H +#define TRAVEL_GSEG_H +// +// Created by Minho Oh & Euigon Jung on 1/31/22. +// We really appreciate Hyungtae Lim and Prof. Hyun Myung! :) +// +#include <iostream> +#include <Eigen/Dense> +#include <vector> +#include <random> +#include <mutex> +#include <thread> +#include <chrono> +#include <math.h> +#include <fstream> +#include <memory> +#include <signal.h> + +#include <ros/ros.h> +#include <pcl_ros/point_cloud.h> +#include <pcl_conversions/pcl_conversions.h> +#include <pcl/filters/filter.h> +#include <pcl/common/centroid.h> +#include <pcl/filters/voxel_grid.h> +#include <tf/transform_listener.h> +#include <tf/tf.h> +#include <tf/transform_broadcaster.h> + +#include <jsk_recognition_msgs/PolygonArray.h> +#include <visualization_msgs/Marker.h> +#include <visualization_msgs/MarkerArray.h> + +#include <sensor_msgs/PointCloud2.h> +#include <nav_msgs/Odometry.h> +#include <nav_msgs/Path.h> +#include <opencv2/opencv.hpp> + +namespace travel { + + #define PTCLOUD_SIZE 132000 + #define NODEWISE_PTCLOUDSIZE 5000 + + #define UNKNOWN 1 + #define NONGROUND 2 + #define GROUND 3 + + using Eigen::MatrixXf; + using Eigen::JacobiSVD; + using Eigen::VectorXf; + + template <typename PointType> + bool point_z_cmp(PointType a, PointType b){ + return a.z<b.z; + } + + struct TriGridIdx { + int row, col, tri; + }; + + struct TriGridEdge { + std::pair<TriGridIdx, TriGridIdx> Pair; + bool is_traversable; + }; + + template <typename PointType> + struct TriGridNode { + int node_type; + pcl::PointCloud<PointType> ptCloud; + + bool is_curr_data; + + // planar model + Eigen::Vector3f normal; + Eigen::Vector3f mean_pt; + double d; + + Eigen::Vector3f singular_values; + Eigen::Matrix3f eigen_vectors; + double weight; + + double th_dist_d; + double th_outlier_d; + + // graph_searching + bool need_recheck; + bool is_visited; + bool is_rejection; + int check_life; + int depth; + }; + + struct TriGridCorner { + double x, y; + std::vector<double> zs; + std::vector<double> weights; + }; + + template <typename PointType> + using GridNode = std::vector<TriGridNode<PointType>>; + + template <typename PointType> + using TriGridField = std::vector<std::vector<GridNode<PointType>>>; + + template <typename PointType> + class TravelGroundSeg{ + private: + // ros::NodeHandle node_handle_; + pcl::PCLHeader cloud_header_; + std_msgs::Header msg_header_; + + // ros::Publisher pub_trigrid_nodes_; + // ros::Publisher pub_trigrid_edges_; + // ros::Publisher pub_trigrid_corners_; + // ros::Publisher pub_tgseg_ground_cloud; + // ros::Publisher pub_tgseg_nonground_cloud; + // ros::Publisher pub_tgseg_outliers_cloud; + + jsk_recognition_msgs::PolygonArray viz_trigrid_polygons_; + visualization_msgs::Marker viz_trigrid_edges_; + pcl::PointCloud<pcl::PointXYZ> viz_trigrid_corners_; + + bool REFINE_MODE_; + bool VIZ_MDOE_; + + double MAX_RANGE_; + double MIN_RANGE_; + double TGF_RESOLUTION_; + + int NUM_ITER_; + int NUM_LRP_; + int NUM_MIN_POINTS_; + + double TH_SEEDS_; + double TH_DIST_; + double TH_OUTLIER_; + + double TH_NORMAL_; + double TH_WEIGHT_; + double TH_LCC_NORMAL_SIMILARITY_; + double TH_LCC_PLANAR_MODEL_DIST_; + double TH_OBSTACLE_HEIGHT_; + + TriGridField<PointType> trigrid_field_; + std::vector<TriGridEdge> trigrid_edges_; + std::vector<std::vector<TriGridCorner>> trigrid_corners_; + std::vector<std::vector<TriGridCorner>> trigrid_centers_; + + pcl::PointCloud<PointType> empty_cloud_; + TriGridNode<PointType> empty_trigrid_node_; + GridNode<PointType> empty_grid_nodes_; + TriGridCorner empty_trigrid_corner_; + TriGridCorner empty_trigrid_center_; + + pcl::PointCloud<PointType> ptCloud_tgfwise_ground_; + pcl::PointCloud<PointType> ptCloud_tgfwise_nonground_; + pcl::PointCloud<PointType> ptCloud_tgfwise_outliers_; + pcl::PointCloud<PointType> ptCloud_tgfwise_obstacle_; + pcl::PointCloud<PointType> ptCloud_nodewise_ground_; + pcl::PointCloud<PointType> ptCloud_nodewise_nonground_; + pcl::PointCloud<PointType> ptCloud_nodewise_outliers_; + pcl::PointCloud<PointType> ptCloud_nodewise_obstacle_; + + public: + // TravelGroundSeg(ros::NodeHandle* nh):node_handle_(*nh){ + TravelGroundSeg(){ + // Init ROS related + ROS_INFO("Inititalizing Traversable Ground Segmentation..."); + + // pub_trigrid_nodes_ = node_handle_.advertise<jsk_recognition_msgs::PolygonArray>("/travelgseg/nodes", 1); + // pub_trigrid_edges_ = node_handle_.advertise<visualization_msgs::Marker>("/travelgseg/edges", 1); + // pub_trigrid_corners_= node_handle_.advertise<pcl::PointCloud<pcl::PointXYZ>>("/travelgseg/corners", 1); + + // pub_tgseg_ground_cloud = node_handle_.advertise<sensor_msgs::PointCloud2>("/travelgseg/ground_cloud", 1); + // pub_tgseg_nonground_cloud = node_handle_.advertise<sensor_msgs::PointCloud2>("/travelgseg/nonground_cloud", 1); + // pub_tgseg_outliers_cloud = node_handle_.advertise<sensor_msgs::PointCloud2>("/travelgseg/outlier_cloud", 1); + }; + + void setParams(const double max_range, const double min_range, const double resolution, + const int num_iter, const int num_lpr, const int num_min_pts, const double th_seeds, + const double th_dist, const double th_outlier, const double th_normal, const double th_weight, + const double th_lcc_normal_similiarity, const double th_lcc_planar_model_dist, const double th_obstacle, + const bool refine_mode, const bool visualization) { + std::cout<<""<<std::endl; + ROS_INFO("Set TRAVEL_GSEG Parameters"); + + std::cout<<"Just about to assign the max_range value = " << max_range <<std::endl; + std::cout<<"to the variable MAX_RANGE_ = " << MAX_RANGE_ <<std::endl; + + MAX_RANGE_ = max_range; + ROS_INFO("Max Range: %f", MAX_RANGE_); + + MIN_RANGE_ = min_range; + ROS_INFO("Min Range: %f", MIN_RANGE_); + + TGF_RESOLUTION_ = resolution; + ROS_INFO("Resolution: %f", TGF_RESOLUTION_); + + NUM_ITER_ = num_iter; + ROS_INFO("Num of Iteration: %d", NUM_ITER_); + + NUM_LRP_ = num_lpr; + ROS_INFO("Num of LPR: %d", NUM_LRP_); + + NUM_MIN_POINTS_ = num_min_pts; + ROS_INFO("Num of min. points: %d", NUM_MIN_POINTS_); + + TH_SEEDS_ = th_seeds; + ROS_INFO("Seeds Threshold: %f", TH_SEEDS_); + + TH_DIST_ = th_dist; + ROS_INFO("Distance Threshold: %f", TH_DIST_); + + TH_OUTLIER_ = th_outlier; + ROS_INFO("Outlier Threshold: %f", TH_OUTLIER_); + + TH_NORMAL_ = th_normal; + ROS_INFO("Normal Threshold: %f", TH_NORMAL_); + + TH_WEIGHT_ = th_weight; + ROS_INFO("Node Weight Threshold: %f", TH_WEIGHT_); + + TH_LCC_NORMAL_SIMILARITY_ = th_lcc_normal_similiarity; + ROS_INFO("LCC Normal Similarity: %f", TH_LCC_NORMAL_SIMILARITY_); + + TH_LCC_PLANAR_MODEL_DIST_ = th_lcc_planar_model_dist; + ROS_INFO("LCC Plane Distance : %f", TH_LCC_PLANAR_MODEL_DIST_); + + TH_OBSTACLE_HEIGHT_ = th_obstacle; + ROS_INFO("Obstacle Max Height : %f", TH_OBSTACLE_HEIGHT_); + + REFINE_MODE_ = refine_mode; + VIZ_MDOE_ = visualization; + + ROS_INFO("Set TGF Parameters"); + initTriGridField(trigrid_field_); + initTriGridCorners(trigrid_corners_, trigrid_centers_); + + ptCloud_tgfwise_ground_.clear(); + ptCloud_tgfwise_ground_.reserve(PTCLOUD_SIZE); + ptCloud_tgfwise_nonground_.clear(); + ptCloud_tgfwise_nonground_.reserve(PTCLOUD_SIZE); + ptCloud_tgfwise_outliers_.clear(); + ptCloud_tgfwise_outliers_.reserve(PTCLOUD_SIZE); + ptCloud_tgfwise_obstacle_.clear(); + ptCloud_tgfwise_obstacle_.reserve(PTCLOUD_SIZE); + + ptCloud_nodewise_ground_.clear(); + ptCloud_nodewise_ground_.reserve(NODEWISE_PTCLOUDSIZE); + ptCloud_nodewise_nonground_.clear(); + ptCloud_nodewise_nonground_.reserve(NODEWISE_PTCLOUDSIZE); + ptCloud_nodewise_outliers_.clear(); + ptCloud_nodewise_outliers_.reserve(NODEWISE_PTCLOUDSIZE); + ptCloud_nodewise_obstacle_.clear(); + ptCloud_nodewise_obstacle_.reserve(NODEWISE_PTCLOUDSIZE); + + if (VIZ_MDOE_) { + viz_trigrid_polygons_.polygons.clear(); + viz_trigrid_polygons_.polygons.reserve(rows_ * cols_); + viz_trigrid_polygons_.likelihood.clear(); + viz_trigrid_polygons_.likelihood.reserve(rows_ * cols_); + + + viz_trigrid_edges_.ns = "trigrid_edges"; + viz_trigrid_edges_.action = visualization_msgs::Marker::ADD; + viz_trigrid_edges_.type = visualization_msgs::Marker::LINE_LIST; + viz_trigrid_edges_.pose.orientation.w = 1.0; + viz_trigrid_edges_.scale.x = 0.5; + viz_trigrid_edges_.id = 0; + + viz_trigrid_corners_.clear(); + viz_trigrid_corners_.reserve(rows_*cols_ + (rows_+ 1)*(cols_+1)); + } + }; + + void estimateGround(const pcl::PointCloud<PointType>& cloud_in, + pcl::PointCloud<PointType>& cloudGround_out, + pcl::PointCloud<PointType>& cloudNonground_out, + double& time_taken){ + + // 0. Init + static time_t start, end; + cloud_header_ = cloud_in.header; + pcl_conversions::fromPCL(cloud_header_, msg_header_); + // ROS_INFO("TriGrid Field-based Traversable Ground Segmentation..."); + start = clock(); + ptCloud_tgfwise_outliers_.clear(); + ptCloud_tgfwise_outliers_.reserve(cloud_in.size()); + + + // 1. Embed PointCloud to TriGridField + clearTriGridField(trigrid_field_); + clearTriGridCorners(trigrid_corners_, trigrid_centers_); + + embedCloudToTriGridField(cloud_in, trigrid_field_); + + // 2. Node-wise Terrain Modeling + modelNodeWiseTerrain(trigrid_field_); + + // 3. Breadth-first Traversable Graph Search + BreadthFirstTraversableGraphSearch(trigrid_field_); + setTGFCornersCenters(trigrid_field_, trigrid_corners_, trigrid_centers_); + + // 4. TGF-wise Traversable Terrain Model Fitting + if (REFINE_MODE_){ + fitTGFWiseTraversableTerrainModel(trigrid_field_, trigrid_corners_, trigrid_centers_); + } + + // 5. Ground Segmentation + segmentTGFGround(trigrid_field_, ptCloud_tgfwise_ground_, ptCloud_tgfwise_nonground_, ptCloud_tgfwise_obstacle_, ptCloud_tgfwise_outliers_); + cloudGround_out = ptCloud_tgfwise_ground_; + cloudNonground_out = ptCloud_tgfwise_nonground_; + cloudGround_out.header = cloudNonground_out.header = cloud_header_; + + end = clock(); + time_taken = (double)(end - start) / CLOCKS_PER_SEC; + + // 6. Publish Results and Visualization + if (VIZ_MDOE_){ + // publishTriGridFieldGraph(); + // publishTriGridCorners(); + // publishPointClouds(); + } + return; + }; + + TriGridIdx getTriGridIdx(const float& x_in, const float& y_in){ + TriGridIdx tgf_idx; + int r_i = (x_in - tgf_min_x)/TGF_RESOLUTION_; + int c_i = (y_in - tgf_min_y)/TGF_RESOLUTION_; + int t_i = 0; + double angle = atan2(y_in-(c_i*TGF_RESOLUTION_ + TGF_RESOLUTION_/2 + tgf_min_y), x_in-(r_i*TGF_RESOLUTION_ + TGF_RESOLUTION_/2 + tgf_min_x)); + + if (angle>=(M_PI/4) && angle <(3*M_PI/4)){ + t_i = 1; + } else if (angle>=(-M_PI/4) && angle <(M_PI/4)){ + t_i = 0; + } else if (angle>=(-3*M_PI/4) && angle <(-M_PI/4)){ + t_i = 3; + } else{ + t_i = 2; + } + tgf_idx.row = r_i; + tgf_idx.col = c_i; + tgf_idx.tri = t_i; + return tgf_idx; + } + + TriGridNode<PointType> getTriGridNode(const float& x_in, const float& y_in){ + TriGridNode<PointType> node; + TriGridIdx node_idx = getTriGridIdx(x_in, y_in); + node = trigrid_field_[node_idx.row][node_idx.col][node_idx.tri]; + return node; + }; + + TriGridNode<PointType> getTriGridNode(const TriGridIdx& tgf_idx){ + TriGridNode<PointType> node; + node = trigrid_field_[tgf_idx.row][tgf_idx.col][tgf_idx.tri]; + return node; + }; + + bool is_traversable(const float& x_in, const float& y_in){ + TriGridNode<PointType> node = getTriGridNode(x_in, y_in); + if (node.node_type == GROUND){ + return true; + } else{ + return false; + } + }; + + pcl::PointCloud<PointType> getObstaclePC(){ + pcl::PointCloud<PointType> cloud_obstacle; + cloud_obstacle = ptCloud_tgfwise_obstacle_; + return cloud_obstacle; + }; + + private: + double tgf_max_x, tgf_max_y, tgf_min_x, tgf_min_y; + double rows_, cols_; + + void initTriGridField(TriGridField<PointType>& tgf_in){ + // ROS_INFO("Initializing TriGridField..."); + + tgf_max_x = MAX_RANGE_; + tgf_max_y = MAX_RANGE_; + + tgf_min_x = -MAX_RANGE_; + tgf_min_y = -MAX_RANGE_; + + rows_ = (int)(tgf_max_x - tgf_min_x) / TGF_RESOLUTION_; + cols_ = (int)(tgf_max_y - tgf_min_y) / TGF_RESOLUTION_; + empty_cloud_.clear(); + empty_cloud_.reserve(PTCLOUD_SIZE); + + // Set Node structure + empty_trigrid_node_.node_type = UNKNOWN; + empty_trigrid_node_.ptCloud.clear(); + empty_trigrid_node_.ptCloud.reserve(NODEWISE_PTCLOUDSIZE); + + empty_trigrid_node_.is_curr_data = false; + empty_trigrid_node_.need_recheck = false; + empty_trigrid_node_.is_visited = false; + empty_trigrid_node_.is_rejection = false; + + empty_trigrid_node_.check_life = 10; + empty_trigrid_node_.depth = -1; + + empty_trigrid_node_.normal; + empty_trigrid_node_.mean_pt; + empty_trigrid_node_.d = 0; + + empty_trigrid_node_.singular_values; + empty_trigrid_node_.eigen_vectors; + empty_trigrid_node_.weight = 0; + + empty_trigrid_node_.th_dist_d = 0; + empty_trigrid_node_.th_outlier_d = 0; + + // Set TriGridField + tgf_in.clear(); + std::vector<GridNode<PointType>> vec_gridnode; + + for (int i = 0; i < 4 ; i ++) + empty_grid_nodes_.emplace_back(empty_trigrid_node_); + + for (int i=0; i< cols_; i++){ vec_gridnode.emplace_back(empty_grid_nodes_);} + for (int j=0; j< rows_; j++){ tgf_in.emplace_back(vec_gridnode);} + + return; + }; + + void initTriGridCorners(std::vector<std::vector<TriGridCorner>>& trigrid_corners_in, + std::vector<std::vector<TriGridCorner>>& trigrid_centers_in){ + // ROS_INFO("Initializing TriGridCorners..."); + + // Set TriGridCorner + empty_trigrid_corner_.x = empty_trigrid_corner_.y = 0.0; + empty_trigrid_corner_.zs.clear(); + empty_trigrid_corner_.zs.reserve(8); + empty_trigrid_corner_.weights.clear(); + empty_trigrid_corner_.weights.reserve(8); + + empty_trigrid_center_.x = empty_trigrid_center_.y = 0.0; + empty_trigrid_center_.zs.clear(); + empty_trigrid_center_.zs.reserve(4); + empty_trigrid_center_.weights.clear(); + empty_trigrid_center_.weights.reserve(4); + + trigrid_corners_in.clear(); + trigrid_centers_in.clear(); + + // Set Corners and Centers + std::vector<TriGridCorner> col_corners; + std::vector<TriGridCorner> col_centers; + for (int i=0; i< cols_; i++){ + col_corners.emplace_back(empty_trigrid_corner_); + col_centers.emplace_back(empty_trigrid_center_); + } + col_corners.emplace_back(empty_trigrid_corner_); + + for (int j=0; j< rows_; j++){ + trigrid_corners_in.emplace_back(col_corners); + trigrid_centers_in.emplace_back(col_centers); + } + trigrid_corners_in.emplace_back(col_corners); + + return; + }; + + void clearTriGridField(TriGridField<PointType> &tgf_in){ + // ROS_INFO("Clearing TriGridField..."); + + for (int r_i = 0; r_i < rows_; r_i++){ + for (int c_i = 0; c_i < cols_; c_i++){ + tgf_in[r_i][c_i] = empty_grid_nodes_; + } + } + return; + }; + + void clearTriGridCorners(std::vector<std::vector<TriGridCorner>>& trigrid_corners_in, + std::vector<std::vector<TriGridCorner>>& trigrid_centers_in){ + // ROS_INFO("Clearing TriGridCorners..."); + + TriGridCorner tmp_corner = empty_trigrid_corner_; + TriGridCorner tmp_center = empty_trigrid_center_; + for (int r_i = 0; r_i < rows_+1; r_i++){ + for (int c_i = 0; c_i < cols_+1; c_i++){ + tmp_corner.x = (r_i)*TGF_RESOLUTION_+tgf_min_x; tmp_corner.y = (c_i)*TGF_RESOLUTION_+tgf_min_y; + tmp_corner.zs.clear(); + tmp_corner.weights.clear(); + trigrid_corners_in[r_i][c_i] = tmp_corner; + if (r_i < rows_ && c_i < cols_) { + tmp_center.x = (r_i+0.5)*TGF_RESOLUTION_+tgf_min_x; tmp_center.y = (c_i+0.5)*TGF_RESOLUTION_+tgf_min_y; + tmp_center.zs.clear(); + tmp_center.weights.clear(); + trigrid_centers_in[r_i][c_i] = tmp_center; + } + } + } + return; + }; + + double xy_2Dradius(double x, double y){ + return sqrt(x*x + y*y); + }; + + bool filterPoint(const PointType &pt_in){ + double xy_range = xy_2Dradius(pt_in.x, pt_in.y); + if (xy_range >= MAX_RANGE_ || xy_range <= MIN_RANGE_) return true; + + return false; + } + + void embedCloudToTriGridField(const pcl::PointCloud<PointType>& cloud_in, TriGridField<PointType>& tgf_out) { + // ROS_INFO("Embedding PointCloud to TriGridField..."); + + for (auto const &pt: cloud_in.points){ + if (filterPoint(pt)){ + ptCloud_tgfwise_outliers_.points.push_back(pt); + continue; + } + + int r_i = (pt.x - tgf_min_x)/TGF_RESOLUTION_; + int c_i = (pt.y - tgf_min_y)/TGF_RESOLUTION_; + + if (r_i < 0 || r_i >= rows_ || c_i < 0 || c_i >= cols_) { + ptCloud_tgfwise_outliers_.points.push_back(pt); + continue; + } + + double angle = atan2(pt.y-(c_i*TGF_RESOLUTION_ + TGF_RESOLUTION_/2 + tgf_min_y), pt.x-(r_i*TGF_RESOLUTION_ + TGF_RESOLUTION_/2 + tgf_min_x)); + if (angle>=(M_PI/4) && angle <(3*M_PI/4)){ + // left side + tgf_out[r_i][c_i][1].ptCloud.push_back(pt); + if(!tgf_out[r_i][c_i][1].is_curr_data) {tgf_out[r_i][c_i][1].is_curr_data = true;} + } else if (angle>=(-M_PI/4) && angle <(M_PI/4)){ + // upper side + tgf_out[r_i][c_i][0].ptCloud.push_back(pt); + if (!tgf_out[r_i][c_i][0].is_curr_data){tgf_out[r_i][c_i][0].is_curr_data = true;} + + } else if (angle>=(-3*M_PI/4) && angle <(-M_PI/4)){ + // right side + tgf_out[r_i][c_i][3].ptCloud.push_back(pt); + if (!tgf_out[r_i][c_i][3].is_curr_data) {tgf_out[r_i][c_i][3].is_curr_data = true;} + } else{ + // lower side + tgf_out[r_i][c_i][2].ptCloud.push_back(pt); + if (!tgf_out[r_i][c_i][2].is_curr_data) {tgf_out[r_i][c_i][2].is_curr_data = true;} + } + } + + return; + }; + + void extractInitialSeeds(const pcl::PointCloud<PointType>& p_sorted, pcl::PointCloud<PointType>& init_seeds){ + //function for uniform mode + init_seeds.points.clear(); + + // LPR is the mean of Low Point Representative + double sum = 0; + int cnt = 0; + + // Calculate the mean height value. + for (int i=0; i< (int) p_sorted.points.size() && cnt<NUM_LRP_; i++){ + sum += p_sorted.points[i].z; + cnt++; + } + + double lpr_height = cnt!=0?sum/cnt:0; + + for(int i=0 ; i< (int) p_sorted.points.size() ; i++){ + if(p_sorted.points[i].z < lpr_height + TH_SEEDS_){ + if (p_sorted.points[i].z < lpr_height-TH_OUTLIER_) continue; + init_seeds.points.push_back(p_sorted.points[i]); + } + } + + return; + } + + void estimatePlanarModel(const pcl::PointCloud<PointType>& ground_in, TriGridNode<PointType>& node_out) { + + // function for uniform mode + Eigen::Matrix3f cov_; + Eigen::Vector4f pc_mean_; + pcl::computeMeanAndCovarianceMatrix(ground_in, cov_, pc_mean_); + + // Singular Value Decomposition: SVD + Eigen::JacobiSVD<Eigen::MatrixXf> svd(cov_, Eigen::DecompositionOptions::ComputeFullU); + + // Use the least singular vector as normal + node_out.eigen_vectors = svd.matrixU(); + if (node_out.eigen_vectors.col(2)(2,0)<0) { + node_out.eigen_vectors.col(0)*= -1; + node_out.eigen_vectors.col(2)*= -1; + } + node_out.normal = node_out.eigen_vectors.col(2); + node_out.singular_values = svd.singularValues(); + + // mean ground seeds value + node_out.mean_pt = pc_mean_.head<3>(); + + // according to noraml.T*[x,y,z] = -d + node_out.d = -(node_out.normal.transpose()*node_out.mean_pt)(0,0); + + // set distance theshold to 'th_dist - d' + node_out.th_dist_d = TH_DIST_ - node_out.d; + node_out.th_outlier_d = -node_out.d - TH_OUTLIER_; + + return; + } + + void modelPCAbasedTerrain(TriGridNode<PointType>& node_in) { + // Initailization + if (!ptCloud_nodewise_ground_.empty()) ptCloud_nodewise_ground_.clear(); + + // Tri Grid Initialization + // When to initialize the planar model, we don't have prior. so outlier is removed in heuristic parameter. + pcl::PointCloud<PointType> sort_ptCloud = node_in.ptCloud; + + // sort in z-coordinate + sort(sort_ptCloud.points.begin(), sort_ptCloud.end(), point_z_cmp<PointType>); + + // Set init seeds + extractInitialSeeds(sort_ptCloud, ptCloud_nodewise_ground_); + + Eigen::MatrixXf points(sort_ptCloud.points.size(),3); + int j = 0; + for (auto& p:sort_ptCloud.points){ + points.row(j++)<<p.x, p.y, p.z; + } + // Extract Ground + for (int i =0; i < NUM_ITER_; i++){ + estimatePlanarModel(ptCloud_nodewise_ground_, node_in); + if(ptCloud_nodewise_ground_.size() < 3){ + + node_in.node_type = NONGROUND; + break; + } + ptCloud_nodewise_ground_.clear(); + // threshold filter + Eigen::VectorXf result = points*node_in.normal; + for (int r = 0; r<result.rows(); r++){ + if (i < NUM_ITER_-1){ + if (result[r]<node_in.th_dist_d){ + ptCloud_nodewise_ground_.push_back(sort_ptCloud.points[r]); + } + } else { + // Final interation + if (node_in.normal(2,0) < TH_NORMAL_ ){ + node_in.node_type = NONGROUND; + } else { + node_in.node_type = GROUND; + } + } + } + } + + return; + } + + double calcNodeWeight(const TriGridNode<PointType>& node_in){ + double weight = 0; + + // weight = (node_in.singular_values[0]/node_in.singular_values[2] + node_in.singular_values[1]/node_in.singular_values[2])/(node_in.singular_values[0]/node_in.singular_values[1]); + weight = (node_in.singular_values[0] + node_in.singular_values[1])*node_in.singular_values[1]/(node_in.singular_values[0]*node_in.singular_values[2]+0.001); + + return weight; + } + + void modelNodeWiseTerrain(TriGridField<PointType>& tgf_in) { + // ROS_INFO("Node-wise Terrain Modeling..."); + + for (int r_i = 0; r_i < rows_; r_i++){ + for (int c_i = 0; c_i < cols_; c_i++){ + for (int s_i = 0; s_i < 4; s_i++){ + if (tgf_in[r_i][c_i][s_i].is_curr_data){ + if (tgf_in[r_i][c_i][s_i].ptCloud.size() < NUM_MIN_POINTS_){ + tgf_in[r_i][c_i][s_i].node_type = UNKNOWN; + continue; + } else { + modelPCAbasedTerrain(tgf_in[r_i][c_i][s_i]); + if (tgf_in[r_i][c_i][s_i].node_type == GROUND){ tgf_in[r_i][c_i][s_i].weight = calcNodeWeight(tgf_in[r_i][c_i][s_i]); } + } + } + } + } + } + + return; + }; + + void findDominantNode(const TriGridField<PointType>& tgf_in, TriGridIdx& node_idx_out) { + // Find the dominant node + ROS_INFO("Find the dominant node..."); + TriGridIdx max_tri_idx; + TriGridIdx ego_idx; + ego_idx.row = (int)((0-tgf_min_x)/TGF_RESOLUTION_); + ego_idx.col = (int)((0-tgf_min_y)/TGF_RESOLUTION_); + ego_idx.tri = 0; + + max_tri_idx = ego_idx; + for (int r_i = ego_idx.row - 2; r_i < ego_idx.row + 2; r_i++){ + for (int c_i = ego_idx.col - 2; c_i < ego_idx.col + 2; c_i++){ + for (int s_i = 0; s_i < 4; s_i++){ + if (tgf_in[r_i][c_i][s_i].is_curr_data){ + if (tgf_in[r_i][c_i][s_i].node_type == GROUND){ + if (tgf_in[r_i][c_i][s_i].weight > tgf_in[max_tri_idx.row][max_tri_idx.row][max_tri_idx.tri].weight){ + max_tri_idx.row = r_i; + max_tri_idx.col = c_i; + max_tri_idx.tri = s_i; + } + } + } + } + } + } + node_idx_out = max_tri_idx; + return; + }; + + void searchNeighborNodes(const TriGridIdx &cur_idx, std::vector<TriGridIdx> &neighbor_idxs) { + neighbor_idxs.clear(); + neighbor_idxs.reserve(14); + int r_i = cur_idx.row; + int c_i = cur_idx.col; + int s_i = cur_idx.tri; + + std::vector<TriGridIdx> tmp_neighbors; + tmp_neighbors.clear(); + tmp_neighbors.reserve(14); + + TriGridIdx neighbor_idx; + for (int s_i = 0; s_i < 4 ; s_i++){ + if (s_i == cur_idx.tri) continue; + + neighbor_idx = cur_idx; + neighbor_idx.tri = s_i; + tmp_neighbors.push_back(neighbor_idx); + } + + switch (s_i) { + case 0: + tmp_neighbors.push_back({r_i+1, c_i+1, 2}); + tmp_neighbors.push_back({r_i+1, c_i+1, 3}); + tmp_neighbors.push_back({r_i+1, c_i , 1}); + tmp_neighbors.push_back({r_i+1, c_i , 2}); + tmp_neighbors.push_back({r_i+1, c_i , 3}); + tmp_neighbors.push_back({r_i+1, c_i-1, 1}); + tmp_neighbors.push_back({r_i+1, c_i-1, 2}); + tmp_neighbors.push_back({r_i , c_i+1, 0}); + tmp_neighbors.push_back({r_i , c_i+1, 3}); + tmp_neighbors.push_back({r_i , c_i-1, 0}); + tmp_neighbors.push_back({r_i , c_i-1, 1}); + break; + case 1: + tmp_neighbors.push_back({r_i+1, c_i+1, 2}); + tmp_neighbors.push_back({r_i+1, c_i+1, 3}); + tmp_neighbors.push_back({r_i+1, c_i , 1}); + tmp_neighbors.push_back({r_i+1, c_i , 2}); + tmp_neighbors.push_back({r_i , c_i+1, 0}); + tmp_neighbors.push_back({r_i , c_i+1, 2}); + tmp_neighbors.push_back({r_i , c_i+1, 3}); + tmp_neighbors.push_back({r_i-1, c_i+1, 0}); + tmp_neighbors.push_back({r_i-1, c_i+1, 3}); + tmp_neighbors.push_back({r_i-1, c_i+1, 0}); + tmp_neighbors.push_back({r_i-1, c_i , 1}); + break; + case 2: + tmp_neighbors.push_back({r_i , c_i+1, 2}); + tmp_neighbors.push_back({r_i , c_i+1, 3}); + tmp_neighbors.push_back({r_i , c_i-1, 1}); + tmp_neighbors.push_back({r_i , c_i-1, 2}); + tmp_neighbors.push_back({r_i-1, c_i+1, 0}); + tmp_neighbors.push_back({r_i-1, c_i+1, 3}); + tmp_neighbors.push_back({r_i-1, c_i , 0}); + tmp_neighbors.push_back({r_i-1, c_i , 1}); + tmp_neighbors.push_back({r_i-1, c_i , 3}); + tmp_neighbors.push_back({r_i-1, c_i-1, 0}); + tmp_neighbors.push_back({r_i-1, c_i-1, 1}); + break; + case 3: + tmp_neighbors.push_back({r_i+1, c_i , 2}); + tmp_neighbors.push_back({r_i+1, c_i , 3}); + tmp_neighbors.push_back({r_i+1, c_i-1, 1}); + tmp_neighbors.push_back({r_i+1, c_i-1, 2}); + tmp_neighbors.push_back({r_i , c_i-1, 0}); + tmp_neighbors.push_back({r_i , c_i-1, 1}); + tmp_neighbors.push_back({r_i , c_i-1, 2}); + tmp_neighbors.push_back({r_i-1, c_i , 0}); + tmp_neighbors.push_back({r_i-1, c_i , 3}); + tmp_neighbors.push_back({r_i-1, c_i-1, 0}); + tmp_neighbors.push_back({r_i-1, c_i-1, 1}); + break; + default: + break; + } + + for (int n_i = 0 ; n_i < (int) tmp_neighbors.size() ; n_i++) { + + if (tmp_neighbors[n_i].row >= rows_ || tmp_neighbors[n_i].row < 0) { + continue; + } + + if (tmp_neighbors[n_i].col >= cols_ || tmp_neighbors[n_i].col < 0) { + continue; + } + + neighbor_idxs.push_back(tmp_neighbors[n_i]); + } + } + + void searchAdjacentNodes(const TriGridIdx &cur_idx, std::vector<TriGridIdx> &adjacent_idxs) { + adjacent_idxs.clear(); + adjacent_idxs.reserve(3); + int r_i = cur_idx.row; + int c_i = cur_idx.col; + int s_i = cur_idx.tri; + + std::vector<TriGridIdx> tmp_neighbors; + tmp_neighbors.clear(); + tmp_neighbors.reserve(3); + + TriGridIdx neighbor_idx; + + switch (s_i) { + case 0: + tmp_neighbors.push_back({r_i+1, c_i, 2}); + tmp_neighbors.push_back({r_i , c_i, 3}); + tmp_neighbors.push_back({r_i , c_i, 1}); + + break; + case 1: + tmp_neighbors.push_back({r_i, c_i+1, 3}); + tmp_neighbors.push_back({r_i, c_i , 0}); + tmp_neighbors.push_back({r_i, c_i , 2}); + break; + case 2: + tmp_neighbors.push_back({r_i-1, c_i, 0}); + tmp_neighbors.push_back({r_i , c_i, 1}); + tmp_neighbors.push_back({r_i , c_i, 3}); + break; + case 3: + tmp_neighbors.push_back({r_i, c_i-1, 1}); + tmp_neighbors.push_back({r_i, c_i , 2}); + tmp_neighbors.push_back({r_i, c_i , 0}); + break; + default: + break; + } + + for (int n_i = 0 ; n_i < (int) tmp_neighbors.size() ; n_i++) { + + if (tmp_neighbors[n_i].row >= rows_ || tmp_neighbors[n_i].row < 0) { + continue; + } + + if (tmp_neighbors[n_i].col >= cols_ || tmp_neighbors[n_i].col < 0) { + continue; + } + + adjacent_idxs.push_back(tmp_neighbors[n_i]); + } + } + + bool LocalConvecityConcavity(const TriGridField<PointType> &tgf, const TriGridIdx &cur_node_idx, const TriGridIdx &neighbor_idx, + double & thr_local_normal, double & thr_local_dist) { + TriGridNode<PointType> current_node = tgf[cur_node_idx.row][cur_node_idx.col][cur_node_idx.tri]; + TriGridNode<PointType> neighbor_node = tgf[neighbor_idx.row][neighbor_idx.col][neighbor_idx.tri]; + + Eigen::Vector3f normal_src = current_node.normal; + Eigen::Vector3f normal_tgt = neighbor_node.normal; + Eigen::Vector3f meanPt_diff_s2t = neighbor_node.mean_pt - current_node.mean_pt; + + double diff_norm = meanPt_diff_s2t.norm(); + double dist_s2t = normal_src.dot(meanPt_diff_s2t); + double dist_t2s = normal_tgt.dot(-meanPt_diff_s2t); + + double normal_similarity = normal_src.dot(normal_tgt); + double TH_NORMAL_cos_similarity = sin(diff_norm*thr_local_normal); + if ((normal_similarity < (1-TH_NORMAL_cos_similarity))) { + return false; + } + + double TH_DIST_to_planar = diff_norm*sin(thr_local_dist); + if ( (abs(dist_s2t) > TH_DIST_to_planar || abs(dist_t2s) > TH_DIST_to_planar) ) { + return false; + } + + return true; + } + + void BreadthFirstTraversableGraphSearch(TriGridField<PointType>& tgf_in) { + + // Find the dominant node + std::queue<TriGridIdx> searching_idx_queue; + TriGridIdx dominant_node_idx; + findDominantNode(tgf_in, dominant_node_idx); + tgf_in[dominant_node_idx.row][dominant_node_idx.col][dominant_node_idx.tri].is_visited = true; + tgf_in[dominant_node_idx.row][dominant_node_idx.col][dominant_node_idx.tri].depth = 0; + tgf_in[dominant_node_idx.row][dominant_node_idx.col][dominant_node_idx.tri].node_type = GROUND; + + searching_idx_queue.push(dominant_node_idx); + + double max_planar_height = 0; + trigrid_edges_.clear(); + trigrid_edges_.reserve(rows_*cols_*4); + TriGridEdge cur_edge; + TriGridIdx current_node_idx; + while (!searching_idx_queue.empty()){ + // set current node + current_node_idx = searching_idx_queue.front(); + searching_idx_queue.pop(); + + // search the neighbor nodes + std::vector<TriGridIdx> neighbor_idxs; + searchNeighborNodes(current_node_idx, neighbor_idxs); + + // set the traversable edges + for (int i = 0; i < (int) neighbor_idxs.size(); i++){ + // if the neighbor node is traversable, add it to the queue + + TriGridIdx n_i = neighbor_idxs[i]; + + + if (tgf_in[n_i.row][n_i.col][n_i.tri].depth >=0){ + continue; + } + + if (tgf_in[n_i.row][n_i.col][n_i.tri].is_visited) { + if (!tgf_in[n_i.row][n_i.col][n_i.tri].need_recheck){ + continue; + } else { + if (tgf_in[n_i.row][n_i.col][n_i.tri].check_life <= 0){ + continue; + } + } + continue; + } else { + if (tgf_in[n_i.row][n_i.col][n_i.tri].node_type != GROUND) { + continue; + } + } + + tgf_in[n_i.row][n_i.col][n_i.tri].is_visited =true; + + if (!LocalConvecityConcavity(tgf_in, current_node_idx, n_i, TH_LCC_NORMAL_SIMILARITY_, TH_LCC_PLANAR_MODEL_DIST_)) { + tgf_in[n_i.row][n_i.col][n_i.tri].is_rejection = true; + tgf_in[n_i.row][n_i.col][n_i.tri].node_type = NONGROUND; + + if(tgf_in[n_i.row][n_i.col][n_i.tri].check_life > 0) { + tgf_in[n_i.row][n_i.col][n_i.tri].check_life -=1; + tgf_in[n_i.row][n_i.col][n_i.tri].need_recheck = true; + } else { + tgf_in[n_i.row][n_i.col][n_i.tri].need_recheck = false; + } + continue; + } + + if (max_planar_height < tgf_in[n_i.row][n_i.col][n_i.tri].mean_pt[2]) max_planar_height = tgf_in[n_i.row][n_i.col][n_i.tri].mean_pt[2]; + + tgf_in[n_i.row][n_i.col][n_i.tri].node_type = GROUND; + tgf_in[n_i.row][n_i.col][n_i.tri].is_rejection = false; + tgf_in[n_i.row][n_i.col][n_i.tri].depth = tgf_in[current_node_idx.row][current_node_idx.col][current_node_idx.tri].depth + 1; + + if (VIZ_MDOE_){ + cur_edge.Pair.first = current_node_idx; + cur_edge.Pair.second = n_i; + cur_edge.is_traversable = true; + trigrid_edges_.push_back(cur_edge); + } + + searching_idx_queue.push(n_i); + } + + if (searching_idx_queue.empty()){ + // set the new dominant node + for (int r_i = 0; r_i < rows_; r_i++) { + for (int c_i = 0; c_i < cols_; c_i++) { + for (int s_i = 0; s_i < (int) tgf_in[r_i][c_i].size() ; s_i++){ + if (tgf_in[r_i][c_i][s_i].is_visited) { continue; } + + if (tgf_in[r_i][c_i][s_i].node_type != GROUND ) { continue; } + + // if (tgf_in[r_i][c_i][s_i].mean_pt[2] >= max_planar_height+1) { continue; } + + if (tgf_in[r_i][c_i][s_i].depth >= 0) { continue; } + + // if (tgf_in[r_i][c_i][s_i].weight > 5*TH_WEIGHT_){ + tgf_in[r_i][c_i][s_i].depth = 0; + tgf_in[r_i][c_i][s_i].is_visited = true; + + TriGridIdx new_dominant_idx = {r_i, c_i, s_i}; + searching_idx_queue.push(new_dominant_idx); + // } + } + } + } + } + } + return; + }; + + + double getCornerWeight(const TriGridNode<PointType>& node_in, const pcl::PointXYZ &tgt_corner){ + double xy_dist = sqrt( (node_in.mean_pt[0]-tgt_corner.x)*(node_in.mean_pt[0]-tgt_corner.x)+(node_in.mean_pt[1]-tgt_corner.y)*(node_in.mean_pt[1]-tgt_corner.y) ); + return (node_in.weight/xy_dist); + } + + void setTGFCornersCenters(const TriGridField<PointType>& tgf_in, + std::vector<std::vector<TriGridCorner>>& trigrid_corners_out, + std::vector<std::vector<TriGridCorner>>& trigrid_centers_out) { + pcl::PointXYZ corner_TL, corner_BL, corner_BR, corner_TR, corner_C; + + for (int r_i = 0; r_i<rows_; r_i++){ + for (int c_i = 0; c_i<cols_; c_i++){ + corner_TL.x = trigrid_corners_out[r_i+1][c_i+1].x; corner_TL.y = trigrid_corners_out[r_i+1][c_i+1].y; // LT + corner_BL.x = trigrid_corners_out[r_i][c_i+1].x; corner_BL.y = trigrid_corners_out[r_i][c_i+1].y; // LL + corner_BR.x = trigrid_corners_out[r_i][c_i].x; corner_BR.y = trigrid_corners_out[r_i][c_i].y; // RL + corner_TR.x = trigrid_corners_out[r_i+1][c_i].x; corner_TR.y = trigrid_corners_out[r_i+1][c_i].y; // RT + corner_C.x = trigrid_centers_out[r_i][c_i].x; corner_C.y = trigrid_centers_out[r_i][c_i].y; // Center + + for (int s_i = 0; s_i< (int) tgf_in[r_i][c_i].size();s_i++){ + if (tgf_in[r_i][c_i][s_i].node_type != GROUND) { continue; } + if (tgf_in[r_i][c_i][s_i].is_rejection) { continue; } + if (tgf_in[r_i][c_i][s_i].depth == -1) { continue; } + + switch(s_i){ + case 0: // upper Tri-grid bin + // RT / LT / C + trigrid_corners_out[r_i+1][c_i].zs.push_back( (-tgf_in[r_i][c_i][s_i].normal(0,0)*corner_TR.x - tgf_in[r_i][c_i][s_i].normal(1,0)*corner_TR.y-tgf_in[r_i][c_i][s_i].d)/tgf_in[r_i][c_i][s_i].normal(2,0) ); + trigrid_corners_out[r_i+1][c_i].weights.push_back(getCornerWeight(tgf_in[r_i][c_i][s_i],corner_TR)); + + trigrid_corners_out[r_i+1][c_i+1].zs.push_back( (-tgf_in[r_i][c_i][s_i].normal(0,0)*corner_TL.x - tgf_in[r_i][c_i][s_i].normal(1,0)*corner_TL.y-tgf_in[r_i][c_i][s_i].d)/tgf_in[r_i][c_i][s_i].normal(2,0) ); + trigrid_corners_out[r_i+1][c_i+1].weights.push_back(getCornerWeight(tgf_in[r_i][c_i][s_i],corner_TL)); + + trigrid_centers_out[r_i][c_i].zs.push_back( (-tgf_in[r_i][c_i][s_i].normal(0,0)*corner_C.x - tgf_in[r_i][c_i][s_i].normal(1,0)*corner_C.y-tgf_in[r_i][c_i][s_i].d)/tgf_in[r_i][c_i][s_i].normal(2,0) ); + trigrid_centers_out[r_i][c_i].weights.push_back(getCornerWeight(tgf_in[r_i][c_i][s_i],corner_C)); + + break; + case 1: // left Tri-grid bin + // LT / LL / C + trigrid_corners_out[r_i+1][c_i+1].zs.push_back( (-tgf_in[r_i][c_i][s_i].normal(0,0)*corner_TL.x - tgf_in[r_i][c_i][s_i].normal(1,0)*corner_TL.y-tgf_in[r_i][c_i][s_i].d)/tgf_in[r_i][c_i][s_i].normal(2,0) ); + trigrid_corners_out[r_i+1][c_i+1].weights.push_back(getCornerWeight(tgf_in[r_i][c_i][s_i],corner_TL)); + + trigrid_corners_out[r_i][c_i+1].zs.push_back( (-tgf_in[r_i][c_i][s_i].normal(0,0)*corner_BL.x - tgf_in[r_i][c_i][s_i].normal(1,0)*corner_BL.y-tgf_in[r_i][c_i][s_i].d)/tgf_in[r_i][c_i][s_i].normal(2,0) ); + trigrid_corners_out[r_i][c_i+1].weights.push_back(getCornerWeight(tgf_in[r_i][c_i][s_i],corner_BL)); + + trigrid_centers_out[r_i][c_i].zs.push_back( (-tgf_in[r_i][c_i][s_i].normal(0,0)*corner_C.x - tgf_in[r_i][c_i][s_i].normal(1,0)*corner_C.y-tgf_in[r_i][c_i][s_i].d)/tgf_in[r_i][c_i][s_i].normal(2,0) ); + trigrid_centers_out[r_i][c_i].weights.push_back(getCornerWeight(tgf_in[r_i][c_i][s_i],corner_C)); + + break; + case 2: // lower Tri-grid bin + // LL / RL / C + trigrid_corners_out[r_i][c_i+1].zs.push_back( (-tgf_in[r_i][c_i][s_i].normal(0,0)*corner_BL.x - tgf_in[r_i][c_i][s_i].normal(1,0)*corner_BL.y-tgf_in[r_i][c_i][s_i].d)/tgf_in[r_i][c_i][s_i].normal(2,0) ); + trigrid_corners_out[r_i][c_i+1].weights.push_back(getCornerWeight(tgf_in[r_i][c_i][s_i],corner_BL)); + + trigrid_corners_out[r_i][c_i].zs.push_back( (-tgf_in[r_i][c_i][s_i].normal(0,0)*corner_BR.x - tgf_in[r_i][c_i][s_i].normal(1,0)*corner_BR.y-tgf_in[r_i][c_i][s_i].d)/tgf_in[r_i][c_i][s_i].normal(2,0) ); + trigrid_corners_out[r_i][c_i].weights.push_back(getCornerWeight(tgf_in[r_i][c_i][s_i],corner_BR)); + + trigrid_centers_out[r_i][c_i].zs.push_back( (-tgf_in[r_i][c_i][s_i].normal(0,0)*corner_C.x - tgf_in[r_i][c_i][s_i].normal(1,0)*corner_C.y-tgf_in[r_i][c_i][s_i].d)/tgf_in[r_i][c_i][s_i].normal(2,0) ); + trigrid_centers_out[r_i][c_i].weights.push_back(getCornerWeight(tgf_in[r_i][c_i][s_i],corner_C)); + + break; + case 3: // right Tri-grid bin + // RL / RT / C + trigrid_corners_out[r_i][c_i].zs.push_back( (-tgf_in[r_i][c_i][s_i].normal(0,0)*corner_BR.x - tgf_in[r_i][c_i][s_i].normal(1,0)*corner_BR.y-tgf_in[r_i][c_i][s_i].d)/tgf_in[r_i][c_i][s_i].normal(2,0) ); + trigrid_corners_out[r_i][c_i].weights.push_back(getCornerWeight(tgf_in[r_i][c_i][s_i],corner_BR)); + + trigrid_corners_out[r_i+1][c_i].zs.push_back( (-tgf_in[r_i][c_i][s_i].normal(0,0)*corner_TR.x - tgf_in[r_i][c_i][s_i].normal(1,0)*corner_TR.y-tgf_in[r_i][c_i][s_i].d)/tgf_in[r_i][c_i][s_i].normal(2,0) ); + trigrid_corners_out[r_i+1][c_i].weights.push_back(getCornerWeight(tgf_in[r_i][c_i][s_i],corner_TR)); + + trigrid_centers_out[r_i][c_i].zs.push_back( (-tgf_in[r_i][c_i][s_i].normal(0,0)*corner_C.x - tgf_in[r_i][c_i][s_i].normal(1,0)*corner_C.y-tgf_in[r_i][c_i][s_i].d)/tgf_in[r_i][c_i][s_i].normal(2,0) ); + trigrid_centers_out[r_i][c_i].weights.push_back(getCornerWeight(tgf_in[r_i][c_i][s_i],corner_C)); + + break; + default: + break; + } + } + } + } + return; + }; + + TriGridCorner getMeanCorner(const TriGridCorner &corners_in){ + // get the mean of the corners + + TriGridCorner corners_out; + corners_out.x = corners_in.x; + corners_out.y = corners_in.y; + corners_out.zs.clear(); + corners_out.weights.clear(); + + double weighted_sum_z = 0.0; + double sum_w = 0.0; + for (int i = 0; i < (int) corners_in.zs.size(); i++){ + weighted_sum_z += corners_in.zs[i]*corners_in.weights[i]; + sum_w += corners_in.weights[i]; + } + + corners_out.zs.push_back(weighted_sum_z/sum_w); + corners_out.weights.push_back(sum_w); + + return corners_out; + } + + void updateTGFCornersCenters(std::vector<std::vector<TriGridCorner>>& trigrid_corners_out, + std::vector<std::vector<TriGridCorner>>& trigrid_centers_out) { + + // update corners + TriGridCorner updated_corner = empty_trigrid_corner_; + for (int r_i = 0; r_i < rows_ +1; r_i++) { + for (int c_i = 0; c_i < cols_ +1; c_i++) { + if (trigrid_corners_out[r_i][c_i].zs.size() > 0 && trigrid_corners_out[r_i][c_i].weights.size() > 0) { + updated_corner = getMeanCorner(trigrid_corners_out[r_i][c_i]); + trigrid_corners_out[r_i][c_i] = updated_corner; + } else { + trigrid_corners_out[r_i][c_i].zs.clear(); + trigrid_corners_out[r_i][c_i].weights.clear(); + } + } + } + + // update centers + TriGridCorner updated_center = empty_trigrid_center_; + for (int r_i = 0; r_i < rows_; r_i++) { + for (int c_i = 0; c_i < cols_; c_i++) { + if (trigrid_centers_out[r_i][c_i].zs.size() > 0 && trigrid_centers_out[r_i][c_i].weights.size() > 0) { + updated_center = getMeanCorner(trigrid_centers_out[r_i][c_i]); + trigrid_centers_out[r_i][c_i] = updated_center; + // trigrid_centers_out[r_i][c_i].z = get_mean(trigrid_centers_out[r_i][c_i].zs,trigrid_centers_out[r_i][c_i].weights); + } else { + trigrid_centers_out[r_i][c_i].zs.clear(); + trigrid_centers_out[r_i][c_i].weights.clear(); + } + } + } + + return; + }; + + Eigen::Vector3f convertCornerToEigen(TriGridCorner &corner_in) { + Eigen::Vector3f corner_out; + if (corner_in.zs.size() != corner_in.weights.size()){ + ROS_WARN("ERROR in corners"); + } + corner_out[0] = corner_in.x; + corner_out[1] = corner_in.y; + corner_out[2] = corner_in.zs[0]; + return corner_out; + }; + + void revertTraversableNodes(std::vector<std::vector<TriGridCorner>>& trigrid_corners_in, + std::vector<std::vector<TriGridCorner>>& trigrid_centers_in, + TriGridField<PointType>& tgf_out) { + Eigen::Vector3f refined_corner_1, refined_corner_2, refined_center; + for (int r_i = 0; r_i < rows_; r_i++) { + for (int c_i = 0; c_i < cols_; c_i++) { + for (int s_i = 0; s_i < (int) tgf_out[r_i][c_i].size(); s_i++) { + // set the corners for the current trigrid node + switch (s_i) + { + case 0: + if ( trigrid_corners_in[r_i+1][c_i].zs.size()==0 || trigrid_corners_in[r_i+1][c_i+1].zs.size()==0 || trigrid_centers_in[r_i][c_i].zs.size()==0 ){ + if (tgf_out[r_i][c_i][s_i].node_type != NONGROUND){ + tgf_out[r_i][c_i][s_i].node_type = UNKNOWN; + } + continue; + } + refined_corner_1 = convertCornerToEigen(trigrid_corners_in[r_i+1][c_i]); + refined_corner_2 = convertCornerToEigen(trigrid_corners_in[r_i+1][c_i+1]); + refined_center = convertCornerToEigen(trigrid_centers_in[r_i][c_i]); + break; + case 1: + if ( trigrid_corners_in[r_i+1][c_i+1].zs.size()==0 || trigrid_corners_in[r_i][c_i+1].zs.size()==0 || trigrid_centers_in[r_i][c_i].zs.size()==0 ){ + if (tgf_out[r_i][c_i][s_i].node_type != NONGROUND){ + tgf_out[r_i][c_i][s_i].node_type = UNKNOWN; + } + continue; + } + refined_corner_1 = convertCornerToEigen(trigrid_corners_in[r_i+1][c_i+1]); + refined_corner_2 = convertCornerToEigen(trigrid_corners_in[r_i][c_i+1]); + refined_center = convertCornerToEigen(trigrid_centers_in[r_i][c_i]); + break; + case 2: + if ( trigrid_corners_in[r_i][c_i+1].zs.size()==0 || trigrid_corners_in[r_i][c_i].zs.size()==0 || trigrid_centers_in[r_i][c_i].zs.size()==0 ){ + if (tgf_out[r_i][c_i][s_i].node_type != NONGROUND){ + tgf_out[r_i][c_i][s_i].node_type = UNKNOWN; + } + continue; + } + refined_corner_1 = convertCornerToEigen(trigrid_corners_in[r_i][c_i+1]); + refined_corner_2 = convertCornerToEigen(trigrid_corners_in[r_i][c_i]); + refined_center = convertCornerToEigen(trigrid_centers_in[r_i][c_i]); + break; + case 3: + if ( trigrid_corners_in[r_i][c_i].zs.size()==0 || trigrid_corners_in[r_i+1][c_i].zs.size()==0 || trigrid_centers_in[r_i][c_i].zs.size()==0 ){ + if (tgf_out[r_i][c_i][s_i].node_type != NONGROUND){ + tgf_out[r_i][c_i][s_i].node_type = UNKNOWN; + } + continue; + } + refined_corner_1 = convertCornerToEigen(trigrid_corners_in[r_i][c_i]); + refined_corner_2 = convertCornerToEigen(trigrid_corners_in[r_i+1][c_i]); + refined_center = convertCornerToEigen(trigrid_centers_in[r_i][c_i]); + break; + default: + ROS_ERROR("WRONG tri-grid indexing"); + break; + } + + // calculate the refined planar model in the node + Eigen::Vector3f udpated_normal = (refined_corner_1-refined_center).cross(refined_corner_2-refined_center); + udpated_normal /= udpated_normal.norm(); + // if (udpated_normal[2] < 0){ + // std::cout << "Origin normal: " << tgf_out[r_i][c_i][s_i].normal << std::endl; + // std::cout << "Update normal: " << udpated_normal << std::endl; + // } + if (udpated_normal(2,0) < TH_NORMAL_ ){ // non-planar + tgf_out[r_i][c_i][s_i].normal = udpated_normal; + tgf_out[r_i][c_i][s_i].node_type = NONGROUND; + } else { + // planar + Eigen::Vector3f updated_mean_pt; + updated_mean_pt[0] = (refined_corner_1[0] + refined_corner_2[0] + refined_center[0])/3; + updated_mean_pt[1] = (refined_corner_1[1] + refined_corner_2[1] + refined_center[1])/3; + updated_mean_pt[2] = (refined_corner_1[2] + refined_corner_2[2] + refined_center[2])/3; + + tgf_out[r_i][c_i][s_i].normal = udpated_normal; + tgf_out[r_i][c_i][s_i].mean_pt = updated_mean_pt; + tgf_out[r_i][c_i][s_i].d = -(udpated_normal.dot(updated_mean_pt)); + tgf_out[r_i][c_i][s_i].th_dist_d = TH_DIST_ - tgf_out[r_i][c_i][s_i].d; + tgf_out[r_i][c_i][s_i].th_outlier_d = -TH_OUTLIER_ - tgf_out[r_i][c_i][s_i].d; + + tgf_out[r_i][c_i][s_i].node_type = GROUND; + } + } + } + } + + return; + }; + + void fitTGFWiseTraversableTerrainModel(TriGridField<PointType>& tgf, + std::vector<std::vector<TriGridCorner>>& trigrid_corners, + std::vector<std::vector<TriGridCorner>>& trigrid_centers) { + + updateTGFCornersCenters(trigrid_corners, trigrid_centers); + + revertTraversableNodes(trigrid_corners, trigrid_centers, tgf); + + return; + }; + + void segmentNodeGround(const TriGridNode<PointType>& node_in, + pcl::PointCloud<PointType>& node_ground_out, + pcl::PointCloud<PointType>& node_nonground_out, + pcl::PointCloud<PointType>& node_obstacle_out, + pcl::PointCloud<PointType>& node_outlier_out) { + node_ground_out.clear(); + node_nonground_out.clear(); + node_obstacle_out.clear(); + node_outlier_out.clear(); + + // segment ground + Eigen::MatrixXf points(node_in.ptCloud.points.size(),3); + int j = 0; + for (auto& p:node_in.ptCloud.points){ + points.row(j++)<<p.x, p.y, p.z; + } + + Eigen::VectorXf result = points*node_in.normal; + for (int r = 0; r<result.rows(); r++){ + if (result[r]<node_in.th_dist_d){ + if (result[r]<node_in.th_outlier_d){ + node_outlier_out.push_back(node_in.ptCloud.points[r]); + } else { + node_ground_out.push_back(node_in.ptCloud.points[r]); + } + } else { + node_nonground_out.push_back(node_in.ptCloud.points[r]); + if (result[r]<TH_OBSTACLE_HEIGHT_ - node_in.d){ + node_obstacle_out.push_back(node_in.ptCloud.points[r]); + node_obstacle_out.points.back().intensity = result[r] + node_in.d; + } + } + } + + return; + } + + void segmentTGFGround(const TriGridField<PointType>& tgf_in, + pcl::PointCloud<PointType>& ground_cloud_out, + pcl::PointCloud<PointType>& nonground_cloud_out, + pcl::PointCloud<PointType>& obstacle_cloud_out, + pcl::PointCloud<PointType>& outlier_cloud_out) { + ground_cloud_out.clear(); + nonground_cloud_out.clear(); + obstacle_cloud_out.clear(); + + for (int r_i = 0; r_i < rows_; r_i++){ + for (int c_i = 0; c_i < cols_; c_i++){ + for (int s_i = 0; s_i < tgf_in[r_i][c_i].size(); s_i++) { + if (!tgf_in[r_i][c_i][s_i].is_curr_data) { + continue; + } + if (tgf_in[r_i][c_i][s_i].node_type == GROUND) { + segmentNodeGround(tgf_in[r_i][c_i][s_i], ptCloud_nodewise_ground_, ptCloud_nodewise_nonground_, ptCloud_nodewise_obstacle_, ptCloud_nodewise_outliers_); + } else { + ptCloud_nodewise_nonground_ = tgf_in[r_i][c_i][s_i].ptCloud; + ptCloud_nodewise_obstacle_ = tgf_in[r_i][c_i][s_i].ptCloud; + } + ground_cloud_out += ptCloud_nodewise_ground_; + nonground_cloud_out += ptCloud_nodewise_nonground_; + outlier_cloud_out += ptCloud_nodewise_outliers_; + obstacle_cloud_out += ptCloud_nodewise_obstacle_; + } + } + } + + return; + }; + + void segmentTGFGround_developing(const TriGridField<PointType>& tgf_in, + pcl::PointCloud<PointType>& ground_cloud_out, + pcl::PointCloud<PointType>& nonground_cloud_out, + pcl::PointCloud<PointType>& obstacle_cloud_out, + pcl::PointCloud<PointType>& outlier_cloud_out) { + ground_cloud_out.clear(); + nonground_cloud_out.clear(); + obstacle_cloud_out.clear(); + TriGridIdx curr_tgf_idx; + std::vector<TriGridIdx> adj_idx_vec; + pcl::PointCloud<PointType> outlier_tmp; + outlier_tmp.clear(); + outlier_tmp.reserve(NODEWISE_PTCLOUDSIZE); + for (int r_i = 0; r_i < rows_; r_i++){ + for (int c_i = 0; c_i < cols_; c_i++){ + for (int s_i = 0; s_i < (int) tgf_in[r_i][c_i].size(); s_i++) { + if (!tgf_in[r_i][c_i][s_i].is_curr_data) { + continue; + } + if (tgf_in[r_i][c_i][s_i].node_type == UNKNOWN){ + continue; + } + if (tgf_in[r_i][c_i][s_i].node_type == GROUND) { + segmentNodeGround(tgf_in[r_i][c_i][s_i], ptCloud_nodewise_ground_, ptCloud_nodewise_nonground_, ptCloud_nodewise_obstacle_, ptCloud_nodewise_outliers_); + } else { + curr_tgf_idx.row = r_i; + curr_tgf_idx.col = c_i; + curr_tgf_idx.tri = s_i; + + searchAdjacentNodes(curr_tgf_idx, adj_idx_vec); + if (adj_idx_vec.empty()){ + ptCloud_nodewise_nonground_ = tgf_in[r_i][c_i][s_i].ptCloud; + ptCloud_nodewise_obstacle_ = tgf_in[r_i][c_i][s_i].ptCloud; + } else { + TriGridIdx highest_adj_tri_idx; + double highest_weight = 0; + bool is_adjacent = false; + for (int adj_i = 0; adj_i<adj_idx_vec.size() ; adj_i++){ + if (getTriGridNode(adj_idx_vec[adj_i]).weight < TH_WEIGHT_) { + continue; + } + is_adjacent = true; + if (getTriGridNode(adj_idx_vec[adj_i]).weight > highest_weight) { + highest_weight = getTriGridNode(adj_idx_vec[adj_i]).weight; + highest_adj_tri_idx = adj_idx_vec[adj_i]; + } + } + + if (is_adjacent){ + TriGridNode<PointType> tmp_node = getTriGridNode(highest_adj_tri_idx); + tmp_node.ptCloud = getTriGridNode(curr_tgf_idx).ptCloud; + segmentNodeGround(tmp_node, ptCloud_nodewise_ground_, ptCloud_nodewise_nonground_, ptCloud_nodewise_obstacle_, ptCloud_nodewise_outliers_); + } else { + ptCloud_nodewise_nonground_ = tgf_in[r_i][c_i][s_i].ptCloud; + ptCloud_nodewise_obstacle_ = tgf_in[r_i][c_i][s_i].ptCloud; + } + } + } + ground_cloud_out += ptCloud_nodewise_ground_; + nonground_cloud_out += ptCloud_nodewise_nonground_; + obstacle_cloud_out += ptCloud_nodewise_obstacle_; + outlier_cloud_out += ptCloud_nodewise_outliers_; + } + } + } + + return; + }; + + // functions for visualization + + geometry_msgs::PolygonStamped setPlanarModel (const TriGridNode<PointType>& node_in, const TriGridIdx& node_idx) { + geometry_msgs::PolygonStamped polygon_out; + polygon_out.header = msg_header_; + geometry_msgs::Point32 corner_0, corner_1, corner_2; + int r_i = node_idx.row; + int c_i = node_idx.col; + int s_i = node_idx.tri; + if (node_in.node_type == GROUND){ + switch (s_i){ + case 0: + //topx lowy & topx topy + corner_1.x = (r_i+1)*TGF_RESOLUTION_+tgf_min_x; corner_1.y = (c_i)*TGF_RESOLUTION_+tgf_min_y; + corner_1.z = (-node_in.normal(0,0)*corner_1.x - node_in.normal(1,0)*corner_1.y-node_in.d)/node_in.normal(2,0); + + corner_2.x = (r_i+1)*TGF_RESOLUTION_+tgf_min_x; corner_2.y = (c_i+1)*TGF_RESOLUTION_+tgf_min_y; + corner_2.z = (-node_in.normal(0,0)*corner_2.x - node_in.normal(1,0)*corner_2.y-node_in.d)/node_in.normal(2,0); + + corner_0.x = (r_i+0.5)*TGF_RESOLUTION_+tgf_min_x; corner_0.y = (c_i+0.5)*TGF_RESOLUTION_+tgf_min_y; + corner_0.z = (-node_in.normal(0,0)*corner_0.x - node_in.normal(1,0)*corner_0.y-node_in.d)/node_in.normal(2,0); + break; + case 1: + //topx topy & lowx topy + corner_1.x = (r_i+1)*TGF_RESOLUTION_+tgf_min_x; corner_1.y = (c_i+1)*TGF_RESOLUTION_+tgf_min_y; + corner_1.z = (-node_in.normal(0,0)*corner_1.x - node_in.normal(1,0)*corner_1.y-node_in.d)/node_in.normal(2,0); + + corner_2.x = (r_i)*TGF_RESOLUTION_+tgf_min_x; corner_2.y = (c_i+1)*TGF_RESOLUTION_+tgf_min_y; + corner_2.z = (-node_in.normal(0,0)*corner_2.x - node_in.normal(1,0)*corner_2.y-node_in.d)/node_in.normal(2,0); + + corner_0.x = (r_i+0.5)*TGF_RESOLUTION_+tgf_min_x; corner_0.y = (c_i+0.5)*TGF_RESOLUTION_+tgf_min_y; + corner_0.z = (-node_in.normal(0,0)*corner_0.x - node_in.normal(1,0)*corner_0.y-node_in.d)/node_in.normal(2,0); + break; + case 2: + //lowx topy & lowx lowy + corner_1.x = (r_i)*TGF_RESOLUTION_+tgf_min_x; corner_1.y = (c_i+1)*TGF_RESOLUTION_+tgf_min_y; + corner_1.z = (-node_in.normal(0,0)*corner_1.x - node_in.normal(1,0)*corner_1.y-node_in.d)/node_in.normal(2,0); + + corner_2.x = (r_i)*TGF_RESOLUTION_+tgf_min_x; corner_2.y = c_i*TGF_RESOLUTION_+tgf_min_y; + corner_2.z = (-node_in.normal(0,0)*corner_2.x - node_in.normal(1,0)*corner_2.y-node_in.d)/node_in.normal(2,0); + + corner_0.x = (r_i+0.5)*TGF_RESOLUTION_+tgf_min_x; corner_0.y = (c_i+0.5)*TGF_RESOLUTION_+tgf_min_y; + corner_0.z = (-node_in.normal(0,0)*corner_0.x - node_in.normal(1,0)*corner_0.y-node_in.d)/node_in.normal(2,0); + break; + case 3: + //lowx lowy & topx lowy + corner_1.x = (r_i)*TGF_RESOLUTION_+tgf_min_x; corner_1.y = (c_i)*TGF_RESOLUTION_+tgf_min_y; + corner_1.z = (-node_in.normal(0,0)*corner_1.x - node_in.normal(1,0)*corner_1.y-node_in.d)/node_in.normal(2,0); + + corner_2.x = (r_i+1)*TGF_RESOLUTION_+tgf_min_x; corner_2.y = (c_i)*TGF_RESOLUTION_+tgf_min_y; + corner_2.z = (-node_in.normal(0,0)*corner_2.x - node_in.normal(1,0)*corner_2.y-node_in.d)/node_in.normal(2,0); + + corner_0.x = (r_i+0.5)*TGF_RESOLUTION_+tgf_min_x; corner_0.y = (c_i+0.5)*TGF_RESOLUTION_+tgf_min_y; + corner_0.z = (-node_in.normal(0,0)*corner_0.x - node_in.normal(1,0)*corner_0.y-node_in.d)/node_in.normal(2,0); + break; + default: + break; + } + } else { + switch (s_i){ + case 0: + //topx lowy & topx topy + corner_1.x = (r_i+1)*TGF_RESOLUTION_+tgf_min_x; corner_1.y = (c_i)*TGF_RESOLUTION_+tgf_min_y; + corner_1.z = -2.0; + + corner_2.x = (r_i+1)*TGF_RESOLUTION_+tgf_min_x; corner_2.y = (c_i+1)*TGF_RESOLUTION_+tgf_min_y; + corner_2.z = -2.0; + + corner_0.x = (r_i+0.5)*TGF_RESOLUTION_+tgf_min_x; corner_0.y = (c_i+0.5)*TGF_RESOLUTION_+tgf_min_y; + corner_0.z = -2.0; + break; + case 1: + //topx topy & lowx topy + corner_1.x = (r_i+1)*TGF_RESOLUTION_+tgf_min_x; corner_1.y = (c_i+1)*TGF_RESOLUTION_+tgf_min_y; + corner_1.z = -2.0; + + corner_2.x = (r_i)*TGF_RESOLUTION_+tgf_min_x; corner_2.y = (c_i+1)*TGF_RESOLUTION_+tgf_min_y; + corner_2.z = -2.0; + + corner_0.x = (r_i+0.5)*TGF_RESOLUTION_+tgf_min_x; corner_0.y = (c_i+0.5)*TGF_RESOLUTION_+tgf_min_y; + corner_0.z = -2.0; + break; + case 2: + //lowx topy & lowx lowy + corner_1.x = (r_i)*TGF_RESOLUTION_+tgf_min_x; corner_1.y = (c_i+1)*TGF_RESOLUTION_+tgf_min_y; + corner_1.z = -2.0; + + corner_2.x = (r_i)*TGF_RESOLUTION_+tgf_min_x; corner_2.y = c_i*TGF_RESOLUTION_+tgf_min_y; + corner_2.z = -2.0; + + corner_0.x = (r_i+0.5)*TGF_RESOLUTION_+tgf_min_x; corner_0.y = (c_i+0.5)*TGF_RESOLUTION_+tgf_min_y; + corner_0.z = -2.0; + break; + case 3: + //lowx lowy & topx lowy + corner_1.x = (r_i)*TGF_RESOLUTION_+tgf_min_x; corner_1.y = (c_i)*TGF_RESOLUTION_+tgf_min_y; + corner_1.z = -2.0; + + corner_2.x = (r_i+1)*TGF_RESOLUTION_+tgf_min_x; corner_2.y = (c_i)*TGF_RESOLUTION_+tgf_min_y; + corner_2.z = -2.0; + + corner_0.x = (r_i+0.5)*TGF_RESOLUTION_+tgf_min_x; corner_0.y = (c_i+0.5)*TGF_RESOLUTION_+tgf_min_y; + corner_0.z = -2.0; + break; + default: + std::cout << "the error in sub-idx" << std::endl; + break; + } + } + + polygon_out.polygon.points.reserve(3); + polygon_out.polygon.points.push_back(corner_0); + polygon_out.polygon.points.push_back(corner_2); + polygon_out.polygon.points.push_back(corner_1); + return polygon_out; + } + + void publishTriGridFieldGraph() { + viz_trigrid_polygons_.header = msg_header_; + viz_trigrid_polygons_.polygons.clear(); + viz_trigrid_polygons_.likelihood.clear(); + + // visualize the graph: nodes + for (int r_i = 0; r_i < rows_; r_i++){ + for (int c_i = 0; c_i < cols_; c_i++){ + for (int s_i = 0; s_i < (int) trigrid_field_[r_i][c_i].size(); s_i++){ + TriGridIdx curr_idx = {r_i, c_i, s_i}; + if (trigrid_field_[r_i][c_i][s_i].node_type == GROUND) { + viz_trigrid_polygons_.polygons.push_back(setPlanarModel(trigrid_field_[r_i][c_i][s_i], curr_idx)); + // if (trigrid_field_[r_i][c_i][s_i].normal[2] < 0.99) { + // viz_trigrid_polygons_.likelihood.push_back(0.25); + // } else { + viz_trigrid_polygons_.likelihood.push_back(0.0); + // } + + } else if (trigrid_field_[r_i][c_i][s_i].node_type == NONGROUND) { + // continue; + viz_trigrid_polygons_.polygons.push_back(setPlanarModel(trigrid_field_[r_i][c_i][s_i], curr_idx)); + viz_trigrid_polygons_.likelihood.push_back(1.0); + + } else if (trigrid_field_[r_i][c_i][s_i].node_type == UNKNOWN) { + // continue; + if (trigrid_field_[r_i][c_i][s_i].is_curr_data) { + viz_trigrid_polygons_.polygons.push_back(setPlanarModel(trigrid_field_[r_i][c_i][s_i], curr_idx)); + viz_trigrid_polygons_.likelihood.push_back(0.5); + } + } else { + ROS_WARN("Unknown Node Type"); + } + } + } + } + + //visualize the graph: edges + viz_trigrid_edges_.header = msg_header_; + viz_trigrid_edges_.points.clear(); + geometry_msgs::Point src_pt; + geometry_msgs::Point tgt_pt; + for (int e_i = 0; e_i < (int) trigrid_edges_.size(); e_i++){ + if (trigrid_edges_[e_i].is_traversable){ + viz_trigrid_edges_.color.a = 1.0; + viz_trigrid_edges_.color.r = 1.0; + viz_trigrid_edges_.color.g = 1.0; + viz_trigrid_edges_.color.b = 0.0; + } else { + viz_trigrid_edges_.color.a = 0.1; + viz_trigrid_edges_.color.r = 1.0; + viz_trigrid_edges_.color.g = 1.0; + viz_trigrid_edges_.color.b = 1.0; + } + + src_pt.x = trigrid_field_[trigrid_edges_[e_i].Pair.first.row][trigrid_edges_[e_i].Pair.first.col][trigrid_edges_[e_i].Pair.first.tri].mean_pt[0]; + src_pt.y = trigrid_field_[trigrid_edges_[e_i].Pair.first.row][trigrid_edges_[e_i].Pair.first.col][trigrid_edges_[e_i].Pair.first.tri].mean_pt[1]; + src_pt.z = trigrid_field_[trigrid_edges_[e_i].Pair.first.row][trigrid_edges_[e_i].Pair.first.col][trigrid_edges_[e_i].Pair.first.tri].mean_pt[2]; + + tgt_pt.x = trigrid_field_[trigrid_edges_[e_i].Pair.second.row][trigrid_edges_[e_i].Pair.second.col][trigrid_edges_[e_i].Pair.second.tri].mean_pt[0]; + tgt_pt.y = trigrid_field_[trigrid_edges_[e_i].Pair.second.row][trigrid_edges_[e_i].Pair.second.col][trigrid_edges_[e_i].Pair.second.tri].mean_pt[1]; + tgt_pt.z = trigrid_field_[trigrid_edges_[e_i].Pair.second.row][trigrid_edges_[e_i].Pair.second.col][trigrid_edges_[e_i].Pair.second.tri].mean_pt[2]; + + viz_trigrid_edges_.points.push_back(src_pt); + viz_trigrid_edges_.points.push_back(tgt_pt); + } + + // pub_trigrid_nodes_.publish(viz_trigrid_polygons_); + // pub_trigrid_edges_.publish(viz_trigrid_edges_); + return; + }; + + // void publishTriGridCorners() { + // viz_trigrid_corners_.header = cloud_header_; + // viz_trigrid_corners_.points.clear(); + + // TriGridCorner curr_corner; + // pcl::PointXYZ corner_pt; + // // for corners + // for (int r_i = 0; r_i < (int) trigrid_corners_.size(); r_i++){ + // for (int c_i = 0; c_i < (int) trigrid_corners_[0].size(); c_i++){ + // curr_corner = trigrid_corners_[r_i][c_i]; + // if (curr_corner.zs.size() != curr_corner.weights.size()){ + // ROS_WARN("ERROR in corners"); + // } + // for (int i = 0; i < (int) curr_corner.zs.size(); i++){ + // corner_pt.x = curr_corner.x; + // corner_pt.y = curr_corner.y; + // corner_pt.z = curr_corner.zs[i]; + // viz_trigrid_corners_.points.push_back(corner_pt); + // } + // } + // } + + // // for centers + // for (int r_i = 0; r_i < (int) trigrid_centers_.size(); r_i++){ + // for (int c_i = 0; c_i < (int) trigrid_centers_[0].size(); c_i++){ + // curr_corner = trigrid_centers_[r_i][c_i]; + // if (curr_corner.zs.size() != curr_corner.weights.size()){ + // ROS_WARN("ERROR in corners"); + // } + // for (int i = 0; i < (int) curr_corner.zs.size(); i++){ + // corner_pt.x = curr_corner.x; + // corner_pt.y = curr_corner.y; + // corner_pt.z = curr_corner.zs[i]; + // viz_trigrid_corners_.points.push_back(corner_pt); + // } + // } + // } + + // pub_trigrid_corners_.publish(viz_trigrid_corners_); + // return; + // }; + + // sensor_msgs::PointCloud2 convertCloudToRosMsg(pcl::PointCloud<PointType>& cloud, std::string &frame_id) { + // sensor_msgs::PointCloud2 cloud_msg; + // pcl::toROSMsg(cloud, cloud_msg); + // cloud_msg.header.frame_id = frame_id; + // return cloud_msg; + // }; + + // void publishPointClouds(){ + // ptCloud_tgfwise_ground_.header = cloud_header_; + // pub_tgseg_ground_cloud.publish(convertCloudToRosMsg(ptCloud_tgfwise_ground_, cloud_header_.frame_id)); + + // ptCloud_tgfwise_nonground_.header = cloud_header_; + // pub_tgseg_nonground_cloud.publish(convertCloudToRosMsg(ptCloud_tgfwise_nonground_, cloud_header_.frame_id)); + + // ptCloud_tgfwise_outliers_.header = cloud_header_; + // pub_tgseg_outliers_cloud.publish(convertCloudToRosMsg(ptCloud_tgfwise_outliers_, cloud_header_.frame_id)); + // } + }; +} +#endif diff --git a/launch/iri_ground_segmentation.launch b/launch/iri_ground_segmentation.launch index 1e64bbdacc744af860cb1f5f756bcf6258c04d36..eea1a684fbdaff7c5917ecbf71b1102f8b9a1a45 100644 --- a/launch/iri_ground_segmentation.launch +++ b/launch/iri_ground_segmentation.launch @@ -1,5 +1,7 @@ <?xml version="1.0"?> <launch> <rosparam command="load" file="$(find iri_ground_segmentation)/params/iri_ground_segmentation.yaml" /> - <node pkg="iri_ground_segmentation" type="iri_ground_segmentation" name="iri_ground_segmentation" output="screen"/> + <remap from="/lidar_points_extended_structure" to="/xyzi_to_extended_structure_converter/lidar_points_extended_structure"/> + <node pkg="iri_ground_segmentation" type="iri_ground_segmentation" name="iri_ground_segmentation" output="screen" respawn="false"> + </node> </launch> diff --git a/package.xml b/package.xml index 3e435087400fa895afd3e5d2168353f28199ef0c..681853aa9ed150a13a45e7056bf1d9ee7a19c147 100644 --- a/package.xml +++ b/package.xml @@ -63,7 +63,9 @@ <exec_depend>std_msgs</exec_depend> <exec_depend>message_runtime</exec_depend> - + <exec_depend>jsk_recognition</exec_depend> + <exec_depend>jsk_common_msgs</exec_depend> + <exec_depend>jsk_rviz_plugins</exec_depend> <!-- The export tag contains other, unspecified, tags --> <export> diff --git a/params/iri_ground_segmentation.yaml b/params/iri_ground_segmentation.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6f4b292e118fff76d3bdde19233a6e0bad3cd00a --- /dev/null +++ b/params/iri_ground_segmentation.yaml @@ -0,0 +1,58 @@ +iri_ground_segmentation: { + rate: 10.00, + + # Robot related geometric parameters + sensor_height: 1.73, ## Ground is expected to be at z = -1*sensor_height + robot_height: 2.00, ## All obstacle points above this value (w.r.t the z ground predicted by the algorithm) are treated as overhanging obstacles + + # Parameters affecting the exploration of the pointcloud + ROI_delta_x_and_y: 3.0, #4.0, ## This value sets the size of the ROIs: ROI_size = (2*ROI_delta_x_and_y)^2 + ROI_shadow_area: 7.0, #6.5, ## This value is the same that the above, but only used in the root vertex to overcome the shadow area + + ground_reference_search_resolution_deg: 12, #40.0, #6.0, #12.00, ## It is the angular resolution when generating new ground references (graph nodes), + ## it will affect the number of nodes in the graph: lower values generate more nodes + + elevation_grid_resolution: 2.1, #1.2, #1.5, #1.0 #0.5, ## This value is used to create the "elevation point cloud" which is a reduction of the original pointcloud, where + ## only the lowest z values in each cell are stored (along with a vector of indexes pointing to the remaining points + ## in the cell, so that the original pointcloud can be recovered or labeled using the info in the "elevation cloud"). + ## Big values can speed up the algorithm but generates a lower resolution ground model, on the other hand, small values + ## can produce outliers because we would like to have only ground points in the elevation cloud (however these outliers + ## are usually succesfully rejected by the mahalanobis threshold and the "prior" information) + + # Kalman filter noise parameters + ## initial uncertainties + z_initial_std_dev: 0.05, ## Uncertainty in z = -1*sensor_height + initial_angular_std_dev_deg: 1.5, ## Used to initialize x_slope and y_slope variances + + ## propagation additive noises + propagation_z_additive_noise_per_meter: 0.01, ## Uncertainty added to var_z as a function of the distance with the parent vertex + propagation_additive_noise_deg_per_meter: 0.4, ## Uncertainty added to x_slope and y_slope as a function of the distance with the parent vertex + ## (it is expressed in degrees per meter, but inside the code it is converted to slope per meter) + # measurement noise + z_observation_std_dev: 0.3, #0.15, #0.3, #0.45, for elevation_grid_resolution = 0.5 ## Uncertainty in the Lidar observations + + # threshold for outlier rejection and classification + mahalanobis_threshold: 3.0, #2.7, #3.0, ## To classify points as obstacles or ground (a small value will cause false obstacle points, + ## and a big value will increase the number of false ground points) + + # Neural Network related parameters + use_neural_network: true, + extract_data_to_external_training_of_the_network: false, + dataset_filename_with_global_path: '/home/idelpino/Documentos/dataset_rgb_hsv_olbp_10_frame_inc.csv', + neural_net_filename_with_global_path: '/media/sf_virtual_box_shared/neural_networks/veg_terrain_and_obs_13_features_39_neurons.csv', #five_classes_13_features_39_neurons.csv' + neural_net_number_of_features: 13, + neural_net_number_of_neurons_in_hidden_layer: 39, + neural_net_number_of_neurons_in_output_layer: 2, + + # labeling parameters + max_pred_std_dev_for_labelling: 0.333, ## ONLY IN USE TO GIVE COLOUR TO DENSE RECONSTRUCTION + score_threshold: 0.0, ## for assigning ground class label: one means maha. dist. equal to zero, zero means mahalanobis dist equal to maha. thres + classify_not_labeled_points_as_obstacles: true, ## when a point has no reference satisfying the max_pred_std_dev_for_labelling threshold we can leave as unknown or label it as obstacle + ground_threshold_in_not_analyzed_areas: 0.1, ## when it is not possible to make a local analysis of the data, we will use the lowest point (the one in elevation_cloud) as ground height, and + ## all the points above it and below this threshold will be classified as ground + + # visualization and debug parameters + measure_performance: false, ## (feature still not debugged) Flag to measure number of execution and execution times of the different functions of the algorithm + show_dense_reconstruction: false, ## To show a dense ground surface reconstruction using the predictions of the ground mode (colored using the std_dev of z coordinate) + ## or alternatively the "elevation point cloud" (useful for parameter tunning) +} diff --git a/params/iri_ground_segmentation_no_random_forest.yaml b/params/iri_ground_segmentation_no_random_forest.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b0f2b2952c5d3578dc4a8aabf4c4d92f3d7e0f6f --- /dev/null +++ b/params/iri_ground_segmentation_no_random_forest.yaml @@ -0,0 +1,49 @@ +iri_ground_segmentation: { + rate: 10.00, + + # Robot related geometric parameters + sensor_height: 1.73, ## Ground is expected to be at z = -1*sensor_height + robot_height: 2.00, ## All obstacle points above this value (w.r.t the z ground predicted by the algorithm) are treated as overhanging obstacles + + # Parameters affecting the exploration of the pointcloud + ROI_delta_x_and_y: 3.0, ## This value sets the size of the ROIs: ROI_size = (2*ROI_delta_x_and_y)^2 + ROI_shadow_area: 5.5, ## This value is the same that the above, but only used in the root vertex to overcome the shadow area + + ground_reference_search_resolution_deg: 40.0, #12.00, ## It is the angular resolution when generating new ground references (graph nodes), + ## it will affect the number of nodes in the graph: lower values generate more nodes + + elevation_grid_resolution: 2.1, #0.333, ## This value is used to create the "elevation point cloud" which is a reduction of the original pointcloud, where + ## only the lowest z values in each cell are stored (along with a vector of indexes pointing to the remaining points + ## in the cell, so that the original pointcloud can be recovered or labeled using the info in the "elevation cloud"). + ## Big values can speed up the algorithm but generates a lower resolution ground model, on the other hand, small values + ## can produce outliers because we would like to have only ground points in the elevation cloud (however these outliers + ## are usually succesfully rejected by the mahalanobis threshold and the "prior" information) + + # Kalman filter noise parameters + ## initial uncertainties + z_initial_std_dev: 0.05, ## Uncertainty in z = -1*sensor_height + initial_angular_std_dev_deg: 1.5, ## Used to initialize x_slope and y_slope variances + + ## propagation additive noises + propagation_z_additive_noise_per_meter: 0.01, ## Uncertainty added to var_z as a function of the distance with the parent vertex + propagation_additive_noise_deg_per_meter: 0.4, ## Uncertainty added to x_slope and y_slope as a function of the distance with the parent vertex + ## (it is expressed in degrees per meter, but inside the code it is converted to slope per meter) + # measurement noise + z_observation_std_dev: 0.15, ## Uncertainty in the Lidar observations + + # threshold for outlier rejection and classification + mahalanobis_threshold: 2.7, ## To classify points as obstacles or ground (a small value will cause false obstacle points, + ## and a big value will increase the number of false ground points) + + # labeling parameters + max_pred_std_dev_for_labelling: 0.5, ## ONLY IN USE TO GIVE COLOUR TO DENSE RECONSTRUCTION + score_threshold: 0.0, ## for assigning ground class label: one means maha. dist. equal to zero, zero means mahalanobis dist equal to maha. thres + classify_not_labeled_points_as_obstacles: true, ## when a point has no reference satisfying the max_pred_std_dev_for_labelling threshold we can leave as unknown or label it as obstacle + ground_threshold_in_not_analyzed_areas: 0.1, ## when it is not possible to make a local analysis of the data, we will use the lowest point (the one in elevation_cloud) as ground height, and + ## all the points above it and below this threshold will be classified as ground + + # visualization and debug parameters + measure_performance: false, ## (feature still not debugged) Flag to measure number of execution and execution times of the different functions of the algorithm + show_dense_reconstruction: false, ## To show a dense ground surface reconstruction using the predictions of the ground mode (colored using the std_dev of z coordinate) + ## or alternatively the "elevation point cloud" (useful for parameter tunning) +} diff --git a/src/ground_segmentation_alg.cpp b/src/ground_segmentation_alg.cpp index 410b623e0ab4f2da97028f9c7525479a250df499..70c807a7abb918fbfd6e1042729e4eee0ce6ea66 100644 --- a/src/ground_segmentation_alg.cpp +++ b/src/ground_segmentation_alg.cpp @@ -1,4 +1,6 @@ #include "ground_segmentation_alg.h" +#include "tgs.hpp" +#include "aos.hpp" GroundSegmentationAlgorithm::GroundSegmentationAlgorithm(void) { @@ -40,204 +42,306 @@ void GroundSegmentationAlgorithm::segmentGround( std::vector<std::vector<int>> edges(100000); std::vector < std::vector < kf_based_terrain_analysis_lib::Edge >> discarded_edges(100000); - //std::cout << "Calling to processor_.groundSegmentation!" << std::endl; - - processor_.groundSegmentation(lidar_configuration_, filtering_configuration_, lidar_pcl_cloud, - roadmap_vertices_pcl_cloud, edges, discarded_edges, - ground_dense_reconstruction_pcl_cloud); + bool use_travel = false; + if (use_travel) + { + // Using TRAVEL for comparison! + pcl::PointCloud<pcl::PointXYZI>::Ptr cloud_in; + pcl::PointCloud<pcl::PointXYZI>::Ptr ground_pc; + pcl::PointCloud<pcl::PointXYZI>::Ptr nonground_pc; + + cloud_in.reset(new pcl::PointCloud<pcl::PointXYZI>()); + ground_pc.reset(new pcl::PointCloud<pcl::PointXYZI>()); + nonground_pc.reset(new pcl::PointCloud<pcl::PointXYZI>()); + + boost::shared_ptr < travel::TravelGroundSeg < pcl::PointXYZI >> travel_ground_seg; + + travel_ground_seg.reset(new travel::TravelGroundSeg<pcl::PointXYZI>()); + + double min_range = 1.0; + double max_range = 64.0; + + double resolution = 8.0; + int num_iter = 3; + int num_lpr = 5; + int num_min_pts = 10; + double th_seeds = 0.5; + double th_dist = 0.125; + double th_outlier = 0.3; + double th_normal = 0.940; + double th_weight = 200; + double th_lcc_normal = 0.03; + double th_lcc_planar = 0.1; + double th_obstacle = 1.0; + bool refine_mode = true; + bool visualization = true; + + std::cout << "Setting TRAVEL parameters!" << std::endl; + travel_ground_seg->setParams(max_range, min_range, resolution, num_iter, num_lpr, num_min_pts, th_seeds, th_dist, + th_outlier, th_normal, th_weight, th_lcc_normal, th_lcc_planar, th_obstacle, + refine_mode, visualization); + std::cout << "Parameters set!" << std::endl; + + std::cout << "Copying input data to XYZI format!" << std::endl; + for (pcl::PointCloud<pcl::PointXYZRGBNormal>::iterator i = lidar_pcl_cloud.begin(); i != lidar_pcl_cloud.end(); ++i) + { + pcl::PointXYZI point; + point.x = i->x; + point.y = i->y; + point.z = i->z; + point.intensity = 0; + cloud_in->points.push_back(point); + } + std::cout << "Data copied!" << std::endl; - //std::cout << "Number of vertices = " << roadmap_vertices_pcl_cloud.points.size(); - //std::cout << "Number of edge vectors = " << edges.size() << std::endl; + double ground_seg_time = 0.0; + std::cout << "Starting TRAVEL ground segmentation!" << std::endl; + travel_ground_seg->estimateGround(*cloud_in, *ground_pc, *nonground_pc, ground_seg_time); + std::cout << "\033[1;35m Traversable-Ground Seg: " << cloud_in->size() << " -> Ground: " << ground_pc->size() + << ", NonGround: " << nonground_pc->size() << "\033[0m" << std::endl; + std::cout << "Traversable-Ground Seg time: " << ground_seg_time << std::endl; - // Now we create the markers for visualization and change the format of edges to implement the interface - // between the library and the node + pcl::KdTreeFLANN < pcl::PointXYZI > kdtree_ground; + kdtree_ground.setInputCloud(ground_pc); - int vertex_id = 0; - int discarded_vertex_id = 0; - int edge_id = 0; - int discarded_edge_id = 0; + int K = 1; + std::vector<int> ground_pointIdxKNNSearch(K); + std::vector<float> ground_pointKNNSquaredDistance(K); - for (pcl::PointCloud<pcl::PointXYZRGBNormal>::iterator i = roadmap_vertices_pcl_cloud.begin(); - i != roadmap_vertices_pcl_cloud.end(); ++i) - { - pcl::PointXYZRGBNormal ground_ref = *i; + int index = 0; + for (pcl::PointCloud<pcl::PointXYZI>::iterator i = cloud_in->begin(); i != cloud_in->end(); ++i) + { + kdtree_ground.nearestKSearch(*i, K, ground_pointIdxKNNSearch, ground_pointKNNSquaredDistance); - // DEBUG! - //std::cout << "Vertex Pitch = " << ground_ref.data_c[GRND_REF_DATA_C_2_PITCH] << " ; Vertex Roll = " - // << ground_ref.data_c[GRND_REF_DATA_C_1_ROLL] << std::endl; + bool ground = false; + if (sqrt(ground_pointKNNSquaredDistance[0]) < 0.001) + ground = true; - assert( - vertex_id == ground_ref.data_n[GRND_REF_DATA_N_3_ORIGINAL_INDEX] - && "Something unexpected in GroundSegmentationAlgorithm::segmentGround happened! vertex_id not equal to ground_ref.data_n[GRND_REF_DATA_N_3_ORIGINAL_INDEX]!"); + if (ground) + { + lidar_pcl_cloud.points[index].r = 0; + lidar_pcl_cloud.points[index].g = 0; + lidar_pcl_cloud.points[index].b = 255; - //std::cout << "Converting vertex with id = " << vertex_id << " to marker!" << std::endl; - //std::cout << "Number of edges for vertex " << vertex_id << " = " << edges[ground_ref.data_n[3]].size() << std::endl; + lidar_pcl_cloud.points[index].data_c[DATA_C_1_ID_CLASS] = (float)CLASS_GROUND + 0.995; + } + else + { + lidar_pcl_cloud.points[index].r = 0; + lidar_pcl_cloud.points[index].g = 255; + lidar_pcl_cloud.points[index].b = 0; - visualization_msgs::Marker vertex; - vertex.action = visualization_msgs::Marker::ADD; - vertex.header = common_header; + lidar_pcl_cloud.points[index].data_c[DATA_C_1_ID_CLASS] = (float)CLASS_OBSTACLE + 0.0; + } - if (ground_ref.data_n[GRND_REF_DATA_N_2_VERTEX_CLASS] < VERTEX_NOT_CONNECTED_TO_ROOT) - { - vertex.ns = "vertices_connected_to_root"; - vertex.id = vertex_id++; - } - else - { - vertex.ns = "vertices_NOT_connected_to_root"; - vertex.id = discarded_vertex_id++; + ++index; } + } + else + { + //std::cout << "Calling to processor_.groundSegmentation!" << std::endl; - vertex.type = visualization_msgs::Marker::SPHERE; + processor_.groundSegmentation(lidar_configuration_, filtering_configuration_, lidar_pcl_cloud, + roadmap_vertices_pcl_cloud, edges, discarded_edges, + ground_dense_reconstruction_pcl_cloud); - vertex.pose.position.x = ground_ref.x; - vertex.pose.position.y = ground_ref.y; - vertex.pose.position.z = ground_ref.z; + //std::cout << "Number of vertices = " << roadmap_vertices_pcl_cloud.points.size(); + //std::cout << "Number of edge vectors = " << edges.size() << std::endl; - vertex.pose.orientation.x = 0.0; - vertex.pose.orientation.y = 0.0; - vertex.pose.orientation.z = 0.0; - vertex.pose.orientation.w = 1.0; + // Now we create the markers for visualization and change the format of edges to implement the interface + // between the library and the node - vertex.scale.x = 0.15; - vertex.scale.y = 0.15; - vertex.scale.z = 0.15; + int vertex_id = 0; + int discarded_vertex_id = 0; + int edge_id = 0; + int discarded_edge_id = 0; - vertex.color.a = 1.0; // Don't forget to set the alpha! - if (ground_ref.data_n[GRND_REF_DATA_N_2_VERTEX_CLASS] == VERTEX_CONNECTED_TO_ROOT) - { - vertex.color.r = 1.0; - vertex.color.g = 0.66; - vertex.color.b = 0.0; - } - else + for (pcl::PointCloud<pcl::PointXYZRGBNormal>::iterator i = roadmap_vertices_pcl_cloud.begin(); + i != roadmap_vertices_pcl_cloud.end(); ++i) { - vertex.color.r = 1.0; - vertex.color.g = 0.0; - vertex.color.b = 0.0; - } - vertex.lifetime = ros::Duration(0.1); + pcl::PointXYZRGBNormal ground_ref = *i; - roadmap_markers.markers.push_back(vertex); + // DEBUG! + //std::cout << "Vertex Pitch = " << ground_ref.data_c[GRND_REF_DATA_C_2_PITCH] << " ; Vertex Roll = " + // << ground_ref.data_c[GRND_REF_DATA_C_1_ROLL] << std::endl; - iri_ground_segmentation::edges edges_temp; - for (std::vector<int>::iterator e = edges[ground_ref.data_n[GRND_REF_DATA_N_3_ORIGINAL_INDEX]].begin(); - e != edges[ground_ref.data_n[GRND_REF_DATA_N_3_ORIGINAL_INDEX]].end(); e++) - { - edges_temp.edges.push_back(*e); + assert( + vertex_id == ground_ref.data_n[GRND_REF_DATA_N_3_ORIGINAL_INDEX] + && "Something unexpected in GroundSegmentationAlgorithm::segmentGround happened! vertex_id not equal to ground_ref.data_n[GRND_REF_DATA_N_3_ORIGINAL_INDEX]!"); - visualization_msgs::Marker edge; - edge.action = visualization_msgs::Marker::ADD; - edge.header = common_header; + //std::cout << "Converting vertex with id = " << vertex_id << " to marker!" << std::endl; + //std::cout << "Number of edges for vertex " << vertex_id << " = " << edges[ground_ref.data_n[3]].size() << std::endl; - if (ground_ref.data_n[GRND_REF_DATA_N_2_VERTEX_CLASS] == VERTEX_CONNECTED_TO_ROOT) + // TODO: comment out the marker array creation to see how much time it consumes + visualization_msgs::Marker vertex; + vertex.action = visualization_msgs::Marker::ADD; + vertex.header = common_header; + + if (ground_ref.data_n[GRND_REF_DATA_N_2_VERTEX_CLASS] < VERTEX_NOT_CONNECTED_TO_ROOT) { - edge.ns = "edges"; - edge.id = edge_id++; + vertex.ns = "vertices_connected_to_root"; + vertex.id = vertex_id++; } else { - edge.ns = "discarded_edges"; - edge.id = discarded_edge_id++; + vertex.ns = "vertices_NOT_connected_to_root"; + vertex.id = discarded_vertex_id++; } - edge.type = visualization_msgs::Marker::LINE_LIST; - - geometry_msgs::Point p; - p.x = ground_ref.x; - p.y = ground_ref.y; - p.z = ground_ref.z; - edge.points.push_back(p); + vertex.type = visualization_msgs::Marker::SPHERE; - pcl::PointXYZRGBNormal child_ground_ref = roadmap_vertices_pcl_cloud.points[*e]; - p.x = child_ground_ref.x; - p.y = child_ground_ref.y; - p.z = child_ground_ref.z; - edge.points.push_back(p); + vertex.pose.position.x = ground_ref.x; + vertex.pose.position.y = ground_ref.y; + vertex.pose.position.z = ground_ref.z; - edge.scale.x = 0.05; + vertex.pose.orientation.x = 0.0; + vertex.pose.orientation.y = 0.0; + vertex.pose.orientation.z = 0.0; + vertex.pose.orientation.w = 1.0; - edge.pose.orientation.x = 0.0; - edge.pose.orientation.y = 0.0; - edge.pose.orientation.z = 0.0; - edge.pose.orientation.w = 1.0; + vertex.scale.x = 0.25; + vertex.scale.y = 0.25; + vertex.scale.z = 0.25; - edge.color.a = 0.7; // Don't forget to set the alpha! + vertex.color.a = 1.0; // Don't forget to set the alpha! if (ground_ref.data_n[GRND_REF_DATA_N_2_VERTEX_CLASS] == VERTEX_CONNECTED_TO_ROOT) { - edge.color.r = 0.8; - edge.color.g = 0.53; - edge.color.b = 0.0; + vertex.color.r = 1.0; + vertex.color.g = 0.66; + vertex.color.b = 0.0; } else { - edge.color.r = R_VERTEX_NOT_CONNECTED_TO_ROOT / 255.0; - edge.color.g = G_VERTEX_NOT_CONNECTED_TO_ROOT / 255.0; - edge.color.b = B_VERTEX_NOT_CONNECTED_TO_ROOT / 255.0; + vertex.color.r = 1.0; + vertex.color.g = 0.0; + vertex.color.b = 0.0; } - edge.lifetime = ros::Duration(0.1); + vertex.lifetime = ros::Duration(0); //0.1 - roadmap_markers.markers.push_back(edge); - } + roadmap_markers.markers.push_back(vertex); - edges_array.edges_array.push_back(edges_temp); + iri_ground_segmentation::edges edges_temp; + for (std::vector<int>::iterator e = edges[ground_ref.data_n[GRND_REF_DATA_N_3_ORIGINAL_INDEX]].begin(); + e != edges[ground_ref.data_n[GRND_REF_DATA_N_3_ORIGINAL_INDEX]].end(); e++) + { + edges_temp.edges.push_back(*e); + + pcl::PointXYZRGBNormal child_ground_ref = roadmap_vertices_pcl_cloud.points[*e]; + + visualization_msgs::Marker edge; + edge.action = visualization_msgs::Marker::ADD; + edge.header = common_header; + + if (ground_ref.data_n[GRND_REF_DATA_N_2_VERTEX_CLASS] == VERTEX_CONNECTED_TO_ROOT && + child_ground_ref.data_n[GRND_REF_DATA_N_2_VERTEX_CLASS] == VERTEX_CONNECTED_TO_ROOT) + { + edge.ns = "edges"; + edge.id = edge_id++; + } + else + { + edge.ns = "discarded_edges"; + edge.id = discarded_edge_id++; + } + + edge.type = visualization_msgs::Marker::LINE_LIST; + + geometry_msgs::Point p; + p.x = ground_ref.x; + p.y = ground_ref.y; + p.z = ground_ref.z; + edge.points.push_back(p); + + p.x = child_ground_ref.x; + p.y = child_ground_ref.y; + p.z = child_ground_ref.z; + edge.points.push_back(p); + + edge.scale.x = 0.1; + + edge.pose.orientation.x = 0.0; + edge.pose.orientation.y = 0.0; + edge.pose.orientation.z = 0.0; + edge.pose.orientation.w = 1.0; + + edge.color.a = 1.0; // Don't forget to set the alpha! + if (ground_ref.data_n[GRND_REF_DATA_N_2_VERTEX_CLASS] == VERTEX_CONNECTED_TO_ROOT && + child_ground_ref.data_n[GRND_REF_DATA_N_2_VERTEX_CLASS] == VERTEX_CONNECTED_TO_ROOT) + { + edge.color.r = 0.8; + edge.color.g = 0.53; + edge.color.b = 0.0; + } + else + { + edge.color.r = R_VERTEX_NOT_CONNECTED_TO_ROOT / 255.0; + edge.color.g = G_VERTEX_NOT_CONNECTED_TO_ROOT / 255.0; + edge.color.b = B_VERTEX_NOT_CONNECTED_TO_ROOT / 255.0; + } + edge.lifetime = ros::Duration(0); // 0.1 + + roadmap_markers.markers.push_back(edge); + } - // We create markers for the discarded edges - for (int e = 0; e < discarded_edges[ground_ref.data_n[GRND_REF_DATA_N_3_ORIGINAL_INDEX]].size(); e++) - { - kf_based_terrain_analysis_lib::Edge discarded_edge_index_and_class = - discarded_edges[ground_ref.data_n[GRND_REF_DATA_N_3_ORIGINAL_INDEX]][e]; - - visualization_msgs::Marker discarded_edge; - discarded_edge.action = visualization_msgs::Marker::ADD; - discarded_edge.header = common_header; - discarded_edge.ns = "discarded_edges"; - discarded_edge.id = discarded_edge_id++; - discarded_edge.type = visualization_msgs::Marker::LINE_LIST; - - geometry_msgs::Point p; - p.x = ground_ref.x; - p.y = ground_ref.y; - p.z = ground_ref.z; - discarded_edge.points.push_back(p); - - pcl::PointXYZRGBNormal child_ground_ref = - roadmap_vertices_pcl_cloud.points[discarded_edge_index_and_class.vertex_index]; - p.x = child_ground_ref.x; - p.y = child_ground_ref.y; - p.z = child_ground_ref.z; - discarded_edge.points.push_back(p); - - discarded_edge.scale.x = 0.05; - - discarded_edge.pose.orientation.x = 0.0; - discarded_edge.pose.orientation.y = 0.0; - discarded_edge.pose.orientation.z = 0.0; - discarded_edge.pose.orientation.w = 1.0; - - discarded_edge.color.a = 0.7; - switch (discarded_edge_index_and_class.edge_class) + edges_array.edges_array.push_back(edges_temp); + + // We create markers for the discarded edges + for (int e = 0; e < discarded_edges[ground_ref.data_n[GRND_REF_DATA_N_3_ORIGINAL_INDEX]].size(); e++) { - case CLASS_EDGE_DISCARDED_TOO_MUCH_MAHALANOBIS_DISTANCE: - //std::cout << "Coloring edge too much Mahalanobis distance! class = " - // << discarded_edge_index_and_class.edge_class << std::endl; - discarded_edge.color.r = (float)R_EDGE_DISCARDED_TOO_MUCH_MAHALANOBIS_DISTANCE / 255.0; - discarded_edge.color.g = (float)G_EDGE_DISCARDED_TOO_MUCH_MAHALANOBIS_DISTANCE / 255.0; - discarded_edge.color.b = (float)B_EDGE_DISCARDED_TOO_MUCH_MAHALANOBIS_DISTANCE / 255.0; - break; - default: - //std::cout << "WARNING! Discarded edge class not known!! class = " << discarded_edge_index_and_class.edge_class - // << std::endl; - discarded_edge.color.r = 1.0; - discarded_edge.color.g = 1.0; - discarded_edge.color.b = 1.0; - break; + kf_based_terrain_analysis_lib::Edge discarded_edge_index_and_class = + discarded_edges[ground_ref.data_n[GRND_REF_DATA_N_3_ORIGINAL_INDEX]][e]; + + visualization_msgs::Marker discarded_edge; + discarded_edge.action = visualization_msgs::Marker::ADD; + discarded_edge.header = common_header; + discarded_edge.ns = "discarded_edges"; + discarded_edge.id = discarded_edge_id++; + discarded_edge.type = visualization_msgs::Marker::LINE_LIST; + + geometry_msgs::Point p; + p.x = ground_ref.x; + p.y = ground_ref.y; + p.z = ground_ref.z; + discarded_edge.points.push_back(p); + + pcl::PointXYZRGBNormal child_ground_ref = + roadmap_vertices_pcl_cloud.points[discarded_edge_index_and_class.vertex_index]; + p.x = child_ground_ref.x; + p.y = child_ground_ref.y; + p.z = child_ground_ref.z; + discarded_edge.points.push_back(p); + + discarded_edge.scale.x = 0.1; + + discarded_edge.pose.orientation.x = 0.0; + discarded_edge.pose.orientation.y = 0.0; + discarded_edge.pose.orientation.z = 0.0; + discarded_edge.pose.orientation.w = 1.0; + + discarded_edge.color.a = 1.0; + switch (discarded_edge_index_and_class.edge_class) + { + case CLASS_EDGE_DISCARDED_TOO_MUCH_MAHALANOBIS_DISTANCE: + //std::cout << "Coloring edge too much Mahalanobis distance! class = " + // << discarded_edge_index_and_class.edge_class << std::endl; + discarded_edge.color.r = (float)R_EDGE_DISCARDED_TOO_MUCH_MAHALANOBIS_DISTANCE / 255.0; + discarded_edge.color.g = (float)G_EDGE_DISCARDED_TOO_MUCH_MAHALANOBIS_DISTANCE / 255.0; + discarded_edge.color.b = (float)B_EDGE_DISCARDED_TOO_MUCH_MAHALANOBIS_DISTANCE / 255.0; + break; + default: + //std::cout << "WARNING! Discarded edge class not known!! class = " << discarded_edge_index_and_class.edge_class + // << std::endl; + discarded_edge.color.r = 1.0; + discarded_edge.color.g = 1.0; + discarded_edge.color.b = 1.0; + break; + } + discarded_edge.lifetime = ros::Duration(0); // 0.1 + roadmap_markers.markers.push_back(discarded_edge); } - discarded_edge.lifetime = ros::Duration(0.1); - roadmap_markers.markers.push_back(discarded_edge); } - } - //std::cout << "Total number of edges = " << edge_id << std::endl; - //getchar(); + //std::cout << "Total number of edges = " << edge_id << std::endl; + //getchar(); + } } diff --git a/src/ground_segmentation_alg_node.cpp b/src/ground_segmentation_alg_node.cpp index 13b9c38a0fef93e9488ddf52c1bca916b8d35a99..0040cfd9958f53d894a898ec17877c02e8046174 100644 --- a/src/ground_segmentation_alg_node.cpp +++ b/src/ground_segmentation_alg_node.cpp @@ -34,6 +34,13 @@ GroundSegmentationAlgNode::GroundSegmentationAlgNode(void) : else this->alg_.filtering_configuration_.ROI_delta_x_and_y = config_.ROI_delta_x_and_y; + if (!this->private_node_handle_.getParam("ROI_shadow_area", this->config_.ROI_shadow_area)) + { + ROS_WARN("GroundSegmentationAlgNode::GroundSegmentationAlgNode: param 'ROI_shadow_area' not found"); + } + else + this->alg_.filtering_configuration_.ROI_shadow_area = config_.ROI_shadow_area; + if (!this->private_node_handle_.getParam("ground_reference_search_resolution_deg", this->config_.ground_reference_search_resolution_deg)) { @@ -106,17 +113,75 @@ GroundSegmentationAlgNode::GroundSegmentationAlgNode(void) : else this->alg_.filtering_configuration_.mahalanobis_threshold = config_.mahalanobis_threshold; - ////////////////// labeling parameters - if (!this->private_node_handle_.getParam("number_of_references_used_for_labelling", - this->config_.number_of_references_used_for_labelling)) + /////////////////// Neural Network parameters + if (!this->private_node_handle_.getParam("use_neural_network", this->config_.use_neural_network)) + { + ROS_WARN("GroundSegmentationAlgNode::GroundSegmentationAlgNode: param 'use_neural_network' not found"); + } + else + this->alg_.filtering_configuration_.use_neural_network = config_.use_neural_network; + + if (!this->private_node_handle_.getParam("extract_data_to_external_training_of_the_network", + this->config_.extract_data_to_external_training_of_the_network)) + { + ROS_WARN( + "GroundSegmentationAlgNode::GroundSegmentationAlgNode: param 'extract_data_to_external_training_of_the_network' not found"); + } + else + this->alg_.filtering_configuration_.extract_data_to_external_training_of_the_network = + config_.extract_data_to_external_training_of_the_network; + + if (!this->private_node_handle_.getParam("dataset_filename_with_global_path", + this->config_.dataset_filename_with_global_path)) + { + ROS_WARN( + "GroundSegmentationAlgNode::GroundSegmentationAlgNode: param 'dataset_filename_with_global_path' not found"); + } + else + this->alg_.filtering_configuration_.dataset_filename_with_global_path = + config_.dataset_filename_with_global_path; + + if (!this->private_node_handle_.getParam("neural_net_filename_with_global_path", + this->config_.neural_net_filename_with_global_path)) { ROS_WARN( - "GroundSegmentationAlgNode::GroundSegmentationAlgNode: param 'number_of_references_used_for_labelling' not found"); + "GroundSegmentationAlgNode::GroundSegmentationAlgNode: param 'neural_net_filename_with_global_path' not found"); } else - this->alg_.filtering_configuration_.number_of_references_used_for_labelling = - config_.number_of_references_used_for_labelling; + this->alg_.filtering_configuration_.neural_net_filename_with_global_path = + config_.neural_net_filename_with_global_path; + if (!this->private_node_handle_.getParam("neural_net_number_of_features", + this->config_.neural_net_number_of_features)) + { + ROS_WARN( + "GroundSegmentationAlgNode::GroundSegmentationAlgNode: param 'neural_net_number_of_features' not found"); + } + else + this->alg_.filtering_configuration_.neural_net_number_of_features = + config_.neural_net_number_of_features; + + if (!this->private_node_handle_.getParam("neural_net_number_of_neurons_in_hidden_layer", + this->config_.neural_net_number_of_neurons_in_hidden_layer)) + { + ROS_WARN( + "GroundSegmentationAlgNode::GroundSegmentationAlgNode: param 'neural_net_number_of_neurons_in_hidden_layer' not found"); + } + else + this->alg_.filtering_configuration_.neural_net_number_of_neurons_in_hidden_layer = + config_.neural_net_number_of_neurons_in_hidden_layer; + + if (!this->private_node_handle_.getParam("neural_net_number_of_neurons_in_output_layer", + this->config_.neural_net_number_of_neurons_in_output_layer)) + { + ROS_WARN( + "GroundSegmentationAlgNode::GroundSegmentationAlgNode: param 'neural_net_number_of_neurons_in_output_layer' not found"); + } + else + this->alg_.filtering_configuration_.neural_net_number_of_neurons_in_output_layer = + config_.neural_net_number_of_neurons_in_output_layer; + + ////////////////// labeling parameters if (!this->private_node_handle_.getParam("max_pred_std_dev_for_labelling", this->config_.max_pred_std_dev_for_labelling)) { @@ -125,6 +190,13 @@ GroundSegmentationAlgNode::GroundSegmentationAlgNode(void) : else this->alg_.filtering_configuration_.max_pred_std_dev_for_labelling = config_.max_pred_std_dev_for_labelling; + if (!this->private_node_handle_.getParam("score_threshold", this->config_.score_threshold)) + { + ROS_WARN("GroundSegmentationAlgNode::GroundSegmentationAlgNode: param 'score_threshold' not found"); + } + else + this->alg_.filtering_configuration_.score_threshold = config_.score_threshold; + if (!this->private_node_handle_.getParam("classify_not_labeled_points_as_obstacles", this->config_.classify_not_labeled_points_as_obstacles)) { @@ -135,15 +207,15 @@ GroundSegmentationAlgNode::GroundSegmentationAlgNode(void) : this->alg_.filtering_configuration_.classify_not_labeled_points_as_obstacles = config_.classify_not_labeled_points_as_obstacles; - if (!this->private_node_handle_.getParam("discard_not_connected_references_for_labelling", - this->config_.discard_not_connected_references_for_labelling)) + if (!this->private_node_handle_.getParam("ground_threshold_in_not_analyzed_areas", + this->config_.ground_threshold_in_not_analyzed_areas)) { ROS_WARN( - "GroundSegmentationAlgNode::GroundSegmentationAlgNode: param 'discard_not_connected_references_for_labelling' not found"); + "GroundSegmentationAlgNode::GroundSegmentationAlgNode: param 'ground_threshold_in_not_analyzed_areas' not found"); } else - this->alg_.filtering_configuration_.discard_not_connected_references_for_labelling = - config_.discard_not_connected_references_for_labelling; + this->alg_.filtering_configuration_.ground_threshold_in_not_analyzed_areas = + config_.ground_threshold_in_not_analyzed_areas; ////////////////// visualization and debug parameters if (!this->private_node_handle_.getParam("measure_performance", this->config_.measure_performance)) @@ -161,8 +233,8 @@ GroundSegmentationAlgNode::GroundSegmentationAlgNode(void) : this->alg_.filtering_configuration_.show_dense_reconstruction = config_.show_dense_reconstruction; // [init subscribers] - this->lidar_subscriber_ = this->public_node_handle_.subscribe("/lidar_points_extended_structure", 1, - &GroundSegmentationAlgNode::cb_lidar, this); + this->lidar_subscriber_ = this->private_node_handle_.subscribe("lidar_points_extended_structure", 1, + &GroundSegmentationAlgNode::cb_lidar, this); // [init publishers] @@ -194,6 +266,8 @@ GroundSegmentationAlgNode::GroundSegmentationAlgNode(void) : flag_new_lidar_data_ = false; pthread_mutex_init(&this->mutex_, NULL); // [init services] + + std::cout << "Ground segmentation node instantiated!!" << std::endl; } GroundSegmentationAlgNode::~GroundSegmentationAlgNode(void) @@ -269,13 +343,14 @@ void GroundSegmentationAlgNode::unmutexAll(void) bool GroundSegmentationAlgNode::isAllRequiredDataAlreadyReceived(void) { bool ready_to_process = false; - + this->mutex_enter(); if (flag_new_lidar_data_) { ready_to_process = true; //std::cout << "GroundSegmentationAlgNode::isAllRequiredDataAlreadyReceived..." << std::endl; //std::cout << "Ready_to_process = " << ready_to_process << std::endl; } + this->mutex_exit(); return (ready_to_process); } @@ -288,6 +363,24 @@ void GroundSegmentationAlgNode::convertInputsToSuitableFormats(void) { //std::cout << "GroundSegmentationAlgNode::convertInputsToSuitableFormats..." << std::endl; this->rosPointCloudtoPCLPointCloud(lidar_ros_cloud_, lidar_pcl_cloud_); +// std::cout << "x = " << lidar_pcl_cloud_.points[100].x << std::endl; +// std::cout << "y = " << lidar_pcl_cloud_.points[100].y << std::endl; +// std::cout << "z = " << lidar_pcl_cloud_.points[100].z << std::endl; +// +// std::cout << "r = " << (int)lidar_pcl_cloud_.points[100].r << std::endl; +// std::cout << "g = " << (int)lidar_pcl_cloud_.points[100].g << std::endl; +// std::cout << "b = " << (int)lidar_pcl_cloud_.points[100].b << std::endl; +// +// std::cout << "DATA_N_0_INTENSITY = " << lidar_pcl_cloud_.points[100].data_n[DATA_N_0_INTENSITY] << std::endl; +// std::cout << "DATA_N_1_INTENSITY_VARIANCE = " << lidar_pcl_cloud_.points[100].data_n[DATA_N_1_INTENSITY_VARIANCE] << std::endl; +// std::cout << "DATA_N_2_Z_MEAN = " << lidar_pcl_cloud_.points[100].data_n[DATA_N_2_Z_MEAN] << std::endl; +// std::cout << "DATA_N_3_Z_VARIANCE = " << lidar_pcl_cloud_.points[100].data_n[DATA_N_3_Z_VARIANCE] << std::endl; +// +// std::cout << "DATA_C_0_RGB_CAST_INTO_FLOAT = " << lidar_pcl_cloud_.points[100].data_c[DATA_C_0_RGB_CAST_INTO_FLOAT] << std::endl; +// std::cout << "DATA_C_1_ID_CLASS = " << lidar_pcl_cloud_.points[100].data_c[DATA_C_1_ID_CLASS] << std::endl; +// std::cout << "DATA_C_2_MEAN_INTENSITY = " << lidar_pcl_cloud_.points[100].data_c[DATA_C_2_MEAN_INTENSITY] << std::endl; +// std::cout << "DATA_C_3_ORIGINAL_INDEX = " << lidar_pcl_cloud_.points[100].data_c[DATA_C_3_ORIGINAL_INDEX] << std::endl; + flag_new_lidar_data_ = false; } @@ -310,9 +403,15 @@ void GroundSegmentationAlgNode::splitSegmentedCloudInOnePerClass(void) case CLASS_OBSTACLE: obstacle_points_pcl_cloud_.push_back(regular_point); break; + case CLASS_OVERHANGING_OBSTACLE: + // If we want to create a pointcloud with the overhanging obstacles, we should populate it here + break; + case CLASS_UNKNOWN: + // If we ever want to create a pointcloud with unknown class points, we should populate it here + break; default: - // std::cout << "GroundSegmentationAlgNode::splitSegmentedCloudInOnePerClass -> unexpected class value of: " - // << classified_point.data_c[DATA_C_1_ID_CLASS] << std::endl; + //std::cout << "GroundSegmentationAlgNode::splitSegmentedCloudInOnePerClass -> unexpected class value of: " + // << classified_point.data_c[DATA_C_1_ID_CLASS] << std::endl; break; } } @@ -351,6 +450,13 @@ void GroundSegmentationAlgNode::publishAll(void) // Visualization topics this->ground_dense_reconstruction_publisher_.publish(this->ground_dense_reconstruction_ros_cloud_); + + + visualization_msgs::Marker markerD; + markerD.header = common_header_; + markerD.action = visualization_msgs::Marker::DELETEALL; + this->roadmap_markers_publisher_.publish(markerD); + this->roadmap_markers_publisher_.publish(this->roadmap_MarkerArray_msg_); } @@ -395,8 +501,12 @@ void GroundSegmentationAlgNode::mainNodeThread(void) this->mutex_exit(); // Then we can free the callback + this->alg_.lock(); + //std::cout << "Starting ground segmentation!" << std::endl; this->alg_.segmentGround(common_header_, lidar_pcl_cloud_, roadmap_vertices_pcl_cloud_, roadmap_edges_array_, ground_dense_reconstruction_pcl_cloud_, roadmap_MarkerArray_msg_); + //std::cout << "Ground segmentation finished!" << std::endl; + this->alg_.unlock(); this->convertOutputsToSuitableFormats(); this->publishAll(); @@ -408,13 +518,13 @@ void GroundSegmentationAlgNode::mainNodeThread(void) void GroundSegmentationAlgNode::cb_lidar(const sensor_msgs::PointCloud2::ConstPtr &msg) { + this->mutex_enter(); lidar_ros_cloud_ = *msg; //std::cout << "GroundSegmentationAlgNode::cb_lidar --> lidar msg received!" << std::endl; assert(msg != NULL && "Null pointer!!! in function cb_lidar!"); - this->mutex_enter(); - flag_new_lidar_data_ = true; // This flag is shared with the main node thread, as the lidar_ros_cloud, but the latter is protected - // by the flag, so it is not required to use the mutex + flag_new_lidar_data_ = true; + this->mutex_exit(); } @@ -448,6 +558,7 @@ void GroundSegmentationAlgNode::node_config_update(Config &config, uint32_t leve // Parameters affecting the exploration of the pointcloud this->alg_.filtering_configuration_.ROI_delta_x_and_y = config.ROI_delta_x_and_y; + this->alg_.filtering_configuration_.ROI_shadow_area = config.ROI_shadow_area; this->alg_.filtering_configuration_.ground_reference_search_resolution = config.ground_reference_search_resolution_deg * M_PI / 180.0; this->alg_.filtering_configuration_.elevation_grid_resolution = config.elevation_grid_resolution; @@ -467,14 +578,16 @@ void GroundSegmentationAlgNode::node_config_update(Config &config, uint32_t leve // threshold for outlier rejection and classification this->alg_.filtering_configuration_.mahalanobis_threshold = config.mahalanobis_threshold; + // Neural Network parameters + this->alg_.filtering_configuration_.use_neural_network = config.use_neural_network; + this->alg_.filtering_configuration_.extract_data_to_external_training_of_the_network = + config.extract_data_to_external_training_of_the_network; + // labeling parameters - this->alg_.filtering_configuration_.number_of_references_used_for_labelling = - config.number_of_references_used_for_labelling; + this->alg_.filtering_configuration_.score_threshold = config_.score_threshold; this->alg_.filtering_configuration_.max_pred_std_dev_for_labelling = config.max_pred_std_dev_for_labelling; this->alg_.filtering_configuration_.classify_not_labeled_points_as_obstacles = config_.classify_not_labeled_points_as_obstacles; - this->alg_.filtering_configuration_.discard_not_connected_references_for_labelling = - config_.discard_not_connected_references_for_labelling; // visualization and debug parameters this->alg_.filtering_configuration_.measure_performance = config.measure_performance; @@ -490,6 +603,10 @@ void GroundSegmentationAlgNode::node_config_update(Config &config, uint32_t leve std::cout << "ROI_delta_x_and_y = " << this->alg_.filtering_configuration_.ROI_delta_x_and_y << std::endl; + + std::cout << "ROI_shadow_area = " << this->alg_.filtering_configuration_.ROI_shadow_area + << std::endl; + std::cout << "ground_reference_search_resolution (deg) = " << this->alg_.filtering_configuration_.ground_reference_search_resolution * 180.0 / M_PI << std::endl; std::cout << "elevation_grid_resolution = " @@ -511,14 +628,19 @@ void GroundSegmentationAlgNode::node_config_update(Config &config, uint32_t leve std::cout << "mahalanobis_threshold = " << this->alg_.filtering_configuration_.mahalanobis_threshold << std::endl; - std::cout << "number_of_references_used_for_labelling = " - << this->alg_.filtering_configuration_.number_of_references_used_for_labelling << std::endl; + std::cout << "score_threshold = " + << this->alg_.filtering_configuration_.score_threshold << std::endl; + + std::cout << "use_neural_network = " + << this->alg_.filtering_configuration_.use_neural_network << std::endl; + + std::cout << "extract_data_to_external_training_of_the_network = " + << this->alg_.filtering_configuration_.extract_data_to_external_training_of_the_network << std::endl; + std::cout << "max_pred_std_dev_for_labelling = " << this->alg_.filtering_configuration_.max_pred_std_dev_for_labelling << std::endl; std::cout << "classify_not_labeled_points_as_obstacles = " << this->alg_.filtering_configuration_.classify_not_labeled_points_as_obstacles << std::endl; - std::cout << "discard_not_connected_references_for_labelling = " - << this->alg_.filtering_configuration_.discard_not_connected_references_for_labelling << std::endl; std::cout << "measure_performance = " << this->alg_.filtering_configuration_.measure_performance << std::endl; std::cout << "show_dense_reconstruction = " << this->alg_.filtering_configuration_.show_dense_reconstruction