Thứ Hai, 4 tháng 2, 2013

Thư viện PCL (Phần 2)


Phần 2: Sử dụng PCL trong các project

Trong hướng dẫn này sử dụng MSVS 2010.
Các thành phần yêu cầu:
1.      PCL All-in-one installer Download
2.      QT bản 4.8.0 (download bản 32bit, hoặc bản 64bit)
3.      CMake (Download)
Chú ý khi cài đặt PCL và QT nên cài ở chế độ mặc định để đỡ phải chỉnh sửa khi tạo project. Với PCL nên thêm tùy chọn “Add PCL to system PATH for all users”
Bước 1:
Tạo một thư mục chứa dự án cần tạo (1) và một thư mục chứa file buid (2).
Trong thư mục (1) tạo các file .cpp chứa code và thêm một file CMakeList.txt chứa các tùy chọn biên dịch cho chương trình CMake.


Chi tiết file CmakeList.txt như sau:
(Ví dụ cho project pcd_write.cpp  dùng để ghi dữ liệu ra định dạng PCD)
cmake_minimum_required(VERSION 2.6 FATAL_ERROR)
project(MY_GRAND_PROJECT)
find_package(PCL 1.3 REQUIRED COMPONENTS common io)
include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})
add_executable(pcd_write_test pcd_write.cpp)
target_link_libraries(pcd_write_test ${PCL_COMMON_LIBRARIES} ${PCL_IO_LIBRARIES})

Ý nghĩa của các lệnh:




cmake_minimum_required(VERSION 2.6 FATAL_ERROR)
Yêu cầu bản CMake từ 2.6 hoặc cao hơn.

project
(MY_PROJECT)
Dòng này dùng để đặt tên cho dự án. Tên dự án trong ví dụ này là MY_PROJECT.
find_package(PCL 1.3 REQUIRED COMPONENTS common io)
Thiết lập phiên bản tối thiểu của PCL, ở ví dụ này là 1.3. Nếu phiên bản thấp hơn sẽ báo lỗi.
Các module cần thiết cho dự án được khai báo sau từ khóa COMPONENTS            , nếu không có phần này, tất cả các module sẽ được đưa vào dự án. Ở ví dụ này, các module cần thiết là common và io.
include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})
Thiết lập một số tham số:
  • PCL_FOUND: Đặt 1 nếu tìm thấy PCL, các trường hợp khác không thiết lập
·         PCL_INCLUDE_DIRS: Thiết lập đường dẫn đến các file headers và headers phụ thuộc của PCL
·         PCL_LIBRARIES: Thiết lập tên của thư viện PCL khi biên dịch
  • PCL_LIBRARY_DIRSThiết lập các đường dẫn đến  nơi chứa các file thư viện PCL và  3rd party dependencies
  • PCL_COMPONENTS: Danh sách tất cả các thành phần.
  • PCL_DEFINITIONS: Liệt kê các định nghĩa tiền xử lý cần thiết và cờ biên dịch
Với một dự án thông thường thì chỉ cần thiết lập giống trong ví dụ là đủ.
add_executable(pcd_write_test pcd_write.cpp)
Add file pcd_write.cpp vào phần mã nguồn của dự án.
target_link_libraries(pcd_write_test ${PCL_COMMON_LIBRARIES} ${PCL_IO_LIBRARIES})
Nơi chứa các hàm chức năng khi thực hiện chương trình. Ở đây sử dụng các thư viện common và IO.

Bước 2:
Chạy chương trình CMake:

Dòng đầu tiên là đường dẫn đến thư mục chứa code và file CmakeLists.txt. Dòng thứ 2 dẫn đến thư mục chứa project sẽ được tạo ra.
Bấm Generate sẽ hiện ra bảng thiết lập như hình vẽ. Chọn các trình biên dịch và chế độ biên dịch thích hợp.

Sau khi biên dịch thành công, trong thư mục buid sẽ chứa project tương thích với MSVS2010 và sẵn sàng để biên dịch.

Chú ý, khi biên dịch bằng MSVS 2010  thành công nhưng báo lỗi không chạy tự động được thì có thể vào thư mục Debug để tìm file  exe để chạy.

