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