2011年8月24日水曜日

Point Cloud Libraryを試す(その4:平面抽出)

今回はポイントクラウドから平面を抽出します。

その前に、前回使ったPointXYZRGBの色の部分がfloat rgbとなっていて気になったので調べてみました。

// pack r/g/b into rgb
 uint8_t r = 255, g = 0, b = 0;    // Example: Red color
 uint32_t rgb = ((uint32_t)r << 16 | (uint32_t)g << 8 | (uint32_t)b);
 p.rgb = *reinterpret_cast<float*>(&rgb);
↑のような感じで使うみたいです。
悪名高きreinterpret_castか!と思ったら、
ver.1.1.0からはr,g,b変数も使えるみたいで、
point.r = 255;
point.g = 100;
point.b = 10;

みたいに指定できるようです。


PCLのAPIリファレンスはこちらです。
Point Cloud Library (PCL): PCL API Documentation

という小ネタを挟みまして、今回は平面検出です。

↓のようにPointCloudから平面っぽいところを切りだしてくれるはずです。

チュートリアルはこちら

チュートリアルにあるコードは簡単なんですが、ビューワが付いていないので、ビューワを付けてみました。
以下をplanar_segmentation.cppとして保存してください。

#include <iostream>
#include <pcl/ModelCoefficients.h>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/sample_consensus/method_types.h>
#include <pcl/sample_consensus/model_types.h>
#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/visualization/cloud_viewer.h>

int
main (int argc, char** argv)
{
  pcl::PointCloud<pcl::PointXYZRGB> cloud;

  // Fill in the cloud data
  cloud.width  = 15;
  cloud.height = 10;
  cloud.points.resize (cloud.width * cloud.height);

  // Generate the data
  size_t i;
  for (i = 0; i < cloud.points.size ()/2; ++i)
    {
      // 平面におく
      cloud.points[i].x = 1024 * rand () / (RAND_MAX + 1.0);
      cloud.points[i].y = 1024 * rand () / (RAND_MAX + 1.0);
      cloud.points[i].z = 0.1 * rand () / (RAND_MAX + 1.0);
      cloud.points[i].r = 255;
      cloud.points[i].g = 255;
      cloud.points[i].b = 255;
    }

  for (; i < cloud.points.size (); ++i)
    {
      // ちらばらせる
      cloud.points[i].x = 1024 * rand () / (RAND_MAX + 1.0);
      cloud.points[i].y = 1024 * rand () / (RAND_MAX + 1.0);
      cloud.points[i].z = 1024 * rand () / (RAND_MAX + 1.0);
      cloud.points[i].r = 255;
      cloud.points[i].g = 255;
      cloud.points[i].b = 255;
    }

  // Set a few outliers
  cloud.points[0].z = 2.0;
  cloud.points[3].z = -2.0;
  cloud.points[6].z = 4.0;

  std::cerr << "Point cloud data: " << cloud.points.size () << " points" << std::endl;
  for (size_t i = 0; i < cloud.points.size (); ++i)
    std::cerr << "    " << cloud.points[i].x << " "
     << cloud.points[i].y << " "
     << cloud.points[i].z << std::endl;

  pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients);
  pcl::PointIndices::Ptr inliers (new pcl::PointIndices);
  // Create the segmentation object
  pcl::SACSegmentation<pcl::PointXYZRGB> seg;
  // Optional
  seg.setOptimizeCoefficients (true);
  // Mandatory
  seg.setModelType (pcl::SACMODEL_PLANE);
  seg.setMethodType (pcl::SAC_RANSAC);
  seg.setDistanceThreshold (0.1);

  seg.setInputCloud (cloud.makeShared ());
  seg.segment (*inliers, *coefficients);

  if (inliers->indices.size () == 0)
    {
      PCL_ERROR ("Could not estimate a planar model for the given dataset.");
      return (-1);
    }

  std::cerr << "Model coefficients: " << coefficients->values[0] << " "
   << coefficients->values[1] << " "
   << coefficients->values[2] << " "
   << coefficients->values[3] << std::endl;

  std::cerr << "Model inliers: " << inliers->indices.size () << std::endl;
  for (size_t i = 0; i < inliers->indices.size (); ++i) {
    std::cerr << inliers->indices[i] << "    " << cloud.points[inliers->indices[i]].x << " "
     << cloud.points[inliers->indices[i]].y << " "
     << cloud.points[inliers->indices[i]].z << std::endl;
    cloud.points[inliers->indices[i]].r = 255;
    cloud.points[inliers->indices[i]].g = 0;
    cloud.points[inliers->indices[i]].b = 0;
  }
  pcl::visualization::CloudViewer viewer("Cloud Viewer");

  viewer.showCloud(cloud.makeShared());

  while (!viewer.wasStopped ())
    {
     
    }

  return (0);
}
するとこんな感じで平面っぽいところにあるポイントが赤く表示されました。