Cách trên có ưu điểm là có thể tạo project cho nhiều trình biên dịch khác ngoài MSVS. Tuy nhiên, có một cách đơn giản hơn đó là cấu hình ngay trong MSVS2010 như sau:
Tạo một empty  project C++. Chuột phải vào project đã tạo. Tìm đến phần VC++ Directories và edit các phần như trong hình vẽ thành các đường dẫn đến các thư mục chứa file thư viện đã cài đặt.
Nếu cài theo mặc định, đường dẫn sẽ như sau:
#Include Directories

C:\Program Files\PCL 1.6.0\3rdParty\VTK\include\vtk-5.8;
C:\Program Files\PCL 1.6.0\3rdParty\Qhull\include;C:\Program Files\PCL 1.6.0\3rdParty\FLANN\include;
C:\Program Files\PCL 1.6.0\3rdParty\Eigen\include;C:\Program Files\PCL 1.6.0\3rdParty\Boost\include;
C:\Program Files\PCL 1.6.0\include\pcl-1.6;C:\Program Files\OpenNI\Include;C:\Qt\4.8.0\include;

#Library Directories

C:\Program Files\PCL 1.6.0\3rdParty\VTK\lib\vtk-5.8;
C:\Program Files\PCL 1.6.0\3rdParty\Qhull\lib;C:\Program Files\PCL 1.6.0\3rdParty\FLANN\lib;
C:\Program Files\PCL 1.6.0\3rdParty\Boost\lib;C:\Program Files\PCL 1.6.0\lib;C:\Qt\4.8.0\lib;


Trong mục Linker tìm đến Input và edit
pcl_common_debug.lib
pcl_apps_debug.lib
pcl_features_debug.lib
pcl_filters_debug.lib
pcl_io_debug.lib
pcl_io_ply_debug.lib
pcl_kdtree_debug.lib
pcl_keypoints_debug.lib
pcl_octree_debug.lib
pcl_registration_debug.lib
pcl_sample_consensus_debug.lib
pcl_search_debug.lib
pcl_segmentation_debug.lib
pcl_surface_debug.lib
pcl_tracking_debug.lib
pcl_visualization_debug.lib
vtkRendering-gd.lib
QVTK-gd.lib
vtkalglib-gd.lib
vtkCharts-gd.lib
vtkCommon-gd.lib
vtkDICOMParser-gd.lib
vtkexoIIc-gd.lib
vtkexpat-gd.lib
vtkFiltering-gd.lib
vtkfreetype-gd.lib
vtkftgl-gd.lib
vtkGenericFiltering-gd.lib
vtkGeovis-gd.lib
vtkGraphics-gd.lib
vtkhdf5-gd.lib
vtkHybrid-gd.lib
vtkImaging-gd.lib
vtkInfovis-gd.lib
vtkIO-gd.lib
vtkjpeg-gd.lib
vtklibxml2-gd.lib
vtkmetaio-gd.lib
vtkNetCDF_cxx-gd.lib
vtkNetCDF-gd.lib
vtkpng-gd.lib
vtkproj4-gd.lib
vtksqlite-gd.lib
vtksys-gd.lib
vtktiff-gd.lib
vtkverdict-gd.lib
vtkViews-gd.lib
vtkVolumeRendering-gd.lib
vtkWidgets-gd.lib
vtkzlib-gd.lib
OpenGL32.Lib


   

Phần 3: Một số ứng dụng cơ bản của PCL

3.1 Đọc dữ liệu từ Kinect:

Để đọc dữ liệu thu được từ Kinect,  chúng ta có thể sử dụng OpenNIGrabber, đây là một lớp có sử dụng thư viện OpenNI có sẵn trong PCL.
Các bước thực hiện như sau:
Bước 1: Khai báo 
      #include <pcl/io/openni_grabber.h>

Bước 2:  Tạo một Grabber mới:
 
        pcl::Grabber* interface = new pcl::OpenNIGrabber();

Bước 3: Tạo callback function:
 
boost::function<void (const pcl::PointCloud<pcl::PointXYZRGBA>::ConstPtr&)>                                                              
      boost::bind (&SimpleOpenNIProcessor::cloud_cb_, this, _1);
