瀏覽代碼

add detection_lidar_EnclideanClustering.

yuchuli 2 年之前
父節點
當前提交
e98852c1e5

+ 73 - 0
src/detection/detection_lidar_EuclideanClustering/.gitignore

@@ -0,0 +1,73 @@
+# This file is used to ignore files which are generated
+# ----------------------------------------------------------------------------
+
+*~
+*.autosave
+*.a
+*.core
+*.moc
+*.o
+*.obj
+*.orig
+*.rej
+*.so
+*.so.*
+*_pch.h.cpp
+*_resource.rc
+*.qm
+.#*
+*.*#
+core
+!core/
+tags
+.DS_Store
+.directory
+*.debug
+Makefile*
+*.prl
+*.app
+moc_*.cpp
+ui_*.h
+qrc_*.cpp
+Thumbs.db
+*.res
+*.rc
+/.qmake.cache
+/.qmake.stash
+
+# qtcreator generated files
+*.pro.user*
+
+# xemacs temporary files
+*.flc
+
+# Vim temporary files
+.*.swp
+
+# Visual Studio generated files
+*.ib_pdb_index
+*.idb
+*.ilk
+*.pdb
+*.sln
+*.suo
+*.vcproj
+*vcproj.*.*.user
+*.ncb
+*.sdf
+*.opensdf
+*.vcxproj
+*vcxproj.*
+
+# MinGW generated files
+*.Debug
+*.Release
+
+# Python byte code
+*.pyc
+
+# Binaries
+# --------
+*.dll
+*.exe
+

+ 96 - 0
src/detection/detection_lidar_EuclideanClustering/README.md

