From 0fb8f2b62920cf1d1afce17057d43796d9ff6be2 Mon Sep 17 00:00:00 2001
From: idelpino <telecosg@gmail.com>
Date: Thu, 16 Feb 2023 20:57:45 +0100
Subject: [PATCH] added neural network of different possible sizes

---
 include/kf_based_terrain_analysis.h |   2 +-
 include/shallow_neural_net.h        |  31 ++++++
 include/structs_definitions.h       |   3 +
 src/kf_based_terrain_analysis.cpp   | 154 +++++++++++++++++++---------
 src/shallow_neural_net.cpp          | 101 ++++++++++++++++++
 5 files changed, 240 insertions(+), 51 deletions(-)
 create mode 100644 include/shallow_neural_net.h
 create mode 100644 src/shallow_neural_net.cpp

diff --git a/include/kf_based_terrain_analysis.h b/include/kf_based_terrain_analysis.h
index a7ab334..129cf8c 100644
--- a/include/kf_based_terrain_analysis.h
+++ b/include/kf_based_terrain_analysis.h
@@ -117,7 +117,7 @@ private:
 
   void createFileToDumpDataset(const std::string filename);
 
-  void loadNeuralNetwork(const std::string filename);
+  void loadNeuralNetwork(const kf_based_terrain_analysis_lib::FilteringConfiguration filtering_configuration);
 
   void prepareFileInterface(const kf_based_terrain_analysis_lib::FilteringConfiguration filtering_configuration);
 
diff --git a/include/shallow_neural_net.h b/include/shallow_neural_net.h
new file mode 100644
index 0000000..bcc00ae
--- /dev/null
+++ b/include/shallow_neural_net.h
@@ -0,0 +1,31 @@
+#ifndef _shallow_neural_net_h_
+#define _shallow_neural_net_h_
+
+#include <iostream>
+#include <Eigen/Dense>
+#include "structs_definitions.h"
+
+class ShallowNeuralNet
+{
+protected:
+
+public:
+  Eigen::MatrixXd b1_;
+  Eigen::MatrixXd b2_;
+  Eigen::MatrixXd gain_;
+  Eigen::MatrixXd w1_;
+  Eigen::MatrixXd w2_;
+  Eigen::MatrixXd x_offset_;
+  Eigen::MatrixXd y_min_;
+
+  int number_of_features_;
+  int number_of_neurons_in_hidden_layer_;
+  int number_of_neurons_in_output_layer_;
+
+  ShallowNeuralNet(void);
+  ~ShallowNeuralNet(void);
+
+  double predict(Eigen::VectorXd features);
+};
+
+#endif
diff --git a/include/structs_definitions.h b/include/structs_definitions.h
index a0f44d1..05773ca 100644
--- a/include/structs_definitions.h
+++ b/include/structs_definitions.h
@@ -170,6 +170,9 @@ struct FilteringConfiguration
   bool extract_data_to_external_training_of_the_network;
   std::string dataset_filename_with_global_path;
   std::string neural_net_filename_with_global_path;
+  int neural_net_number_of_features;
+  int neural_net_number_of_neurons_in_hidden_layer;
+  int neural_net_number_of_neurons_in_output_layer;
 
   float max_pred_std_dev_for_labelling;
   float score_threshold;
