$search
00001 /********************************************************************* 00002 * 00003 * Software License Agreement (BSD License) 00004 * 00005 * Copyright (c) 2011, Robert Bosch LLC. 00006 * All rights reserved. 00007 * 00008 * Redistribution and use in source and binary forms, with or without 00009 * modification, are permitted provided that the following conditions 00010 * are met: 00011 * 00012 * * Redistributions of source code must retain the above copyright 00013 * notice, this list of conditions and the following disclaimer. 00014 * * Redistributions in binary form must reproduce the above 00015 * copyright notice, this list of conditions and the following 00016 * disclaimer in the documentation and/or other materials provided 00017 * with the distribution. 00018 * * Neither the name of the Robert Bosch nor the names of its 00019 * contributors may be used to endorse or promote products derived 00020 * from this software without specific prior written permission. 00021 * 00022 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 00023 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 00024 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 00025 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 00026 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 00027 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 00028 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 00029 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 00030 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 00031 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 00032 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 00033 * POSSIBILITY OF SUCH DAMAGE. 00034 * 00035 *********************************************************************/ 00036 00037 00038 #include "bosch_object_segmentation_gui/object_segmentation_rviz_ui.h" 00039 #include "bosch_object_segmentation_gui/utils.h" 00040 00041 #include "rviz_interaction_tools/image_tools.h" 00042 #include "rviz_interaction_tools/image_overlay.h" 00043 #include "rviz_interaction_tools/camera_tools.h" 00044 #include "rviz_interaction_tools/unique_string_manager.h" 00045 00046 #include <rviz/window_manager_interface.h> 00047 #include <rviz/render_panel.h> 00048 #include <rviz/visualization_manager.h> 00049 #include <rviz/validate_floats.h> 00050 #include <rviz/frame_manager.h> 00051 00052 #include <OGRE/OgreRenderWindow.h> 00053 #include <OGRE/OgreRoot.h> 00054 00055 #include "tabletop_object_detector/marker_generator.h" 00056 00057 #include "pcl/point_cloud.h" 00058 #include "pcl/point_types.h" 00059 #include "pcl/filters/statistical_outlier_removal.h" 00060 #include "pcl/filters/voxel_grid.h" 00061 00062 #include <sensor_msgs/PointCloud.h> 00063 #include <sensor_msgs/PointCloud2.h> 00064 #include <sensor_msgs/point_cloud_conversion.h> 00065 #include <sensor_msgs/image_encodings.h> 00066 00067 #include <cv_bridge/cv_bridge.h> 00068 #include <sensor_msgs/image_encodings.h> 00069 #include <opencv2/highgui/highgui.hpp> 00070 00071 namespace enc = sensor_msgs::image_encodings; 00072 00073 00074 namespace bosch_object_segmentation_gui 00075 { 00076 00077 static const char 00078 * HELP_TEXT = 00079 "Left Mouse Button: Drag a rectangle around an object to segment it. \n\ 00080 SHIFT+Left Mouse Button: Mark pixel belonging to the object. \n\ 00081 CTRL+Left Mouse Button: Mark pixel belonging to the background. \n\ 00082 \n\ 00083 Segment Button: Perform Segmentation. \n\ 00084 Reset Button: Reset Segmentation. \n\ 00085 Cancel Button: Cancel Segmentation. \n\ 00086 OK Button: Accept Segmentation. "; 00087 00088 ObjectSegmentationRvizUI::ObjectSegmentationRvizUI( 00089 rviz::VisualizationManager *visualization_manager ) : 00090 ObjectSegmentationFrame( 00091 visualization_manager->getWindowManager()->getParentWindow()), 00092 object_segmentation_server_(0), nh_(""), priv_nh_("~"), 00093 object_segmenter_(0), num_markers_published_(1), current_marker_id_(1) 00094 { 00095 rviz_interaction_tools::UniqueStringManager usm; 00096 00097 //construct basic ogre scene 00098 scene_manager_ = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC, usm.unique("ObjectSegmentationRvizUI")); 00099 scene_root_ = scene_manager_->getRootSceneNode()->createChildSceneNode(); 00100 image_overlay_ = new rviz_interaction_tools::ImageOverlay(scene_root_, Ogre::RENDER_QUEUE_OVERLAY - 2); 00101 00102 //put a render panel into the window 00103 createRenderPanel(visualization_manager); 00104 00105 SetTitle(wxString::FromAscii("GrabCut3D Interactive Object Segmentation")); 00106 bottom_label_->SetLabel(wxString::FromAscii(HELP_TEXT)); 00107 00108 marker_pub_ = nh_.advertise<visualization_msgs::Marker> (nh_.resolveName("tabletop_segmentation_markers"), 10); 00109 00110 //initialize operational flags 00111 priv_nh_.param<int>("inlier_threshold", table_detector_.inlier_threshold_, 300); 00112 priv_nh_.param<double>("plane_detection_voxel_size", table_detector_.plane_detection_voxel_size_, 0.01); 00113 priv_nh_.param<double>("clustering_voxel_size", table_detector_.clustering_voxel_size_, 0.003); 00114 priv_nh_.param<double>("z_filter_min", table_detector_.z_filter_min_, 0.4); 00115 priv_nh_.param<double>("z_filter_max", table_detector_.z_filter_max_, 1.25); 00116 priv_nh_.param<double>("table_z_filter_min", table_detector_.table_z_filter_min_, 0.01); 00117 priv_nh_.param<double>("table_z_filter_max", table_detector_.table_z_filter_max_, 0.50); 00118 priv_nh_.param<double>("cluster_distance", table_detector_.cluster_distance_, 0.03); 00119 priv_nh_.param<int>("min_cluster_size", table_detector_.min_cluster_size_, 300); 00120 priv_nh_.param<std::string>("processing_frame", table_detector_.processing_frame_, ""); 00121 priv_nh_.param<double>("up_direction", table_detector_.up_direction_, -1.0); 00122 00123 object_segmenter_ = new GrabCut3DObjectSegmenter(); 00124 } 00125 00126 ObjectSegmentationRvizUI::~ObjectSegmentationRvizUI() 00127 { 00128 if (object_segmentation_server_) 00129 stopActionServer(); 00130 00131 render_panel_->getRenderWindow()->setActive(false); 00132 delete render_panel_; 00133 delete image_overlay_; 00134 delete object_segmenter_; 00135 } 00136 00137 void ObjectSegmentationRvizUI::createRenderPanel( 00138 rviz::VisualizationManager *visualization_manager ) 00139 { 00140 //put a render panel into the window 00141 render_panel_ = new rviz::RenderPanel(this, false); 00142 00143 render_panel_->SetSize(m_panel->GetRect()); 00144 00145 // notify us when the user does something with the mouse 00146 render_panel_->Connect(wxEVT_MOTION,wxMouseEventHandler( ObjectSegmentationRvizUI::onRenderWindowMouseEvents ), NULL, this); 00147 render_panel_->Connect(wxEVT_LEFT_DOWN,wxMouseEventHandler( ObjectSegmentationRvizUI::onRenderWindowMouseEvents ), NULL, this); 00148 render_panel_->Connect(wxEVT_MIDDLE_DOWN,wxMouseEventHandler( ObjectSegmentationRvizUI::onRenderWindowMouseEvents ), NULL, this); 00149 render_panel_->Connect(wxEVT_RIGHT_DOWN,wxMouseEventHandler( ObjectSegmentationRvizUI::onRenderWindowMouseEvents ), NULL, this); 00150 render_panel_->Connect(wxEVT_LEFT_UP,wxMouseEventHandler( ObjectSegmentationRvizUI::onRenderWindowMouseEvents ), NULL, this); 00151 render_panel_->Connect(wxEVT_MIDDLE_UP, wxMouseEventHandler( ObjectSegmentationRvizUI::onRenderWindowMouseEvents ), NULL, this); 00152 render_panel_->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler( ObjectSegmentationRvizUI::onRenderWindowMouseEvents ), NULL, this); 00153 render_panel_->Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler( ObjectSegmentationRvizUI::onRenderWindowMouseEvents ), NULL, this); 00154 render_panel_->Connect(wxEVT_LEFT_DCLICK, wxMouseEventHandler( ObjectSegmentationRvizUI::onRenderWindowMouseEvents ), NULL, this); 00155 00156 render_panel_->createRenderWindow(); 00157 render_panel_->initialize(scene_manager_, visualization_manager); 00158 00159 render_panel_->setAutoRender(false); 00160 render_panel_->getViewport()->setOverlaysEnabled(false); 00161 render_panel_->getViewport()->setClearEveryFrame(true); 00162 render_panel_->getRenderWindow()->setAutoUpdated(false); 00163 render_panel_->getRenderWindow()->setActive(true); 00164 00165 } 00166 00167 void ObjectSegmentationRvizUI::startActionServer( ros::NodeHandle &node_handle ) 00168 { 00169 if (object_segmentation_server_) 00170 { 00171 ROS_ERROR( "ObjectSegmentationGuiAction server already started!" ); 00172 return; 00173 } 00174 00175 ROS_INFO("Starting ObjectSegmentationGuiAction server."); 00176 00177 //create non-threaded action server 00178 object_segmentation_server_ = new actionlib::SimpleActionServer< ObjectSegmentationGuiAction>(node_handle, "segmentation_popup", false); 00179 00180 object_segmentation_server_->registerGoalCallback(boost::bind(&ObjectSegmentationRvizUI::acceptNewGoal, this)); 00181 object_segmentation_server_->registerPreemptCallback(boost::bind(&ObjectSegmentationRvizUI::preempt, this)); 00182 00183 object_segmentation_server_->start(); 00184 } 00185 00186 void ObjectSegmentationRvizUI::stopActionServer() 00187 { 00188 if (!object_segmentation_server_) 00189 { 00190 ROS_ERROR("ObjectSegmentationGuiAction server cannot be stopped because it is not running."); 00191 return; 00192 } 00193 00194 //if we're currently being active, we have to cancel everything, clean up & hide the window 00195 if (object_segmentation_server_->isActive()) 00196 { 00197 ROS_WARN("Aborting ObjectSegmentationGuiAction goal."); 00198 stopSegmentation(); 00199 object_segmentation_server_->setAborted(); 00200 cleanupAndHide(); 00201 } 00202 00203 ROS_INFO("Stopping ObjectSegmentationGuiAction server."); 00204 delete object_segmentation_server_; 00205 object_segmentation_server_ = 0; 00206 } 00207 00208 bool ObjectSegmentationRvizUI::getDepthImage(sensor_msgs::Image& image_msg) 00209 { 00210 // get image 00211 std::string image_topic = nh_.resolveName("/camera/depth/image"); 00212 ROS_INFO("Waiting for image on topic %s", image_topic.c_str()); 00213 sensor_msgs::ImageConstPtr image_ptr = ros::topic::waitForMessage<sensor_msgs::Image>(image_topic, nh_, ros::Duration(30.0)); 00214 if(!image_ptr) { 00215 ROS_ERROR("Could not receive an image!"); 00216 return false; 00217 } 00218 image_msg = *image_ptr; 00219 00220 float max_val = -FLT_MAX; 00221 float min_val = FLT_MAX; 00222 for(unsigned int x=0; x<image_msg.width; ++x) 00223 { 00224 for(unsigned int y=0; y<image_msg.height; ++y) 00225 { 00226 int index_src = y * image_ptr->step + x*4; 00227 float* val = (float*)(&image_ptr->data[index_src]); 00228 max_val = std::max(*val,max_val); 00229 min_val = std::min(*val,min_val); 00230 } 00231 } 00232 00233 image_msg.encoding = "bgr8"; 00234 image_msg.width = image_ptr->width; 00235 image_msg.height = image_ptr->height; 00236 image_msg.step = 3 * image_ptr->width; 00237 image_msg.data.resize(image_ptr->step * image_ptr->height); 00238 for(unsigned int x=0; x<image_msg.width; ++x) 00239 { 00240 for(unsigned int y=0; y<image_msg.height; ++y) 00241 { 00242 int index_src = y * image_ptr->step + x*4; 00243 float* val = (float*)(&image_ptr->data[index_src]); 00244 00245 int index_dest = y * image_msg.step + x*3; 00246 image_msg.data[index_dest+0] = round(*val); 00247 image_msg.data[index_dest+1] = round(*val); 00248 image_msg.data[index_dest+2] = round(*val); 00249 } 00250 } 00251 00252 return true; 00253 } 00254 00255 void ObjectSegmentationRvizUI::acceptNewGoal() 00256 { 00257 00258 // only if segmentation is running labeling can be accepted 00259 accept_button_->Enable(false); 00260 00261 // only if segmentation is not running, it can be started 00262 segment_button_->Enable(true); 00263 00264 const ObjectSegmentationGuiGoal::ConstPtr &goal = object_segmentation_server_->acceptNewGoal(); 00265 00266 rviz_interaction_tools::updateCamera(render_panel_->getCamera(), goal->camera_info); 00267 00268 ROS_ASSERT( goal->point_cloud.height == goal->disparity_image.image.height && 00269 goal->point_cloud.width == goal->disparity_image.image.width); 00270 00271 current_point_cloud_ = goal->point_cloud; 00272 current_camera_info_ = goal->camera_info; 00273 current_disparity_image_ = goal->disparity_image; 00274 00275 00276 fillRgbImage(current_image_, current_point_cloud_); 00277 //current_image_ = goal->image; 00278 00279 getDepthImage(current_depth_image_); 00280 00281 //store data for later use in update() 00282 //we can't directly put in in ogre because 00283 //this runs in a different thread 00284 image_overlay_->setImage(current_image_); 00285 image_overlay_->update(); 00286 00287 Show(); 00288 00289 // initialise segmentation 00290 Mat image, depth_image; 00291 convertImageMessageToMat(current_image_, image); 00292 convertImageMessageToMat(current_depth_image_, depth_image); 00293 object_segmenter_->setImages(image, depth_image); 00294 } 00295 00296 bool ObjectSegmentationRvizUI::convertImageMessageToMat(const sensor_msgs::Image& image_msg, Mat& image) 00297 { 00298 // converts ROS images to OpenCV 00299 cv_bridge::CvImagePtr cv_ptr; 00300 try 00301 { 00302 cv_ptr = cv_bridge::toCvCopy(image_msg, sensor_msgs::image_encodings::BGR8); 00303 } 00304 catch (cv_bridge::Exception& e) 00305 { 00306 ROS_ERROR("cv_bridge exception: %s", e.what()); 00307 return false; 00308 } 00309 00310 image = cv_ptr->image; 00311 00312 return true; 00313 } 00314 00315 bool ObjectSegmentationRvizUI::convertMatToImageMessage(const Mat& image, sensor_msgs::Image& image_msg) 00316 { 00317 // converts OpenCV to ROS images 00318 cv_bridge::CvImage cv_image; 00319 cv_image.image = image; 00320 cv_image.encoding = "bgr8"; 00321 00322 try 00323 { 00324 cv_image.toImageMsg(image_msg); 00325 } 00326 catch (cv_bridge::Exception& e) 00327 { 00328 ROS_ERROR("cv_bridge exception: %s", e.what()); 00329 return false; 00330 } 00331 return true; 00332 } 00333 00334 void ObjectSegmentationRvizUI::preempt() 00335 { 00336 stopSegmentation(); 00337 object_segmentation_server_->setPreempted(); 00338 cleanupAndHide(); 00339 } 00340 00341 void ObjectSegmentationRvizUI::cleanupAndHide() 00342 { 00343 image_overlay_->clear(); 00344 resetVars(); 00345 Hide(); 00346 } 00347 00348 void ObjectSegmentationRvizUI::stopSegmentation() 00349 { 00350 } 00351 00352 void ObjectSegmentationRvizUI::resetVars() 00353 { 00354 for (size_t i = 0; i < clusters_.size(); ++i) 00355 clusters_[i].points.clear(); 00356 00357 clusters_.clear(); 00358 00359 image_overlay_->setImage(current_image_); 00360 image_overlay_->update(); 00361 00362 // only if segmentation is running labeling can be accepted 00363 accept_button_->Enable(false); 00364 00365 // only if segmentation is not running, it can be started 00366 segment_button_->Enable(true); 00367 } 00368 00369 void ObjectSegmentationRvizUI::update( float wall_dt, float ros_dt ) 00370 { 00371 render_panel_->getRenderWindow()->update(); 00372 } 00373 00374 void ObjectSegmentationRvizUI::onRenderWindowMouseEvents( wxMouseEvent& event ) 00375 { 00376 int x = event.GetX(); 00377 int y = event.GetY(); 00378 //convert to coordinates in the original image resolution 00379 wxSize size = render_panel_->GetSize(); 00380 x = floor(x * current_image_.width / size.GetWidth()); 00381 y = floor(y * current_image_.height / size.GetHeight()); 00382 00383 GrabCut3DObjectSegmenter::MouseEvent mouse_event; 00384 if (event.ButtonDown(wxMOUSE_BTN_LEFT)) 00385 { 00386 mouse_event = GrabCut3DObjectSegmenter::LEFT_BUTTON_DOWN; 00387 } 00388 else if (event.ButtonUp(wxMOUSE_BTN_LEFT)) 00389 { 00390 mouse_event = GrabCut3DObjectSegmenter::LEFT_BUTTON_UP; 00391 } 00392 else if (event.ButtonDown(wxMOUSE_BTN_RIGHT)) 00393 { 00394 mouse_event = GrabCut3DObjectSegmenter::RIGHT_BUTTON_DOWN; 00395 } 00396 else if (event.ButtonUp(wxMOUSE_BTN_RIGHT)) 00397 { 00398 mouse_event = GrabCut3DObjectSegmenter::RIGHT_BUTTON_UP; 00399 } 00400 else 00401 { 00402 mouse_event = GrabCut3DObjectSegmenter::MOUSE_MOVE; 00403 } 00404 00405 if(event.Dragging()) 00406 { 00407 mouse_event = GrabCut3DObjectSegmenter::MOUSE_MOVE; 00408 } 00409 00410 object_segmenter_->mouseClick(mouse_event, x, y, event.ControlDown(), event.ShiftDown()); 00411 00412 // update image overlay 00413 updateImageOverlay(); 00414 } 00415 00416 void ObjectSegmentationRvizUI::updateImageOverlay() 00417 { 00418 sensor_msgs::Image display_image; 00419 convertMatToImageMessage(object_segmenter_->displayImage(), display_image); 00420 image_overlay_->setImage(display_image); 00421 image_overlay_->update(); 00422 } 00423 00424 void ObjectSegmentationRvizUI::fillRgbImage(sensor_msgs::Image &rgb_img, 00425 const sensor_msgs::PointCloud2 &point_cloud) 00426 { 00427 00428 ROS_DEBUG("Width and Height: (%d %d)",point_cloud.height, point_cloud.width ); 00429 00430 rgb_img.header = point_cloud.header; 00431 rgb_img.height = point_cloud.height; 00432 rgb_img.width = point_cloud.width; 00433 rgb_img.encoding = enc::RGB8; 00434 rgb_img.is_bigendian = false; 00435 rgb_img.step = 3 * rgb_img.width; 00436 size_t size = rgb_img.step * rgb_img.height; 00437 rgb_img.data.resize(size); 00438 00439 for(unsigned int x=0; x<rgb_img.width; ++x) 00440 { 00441 for(unsigned int y=0; y<rgb_img.height; ++y) 00442 { 00443 int i = y * rgb_img.width + x; 00444 00445 float rgb; 00446 memcpy ( &rgb, &point_cloud.data[i * point_cloud.point_step + point_cloud.fields[3].offset], sizeof (float)); 00447 float r, g, b; 00448 transformRgb(rgb, r, g, b); 00449 00450 int wide_i = y * rgb_img.step + x*3; 00451 rgb_img.data[wide_i+0] = round(r*255.0f); 00452 rgb_img.data[wide_i+1] = round(g*255.0f); 00453 rgb_img.data[wide_i+2] = round(b*255.0f); 00454 00455 } 00456 } 00457 } 00458 00459 void ObjectSegmentationRvizUI::acceptButtonClicked(wxCommandEvent&) 00460 { 00461 ObjectSegmentationGuiResult result; 00462 00463 // Fetch binary mask from GCApp object 00464 binary_mask_ = object_segmenter_->binaryMask(); 00465 00466 // reconstruct clusters 00467 reconstructAndClusterPointCloud(result); 00468 00469 // get table parameters in 3d 00470 if(table_detector_.detectTable(current_point_cloud_, result.table)) 00471 result.result = result.SUCCESS; 00472 else 00473 result.result = result.NO_TABLE; 00474 00475 00476 ROS_INFO("ObjectSegmentation was successful."); 00477 00478 object_segmentation_server_->setSucceeded(result); 00479 00480 cleanupAndHide(); 00481 } 00482 00483 void ObjectSegmentationRvizUI::cancelButtonClicked(wxCommandEvent&) 00484 { 00485 stopSegmentation(); 00486 object_segmentation_server_->setAborted(); 00487 cleanupAndHide(); 00488 } 00489 00490 void ObjectSegmentationRvizUI::resetButtonClicked( wxCommandEvent& ) 00491 { 00492 object_segmenter_->initializedIs(false); 00493 image_overlay_->clear(); 00494 00495 resetVars(); 00496 } 00497 00498 void ObjectSegmentationRvizUI::segmentButtonClicked( wxCommandEvent& ) 00499 { 00500 if( object_segmenter_->rectState() == GrabCut3DObjectSegmenter::SET) 00501 { 00502 object_segmenter_->iterCountInc(); 00503 // update image overlay 00504 updateImageOverlay(); 00505 } 00506 else 00507 ROS_INFO("Rectangle must be drawn first."); 00508 00509 // only if segmentation is ran once labeling can be accepted 00510 accept_button_->Enable(true); 00511 } 00512 00513 void ObjectSegmentationRvizUI::reconstructAndClusterPointCloud( ObjectSegmentationGuiResult &result) 00514 { 00515 clusters_.clear(); 00516 clusters_.resize(1); 00517 clusters_[0].header.frame_id = current_point_cloud_.header.frame_id; 00518 clusters_[0].header.stamp = ros::Time(0); 00519 clusters_[0].points.reserve(1000); 00520 00521 for(unsigned int x=0; x<current_point_cloud_.width; ++x) 00522 { 00523 for(unsigned int y=0; y<current_point_cloud_.height; ++y) 00524 { 00525 int i = y * current_point_cloud_.width + x; 00526 int mask_val = int(binary_mask_.at<unsigned char>(y, x)); 00527 float nan; 00528 memcpy (&nan, 00529 ¤t_point_cloud_.data[i * current_point_cloud_.point_step + current_point_cloud_.fields[0].offset], 00530 sizeof (float)); 00531 if(!isnan(nan)) 00532 { 00533 geometry_msgs::Point32 p; 00534 float x, y, z; 00535 memcpy (&x, 00536 ¤t_point_cloud_.data[i 00537 * current_point_cloud_.point_step 00538 + current_point_cloud_.fields[0].offset], 00539 sizeof (float)); 00540 memcpy (&y, 00541 ¤t_point_cloud_.data[i 00542 * current_point_cloud_.point_step 00543 + current_point_cloud_.fields[1].offset], 00544 sizeof (float)); 00545 memcpy (&z, 00546 ¤t_point_cloud_.data[i 00547 * current_point_cloud_.point_step 00548 + current_point_cloud_.fields[2].offset], 00549 sizeof (float)); 00550 p.x = x; 00551 p.y = y; 00552 p.z = z; 00553 00554 if (mask_val > 0) 00555 clusters_[0].points.push_back(p); 00556 } 00557 } 00558 } 00559 result.clusters = clusters_; 00560 } 00561 00562 } // namespace object_segmentation_gui 00563