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