@@ -0,0 +1,96 @@
+# lidar_obstacle_detector
+
+3D LiDAR Object Detection & Tracking using Euclidean Clustering & Hungarian algorithm
+
+[![CodeFactor](https://www.codefactor.io/repository/github/ss47816/lidar_obstacle_detector/badge)](https://www.codefactor.io/repository/github/ss47816/lidar_obstacle_detector)
+![Code Grade](https://api.codiga.io/project/30668/status/svg)
+![Code Quality Score](https://api.codiga.io/project/30668/score/svg)
+![GitHub Repo stars](https://img.shields.io/github/stars/ss47816/lidar_obstacle_detector?color=FFE333)
+![GitHub Repo forks](https://img.shields.io/github/forks/ss47816/lidar_obstacle_detector?color=FFE333)
+
+![Ubuntu](https://img.shields.io/badge/OS-Ubuntu-informational?style=flat&logo=ubuntu&logoColor=white&color=2bbc8a)
+![ROS](https://img.shields.io/badge/Tools-ROS-informational?style=flat&logo=ROS&logoColor=white&color=2bbc8a)
+![C++](https://img.shields.io/badge/Code-C++-informational?style=flat&logo=c%2B%2B&logoColor=white&color=2bbc8a)
+
+![demo_1](media/demo_1.gif)
+
+![demo_2](media/demo_2.gif)
+
+## Features
+* Segmentation of ground plane and obstacle point clouds
+* Customizable Region of Interest (ROI) for obstacle detection
+* Customizable region for removing ego vehicle points from the point cloud
+* Tracking of obstacles between frames using IOU gauge and Hungarian algorithm
+* In order to help you tune the parameters to suit your own applications better, all the key parameters of the algorithm are controllable in live action using the ros param dynamic reconfigure feature
+
+**TODOs**
+* LiDAR pointcloud motion undistortion
+* Drive Space/Kurb Segmentation
+* Refine PCA Bounding Boxes by L-Shape fitting
+* Add trackers such as UKF
+
+**Known Issues**
+* PCA Bounding Boxes might not be accurate in certain situations
+
+## Dependencies
+* autoware-msgs
+* jsk-recognition-msgs
+
+## Installation
+```bash
+# clone the repo
+cd catkin_ws/src
+git clone https://github.com/SS47816/lidar_obstacle_detector.git
+
+# install dependencies & build 
+cd ..
+rosdep install --from-paths src --ignore-src -r -y
+catkin_make
+source devel/setup.bash
+```
+
+## Usage
+
+### 1. (Easy) Use this pkg with ROS Bags (`mai_city` dataset as an example here)
+
+![demo_mai_city](media/mai_city_00.gif)
+
+**Step 1**: Download the `mai_city` dataset from their [Official Website](https://www.ipb.uni-bonn.de/data/mai-city-dataset/)
+
+**Step 2**: Launch the nodes using the `mai_city.launch` launch file
+```bash
+# this will launch the obstacle_detector node, rviz, and rqt_reconfigure GUI together
+roslaunch lidar_obstacle_detector mai_city.launch
+```
+
+**Step 3**: Run any of the bags from the dataset
+```bash
+# go to the folder where the dataset is located
+cd mai_city/bags
+# play the rosbag
+rosbag play 00.bag
+```
+
+### 2. Use this pkg with LGSVL Simulator (with the help of the [`lgsvl_utils`](https://github.com/SS47816/lgsvl_utils) pkg)
+
+![demo_lgsvl](media/lgsvl.gif)
+
+**Step 1**: Launch the LGSVL simulator and `lgsvl_utils` nodes 
+> Please refer this step to the [`README` Usage Section](https://github.com/SS47816/lgsvl_utils) of the `lgsvl_utils` pkg
+
+**Step 2**: Launch the nodes using the `launch/lgsvl.launch` launch file
+```bash
+# launch node
+roslaunch lidar_obstacle_detector lgsvl.launch 
+```
+
+## Contribution
+You are welcome contributing to the package by opening a pull-request
+
+We are following: 
+[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html), 
+[C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#main), 
+and [ROS C++ Style Guide](http://wiki.ros.org/CppStyleGuide)
+
+## License
+MIT License

+ 34 - 0
src/detection/detection_lidar_EuclideanClustering/detection_lidar_EuclideanClustering.pro

@@ -0,0 +1,34 @@
+QT -= gui
+
+CONFIG += c++11 console
+CONFIG -= app_bundle
+
+CONFIG += c++14
+
+# The following define makes your compiler emit warnings if you use
+# any feature of Qt which as been marked deprecated (the exact warnings
+# depend on your compiler). Please consult the documentation of the
+# deprecated API in order to know how to port your code away from it.
+DEFINES += QT_DEPRECATED_WARNINGS
+
+# You can also make your code fail to compile if you use deprecated APIs.
+# In order to do so, uncomment the following line.
+# You can also select to disable deprecated APIs only up to a certain version of Qt.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
+
+SOURCES += main.cpp
+
+!include(../../../include/ivpcl.pri ) {
+    error( "Couldn't find the ivpcl.pri file!" )
+}
+
+
+!include(../../../include/common.pri ) {
+    error( "Couldn't find the common.pri file!" )
+}
+
+HEADERS += \
+    lidar_obstacle_detector/box.hpp \
+    lidar_obstacle_detector/obstacle_detector.hpp
+
+LIBS += -lpcl_filters -lpcl_common -lpcl_search -lpcl_segmentation

+ 32 - 0
src/detection/detection_lidar_EuclideanClustering/lidar_obstacle_detector/box.hpp

@@ -0,0 +1,32 @@
+/* box.hpp
+
+ * Copyright (C) 2021 SS47816
+ 
+ * Implementation of the Box and BoxQ class
+
+**/
+
+#pragma once
+
+#include <Eigen/Geometry> 
+
+struct Box
+{
+ public:
+	int id;
+	Eigen::Vector3f position;
+	Eigen::Vector3f dimension;
+	Eigen::Quaternionf quaternion;
+
+	Box() {};
+
+	Box(int id, Eigen::Vector3f position, Eigen::Vector3f dimension)
+		: id(id), position(position), dimension(dimension)
+	{
+		quaternion = Eigen::Quaternionf(1, 0, 0, 0);
+	}
+
+	Box(int id, Eigen::Vector3f position, Eigen::Vector3f dimension, Eigen::Quaternionf quaternion)
+		: id(id), position(position), dimension(dimension), quaternion(quaternion)
+	{}
+};

+ 469 - 0
src/detection/detection_lidar_EuclideanClustering/lidar_obstacle_detector/obstacle_detector.hpp

@@ -0,0 +1,469 @@
+/* obstacle_detector.hpp
+
+ * Copyright (C) 2021 SS47816
+
+ * Implementation of 3D LiDAR Obstacle Detection & Tracking Algorithms
+
+**/
+
+#pragma once
+
+#include <iostream>
+#include <string>
+#include <vector>
+#include <ctime>
+#include <chrono>
+#include <unordered_set>
+
+#include <pcl/common/common.h>
+#include <pcl/common/pca.h>
+#include <pcl/common/transforms.h>
+#include <pcl/filters/extract_indices.h>
+#include <pcl/filters/voxel_grid.h>
+#include <pcl/filters/crop_box.h>
+#include <pcl/kdtree/kdtree.h>
+#include <pcl/segmentation/sac_segmentation.h>
+#include <pcl/segmentation/extract_clusters.h>
+
+#include "box.hpp"
+
+namespace lidar_obstacle_detector
+{
+template <typename PointT>
+class ObstacleDetector
+{
+ public:
+  ObstacleDetector();
+  virtual ~ObstacleDetector();
+
+  // ****************** Detection ***********************
+
+  typename pcl::PointCloud<PointT>::Ptr filterCloud(const typename pcl::PointCloud<PointT>::ConstPtr& cloud, const float filter_res, const Eigen::Vector4f& min_pt, const Eigen::Vector4f& max_pt);
+  
+  std::pair<typename pcl::PointCloud<PointT>::Ptr, typename pcl::PointCloud<PointT>::Ptr> segmentPlane(const typename pcl::PointCloud<PointT>::ConstPtr& cloud, const int max_iterations, const float distance_thresh);
+
+  std::vector<typename pcl::PointCloud<PointT>::Ptr> clustering(const typename pcl::PointCloud<PointT>::ConstPtr& cloud, const float cluster_tolerance, const int min_size, const int max_size);
+
+  Box axisAlignedBoundingBox(const typename pcl::PointCloud<PointT>::ConstPtr& cluster, const int id);
+
+  Box pcaBoundingBox(typename pcl::PointCloud<PointT>::Ptr& cluster, const int id);
+
+  // ****************** Tracking ***********************
+  void obstacleTracking(const std::vector<Box>& prev_boxes, std::vector<Box>& curr_boxes, const float displacement_thresh, const float iou_thresh);
+
+ private:
+  // ****************** Detection ***********************
+  std::pair<typename pcl::PointCloud<PointT>::Ptr, typename pcl::PointCloud<PointT>::Ptr> separateClouds(const pcl::PointIndices::ConstPtr& inliers, const typename pcl::PointCloud<PointT>::ConstPtr& cloud);
+
+  // ****************** Tracking ***********************
+  bool compareBoxes(const Box& a, const Box& b, const float displacement_thresh, const float iou_thresh);
+
+  // Link nearby bounding boxes between the previous and previous frame
+  std::vector<std::vector<int>> associateBoxes(const std::vector<Box>& prev_boxes, const std::vector<Box>& curr_boxes, const float displacement_thresh, const float iou_thresh);
+
+  // Connection Matrix
+  std::vector<std::vector<int>> connectionMatrix(const std::vector<std::vector<int>>& connection_pairs, std::vector<int>& left, std::vector<int>& right);
+
+  // Helper function for Hungarian Algorithm
+  bool hungarianFind(const int i, const std::vector<std::vector<int>>& connection_matrix, std::vector<bool>& right_connected, std::vector<int>& right_pair);
+
+  // Customized Hungarian Algorithm
+  std::vector<int> hungarian(const std::vector<std::vector<int>>& connection_matrix);
+
+  // Helper function for searching the box index in boxes given an id
+  int searchBoxIndex(const std::vector<Box>& Boxes, const int id);
+};
+
+// constructor:
+template <typename PointT>
+ObstacleDetector<PointT>::ObstacleDetector() {}
+
+// de-constructor:
+template <typename PointT>
+ObstacleDetector<PointT>::~ObstacleDetector() {}
+
+template <typename PointT>
+typename pcl::PointCloud<PointT>::Ptr ObstacleDetector<PointT>::filterCloud(const typename pcl::PointCloud<PointT>::ConstPtr& cloud, const float filter_res, const Eigen::Vector4f& min_pt, const Eigen::Vector4f& max_pt)
+{
+  // Time segmentation process
+  // const auto start_time = std::chrono::steady_clock::now();
+
+  // Create the filtering object: downsample the dataset using a leaf size
+  pcl::VoxelGrid<PointT> vg;
+  typename pcl::PointCloud<PointT>::Ptr cloud_filtered(new pcl::PointCloud<PointT>);
+  vg.setInputCloud(cloud);
+  vg.setLeafSize(filter_res, filter_res, filter_res);
+  vg.filter(*cloud_filtered);
+
+  // Cropping the ROI
+  typename pcl::PointCloud<PointT>::Ptr cloud_roi(new pcl::PointCloud<PointT>);
+  pcl::CropBox<PointT> region(true);
+  region.setMin(min_pt);
+  region.setMax(max_pt);
+  region.setInputCloud(cloud_filtered);
+  region.filter(*cloud_roi);
+
+  // Removing the car roof region
+  std::vector<int> indices;
+  pcl::CropBox<PointT> roof(true);
+  roof.setMin(Eigen::Vector4f(-1.5, -1.7, -1, 1));
+  roof.setMax(Eigen::Vector4f(2.6, 1.7, -0.4, 1));
+  roof.setInputCloud(cloud_roi);
+  roof.filter(indices);
+
+  pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
+  for (auto& point : indices)
+    inliers->indices.push_back(point);
+
+  pcl::ExtractIndices<PointT> extract;
+  extract.setInputCloud(cloud_roi);
+  extract.setIndices(inliers);
+  extract.setNegative(true);
+  extract.filter(*cloud_roi);
+
+  // const auto end_time = std::chrono::steady_clock::now();
+  // const auto elapsed_time = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
+  // std::cout << "filtering took " << elapsed_time.count() << " milliseconds" << std::endl;
+
+  return cloud_roi;
+}
+
+template <typename PointT>
+std::pair<typename pcl::PointCloud<PointT>::Ptr, typename pcl::PointCloud<PointT>::Ptr> ObstacleDetector<PointT>::separateClouds(const pcl::PointIndices::ConstPtr& inliers, const typename pcl::PointCloud<PointT>::ConstPtr& cloud)
+{
+  typename pcl::PointCloud<PointT>::Ptr obstacle_cloud(new pcl::PointCloud<PointT>());
+  typename pcl::PointCloud<PointT>::Ptr ground_cloud(new pcl::PointCloud<PointT>());
+
+  // Pushback all the inliers into the ground_cloud
+  for (int index : inliers->indices)
+  {
+    ground_cloud->points.push_back(cloud->points[index]);
+  }
+
+  // Extract the points that are not in the inliers to obstacle_cloud
+  pcl::ExtractIndices<PointT> extract;
+  extract.setInputCloud(cloud);
+  extract.setIndices(inliers);
+  extract.setNegative(true);
+  extract.filter(*obstacle_cloud);
+
+  return std::pair<typename pcl::PointCloud<PointT>::Ptr, typename pcl::PointCloud<PointT>::Ptr>(obstacle_cloud, ground_cloud);
+}
+
+template <typename PointT>
+std::pair<typename pcl::PointCloud<PointT>::Ptr, typename pcl::PointCloud<PointT>::Ptr> ObstacleDetector<PointT>::segmentPlane(const typename pcl::PointCloud<PointT>::ConstPtr& cloud, const int max_iterations, const float distance_thresh)
+{
+  // Time segmentation process
+  // const auto start_time = std::chrono::steady_clock::now();
+
+  // Find inliers for the cloud.
+  pcl::SACSegmentation<PointT> seg;
+  pcl::PointIndices::Ptr inliers{new pcl::PointIndices};
+  pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
+
+  seg.setOptimizeCoefficients(true);
+  seg.setModelType(pcl::SACMODEL_PLANE);
+  seg.setMethodType(pcl::SAC_RANSAC);
+  seg.setMaxIterations(max_iterations);
+  seg.setDistanceThreshold(distance_thresh);
+
+  // Segment the largest planar component from the input cloud
+  seg.setInputCloud(cloud);
+  seg.segment(*inliers, *coefficients);
+  if (inliers->indices.empty())
+  {
+    std::cout << "Could not estimate a planar model for the given dataset." << std::endl;
+  }
+
+  // const auto end_time = std::chrono::steady_clock::now();
+  // const auto elapsed_time = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
+  // std::cout << "plane segmentation took " << elapsed_time.count() << " milliseconds" << std::endl;
+
+  return separateClouds(inliers, cloud);
+}
+
+template <typename PointT>
+std::vector<typename pcl::PointCloud<PointT>::Ptr> ObstacleDetector<PointT>::clustering(const typename pcl::PointCloud<PointT>::ConstPtr& cloud, const float cluster_tolerance, const int min_size, const int max_size)
+{
+  // Time clustering process
+  // const auto start_time = std::chrono::steady_clock::now();
+
+  std::vector<typename pcl::PointCloud<PointT>::Ptr> clusters;
+
+  // Perform euclidean clustering to group detected obstacles
+  typename pcl::search::KdTree<PointT>::Ptr tree(new pcl::search::KdTree<PointT>);
+  tree->setInputCloud(cloud);
+
+  std::vector<pcl::PointIndices> cluster_indices;
+  pcl::EuclideanClusterExtraction<PointT> ec;
+  ec.setClusterTolerance(cluster_tolerance);
+  ec.setMinClusterSize(min_size);
+  ec.setMaxClusterSize(max_size);
+  ec.setSearchMethod(tree);
+  ec.setInputCloud(cloud);
+  ec.extract(cluster_indices);
+
+  for (auto& getIndices : cluster_indices)
+  {
+    typename pcl::PointCloud<PointT>::Ptr cluster(new pcl::PointCloud<PointT>);
+
+    for (auto& index : getIndices.indices)
+      cluster->points.push_back(cloud->points[index]);
+
+    cluster->width = cluster->points.size();
+    cluster->height = 1;
+    cluster->is_dense = true;
+
+    clusters.push_back(cluster);
+  }
+
+  // const auto end_time = std::chrono::steady_clock::now();
+  // const auto elapsed_time = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
+  // std::cout << "clustering took " << elapsed_time.count() << " milliseconds and found " << clusters.size() << " clusters" << std::endl;
+
+  return clusters;
+}
+
+template <typename PointT>
+Box ObstacleDetector<PointT>::axisAlignedBoundingBox(const typename pcl::PointCloud<PointT>::ConstPtr& cluster, const int id)
+{
+  // Find bounding box for one of the clusters
+  PointT min_pt, max_pt;
+  pcl::getMinMax3D(*cluster, min_pt, max_pt);
+  
+  const Eigen::Vector3f position((max_pt.x + min_pt.x)/2, (max_pt.y + min_pt.y)/2, (max_pt.z + min_pt.z)/2);
+  const Eigen::Vector3f dimension((max_pt.x - min_pt.x), (max_pt.y - min_pt.y), (max_pt.z - min_pt.z));
+
+  return Box(id, position, dimension);
+}
+
+template <typename PointT>
+Box ObstacleDetector<PointT>::pcaBoundingBox(typename pcl::PointCloud<PointT>::Ptr& cluster, const int id)
+{
+  // Compute the bounding box height (to be used later for recreating the box)
+  PointT min_pt, max_pt;
+  pcl::getMinMax3D(*cluster, min_pt, max_pt);
+  const float box_height = max_pt.z - min_pt.z;
+  const float box_z = (max_pt.z + min_pt.z)/2;
+
+  // Compute the cluster centroid 
+  Eigen::Vector4f pca_centroid;
+  pcl::compute3DCentroid(*cluster, pca_centroid);
+
+  // Squash the cluster to x-y plane with z = centroid z 
+  for (size_t i = 0; i < cluster->size(); ++i)
+  {
+    cluster->points[i].z = pca_centroid(2);
+  }
+
+  // Compute principal directions & Transform the original cloud to PCA coordinates
+  pcl::PointCloud<pcl::PointXYZ>::Ptr pca_projected_cloud(new pcl::PointCloud<pcl::PointXYZ>);
+  pcl::PCA<pcl::PointXYZ> pca;
+  pca.setInputCloud(cluster);
+  pca.project(*cluster, *pca_projected_cloud);
+  
+  const auto eigen_vectors = pca.getEigenVectors();
+
+  // Get the minimum and maximum points of the transformed cloud.
+  pcl::getMinMax3D(*pca_projected_cloud, min_pt, max_pt);
+  const Eigen::Vector3f meanDiagonal = 0.5f * (max_pt.getVector3fMap() + min_pt.getVector3fMap());
+
+  // Final transform
+  const Eigen::Quaternionf quaternion(eigen_vectors); // Quaternions are a way to do rotations https://www.youtube.com/watch?v=mHVwd8gYLnI
+  const Eigen::Vector3f position = eigen_vectors * meanDiagonal + pca_centroid.head<3>();
+  const Eigen::Vector3f dimension((max_pt.x - min_pt.x), (max_pt.y - min_pt.y), box_height);
+
+  return Box(id, position, dimension, quaternion);
+}
+
+// ************************* Tracking ***************************
+template <typename PointT>
+void ObstacleDetector<PointT>::obstacleTracking(const std::vector<Box>& prev_boxes, std::vector<Box>& curr_boxes, const float displacement_thresh, const float iou_thresh)
+{
+  // Tracking (based on the change in size and displacement between frames)
+  
+  if (curr_boxes.empty() || prev_boxes.empty())
+  {
+    return;
+  }
+  else
+  {
+    // vectors containing the id of boxes in left and right sets
+    std::vector<int> pre_ids;
+    std::vector<int> cur_ids;
+    std::vector<int> matches;
+
+    // Associate Boxes that are similar in two frames
+    auto connection_pairs = associateBoxes(prev_boxes, curr_boxes, displacement_thresh, iou_thresh);
+
+    if (connection_pairs.empty()) return;
+
+    // Construct the connection matrix for Hungarian Algorithm's use
+    auto connection_matrix = connectionMatrix(connection_pairs, pre_ids, cur_ids);
+
+    // Use Hungarian Algorithm to solve for max-matching
+    matches = hungarian(connection_matrix);
+
+    for (int j = 0; j < matches.size(); ++j)
+    {
+      // find the index of the previous box that the current box corresponds to
+      const auto pre_id = pre_ids[matches[j]];
+      const auto pre_index = searchBoxIndex(prev_boxes, pre_id);
+      
+      // find the index of the current box that needs to be changed
+      const auto cur_id = cur_ids[j]; // right and matches has the same size
+      const auto cur_index = searchBoxIndex(curr_boxes, cur_id);
+      
+      if (pre_index > -1 && cur_index > -1)
+      {
+        // change the id of the current box to the same as the previous box
+        curr_boxes[cur_index].id = prev_boxes[pre_index].id;
+      }
+    }
+  }
+}
+
+template <typename PointT>
+bool ObstacleDetector<PointT>::compareBoxes(const Box& a, const Box& b, const float displacement_thresh, const float iou_thresh)
+{
+  // Percetage Displacements ranging between [0.0, +oo]
+  const float dis = sqrt((a.position[0] - b.position[0]) * (a.position[0] - b.position[0]) + (a.position[1] - b.position[1]) * (a.position[1] - b.position[1]) + (a.position[2] - b.position[2]) * (a.position[2] - b.position[2]));
+
+  const float a_max_dim = std::max(a.dimension[0], std::max(a.dimension[1], a.dimension[2]));
+  const float b_max_dim = std::max(b.dimension[0], std::max(b.dimension[1], b.dimension[2]));
+  const float ctr_dis = dis / std::min(a_max_dim, b_max_dim);
+
+  // Dimension similiarity values between [0.0, 1.0]
+  const float x_dim = 2 * (a.dimension[0] - b.dimension[0]) / (a.dimension[0] + b.dimension[0]);
+  const float y_dim = 2 * (a.dimension[1] - b.dimension[1]) / (a.dimension[1] + b.dimension[1]);
+  const float z_dim = 2 * (a.dimension[2] - b.dimension[2]) / (a.dimension[2] + b.dimension[2]);
+
+  if (ctr_dis <= displacement_thresh && x_dim <= iou_thresh && y_dim <= iou_thresh && z_dim <= iou_thresh)
+  {
+    return true;
+  }
+  else
+  {
+    return false;
+  }
+}
+
+template <typename PointT>
+std::vector<std::vector<int>> ObstacleDetector<PointT>::associateBoxes(const std::vector<Box>& prev_boxes, const std::vector<Box>& curr_boxes, const float displacement_thresh, const float iou_thresh)
+{
+  std::vector<std::vector<int>> connection_pairs;
+
+  for (auto& prev_box : prev_boxes)
+  {
+    for (auto& curBox : curr_boxes)
+    {
+      // Add the indecies of a pair of similiar boxes to the matrix
+      if (this->compareBoxes(curBox, prev_box, displacement_thresh, iou_thresh))
+      {
+        connection_pairs.push_back({prev_box.id, curBox.id});
+      }
+    }
+  }
+
+  return connection_pairs;
+}
+
+template <typename PointT>
+std::vector<std::vector<int>> ObstacleDetector<PointT>::connectionMatrix(const std::vector<std::vector<int>>& connection_pairs, std::vector<int>& left, std::vector<int>& right)
+{
+  // Hash the box ids in the connection_pairs to two vectors(sets), left and right
+  for (auto& pair : connection_pairs)
+  {
+    bool left_found = false;
+    for (auto i : left)
+    {
+      if (i == pair[0])
+        left_found = true;
+    }
+    if (!left_found)
+      left.push_back(pair[0]);
+
+    bool right_found = false;
+    for (auto j : right)
+    {
+      if (j == pair[1])
+        right_found = true;
+    }
+    if (!right_found)
+      right.push_back(pair[1]);
+  }
+
+  std::vector<std::vector<int>> connection_matrix(left.size(), std::vector<int>(right.size(), 0));
+
+  for (auto& pair : connection_pairs)
+  {
+    int left_index = -1;
+    for (int i = 0; i < left.size(); ++i)
+    {
+      if (pair[0] == left[i])
+        left_index = i;
+    }
+
+    int right_index = -1;
+    for (int i = 0; i < right.size(); ++i)
+    {
+      if (pair[1] == right[i])
+        right_index = i;
+    }
+
+    if (left_index != -1 && right_index != -1)
+      connection_matrix[left_index][right_index] = 1;
+  }
+
+  return connection_matrix;
+}
+
+template <typename PointT>
+bool ObstacleDetector<PointT>::hungarianFind(const int i, const std::vector<std::vector<int>>& connection_matrix, std::vector<bool>& right_connected, std::vector<int>& right_pair)
+{
+  for (int j = 0; j < connection_matrix[0].size(); ++j)
+  {
+    if (connection_matrix[i][j] == 1 && right_connected[j] == false)
+    {
+      right_connected[j] = true;
+
+      if (right_pair[j] == -1 || hungarianFind(right_pair[j], connection_matrix, right_connected, right_pair))
+      {
+        right_pair[j] = i;
+        return true;
+      }
+    }
+  }
+}
+
+template <typename PointT>
+std::vector<int> ObstacleDetector<PointT>::hungarian(const std::vector<std::vector<int>>& connection_matrix)
+{
+  std::vector<bool> right_connected(connection_matrix[0].size(), false);
+  std::vector<int> right_pair(connection_matrix[0].size(), -1);
+
+  int count = 0;
+  for (int i = 0; i < connection_matrix.size(); ++i)
+  {
+    if (hungarianFind(i, connection_matrix, right_connected, right_pair))
+      count++;
+  }
+
+  std::cout << "For: " << right_pair.size() << " current frame bounding boxes, found: " << count << " matches in previous frame! " << std::endl;
+
+  return right_pair;
+}
+
+template <typename PointT>
+int ObstacleDetector<PointT>::searchBoxIndex(const std::vector<Box>& boxes, const int id)
+{
+  for (int i = 0; i < boxes.size(); i++)
+  {
+    if (boxes[i].id == id)
+    return i;
+  }
+
+  return -1;
+}
+
+} // namespace lidar_obstacle_detector

+ 169 - 0
src/detection/detection_lidar_EuclideanClustering/main.cpp

@@ -0,0 +1,169 @@
+#include <QCoreApplication>
+
+#include "lidar_obstacle_detector/obstacle_detector.hpp"
+
+#include <iostream>
+
+#include "modulecomm.h"
+
+#include "ivstdcolorout.h"
+
+using namespace lidar_obstacle_detector;
+
+// Pointcloud Filtering Parameters
+bool USE_PCA_BOX;
+bool USE_TRACKING;
+float VOXEL_GRID_SIZE;
+Eigen::Vector4f ROI_MAX_POINT, ROI_MIN_POINT;
+float GROUND_THRESH;
+float CLUSTER_THRESH;
+int CLUSTER_MAX_SIZE, CLUSTER_MIN_SIZE;
+float DISPLACEMENT_THRESH, IOU_THRESH;
+
+size_t obstacle_id_;
+std::string bbox_target_frame_, bbox_source_frame_;
+std::vector<Box> prev_boxes_, curr_boxes_;
+
+std::shared_ptr<ObstacleDetector<pcl::PointXYZ>> obstacle_detector;
+
+
+void publishDetectedObjects(std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr>&& cloud_clusters)
+{
+  for (auto& cluster : cloud_clusters)
+  {
+    // Create Bounding Boxes
+    Box box = USE_PCA_BOX?
+      obstacle_detector->pcaBoundingBox(cluster, obstacle_id_) :
+      obstacle_detector->axisAlignedBoundingBox(cluster, obstacle_id_);
+
+    obstacle_id_ = (obstacle_id_ < SIZE_MAX)? ++obstacle_id_ : 0;
+    curr_boxes_.emplace_back(box);
+  }
+
+  // Re-assign Box ids based on tracking result
+  if (USE_TRACKING)
+    obstacle_detector->obstacleTracking(prev_boxes_, curr_boxes_, DISPLACEMENT_THRESH, IOU_THRESH);
+
+
+  ivstdcolorout("Print Objects:",iv::STDCOLOR_BOLDGREEN);
+
+  // Transform boxes from lidar frame to base_link frame, and convert to jsk and autoware msg formats
+  for (auto& box : curr_boxes_)
+  {
+      std::cout<<"     box x y z"<<box.position(0)<<" "<<box.position(1)<<" "<<box.position(2)<<std::endl;
+//    geometry_msgs::Pose pose, pose_transformed;
+//    pose.position.x = box.position(0);
+//    pose.position.y = box.position(1);
+//    pose.position.z = box.position(2);
+//    pose.orientation.w = box.quaternion.w();
+//    pose.orientation.x = box.quaternion.x();
+//    pose.orientation.y = box.quaternion.y();
+//    pose.orientation.z = box.quaternion.z();
+//    tf2::doTransform(pose, pose_transformed, transform_stamped);
+
+//    jsk_bboxes.boxes.emplace_back(transformJskBbox(box, bbox_header, pose_transformed));
+//    autoware_objects.objects.emplace_back(transformAutowareObject(box, bbox_header, pose_transformed));
+  }
+
+
+  // Update previous bounding boxes
+  prev_boxes_.swap(curr_boxes_);
+  curr_boxes_.clear();
+}
+
+void DectectOnePCD(const pcl::PointCloud<pcl::PointXYZ>::Ptr &raw_cloud)
+{
+    // Downsampleing, ROI, and removing the car roof
+    auto filtered_cloud = obstacle_detector->filterCloud(raw_cloud, VOXEL_GRID_SIZE, ROI_MIN_POINT, ROI_MAX_POINT);
+
+    // Segment the groud plane and obstacles
+    auto segmented_clouds = obstacle_detector->segmentPlane(filtered_cloud, 30, GROUND_THRESH);
+
+    // Cluster objects
+    auto cloud_clusters = obstacle_detector->clustering(segmented_clouds.first, CLUSTER_THRESH, CLUSTER_MIN_SIZE, CLUSTER_MAX_SIZE);
+
+    publishDetectedObjects(std::move(cloud_clusters));
+}
+
+void ListenPointCloud(const char * strdata,const unsigned int nSize,const unsigned int index,const QDateTime * dt,const char * strmemname)
+{
+    if(nSize <=16)return;
+    unsigned int * pHeadSize = (unsigned int *)strdata;
+    if(*pHeadSize > nSize)
+    {
+        std::cout<<"ListenPointCloud data is small headsize ="<<*pHeadSize<<"  data size is"<<nSize<<std::endl;
+    }
+
+    QTime xTime;
+    xTime.start();
+
+    pcl::PointCloud<pcl::PointXYZ>::Ptr point_cloud(
+                new pcl::PointCloud<pcl::PointXYZ>());
+    int nNameSize;
+    nNameSize = *pHeadSize - 4-4-8;
+    char * strName = new char[nNameSize+1];strName[nNameSize] = 0;
+    memcpy(strName,(char *)((char *)strdata +4),nNameSize);
+    point_cloud->header.frame_id = strName;
+    memcpy(&point_cloud->header.seq,(char *)strdata+4+nNameSize,4);
+    memcpy(&point_cloud->header.stamp,(char *)strdata+4+nNameSize+4,8);
+    int nPCount = (nSize - *pHeadSize)/sizeof(pcl::PointXYZI);
+    int i;
+    pcl::PointXYZI * p;
+    p = (pcl::PointXYZI *)((char *)strdata + *pHeadSize);
+    for(i=0;i<nPCount;i++)
+    {
+        pcl::PointXYZ xp;
+//        if((p->x<-30)||(p->x>30)||(p->y>50)||(p->y<-50))
+//        {
+
+//        }
+//        else
+//        {
+//            memcpy(&xp,p,sizeof(pcl::PointXYZI));
+//            xp.z = xp.z;
+            xp.x = p->x;
+            xp.y = p->y;
+            xp.z = p->z;
+            point_cloud->push_back(xp);
+
+//        }
+        p++;
+//        xp.x = p->x;
+//        xp.y = p->y;
+//        xp.z = p->z;
+//        xp.intensity = p->intensity;
+//        point_cloud->push_back(xp);
+//        p++;
+    }
+
+//    std::cout<<"pcl time is "<<xTime.elapsed()<<std::endl;
+
+    DectectOnePCD(point_cloud);
+
+    std::cout<<"time is "<<(QDateTime::currentMSecsSinceEpoch() % 1000)<<" use: "<<xTime.elapsed()<<std::endl;
+
+}
+
+int main(int argc, char *argv[])
+{
+    QCoreApplication a(argc, argv);
+
+    USE_PCA_BOX = false;
+    USE_TRACKING = true;
+    VOXEL_GRID_SIZE = 0.2;
+    ROI_MAX_POINT = Eigen::Vector4f(70,30,1,1);
+    ROI_MIN_POINT = Eigen::Vector4f(-30,-30,-2.5, 1);
+    GROUND_THRESH = 0.3;
+    CLUSTER_THRESH = 0.6;
+    CLUSTER_MAX_SIZE = 5000;
+    CLUSTER_MIN_SIZE = 10;
+    DISPLACEMENT_THRESH = 1.0;
+    IOU_THRESH = 1.0;
+
+
+    obstacle_detector = std::make_shared<ObstacleDetector<pcl::PointXYZ>>();
+
+    void * pa = iv::modulecomm::RegisterRecv("lidar_pc",ListenPointCloud);
+
+    return a.exec();
+}