// cloud_cb_ là hàm dùng để xử lý dữ liệu nhận về, ở ví dụ này cloub_cb_ để hiển thị //ra màn hình.
 
Ở các lệnh trên, dữ liệu ta thu về từ Kinect có định dạng PointXYZRGBA,  có thể sửa lại lệnh để thu về các loại dữ liệu khác. Cụ thể như sau:
-          Dữ liệu thu về là điểm ảnh mầu 3D pointXYZRGB:
void (const boost::shared_ptr<const pcl::pointCloud <pcl::pointXYZRGB>> &)
-          Dữ liệu thu về là điểm ảnh không mầu 3D pointXYZ:
void (const boost::shared_ptr<const pcl::pointCloud <pcl::pointXYZ>> &)
-          Dữ liệu thu về là ảnh RGB từ camera :
void (const boost::shared_ptr< openNI_wrapper::Image> &)
-          Dữ liệu thu về là bản đồ độ sâu:
void (const boost::shared_ptr< openNI_wrapper::DepthImage> &)

Bước 4: Kết nối hàm callback với dữ liệu cần quan tâm và nhận dữ liệu, dừng dữ liệu: 
       interface->registerCallback (f);
 
       interface->start ();
 
interface->stop ();
 

-         Hiển thị dữ liệu ra màn hình:
Để hiển thị dữ liệu ra màn hình, có thể dùng thư viện con visualization.  Thư viện này cho phép hiển thị nhiều định dạng dữ liệu khác nhau như pointXYZ, pointXYZRGB, ảnh RGB, bản đồ độ sâu…
Ví dụ để hiển thị PointXYZRGB trước tiên cần khai báo
#include <pcl/visualization/cloud_viewer.h>
Hàm hiển thị dữ liệu với đầu vào là biến cloud, cửa sổ hiển thị là viewer:
 
     void cloud_cb_ (const pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr &cloud)
     {
       if (!viewer.wasStopped())
         viewer.showCloud (cloud);
     }

Toàn bộ Code chương trình đọc dữ liệu từ Kinect hiển thị ra màn hình như sau:
#include <pcl/io/openni_grabber.h>
 #include <pcl/visualization/cloud_viewer.h>
 
 class SimpleOpenNIViewer
 {
   public:
     SimpleOpenNIViewer () : viewer ("PCL OpenNI Viewer") {}
 
     void cloud_cb_ (const pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr &cloud)
     {
       if (!viewer.wasStopped())
         viewer.showCloud (cloud);
     }
 
     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())
       {
         boost::this_thread::sleep (boost::posix_time::seconds (1));
       }
 
       interface->stop ();
     }
 
     pcl::visualization::CloudViewer viewer;
 };
 
 int main ()
 {
   SimpleOpenNIViewer v;
   v.run ();
   return 0;
 }
File makelists.txt để tạo dự án MSVS 2010 từ Cmake
cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
 
project(openni_grabber)
 
find_package(PCL 1.2 REQUIRED)
 
include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})
 
add_executable (openni_grabber openni_grabber.cpp)
target_link_libraries (openni_grabber ${PCL_LIBRARIES})

Kết quả:
Ảnh thực tế

Ảnh thu được khi chạy chương trình ở trên.:


 

3.2 Class bộ lọc sử dụng Voxel Grid:

Nhằm tăng tốc độ xử lý, ta có thể đưa dữ liệu qua bộ lọc voxel Grid để giảm số lượng điểm ảnh và giới hạn vùng lấy dữ liệu phía trước cảm biến.
Tập hợp các điểm gần nhau sẽ chỉ lấy một điểm đại diện.
Các hàm PCL cung cấp cho bộ lọc bao gồm:
-         pcl::VoxelGrid<PointType> grid_;
 Khai báo một bộ lọc VoxtelGrid có tên là grid_, PointType là các kiểu biểu diễn điểm như PointXYZ, PointXYZRGB…
-         grid_.setLeafSize (leaf_size_x, leaf_size_y, leaf_size_z);
Chọn mật độ điểm theo các chiều lần lượt là X,Y,Z. Đơn vị là Met
-         grid_.setFilterFieldName (field_name);
Chiều cần giới hạn ví dụ giới hạn theo chiều Z
-         grid_.setFilterLimits (min_v, max_v);
Thiết lập khoảng giới hạn hoảng giới hạn

