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
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
48using namespace armarx;
49
56
57void
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
75void
77{
79 getProperty<std::string>("DebugObserverName").getValue());
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
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
107void
109{
110 if (remoteGuiTask)
111 {
112 remoteGuiTask->stop();
113 remoteGuiTask = nullptr;
114 }
115}
116
117void
121
122void
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(),
169 std::numeric_limits<float>::max(),
170 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
258void
259armarx::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
297void
298TabletopSegmentation::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
313void
314TabletopSegmentation::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
404void
405TabletopSegmentation::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);
413 ARMARX_REMOTE_GUI_GET_PROP(ClusterTolerance);
414 ARMARX_REMOTE_GUI_GET_PROP(MinClusterSize);
415 ARMARX_REMOTE_GUI_GET_PROP(MaxClusterSize);
418 ARMARX_REMOTE_GUI_GET_PROP(IncludeTabletopPlane);
419 ARMARX_REMOTE_GUI_GET_PROP(CalculatePlane);
420
421#undef ARMARX_REMOTE_GUI_GET_PROP
422}
423
424visionx::TabletopSegmentationPlanePtr
426{
427 std::unique_lock guard(tablePlaneMutex);
428 return tablePlane;
429}
430
431ServiceProvider::ServiceProvider(const std::string& serviceName,
432 const std::function<void(int)>& callback) :
433 serviceName(serviceName), callback(callback)
434{
435}
436
437void
438ServiceProvider::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}
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition Logging.cpp:75
#define ARMARX_REMOTE_GUI_GET_PROP(name)
constexpr T c
ProxyType getProxyFromProperty(const std::string &propertyName, bool addToDependencies=false, const std::string &endpoints="", bool throwOnProxyError=true)
Get a proxy whose name is specified by the given property.
Definition Component.h:242
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Definition Component.cpp:90
Property< PropertyType > getProperty(const std::string &name)
void offeringTopic(const std::string &name)
Registers a topic for retrival after initialization.
TopicProxyType getTopic(const std::string &name)
Returns a proxy of the specified topic.
std::string getName() const
Retrieve name of object.
IceManagerPtr getIceManager() const
Returns the IceManager.
ServiceProvider(const std::string &serviceName, const std::function< void(int)> &callback)
void requestService(const std::string &, Ice::Int relativeTimeoutMs, const Ice::Current &)
visionx::TabletopSegmentationPlanePtr getTablePlane(const Ice::Current &) override
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
static IceUtil::Time GetTime(TimeMode timeMode=TimeMode::VirtualTime)
Get the current time.
Definition TimeUtil.cpp:42
The Vector3 class.
Definition Pose.h:113
void enableResultPointClouds(std::string resultProviderName="")
Enables visualization.
int getPointClouds(const PointCloudPtrT &pointCloudPtr)
Poll PointClouds from provider.
bool waitForPointClouds(int milliseconds=1000)
Wait for new PointClouds.
void provideResultPointClouds(const PointCloudPtrT &pointClouds, std::string providerName="")
sends result PointClouds for visualization
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
detail::CheckBoxBuilder makeCheckBox(std::string const &name)
Definition BoolWidgets.h:27
detail::FloatSliderBuilder makeFloatSlider(std::string const &name)
detail::GridLayoutBuilder makeGridLayout(std::string const &name="")
detail::IntSliderBuilder makeIntSlider(std::string const &name)
This file offers overloads of toIce() and fromIce() functions for STL container types.
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
pcl::PointXYZRGBL PointL
pcl::PointXYZRGBL PointT
Definition Common.h:30
Derived & value(ValueT const &value)
Definition Basic.h:84