CMakeLists.txtはいつもと同じです。

cmake_minimum_required(VERSION 2.8 FATAL_ERROR)

project(planar_segmentation)

find_package(PCL 1.1 REQUIRED)

include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})

add_executable (planar_segmentation planar_segmentation.cpp)
target_link_libraries (planar_segmentation ${PCL_LIBRARIES})
$ cmake .
$ make
でビルドして、
$ ./planar_segmentation
で実行してみます。


SACSegmentationというClassが今回の本質のようです。ここにSACMODEL_PLANE=平面モデルを指定したり、
setDistanceThresholdでモデルとの誤差の閾値を指定しています。setMethodTypeで計算方法を選択(今回はSAC_RANSAC)しています。RANSACは確率的なモデル推定だったきがします。
coefficients->valuesに入っているのは推定されたモデル
ax+by+cz+d=0の平面の式になります。

これで平面上にあるポイントが検出できました。

さらにKinectでやってみました。前回のKinectのキャプチャサンプルに今回のを合体させてみました。
一番広い平面が真っ赤にそまります。これは楽しい!

一応ソースコードです。適当です。
#include <iostream>
#include <pcl/ModelCoefficients.h>
#include <pcl/io/openni_grabber.h>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/sample_consensus/method_types.h>
#include <pcl/sample_consensus/model_types.h>
#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/visualization/cloud_viewer.h>

void segmentate(pcl::PointCloud<pcl::PointXYZRGB>& cloud, double threshould) {
  pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients);
  pcl::PointIndices::Ptr inliers (new pcl::PointIndices);
  // Create the segmentation object
  pcl::SACSegmentation<pcl::PointXYZRGB> seg;
  // Optional
  seg.setOptimizeCoefficients (true);
  // Mandatory
  seg.setModelType (pcl::SACMODEL_PLANE);
  seg.setMethodType (pcl::SAC_RANSAC);
  seg.setDistanceThreshold (threshould);

  seg.setInputCloud (cloud.makeShared ());
  seg.segment (*inliers, *coefficients);

  for (size_t i = 0; i < inliers->indices.size (); ++i) {
    cloud.points[inliers->indices[i]].r = 255;
    cloud.points[inliers->indices[i]].g = 0;
    cloud.points[inliers->indices[i]].b = 0;
  }
}

 class SimpleOpenNIViewer
 {
   public:
     SimpleOpenNIViewer () : viewer ("PCL OpenNI Viewer") {}

     void cloud_cb_ (const pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr &cloud)
     {
       if (!viewer.wasStopped()) {
  pcl::PointCloud<pcl::PointXYZRGB> segmented_cloud(*cloud);
  segmentate(segmented_cloud, 0.01);
         viewer.showCloud (segmented_cloud.makeShared());
       }
     }

     void run ()
     {
       pcl::Grabber* interface = new pcl::OpenNIGrabber();

       boost::function<void (const pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr&)> f =
         boost::bind (&SimpleOpenNIViewer::cloud_cb_, this, _1);

       interface->registerCallback (f);
       interface->start ();
       while (!viewer.wasStopped())
       {
         sleep (1);
       }

       interface->stop ();
     }

     pcl::visualization::CloudViewer viewer;
 };

 int main ()
 {
   SimpleOpenNIViewer v;
   v.run ();
   return 0;
 }

1 件のコメント:

  1. PointXYZRGBのRGBは私も悩みました。きちんとキャストしないと変な色になってしまいます..

    返信削除