Code tham khảo:

#include <boost/thread/thread.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/io/openni_grabber.h>
#include <pcl/visualization/cloud_viewer.h>
#include <pcl/io/openni_camera/openni_driver.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/console/parse.h>
#include <pcl/common/time.h>

// Ham Tinh framerate trung binh (Hz)
#define FPS_CALC(_WHAT_) \
do \
{ \
    static unsigned count = 0;\
    static double last = pcl::getTime ();\
    double now = pcl::getTime (); \
    ++count; \
    if (now - last >= 1.0) \
    { \
      std::cout << "Average framerate("<< _WHAT_ << "): " << double(count)/double(now - last) << " Hz" <<  std::endl; \
      count = 0; \
      last = now; \
    } \
}while(false)

// Khuon mau ham Voxel Grid, PointType co the la PointXYZ,PointXYZRGB,...
template <typename PointType>
class OpenNIVoxelGrid
{
      
  public:
    typedef pcl::PointCloud<PointType> Cloud;
    typedef typename Cloud::Ptr CloudPtr;
    typedef typename Cloud::ConstPtr CloudConstPtr;

       pcl::VoxelGrid<PointType> grid_;                // Bo loc VoxelGrid
       pcl::visualization::CloudViewer viewer; // Khai bao 1 cua so hien thi
       std::string device_id_;                                // Id cua thiet bi
       boost::mutex mtx_;                                     // Quan ly mutex
       CloudConstPtr cloud_;                                  // Con tro pointCloud const
       //Ham tao
    OpenNIVoxelGrid (const std::string& device_id = "",
                   const std::string& field_name = "z"float min_v = 0, float max_v = 5.0,
//Gioi han theo truc z tu 0-5m
float leaf_size_x = 0.01, float leaf_size_y = 0.01, float leaf_size_z = 0.01) // Tham so cua bo loc
    : viewer ("PCL OpenNI VoxelGrid Viewer")
    , device_id_(device_id)
    {
      grid_.setLeafSize (leaf_size_x, leaf_size_y, leaf_size_z);
      grid_.setFilterFieldName (field_name);
      grid_.setFilterLimits (min_v, max_v);
    }
   
    void
    cloud_cb_ (const CloudConstPtr& cloud)//Ham callback khi nhan duoc tin hieu tu Kinect
    {
      set (cloud);
    }

    void
    set (const CloudConstPtr& cloud)// Ham set du lieu
    {
         // Khoa mutex trong khi set du lieu
      boost::mutex::scoped_lock lock (mtx_);
      cloud_  = cloud;
    }

    CloudPtr
    get ()
    {
         // Khoa khi swap du lieu va reset chung.
      boost::mutex::scoped_lock lock (mtx_);
      CloudPtr temp_cloud (new Cloud);
       // Truyen tham so cho bo loc
      grid_.setInputCloud (cloud_);
      grid_.filter (*temp_cloud);

      return (temp_cloud);
    }

    void
    run ()
    {
         // Nhan du lieu tu Kinect
      pcl::Grabber* interface = new pcl::OpenNIGrabber (device_id_);

      boost::function<void (const CloudConstPtr&)> f = boost::bind (&OpenNIVoxelGrid::cloud_cb_, this, _1);
      boost::signals2::connection c = interface->registerCallback (f);
     
      interface->start ();
     
      while (!viewer.wasStopped ())
      {
        if (cloud_)
        {
          FPS_CALC ("drawing");// Hien thi framerate trung binh (Hz)
          viewer.showCloud (get ());
        }
      }

      interface->stop ();
    }


};


int
main (int argc, char ** argv)
{


  double min_v = 0.5, max_v = 1.4;
  std::string field_name ("z");

  double leaf_x = 0.01, leaf_y = 0.01, leaf_z = 0.01;
  pcl::OpenNIGrabber grabber ("");
      OpenNIVoxelGrid<pcl::PointXYZI> v ("", field_name, min_v, max_v, leaf_x, leaf_y, leaf_z);
    v.run ();


  return (0);
}