Point Cloud Library (PCL)  1.7.1
region_growing.hpp
1 /*
2  * Software License Agreement (BSD License)
3  *
4  * Point Cloud Library (PCL) - www.pointclouds.org
5  *
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * * Redistributions of source code must retain the above copyright
13  * notice, this list of conditions and the following disclaimer.
14  * * Redistributions in binary form must reproduce the above
15  * copyright notice, this list of conditions and the following
16  * disclaimer in the documentation and/or other materials provided
17  * with the distribution.
18  * * Neither the name of the copyright holder(s) nor the names of its
19  * contributors may be used to endorse or promote products derived
20  * from this software without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33  * POSSIBILITY OF SUCH DAMAGE.
34  *
35  * Author : Sergey Ushakov
36  * Email : mine_all_mine@bk.ru
37  *
38  */
39 
40 #ifndef PCL_SEGMENTATION_REGION_GROWING_HPP_
41 #define PCL_SEGMENTATION_REGION_GROWING_HPP_
42 
43 #include <pcl/segmentation/region_growing.h>
44 
45 #include <pcl/search/search.h>
46 #include <pcl/search/kdtree.h>
47 #include <pcl/point_cloud.h>
48 #include <pcl/point_types.h>
49 
50 #include <queue>
51 #include <list>
52 #include <cmath>
53 #include <time.h>
54 
55 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
56 template <typename PointT, typename NormalT>
58  min_pts_per_cluster_ (1),
59  max_pts_per_cluster_ (std::numeric_limits<int>::max ()),
60  smooth_mode_flag_ (true),
61  curvature_flag_ (true),
62  residual_flag_ (false),
63  theta_threshold_ (30.0f / 180.0f * static_cast<float> (M_PI)),
64  residual_threshold_ (0.05f),
65  curvature_threshold_ (0.05f),
66  neighbour_number_ (30),
67  search_ (),
68  normals_ (),
69  point_neighbours_ (0),
70  point_labels_ (0),
71  normal_flag_ (true),
72  num_pts_in_segment_ (0),
73  clusters_ (0),
74  number_of_segments_ (0)
75 {
76 }
77 
78 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
79 template <typename PointT, typename NormalT>
81 {
82  if (search_ != 0)
83  search_.reset ();
84  if (normals_ != 0)
85  normals_.reset ();
86 
87  point_neighbours_.clear ();
88  point_labels_.clear ();
89  num_pts_in_segment_.clear ();
90  clusters_.clear ();
91 }
92 
93 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
94 template <typename PointT, typename NormalT> int
96 {
97  return (min_pts_per_cluster_);
98 }
99 
100 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
101 template <typename PointT, typename NormalT> void
103 {
104  min_pts_per_cluster_ = min_cluster_size;
105 }
106 
107 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
108 template <typename PointT, typename NormalT> int
110 {
111  return (max_pts_per_cluster_);
112 }
113 
114 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
115 template <typename PointT, typename NormalT> void
117 {
118  max_pts_per_cluster_ = max_cluster_size;
119 }
120 
121 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
122 template <typename PointT, typename NormalT> bool
124 {
125  return (smooth_mode_flag_);
126 }
127 
128 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
129 template <typename PointT, typename NormalT> void
131 {
132  smooth_mode_flag_ = value;
133 }
134 
135 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
136 template <typename PointT, typename NormalT> bool
138 {
139  return (curvature_flag_);
140 }
141 
142 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
143 template <typename PointT, typename NormalT> void
145 {
146  curvature_flag_ = value;
147 
148  if (curvature_flag_ == false && residual_flag_ == false)
149  residual_flag_ = true;
150 }
151 
152 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
153 template <typename PointT, typename NormalT> bool
155 {
156  return (residual_flag_);
157 }
158 
159 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
160 template <typename PointT, typename NormalT> void
162 {
163  residual_flag_ = value;
164 
165  if (curvature_flag_ == false && residual_flag_ == false)
166  curvature_flag_ = true;
167 }
168 
169 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
170 template <typename PointT, typename NormalT> float
172 {
173  return (theta_threshold_);
174 }
175 
176 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
177 template <typename PointT, typename NormalT> void
179 {
180  theta_threshold_ = theta;
181 }
182 
183 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
184 template <typename PointT, typename NormalT> float
186 {
187  return (residual_threshold_);
188 }
189 
190 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
191 template <typename PointT, typename NormalT> void
193 {
194  residual_threshold_ = residual;
195 }
196 
197 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
198 template <typename PointT, typename NormalT> float
200 {
201  return (curvature_threshold_);
202 }
203 
204 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
205 template <typename PointT, typename NormalT> void
207 {
208  curvature_threshold_ = curvature;
209 }
210 
211 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
212 template <typename PointT, typename NormalT> unsigned int
214 {
215  return (neighbour_number_);
216 }
217 
218 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
219 template <typename PointT, typename NormalT> void
221 {
222  neighbour_number_ = neighbour_number;
223 }
224 
225 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
226 template <typename PointT, typename NormalT> typename pcl::RegionGrowing<PointT, NormalT>::KdTreePtr
228 {
229  return (search_);
230 }
231 
232 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
233 template <typename PointT, typename NormalT> void
235 {
236  if (search_ != 0)
237  search_.reset ();
238 
239  search_ = tree;
240 }
241 
242 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
243 template <typename PointT, typename NormalT> typename pcl::RegionGrowing<PointT, NormalT>::NormalPtr
245 {
246  return (normals_);
247 }
248 
249 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
250 template <typename PointT, typename NormalT> void
252 {
253  if (normals_ != 0)
254  normals_.reset ();
255 
256  normals_ = norm;
257 }
258 
259 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
260 template <typename PointT, typename NormalT> void
261 pcl::RegionGrowing<PointT, NormalT>::extract (std::vector <pcl::PointIndices>& clusters)
262 {
263  clusters_.clear ();
264  clusters.clear ();
265  point_neighbours_.clear ();
266  point_labels_.clear ();
267  num_pts_in_segment_.clear ();
268  number_of_segments_ = 0;
269 
270  bool segmentation_is_possible = initCompute ();
271  if ( !segmentation_is_possible )
272  {
273  deinitCompute ();
274  return;
275  }
276 
277  segmentation_is_possible = prepareForSegmentation ();
278  if ( !segmentation_is_possible )
279  {
280  deinitCompute ();
281  return;
282  }
283 
284  findPointNeighbours ();
285  applySmoothRegionGrowingAlgorithm ();
286  assembleRegions ();
287 
288  clusters.resize (clusters_.size ());
289  std::vector<pcl::PointIndices>::iterator cluster_iter_input = clusters.begin ();
290  for (std::vector<pcl::PointIndices>::const_iterator cluster_iter = clusters_.begin (); cluster_iter != clusters_.end (); cluster_iter++)
291  {
292  if ((cluster_iter->indices.size () >= min_pts_per_cluster_) &&
293  (cluster_iter->indices.size () <= max_pts_per_cluster_))
294  {
295  *cluster_iter_input = *cluster_iter;
296  cluster_iter_input++;
297  }
298  }
299 
300  clusters_ = std::vector<pcl::PointIndices> (clusters.begin (), cluster_iter_input);
301 
302  deinitCompute ();
303 }
304 
305 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
306 template <typename PointT, typename NormalT> bool
308 {
309  // if user forgot to pass point cloud or if it is empty
310  if ( input_->points.size () == 0 )
311  return (false);
312 
313  // if user forgot to pass normals or the sizes of point and normal cloud are different
314  if ( normals_ == 0 || input_->points.size () != normals_->points.size () )
315  return (false);
316 
317  // if residual test is on then we need to check if all needed parameters were correctly initialized
318  if (residual_flag_)
319  {
320  if (residual_threshold_ <= 0.0f)
321  return (false);
322  }
323 
324  // if curvature test is on ...
325  // if (curvature_flag_)
326  // {
327  // in this case we do not need to check anything that related to it
328  // so we simply commented it
329  // }
330 
331  // from here we check those parameters that are always valuable
332  if (neighbour_number_ == 0)
333  return (false);
334 
335  // if user didn't set search method
336  if (!search_)
337  search_.reset (new pcl::search::KdTree<PointT>);
338 
339  if (indices_)
340  {
341  if (indices_->empty ())
342  PCL_ERROR ("[pcl::RegionGrowing::prepareForSegmentation] Empty given indices!\n");
343  search_->setInputCloud (input_, indices_);
344  }
345  else
346  search_->setInputCloud (input_);
347 
348  return (true);
349 }
350 
351 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
352 template <typename PointT, typename NormalT> void
354 {
355  int point_number = static_cast<int> (indices_->size ());
356  std::vector<int> neighbours;
357  std::vector<float> distances;
358 
359  point_neighbours_.resize (input_->points.size (), neighbours);
360 
361  for (int i_point = 0; i_point < point_number; i_point++)
362  {
363  int point_index = (*indices_)[i_point];
364  neighbours.clear ();
365  search_->nearestKSearch (i_point, neighbour_number_, neighbours, distances);
366  point_neighbours_[point_index].swap (neighbours);
367  }
368 }
369 
370 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
371 template <typename PointT, typename NormalT> void
373 {
374  int num_of_pts = static_cast<int> (indices_->size ());
375  point_labels_.resize (input_->points.size (), -1);
376 
377  std::vector< std::pair<float, int> > point_residual;
378  std::pair<float, int> pair;
379  point_residual.resize (num_of_pts, pair);
380 
381  if (normal_flag_ == true)
382  {
383  for (int i_point = 0; i_point < num_of_pts; i_point++)
384  {
385  int point_index = (*indices_)[i_point];
386  point_residual[i_point].first = normals_->points[point_index].curvature;
387  point_residual[i_point].second = point_index;
388  }
389  std::sort (point_residual.begin (), point_residual.end (), comparePair);
390  }
391  else
392  {
393  for (int i_point = 0; i_point < num_of_pts; i_point++)
394  {
395  int point_index = (*indices_)[i_point];
396  point_residual[i_point].first = 0;
397  point_residual[i_point].second = point_index;
398  }
399  }
400  int seed_counter = 0;
401  int seed = point_residual[seed_counter].second;
402 
403  int segmented_pts_num = 0;
404  int number_of_segments = 0;
405  while (segmented_pts_num < num_of_pts)
406  {
407  int pts_in_segment;
408  pts_in_segment = growRegion (seed, number_of_segments);
409  segmented_pts_num += pts_in_segment;
410  num_pts_in_segment_.push_back (pts_in_segment);
411  number_of_segments++;
412 
413  //find next point that is not segmented yet
414  for (int i_seed = seed_counter + 1; i_seed < num_of_pts; i_seed++)
415  {
416  int index = point_residual[i_seed].second;
417  if (point_labels_[index] == -1)
418  {
419  seed = index;
420  break;
421  }
422  }
423  }
424 }
425 
426 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
427 template <typename PointT, typename NormalT> int
428 pcl::RegionGrowing<PointT, NormalT>::growRegion (int initial_seed, int segment_number)
429 {
430  std::queue<int> seeds;
431  seeds.push (initial_seed);
432  point_labels_[initial_seed] = segment_number;
433 
434  int num_pts_in_segment = 1;
435 
436  while (!seeds.empty ())
437  {
438  int curr_seed;
439  curr_seed = seeds.front ();
440  seeds.pop ();
441 
442  size_t i_nghbr = 0;
443  while ( i_nghbr < neighbour_number_ && i_nghbr < point_neighbours_[curr_seed].size () )
444  {
445  int index = point_neighbours_[curr_seed][i_nghbr];
446  if (point_labels_[index] != -1)
447  {
448  i_nghbr++;
449  continue;
450  }
451 
452  bool is_a_seed = false;
453  bool belongs_to_segment = validatePoint (initial_seed, curr_seed, index, is_a_seed);
454 
455  if (belongs_to_segment == false)
456  {
457  i_nghbr++;
458  continue;
459  }
460 
461  point_labels_[index] = segment_number;
462  num_pts_in_segment++;
463 
464  if (is_a_seed)
465  {
466  seeds.push (index);
467  }
468 
469  i_nghbr++;
470  }// next neighbour
471  }// next seed
472 
473  return (num_pts_in_segment);
474 }
475 
476 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
477 template <typename PointT, typename NormalT> bool
478 pcl::RegionGrowing<PointT, NormalT>::validatePoint (int initial_seed, int point, int nghbr, bool& is_a_seed) const
479 {
480  is_a_seed = true;
481 
482  float cosine_threshold = cosf (theta_threshold_);
483  float data[4];
484 
485  data[0] = input_->points[point].data[0];
486  data[1] = input_->points[point].data[1];
487  data[2] = input_->points[point].data[2];
488  data[3] = input_->points[point].data[3];
489  Eigen::Map<Eigen::Vector3f> initial_point (static_cast<float*> (data));
490  Eigen::Map<Eigen::Vector3f> initial_normal (static_cast<float*> (normals_->points[point].normal));
491 
492  //check the angle between normals
493  if (smooth_mode_flag_ == true)
494  {
495  Eigen::Map<Eigen::Vector3f> nghbr_normal (static_cast<float*> (normals_->points[nghbr].normal));
496  float dot_product = fabsf (nghbr_normal.dot (initial_normal));
497  if (dot_product < cosine_threshold)
498  {
499  return (false);
500  }
501  }
502  else
503  {
504  Eigen::Map<Eigen::Vector3f> nghbr_normal (static_cast<float*> (normals_->points[nghbr].normal));
505  Eigen::Map<Eigen::Vector3f> initial_seed_normal (static_cast<float*> (normals_->points[initial_seed].normal));
506  float dot_product = fabsf (nghbr_normal.dot (initial_seed_normal));
507  if (dot_product < cosine_threshold)
508  return (false);
509  }
510 
511  // check the curvature if needed
512  if (curvature_flag_ && normals_->points[nghbr].curvature > curvature_threshold_)
513  {
514  is_a_seed = false;
515  }
516 
517  // check the residual if needed
518  data[0] = input_->points[nghbr].data[0];
519  data[1] = input_->points[nghbr].data[1];
520  data[2] = input_->points[nghbr].data[2];
521  data[3] = input_->points[nghbr].data[3];
522  Eigen::Map<Eigen::Vector3f> nghbr_point (static_cast<float*> (data));
523  float residual = fabsf (initial_normal.dot (initial_point - nghbr_point));
524  if (residual_flag_ && residual > residual_threshold_)
525  is_a_seed = false;
526 
527  return (true);
528 }
529 
530 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
531 template <typename PointT, typename NormalT> void
533 {
534  int number_of_segments = static_cast<int> (num_pts_in_segment_.size ());
535  int number_of_points = static_cast<int> (input_->points.size ());
536 
537  pcl::PointIndices segment;
538  clusters_.resize (number_of_segments, segment);
539 
540  for (int i_seg = 0; i_seg < number_of_segments; i_seg++)
541  {
542  clusters_[i_seg].indices.resize ( num_pts_in_segment_[i_seg], 0);
543  }
544 
545  std::vector<int> counter;
546  counter.resize (number_of_segments, 0);
547 
548  for (int i_point = 0; i_point < number_of_points; i_point++)
549  {
550  int segment_index = point_labels_[i_point];
551  if (segment_index != -1)
552  {
553  int point_index = counter[segment_index];
554  clusters_[segment_index].indices[point_index] = i_point;
555  counter[segment_index] = point_index + 1;
556  }
557  }
558 
559  number_of_segments_ = number_of_segments;
560 }
561 
562 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
563 template <typename PointT, typename NormalT> void
565 {
566  cluster.indices.clear ();
567 
568  bool segmentation_is_possible = initCompute ();
569  if ( !segmentation_is_possible )
570  {
571  deinitCompute ();
572  return;
573  }
574 
575  // first of all we need to find out if this point belongs to cloud
576  bool point_was_found = false;
577  int number_of_points = static_cast <int> (indices_->size ());
578  for (size_t point = 0; point < number_of_points; point++)
579  if ( (*indices_)[point] == index)
580  {
581  point_was_found = true;
582  break;
583  }
584 
585  if (point_was_found)
586  {
587  if (clusters_.empty ())
588  {
589  point_neighbours_.clear ();
590  point_labels_.clear ();
591  num_pts_in_segment_.clear ();
592  number_of_segments_ = 0;
593 
594  segmentation_is_possible = prepareForSegmentation ();
595  if ( !segmentation_is_possible )
596  {
597  deinitCompute ();
598  return;
599  }
600 
601  findPointNeighbours ();
602  applySmoothRegionGrowingAlgorithm ();
603  assembleRegions ();
604  }
605  // if we have already made the segmentation, then find the segment
606  // to which this point belongs
607  std::vector <pcl::PointIndices>::iterator i_segment;
608  for (i_segment = clusters_.begin (); i_segment != clusters_.end (); i_segment++)
609  {
610  bool segment_was_found = false;
611  for (size_t i_point = 0; i_point < i_segment->indices.size (); i_point++)
612  {
613  if (i_segment->indices[i_point] == index)
614  {
615  segment_was_found = true;
616  cluster.indices.clear ();
617  cluster.indices.reserve (i_segment->indices.size ());
618  std::copy (i_segment->indices.begin (), i_segment->indices.end (), std::back_inserter (cluster.indices));
619  break;
620  }
621  }
622  if (segment_was_found)
623  {
624  break;
625  }
626  }// next segment
627  }// end if point was found
628 
629  deinitCompute ();
630 }
631 
632 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
633 template <typename PointT, typename NormalT> pcl::PointCloud<pcl::PointXYZRGB>::Ptr
635 {
637 
638  if (!clusters_.empty ())
639  {
640  colored_cloud = (new pcl::PointCloud<pcl::PointXYZRGB>)->makeShared ();
641 
642  srand (static_cast<unsigned int> (time (0)));
643  std::vector<unsigned char> colors;
644  for (size_t i_segment = 0; i_segment < clusters_.size (); i_segment++)
645  {
646  colors.push_back (static_cast<unsigned char> (rand () % 256));
647  colors.push_back (static_cast<unsigned char> (rand () % 256));
648  colors.push_back (static_cast<unsigned char> (rand () % 256));
649  }
650 
651  colored_cloud->width = input_->width;
652  colored_cloud->height = input_->height;
653  colored_cloud->is_dense = input_->is_dense;
654  for (size_t i_point = 0; i_point < input_->points.size (); i_point++)
655  {
656  pcl::PointXYZRGB point;
657  point.x = *(input_->points[i_point].data);
658  point.y = *(input_->points[i_point].data + 1);
659  point.z = *(input_->points[i_point].data + 2);
660  point.r = 255;
661  point.g = 0;
662  point.b = 0;
663  colored_cloud->points.push_back (point);
664  }
665 
666  std::vector< pcl::PointIndices >::iterator i_segment;
667  int next_color = 0;
668  for (i_segment = clusters_.begin (); i_segment != clusters_.end (); i_segment++)
669  {
670  std::vector<int>::iterator i_point;
671  for (i_point = i_segment->indices.begin (); i_point != i_segment->indices.end (); i_point++)
672  {
673  int index;
674  index = *i_point;
675  colored_cloud->points[index].r = colors[3 * next_color];
676  colored_cloud->points[index].g = colors[3 * next_color + 1];
677  colored_cloud->points[index].b = colors[3 * next_color + 2];
678  }
679  next_color++;
680  }
681  }
682 
683  return (colored_cloud);
684 }
685 
686 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
687 template <typename PointT, typename NormalT> pcl::PointCloud<pcl::PointXYZRGBA>::Ptr
689 {
691 
692  if (!clusters_.empty ())
693  {
694  colored_cloud = (new pcl::PointCloud<pcl::PointXYZRGBA>)->makeShared ();
695 
696  srand (static_cast<unsigned int> (time (0)));
697  std::vector<unsigned char> colors;
698  for (size_t i_segment = 0; i_segment < clusters_.size (); i_segment++)
699  {
700  colors.push_back (static_cast<unsigned char> (rand () % 256));
701  colors.push_back (static_cast<unsigned char> (rand () % 256));
702  colors.push_back (static_cast<unsigned char> (rand () % 256));
703  }
704 
705  colored_cloud->width = input_->width;
706  colored_cloud->height = input_->height;
707  colored_cloud->is_dense = input_->is_dense;
708  for (size_t i_point = 0; i_point < input_->points.size (); i_point++)
709  {
710  pcl::PointXYZRGBA point;
711  point.x = *(input_->points[i_point].data);
712  point.y = *(input_->points[i_point].data + 1);
713  point.z = *(input_->points[i_point].data + 2);
714  point.r = 255;
715  point.g = 0;
716  point.b = 0;
717  point.a = 0;
718  colored_cloud->points.push_back (point);
719  }
720 
721  std::vector< pcl::PointIndices >::iterator i_segment;
722  int next_color = 0;
723  for (i_segment = clusters_.begin (); i_segment != clusters_.end (); i_segment++)
724  {
725  std::vector<int>::iterator i_point;
726  for (i_point = i_segment->indices.begin (); i_point != i_segment->indices.end (); i_point++)
727  {
728  int index;
729  index = *i_point;
730  colored_cloud->points[index].r = colors[3 * next_color];
731  colored_cloud->points[index].g = colors[3 * next_color + 1];
732  colored_cloud->points[index].b = colors[3 * next_color + 2];
733  }
734  next_color++;
735  }
736  }
737 
738  return (colored_cloud);
739 }
740 
741 #define PCL_INSTANTIATE_RegionGrowing(T) template class pcl::RegionGrowing<T, pcl::Normal>;
742 
743 #endif // PCL_SEGMENTATION_REGION_GROWING_HPP_