TabletopSegmentation.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of ArmarX.
3  *
4  * ArmarX is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 as
6  * published by the Free Software Foundation.
7  *
8  * ArmarX is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * @package ActiveVision::ArmarXObjects::TabletopSegmentation
17  * @author Markus Grotz ( markus dot grotz at kit dot edu )
18  * @date 2018
19  * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20  * GNU General Public License
21  */
22 
23 #include "TabletopSegmentation.h"
24 
25 
26 #include <Eigen/Core>
29 #include <VirtualRobot/math/FitPlane.h>
30 #include <pcl/features/normal_3d.h>
31 #include <pcl/segmentation/region_growing.h>
33 
34 
35 
36 #include <pcl/common/common.h>
37 #include <pcl/common/transforms.h>
38 
39 
40 #include <pcl/filters/filter.h>
41 #include <pcl/filters/extract_indices.h>
42 #include <pcl/filters/voxel_grid.h>
43 
44 #include <pcl/kdtree/kdtree.h>
45 
46 #include <pcl/sample_consensus/method_types.h>
47 #include <pcl/sample_consensus/model_types.h>
48 
49 
50 #include <limits.h>
51 
52 using namespace armarx;
53 
54 
55 
57 {
60 }
61 
62 
63 
65 {
66  offeringTopic(getProperty<std::string>("DebugObserverName").getValue());
67  offeringTopic(getProperty<std::string>("DebugDrawerTopicName").getValue());
68 
69  serviceProvider = new ServiceProvider("TabletopSegmentation",
70  [&](int timeout)
71  {
72  nextTimeoutAbs = TimeUtil::GetTime() + IceUtil::Time::milliSeconds(timeout);
73  ARMARX_INFO << "Next timeout at " + nextTimeoutAbs.toDateTime();
74  });
75  auto prx = getIceManager()->registerObject(serviceProvider, getName() + "ServiceProvider");
76  getIceManager()->subscribeTopic(prx.first, "ServiceRequests");
77 }
78 
80 {
81  debugObserver = getTopic<DebugObserverInterfacePrx>(getProperty<std::string>("DebugObserverName").getValue());
82  debugDrawer = getTopic<DebugDrawerInterfacePrx>(getProperty<std::string>("DebugDrawerTopicName").getValue());
83 
84  prop_DistanceThreshold = getProperty<double>("DistanceThreshold");
85  prop_EpsAngle = getProperty<double>("EpsAngle");
86 
87  prop_IncludeTabletopPlane = getProperty<bool>("IncludeTabletopPlane");
88  prop_CalculatePlane = getProperty<bool>("CalculatePlane");
89 
90  prop_ClusterTolerance = getProperty<double>("ClusterTolerance");
91  prop_MinClusterSize = getProperty<int>("MinClusterSize");
92  prop_MaxClusterSize = getProperty<int>("MaxClusterSize");
93 
94  prop_HeightMin = getProperty<double>("HeightMin");
95  prop_HeightMax = getProperty<double>("HeightMax");
96 
97  enableResultPointClouds<PointL>(getName() + "Result");
98  enableResultPointClouds<PointT>(getName() + "Plane");
99 
100  getProxyFromProperty(remoteGui, "RemoteGuiName", false, "", false);
101  if (remoteGui)
102  {
103  remoteGuiTask = new RunningTask<TabletopSegmentation>(this, &TabletopSegmentation::guiTask);
104  remoteGuiTask->start();
105  }
106 }
107 
108 
110 {
111  if (remoteGuiTask)
112  {
113  remoteGuiTask->stop();
114  remoteGuiTask = nullptr;
115  }
116 }
117 
119 {
120 }
121 
123 {
124  pcl::PointCloud<PointT>::Ptr inputCloud(new pcl::PointCloud<PointT>());
125  pcl::PointCloud<PointT>::Ptr plane(new pcl::PointCloud<PointT>());
126 
127  if (!getProperty<bool>("AlwaysActive").getValue() && TimeUtil::GetTime() > nextTimeoutAbs)
128  {
129  usleep(10000);
130  return;
131  }
132  if (!waitForPointClouds())
133  {
134  ARMARX_INFO << "Timeout or error while waiting for point cloud data";
135  return;
136  }
137  else
138  {
139  getPointClouds(inputCloud);
140  }
141 
142  ARMARX_VERBOSE << "Got point cloud";
143 
144  if (inputCloud->points.size() == 0)
145  {
146  ARMARX_DEBUG << "empty point cloud.";
147  return;
148  }
149 
150  bool calculatePlane = false;
151  bool includeTabletopPlane = false;
152  {
153  // Update parameters
154  std::unique_lock<std::mutex> lock(prop_mutex);
155 
156  seg.setOptimizeCoefficients(true);
157  seg.setModelType(pcl::SACMODEL_PERPENDICULAR_PLANE);
158  seg.setMethodType(pcl::SAC_RANSAC);
159  seg.setMaxIterations(1000);
160  seg.setDistanceThreshold(prop_DistanceThreshold);
161  seg.setAxis(Eigen::Vector3f::UnitZ());
162  seg.setEpsAngle(prop_EpsAngle);
163  seg.setProbability(0.99);
164 
165  hull.setDimension(2);
166  prism.setHeightLimits(prop_HeightMin, prop_HeightMax);
168 
169  ec.setClusterTolerance(prop_ClusterTolerance);
170  ec.setMinClusterSize(prop_MinClusterSize);
171  ec.setMaxClusterSize(prop_MaxClusterSize);
172 
173  includeTabletopPlane = prop_IncludeTabletopPlane;
174  calculatePlane = prop_CalculatePlane;
175  }
176 
177  pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
178  pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
179  pcl::PointCloud<PointT>::Ptr tempCloud(new pcl::PointCloud<PointT>());
180 
181  // todo maybe downsample the point cloud first.
182  // Segment the largest planar component from the remaining cloud
183  seg.setInputCloud(inputCloud);
184  seg.segment(*inliers, *coefficients);
185 
186  if (!inliers->indices.size())
187  {
188  ARMARX_INFO << deactivateSpam(60) << "Unable to find table";
189  return;
190  }
191 
192  // Extract the planar inliers from the input cloud
193  extract.setInputCloud(inputCloud);
194  extract.setIndices(inliers);
195  extract.setNegative(false);
196  extract.filter(*plane);
197 
198  extract.setNegative(true);
199  extract.filter(*tempCloud);
200 
201  pcl::PointCloud<PointT>::Ptr hullPoints(new pcl::PointCloud<PointT>());
202  hull.setInputCloud(inputCloud);
203  hull.setIndices(inliers);
204  hull.reconstruct(*hullPoints);
205 
206  prism.setInputCloud(tempCloud);
207  prism.setInputPlanarHull(hullPoints);
208  prism.segment(*inliers);
209 
210  extract.setInputCloud(tempCloud);
211  extract.setIndices(inliers);
212  extract.setNegative(false);
213  extract.filter(*tempCloud);
214 
215 
216  ARMARX_VERBOSE << "Plane: " << plane->size();
217  provideResultPointClouds(plane, getName() + "Plane");
218 
219  if (calculatePlane)
220  {
221  std::vector<Eigen::Vector3f> planePoints;
222  for (const PointT& p : plane->points)
223  {
224  planePoints.emplace_back(Eigen::Vector3f(p.x, p.y, p.z));
225  }
226  math::Plane mplane = math::FitPlane::Fit(planePoints);
227  std::unique_lock guard(tablePlaneMutex);
228  Eigen::Vector3f planeNormal = mplane.GetNormal(Eigen::Vector3f::UnitZ());
229  // ARMARX_IMPORTANT << VAROUT(planeNormal.transpose());
230  tablePlane = new visionx::TabletopSegmentationPlane(new Vector3(mplane.Pos()), new Vector3(planeNormal));
231  }
232 
233  pcl::PointCloud<PointL>::Ptr resultCloud(new pcl::PointCloud<PointL>());
234  extractEuclideanClusters(tempCloud, resultCloud);
235 
236  if (includeTabletopPlane)
237  {
238  pcl::PointCloud<PointL>::Ptr planeLabel(new pcl::PointCloud<PointL>());
239  pcl::copyPointCloud(*plane, *planeLabel);
240  for (PointL& p : planeLabel->points)
241  {
242  p.label = 1;
243  }
244  pcl::PointCloud<PointL>::Ptr combinedCloud(new pcl::PointCloud<PointL>());
245  *combinedCloud = *resultCloud + *planeLabel;
246  resultCloud->swap(*combinedCloud);
247  }
248  resultCloud->header.stamp = inputCloud->header.stamp;
249 
250  ARMARX_VERBOSE << "Result: " << resultCloud->size();
251  provideResultPointClouds(resultCloud, getName() + "Result");
252 }
253 
254 
255 
256 void armarx::TabletopSegmentation::extractEuclideanClusters(const pcl::PointCloud<PointT>::ConstPtr& inputCloud, const pcl::PointCloud<PointL>::Ptr& resultCloud)
257 {
258  pcl::search::KdTree<PointT>::Ptr tree(new pcl::search::KdTree<PointT>);
259  tree->setInputCloud(inputCloud);
260 
261  std::vector<pcl::PointIndices> clusterIndices;
262  ec.setSearchMethod(tree);
263  ec.setInputCloud(inputCloud);
264  ec.extract(clusterIndices);
265 
266  uint32_t j = 2;
267  for (std::vector<pcl::PointIndices>::const_iterator it = clusterIndices.begin(); it != clusterIndices.end(); ++it)
268  {
269  for (std::vector<int>::const_iterator pit = it->indices.begin(); pit != it->indices.end(); ++pit)
270  {
271  const unsigned int i = static_cast<unsigned int>(*pit);
272  const PointT& src = inputCloud->points[i];
273 
274  PointL p;
275  p.getVector3fMap() = src.getVector3fMap();
276  p.rgba = src.rgba; // p.getRGBAVector4i() = src.getRGBAVector4i();
277  p.label = j;
278 
279  resultCloud->points.push_back(p);
280  }
281  j++;
282  }
283 
284  resultCloud->width = static_cast<unsigned int>(resultCloud->points.size());
285  resultCloud->height = 1;
286  resultCloud->is_dense = false;
287 }
288 
289 void TabletopSegmentation::guiTask()
290 {
291  guiCreate();
292 
293  RemoteGui::TabProxy tab(remoteGui, getName());
294  CycleUtil c(20);
295  while (!remoteGuiTask->isStopped())
296  {
297  tab.receiveUpdates();
298  guiUpdate(tab);
299 
300  c.waitForCycleDuration();
301  }
302 }
303 
304 void TabletopSegmentation::guiCreate()
305 {
306  auto grid = RemoteGui::makeGridLayout("Grid");
307 
308  int row = 0;
309  {
310  grid.addTextLabel("Distance Threshold", row, 0);
311  auto slider = RemoteGui::makeFloatSlider("DistanceThreshold")
312  .value(prop_DistanceThreshold)
313  .min(1.0f)
314  .max(50.0f)
315  .steps(50);
316  grid.addChild(slider, row, 1);
317  row += 1;
318  }
319  {
320  grid.addTextLabel("Eps Angle", row, 0);
321  auto slider = RemoteGui::makeFloatSlider("EpsAngle")
322  .value(prop_EpsAngle)
323  .min(pcl::deg2rad(1.0))
324  .max(pcl::deg2rad(30.0))
325  .steps(30);
326  grid.addChild(slider, row, 1);
327  row += 1;
328  }
329  {
330  grid.addTextLabel("Cluster Tolerance", row, 0);
331  auto slider = RemoteGui::makeFloatSlider("ClusterTolerance")
332  .value(prop_ClusterTolerance)
333  .min(1.0f)
334  .max(50.0f)
335  .steps(50);
336  grid.addChild(slider, row, 1);
337  row += 1;
338  }
339  {
340  grid.addTextLabel("Min Cluster Size ", row, 0);
341  auto slider = RemoteGui::makeIntSlider("MinClusterSize")
342  .value(prop_MinClusterSize)
343  .min(100)
344  .max(1000000);
345  grid.addChild(slider, row, 1);
346  row += 1;
347  }
348  {
349  grid.addTextLabel("Max Cluster Size ", row, 0);
350  auto slider = RemoteGui::makeIntSlider("MaxClusterSize")
351  .value(prop_MaxClusterSize)
352  .min(25000)
353  .max(4000000);
354  grid.addChild(slider, row, 1);
355  row += 1;
356  }
357  {
358  grid.addTextLabel("Height Min", row, 0);
359  auto slider = RemoteGui::makeFloatSlider("HeightMin")
360  .value(prop_HeightMin)
361  .min(0.0f)
362  .max(500.0f)
363  .steps(50);
364  grid.addChild(slider, row, 1);
365  row += 1;
366  }
367  {
368  grid.addTextLabel("Height Max", row, 0);
369  auto slider = RemoteGui::makeFloatSlider("HeightMax")
370  .value(prop_HeightMax)
371  .min(100.0f)
372  .max(2000.0f)
373  .steps(50);
374  grid.addChild(slider, row, 1);
375  row += 1;
376  }
377  {
378  grid.addTextLabel("Include Tabletop Plane", row, 0);
379  auto checkbox = RemoteGui::makeCheckBox("IncludeTabletopPlane")
380  .value(prop_IncludeTabletopPlane);
381  grid.addChild(checkbox, row, 1);
382  row += 1;
383  }
384  {
385  grid.addTextLabel("Calculate Plane", row, 0);
386  auto checkbox = RemoteGui::makeCheckBox("CalculatePlane")
387  .value(prop_CalculatePlane);
388  grid.addChild(checkbox, row, 1);
389  row += 1;
390  }
391 
392  remoteGui->createTab(getName(), grid);
393 }
394 
395 void TabletopSegmentation::guiUpdate(RemoteGui::TabProxy& tab)
396 {
397  std::unique_lock<std::mutex> lock(prop_mutex);
398 #define ARMARX_REMOTE_GUI_GET_PROP(name) \
399  prop_ ## name = tab.getValue<decltype(prop_ ## name)>(#name).get()
400 
401  ARMARX_REMOTE_GUI_GET_PROP(DistanceThreshold);
402  ARMARX_REMOTE_GUI_GET_PROP(EpsAngle);
403  ARMARX_REMOTE_GUI_GET_PROP(ClusterTolerance);
404  ARMARX_REMOTE_GUI_GET_PROP(MinClusterSize);
405  ARMARX_REMOTE_GUI_GET_PROP(MaxClusterSize);
406  ARMARX_REMOTE_GUI_GET_PROP(HeightMin);
407  ARMARX_REMOTE_GUI_GET_PROP(HeightMax);
408  ARMARX_REMOTE_GUI_GET_PROP(IncludeTabletopPlane);
409  ARMARX_REMOTE_GUI_GET_PROP(CalculatePlane);
410 
411 #undef ARMARX_REMOTE_GUI_GET_PROP
412 }
413 
414 
415 visionx::TabletopSegmentationPlanePtr armarx::TabletopSegmentation::getTablePlane(const Ice::Current&)
416 {
417  std::unique_lock guard(tablePlaneMutex);
418  return tablePlane;
419 }
420 
421 ServiceProvider::ServiceProvider(const std::string& serviceName, const std::function<void(int)>& callback) :
422  serviceName(serviceName),
423  callback(callback)
424 {
425 
426 }
427 
428 void ServiceProvider::requestService(const std::string& serviceName, Ice::Int relativeTimeoutMs, const Ice::Current&)
429 {
430  if (serviceName == this->serviceName)
431  {
432  callback(relativeTimeoutMs);
433  }
434 }
armarx::RemoteGui::detail::MinMaxMixin::min
Derived & min(Type min)
Definition: Basic.h:88
armarx::RemoteGui::makeIntSlider
detail::IntSliderBuilder makeIntSlider(std::string const &name)
Definition: IntegerWidgets.h:43
ARMARX_VERBOSE
#define ARMARX_VERBOSE
Definition: Logging.h:180
armarx::TabletopSegmentation::onDisconnectPointCloudProcessor
void onDisconnectPointCloudProcessor() override
Definition: TabletopSegmentation.cpp:109
armarx::ServiceProvider::ServiceProvider
ServiceProvider(const std::string &serviceName, const std::function< void(int)> &callback)
Definition: TabletopSegmentation.cpp:421
Pose.h
WidgetBuilder.h
armarx::ServiceProvider
Definition: TabletopSegmentation.h:60
armarx::CycleUtil
This util class helps with keeping a cycle time during a control cycle.
Definition: CycleUtil.h:40
armarx::TabletopSegmentationPropertyDefinitions
Definition: TabletopSegmentation.h:81
c
constexpr T c
Definition: UnscentedKalmanFilterTest.cpp:43
armarx::RunningTask
Definition: ArmarXMultipleObjectsScheduler.h:35
deactivateSpam
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition: Logging.cpp:72
armarx::RemoteGui::TabProxy
Definition: WidgetProxy.h:17
armarx::RemoteGui::detail::MinMaxMixin::max
Derived & max(Type max)
Definition: Basic.h:116
armarx::TabletopSegmentation::getTablePlane
visionx::TabletopSegmentationPlanePtr getTablePlane(const Ice::Current &) override
Definition: TabletopSegmentation.cpp:415
armarx::TabletopSegmentation::process
void process() override
Definition: TabletopSegmentation.cpp:122
armarx::RemoteGui::makeCheckBox
detail::CheckBoxBuilder makeCheckBox(std::string const &name)
Definition: BoolWidgets.h:26
ARMARX_DEBUG
#define ARMARX_DEBUG
Definition: Logging.h:177
armarx::RemoteGui::makeFloatSlider
detail::FloatSliderBuilder makeFloatSlider(std::string const &name)
Definition: FloatWidgets.h:57
armarx::Vector3
The Vector3 class.
Definition: Pose.h:112
armarx::TabletopSegmentation::onConnectPointCloudProcessor
void onConnectPointCloudProcessor() override
Definition: TabletopSegmentation.cpp:79
armarx::RemoteGui::detail::ValueMixin::value
Derived & value(ValueT const &value)
Definition: Basic.h:77
armarx::PointT
pcl::PointXYZRGBL PointT
Definition: Common.h:28
max
T max(T t1, T t2)
Definition: gdiam.h:48
armarx::TabletopSegmentation::createPropertyDefinitions
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
Definition: TabletopSegmentation.cpp:56
armarx::TimeUtil::GetTime
static IceUtil::Time GetTime(TimeMode timeMode=TimeMode::VirtualTime)
Get the current time.
Definition: TimeUtil.cpp:42
CycleUtil.h
TabletopSegmentation.h
armarx::Component::getConfigIdentifier
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Definition: Component.cpp:74
armarx::TabletopSegmentation::onInitPointCloudProcessor
void onInitPointCloudProcessor() override
Definition: TabletopSegmentation.cpp:64
ARMARX_INFO
#define ARMARX_INFO
Definition: Logging.h:174
armarx::ServiceProvider::requestService
void requestService(const std::string &, Ice::Int relativeTimeoutMs, const Ice::Current &)
Definition: TabletopSegmentation.cpp:428
IceUtil::Handle< class PropertyDefinitionContainer >
armarx::TabletopSegmentation::onExitPointCloudProcessor
void onExitPointCloudProcessor() override
Definition: TabletopSegmentation.cpp:118
armarx::RemoteGui::makeGridLayout
detail::GridLayoutBuilder makeGridLayout(std::string const &name="")
Definition: LayoutWidgets.h:240
memoryx::KBM::Vector3
Eigen::Vector3d Vector3
Definition: kbm.h:41
armarx::VariantType::Int
const VariantTypeId Int
Definition: Variant.h:916
armarx::ManagedIceObject::getName
std::string getName() const
Retrieve name of object.
Definition: ManagedIceObject.cpp:107
armarx::PropertyDefinitionsPtr
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
Definition: forward_declarations.h:34
ARMARX_REMOTE_GUI_GET_PROP
#define ARMARX_REMOTE_GUI_GET_PROP(name)
armarx::PointL
pcl::PointXYZRGBL PointL
Definition: TabletopSegmentation.h:75
armarx
This file offers overloads of toIce() and fromIce() functions for STL container types.
Definition: ArmarXTimeserver.cpp:28
armarx::RemoteGui::detail::FloatMinMaxMixin::steps
Derived & steps(int steps)
Definition: FloatWidgets.h:10