diff --git a/src/kf_based_terrain_analysis.cpp b/src/kf_based_terrain_analysis.cpp
index 5f5d265..ea46cf7 100644
--- a/src/kf_based_terrain_analysis.cpp
+++ b/src/kf_based_terrain_analysis.cpp
@@ -1004,11 +1004,12 @@ void CKf_Based_Terrain_Analysis::fastLabelPointcloudUsingGroundModel(
 
       // 1) We create a vectors to store the features to be used with the Shallow Neural Network for
       //    ground classification
-//      if (filtering_configuration.use_neural_network
-//          || filtering_configuration.extract_data_to_external_training_of_the_network)
-//      {
       std::vector<int> features_indexes_in_pcl_cloud_ptr;
 
+      std::vector<double> rgb_values;
+      std::vector<double> hsv_values;
+      std::vector<double> local_binary_patterns;
+
       std::vector<double> squared_distances;
       std::vector<double> incidence_angles;
       std::vector<double> intensities;
@@ -1016,7 +1017,6 @@ void CKf_Based_Terrain_Analysis::fastLabelPointcloudUsingGroundModel(
       std::vector<double> prediction_errors;
       std::vector<double> scores;
       std::vector<double> gt_label;
-//      }
 
       // 2) We point to the vector of indices related to the elevation_cloud point under evaluation
       std::vector<std::vector<int>>::const_iterator index_iterator = correspondence_indexes.begin()
@@ -1050,6 +1050,10 @@ void CKf_Based_Terrain_Analysis::fastLabelPointcloudUsingGroundModel(
             features_indexes_in_pcl_cloud_ptr.push_back(*point_iterator); // Index to modify the classification in a later step
             //std::cout << "Index saved = " << *point_iterator << std::endl;
 
+            rgb_values.push_back(point_to_evaluate.data_n[DATA_N_1_INTENSITY_VARIANCE]); // Feature!
+            hsv_values.push_back(point_to_evaluate.data_n[DATA_N_2_Z_MEAN]); // Feature!
+            local_binary_patterns.push_back(point_to_evaluate.data_n[DATA_N_3_Z_VARIANCE]); // Feature!
+
             squared_distances.push_back(
                 (double)(point_to_evaluate.x * point_to_evaluate.x + point_to_evaluate.y * point_to_evaluate.y
                     + point_to_evaluate.z * point_to_evaluate.z)); // Feature!
@@ -1097,9 +1101,6 @@ void CKf_Based_Terrain_Analysis::fastLabelPointcloudUsingGroundModel(
             // probability of being ground is zero, the class label is used to distinguish between general obstacles
             // and overhanging obstacles
             pcl_cloud_ptr->points[*point_iterator].data_c[DATA_C_1_ID_CLASS] = (float)CLASS_OBSTACLE + 0.0;
-
-//            if (flag_reduce_mahalanobis_thres_if_an_obstacle_is_found)
-//              flag_obstacle_found = true;
           }
           else
           {
@@ -1172,55 +1173,100 @@ void CKf_Based_Terrain_Analysis::fastLabelPointcloudUsingGroundModel(
         {
           for (int n = 0; n < num_of_ground_points; ++n)
           {
-            Eigen::VectorXd features(13, 1);
-
-            features(0, 0) = squared_distances[n];
-            features(1, 0) = incidence_angles[n];
-            features(2, 0) = intensities[n];
-            features(3, 0) = point_to_reference_squared_distances[n];
-            features(4, 0) = prediction_errors[n];
-            features(5, 0) = scores[n];
-            features(6, 0) = ratio_ground_points;
-            features(7, 0) = mean_intensity_ground_points_in_cell;
-            features(8, 0) = var_intensity_ground_points_in_cell;
-            features(9, 0) = mean_prediction_error;
-            features(10, 0) = var_prediction_error;
-            features(11, 0) = mean_of_scores;
-            features(12, 0) = var_of_scores;
+            Eigen::VectorXd features(nn_.number_of_features_, 1);
+
+            switch (nn_.number_of_features_)
+            {
+              case 6:
+                features(0, 0) = squared_distances[n];
+                features(1, 0) = incidence_angles[n];
+                features(2, 0) = scores[n];
+                features(3, 0) = ratio_ground_points;
+                features(4, 0) = mean_of_scores;
+                features(5, 0) = var_of_scores;
+                break;
+              case 9:
+                features(0, 0) = squared_distances[n];
+                features(1, 0) = incidence_angles[n];
+                features(2, 0) = intensities[n];
+                features(3, 0) = scores[n];
+                features(4, 0) = ratio_ground_points;
+                features(5, 0) = mean_intensity_ground_points_in_cell;
+                features(6, 0) = var_intensity_ground_points_in_cell;
+                features(7, 0) = mean_of_scores;
+                features(8, 0) = var_of_scores;
+                break;
+
+              case 13:
+                features(0, 0) = squared_distances[n];
+                features(1, 0) = incidence_angles[n];
+                features(2, 0) = intensities[n];
+                features(3, 0) = point_to_reference_squared_distances[n];
+                features(4, 0) = prediction_errors[n];
+                features(5, 0) = scores[n];
+                features(6, 0) = ratio_ground_points;
+                features(7, 0) = mean_intensity_ground_points_in_cell;
+                features(8, 0) = var_intensity_ground_points_in_cell;
+                features(9, 0) = mean_prediction_error;
+                features(10, 0) = var_prediction_error;
+                features(11, 0) = mean_of_scores;
+                features(12, 0) = var_of_scores;
+                break;
+              default:
+                assert(
+                    "ERROR in CKf_Based_Terrain_Analysis::fastLabelPointcloudUsingGroundModel: Number of features not valid!"
+                        && false);
+            }
 
             int predicted_class = nn_.predict(features);
-            pcl_cloud_ptr->points[features_indexes_in_pcl_cloud_ptr[n]].data_c[DATA_C_1_ID_CLASS] = (float)predicted_class + (float)scores[n];
 
-            switch(predicted_class)
+            if (predicted_class == CLASS_OBSTACLE)
+            {
+              pcl_cloud_ptr->points[features_indexes_in_pcl_cloud_ptr[n]].data_c[DATA_C_1_ID_CLASS] =
+                  (float)predicted_class + 0.0;
+            }
+            else
+            {
+              pcl_cloud_ptr->points[features_indexes_in_pcl_cloud_ptr[n]].data_c[DATA_C_1_ID_CLASS] =
+                  (float)predicted_class + (float)scores[n];
+            }
+            //std::cout << "Class + score = " << pcl_cloud_ptr->points[features_indexes_in_pcl_cloud_ptr[n]].data_c[DATA_C_1_ID_CLASS] << std::endl;
+
+            switch (predicted_class)
             {
               case ROAD:
                 pcl_cloud_ptr->points[features_indexes_in_pcl_cloud_ptr[n]].r = R_CLASS_ROAD;
                 pcl_cloud_ptr->points[features_indexes_in_pcl_cloud_ptr[n]].g = G_CLASS_ROAD;
                 pcl_cloud_ptr->points[features_indexes_in_pcl_cloud_ptr[n]].b = B_CLASS_ROAD;
+                //std::cout << "NN says point is road!" << std::endl;
                 break;
 
               case SIDEWALK:
                 pcl_cloud_ptr->points[features_indexes_in_pcl_cloud_ptr[n]].r = R_CLASS_SIDEWALK;
                 pcl_cloud_ptr->points[features_indexes_in_pcl_cloud_ptr[n]].g = G_CLASS_SIDEWALK;
                 pcl_cloud_ptr->points[features_indexes_in_pcl_cloud_ptr[n]].b = B_CLASS_SIDEWALK;
+                std::cout << "NN says point is sidewalk!" << std::endl;
                 break;
 
               case TERRAIN:
                 pcl_cloud_ptr->points[features_indexes_in_pcl_cloud_ptr[n]].r = R_CLASS_TERRAIN;
                 pcl_cloud_ptr->points[features_indexes_in_pcl_cloud_ptr[n]].g = G_CLASS_TERRAIN;
                 pcl_cloud_ptr->points[features_indexes_in_pcl_cloud_ptr[n]].b = B_CLASS_TERRAIN;
+                std::cout << "NN says point is terrain!" << std::endl;
                 break;
 
               case VEGETATION:
                 pcl_cloud_ptr->points[features_indexes_in_pcl_cloud_ptr[n]].r = R_CLASS_VEGETATION;
                 pcl_cloud_ptr->points[features_indexes_in_pcl_cloud_ptr[n]].g = G_CLASS_VEGETATION;
                 pcl_cloud_ptr->points[features_indexes_in_pcl_cloud_ptr[n]].b = B_CLASS_VEGETATION;
+                std::cout << "NN says point is vegetation!" << std::endl;
                 break;
 
               case CLASS_OBSTACLE:
                 pcl_cloud_ptr->points[features_indexes_in_pcl_cloud_ptr[n]].r = R_CLASS_KITTI_OBSTACLE;
                 pcl_cloud_ptr->points[features_indexes_in_pcl_cloud_ptr[n]].g = G_CLASS_KITTI_OBSTACLE;
                 pcl_cloud_ptr->points[features_indexes_in_pcl_cloud_ptr[n]].b = B_CLASS_KITTI_OBSTACLE;
+                std::cout << "NN says point is Obstacle!" << std::endl;
                 break;
             }
           }
@@ -1273,21 +1319,25 @@ void CKf_Based_Terrain_Analysis::fastLabelPointcloudUsingGroundModel(
 
           if (gt_label != OUTLIER)
           {
-            Eigen::VectorXd features(13, 1);
-
-            features(0, 0) = squared_distances[randomIndex];
-            features(1, 0) = incidence_angles[randomIndex];
-            features(2, 0) = intensities[randomIndex];
-            features(3, 0) = point_to_reference_squared_distances[randomIndex];
-            features(4, 0) = prediction_errors[randomIndex];
-            features(5, 0) = scores[randomIndex];
-            features(6, 0) = ratio_ground_points;
-            features(7, 0) = mean_intensity_ground_points_in_cell;
-            features(8, 0) = var_intensity_ground_points_in_cell;
-            features(9, 0) = mean_prediction_error;
-            features(10, 0) = var_prediction_error;
-            features(11, 0) = mean_of_scores;
-            features(12, 0) = var_of_scores;
+            Eigen::VectorXd features(17, 1);
+
+            features(0, 0) = rgb_values[randomIndex];
+            features(1, 0) = hsv_values[randomIndex];
+            features(2, 0) = local_binary_patterns[randomIndex];
+            features(3, 0) = point_to_read_gt_label.z;
+            features(4, 0) = squared_distances[randomIndex];
+            features(5, 0) = incidence_angles[randomIndex];
+            features(6, 0) = intensities[randomIndex];
+            features(7, 0) = point_to_reference_squared_distances[randomIndex];
+            features(8, 0) = prediction_errors[randomIndex];
+            features(9, 0) = scores[randomIndex];
+            features(10, 0) = ratio_ground_points;
+            features(11, 0) = mean_intensity_ground_points_in_cell;
+            features(12, 0) = var_intensity_ground_points_in_cell;
+            features(13, 0) = mean_prediction_error;
+            features(14, 0) = var_prediction_error;
+            features(15, 0) = mean_of_scores;
+            features(16, 0) = var_of_scores;
 
             //std::cout << "features = " << std::endl << features << std::endl;
 
@@ -1298,9 +1348,11 @@ void CKf_Based_Terrain_Analysis::fastLabelPointcloudUsingGroundModel(
             s << features(0, 0) << ", " << features(1, 0) << ", " << features(2, 0) << ", " << features(3, 0) << ", "
                 << features(4, 0) << ", " << features(5, 0) << ", " << features(6, 0) << ", " << features(7, 0) << ", "
                 << features(8, 0) << ", " << features(9, 0) << ", " << features(10, 0) << ", " << features(11, 0)
-                << ", " << features(12, 0) << ", " << gt_label << std::endl;
+                << ", " << features(12, 0) << ", " << features(13, 0) << ", " << features(14, 0) << ", "
+                << features(15, 0) << ", " << features(16, 0) << ", " << gt_label << std::endl;
 
-            dataset_file_ << s.str();
+            if (features(0, 0) >= 0.0)
+              dataset_file_ << s.str();
 
             //std::cout << "gt_label = " << gt_label << std::endl;
           }
@@ -1413,13 +1465,15 @@ void CKf_Based_Terrain_Analysis::ensureThatConnectionsAreReflectedInBothEnds(
   }
 }
 
-void CKf_Based_Terrain_Analysis::loadNeuralNetwork(const std::string filename)
+void CKf_Based_Terrain_Analysis::loadNeuralNetwork(
+    const kf_based_terrain_analysis_lib::FilteringConfiguration filtering_configuration)
 {
-  rapidcsv::Document doc(filename.c_str(), rapidcsv::LabelParams(-1, -1));
+  rapidcsv::Document doc(filtering_configuration.neural_net_filename_with_global_path.c_str(),
+                         rapidcsv::LabelParams(-1, -1));
 
-  nn_.number_of_features_ = 13;
-  nn_.number_of_neurons_in_hidden_layer_ = 10;
-  nn_.number_of_neurons_in_output_layer_ = 5;
+  nn_.number_of_features_ = filtering_configuration.neural_net_number_of_features;
+  nn_.number_of_neurons_in_hidden_layer_ = filtering_configuration.neural_net_number_of_neurons_in_hidden_layer;
+  nn_.number_of_neurons_in_output_layer_ = filtering_configuration.neural_net_number_of_neurons_in_output_layer;
 
   int b1_n_rows = nn_.number_of_neurons_in_hidden_layer_;
   int b1_n_cols = 1;
@@ -1563,8 +1617,8 @@ void CKf_Based_Terrain_Analysis::loadNeuralNetwork(const std::string filename)
   std::cout << "x_offset = " << std::endl << nn_.x_offset_ << std::endl;
   std::cout << "y_min = " << std::endl << nn_.y_min_ << std::endl;
 
-  std::cout << "Waiting for the user to press any key..." << std::endl;
-  getchar();
+  //std::cout << "Waiting for the user to press any key..." << std::endl;
+  //getchar();
   return;
 }
 
@@ -1604,7 +1658,7 @@ void CKf_Based_Terrain_Analysis::prepareFileInterface(
   use_neural_network_ = filtering_configuration.use_neural_network;
 
   if (use_neural_network_)
-    loadNeuralNetwork(filtering_configuration.neural_net_filename_with_global_path);
+    loadNeuralNetwork(filtering_configuration);
 }
 
 void CKf_Based_Terrain_Analysis::groundSegmentation(
@@ -1645,7 +1699,7 @@ void CKf_Based_Terrain_Analysis::groundSegmentation(
 // (ground_reference_cloud_ptr contains the vertices and the vector of int vectors named edges contains the edgesof the graph)
   pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr ground_reference_cloud_ptr(new pcl::PointCloud<pcl::PointXYZRGBNormal>);
 
-// This is an optimized version of a previous function (thats why the "fast" is appended at the begginning of the name),
+// This is an optimized version of a previous function (thats why the "fast" is appended at the beginning of the name),
 // but it became the standard so the previous one was deleted for sake of code clarity. This function generates the
 // representation using the grid to keep only the lowest points in each cell and a vector with indexes to the rest of points
   fastGenerateElevationCloud(filtering_configuration, pcl_cloud_ptr, elevation_cloud_ptr, correspondence_indexes);
diff --git a/src/shallow_neural_net.cpp b/src/shallow_neural_net.cpp
new file mode 100644
index 0000000..ed991b5
--- /dev/null
+++ b/src/shallow_neural_net.cpp
@@ -0,0 +1,101 @@
+#include "shallow_neural_net.h"
+
+ShallowNeuralNet::ShallowNeuralNet(void)
+{
+
+}
+
+ShallowNeuralNet::~ShallowNeuralNet(void)
+{
+
+}
+
+double ShallowNeuralNet::predict(Eigen::VectorXd features)
+{
+  int predicted_class = -1;
+
+  assert(features.rows() == this->number_of_features_ && "Error using ShallowNeuralNet::predict, number of features not expected!");
+
+  //std::cout << "features = " << std::endl << features << std::endl;
+  Eigen::VectorXd x_offset_applied(number_of_features_, 1);
+  x_offset_applied = features - this->x_offset_;
+
+  //std::cout << "x_offset_applied = " << std::endl << x_offset_applied << std::endl;
+
+  Eigen::VectorXd x_gain_plus_y_min(number_of_features_, 1);
+  for (int i = 0; i < number_of_features_; ++i)
+    x_gain_plus_y_min(i,0) = x_offset_applied(i, 0) * this->gain_(i, 0) + this->y_min_(0, 0);
+
+  //std::cout << "x_gain_plus_y_min = " << std::endl << x_gain_plus_y_min << std::endl;
+
+  Eigen::VectorXd z1(this->number_of_neurons_in_hidden_layer_, 1);
+  z1 = this->b1_ + (this->w1_ * x_gain_plus_y_min);
+
+  //std::cout << "z1 = " << std::endl << z1 << std::endl;
+
+  Eigen::VectorXd a1(this->number_of_neurons_in_hidden_layer_, 1);
+  for (int i = 0; i < this->number_of_neurons_in_hidden_layer_; ++i)
+    a1(i,0) = (2.0 / (1.0 + exp(-2.0 * z1(i,0)))) - 1.0;
+
+  //std::cout << "a1 = " << std::endl << a1 << std::endl;
+
+  Eigen::VectorXd z2(this->number_of_neurons_in_output_layer_, 1);
+  z2 = this->b2_ + (this->w2_ * a1);
+  //std::cout << "z2 = " << std::endl << z2 << std::endl;
+
+  double z2_max = z2.maxCoeff();
+  //std::cout << "z2_max = " << std::endl << z2_max << std::endl;
+  z2.array() -= z2_max;
+  //std::cout << "z2 = " << std::endl << z2 << std::endl;
+
+  Eigen::VectorXd numerator(this->number_of_neurons_in_output_layer_, 1);
+  for(int i = 0; i < this->number_of_neurons_in_output_layer_; ++i)
+    numerator(i,0) = exp(z2(i,0));
+
+  //std::cout << "numerator = " << std::endl << numerator << std::endl;
+
+  double denominator = numerator.sum();
+  //std::cout << "denominator = " << std::endl << denominator << std::endl;
+
+  Eigen::VectorXd prediction(this->number_of_neurons_in_output_layer_, 1);
+  prediction = numerator / denominator;
+
+  //std::cout << "prediction = " << std::endl << prediction << std::endl;
+
+  int index_of_max_prob = -1;
+  double max_prob = -1.0;
+  for (int i = 0; i < this->number_of_neurons_in_output_layer_; ++i)
+  {
+    if(prediction(i,0) > max_prob)
+    {
+      max_prob = prediction(i,0);
+      index_of_max_prob = i;
+    }
+  }
+  predicted_class = index_of_max_prob;
+
+  switch(predicted_class)
+  {
+    case 0:
+      predicted_class = CLASS_OBSTACLE;
+      break;
+    case 1:
+      predicted_class = ROAD;
+      break;
+    case 2:
+      predicted_class = SIDEWALK;
+      break;
+    case 3:
+      predicted_class = TERRAIN;
+      break;
+    case 4:
+      predicted_class = VEGETATION;
+      break;
+    default:
+      assert(false && "ERROR in ShallowNeuralNet::predict: unexpected predicted class!!");
+  }
+
+  //std::cout << "predicted_class = " << std::endl << predicted_class << std::endl;
+
+  return(predicted_class);
+}
-- 
GitLab