1. 개요
다중 실린더는 평면보다 처리할 것들이 더 많다. 실린더를 추출하기 전에 평면을 추출해, 제거해야 하며, 적절한 실린더 파라메터를 설정해 주어야 한다.
2. 코딩
다음과 같이 코딩해 본다. 추출할 반경은 0.05 미터에서 0.2 미터 반경 사이의 실린더만 추출해 본다 .빌드 및 실행 방법은 이전 글과 동일하다.
#include <ros/ros.h>
// PCL specific includes
#include <sensor_msgs/PointCloud2.h>
#include <pcl_conversions/pcl_conversions.h>
#include <pcl/ros/conversions.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/sample_consensus/model_types.h>
#include <pcl/sample_consensus/method_types.h>
#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/filters/extract_indices.h>
#include <pcl/ModelCoefficients.h>
#include <pcl/filters/passthrough.h>
#include <pcl/features/normal_3d.h>
ros::Publisher pub;
typedef pcl::PointXYZ PointT;
void
cloud_cb (const sensor_msgs::PointCloud2ConstPtr& input)
{
// Convert the sensor_msgs/PointCloud2 data to pcl/PointCloud
pcl::PointCloud<pcl::PointXYZ> cloud;
pcl::fromROSMsg (*input, cloud);
// Create the segmentation object
pcl::SACSegmentation<pcl::PointXYZ> seg;
// Optional
seg.setOptimizeCoefficients (true);
// Mandatory
seg.setModelType (pcl::SACMODEL_PLANE);
seg.setMethodType (pcl::SAC_RANSAC);
seg.setDistanceThreshold (0.01);
// remove plane
for(int i = 0; i < 4; i++)
{
if(cloud.size() < 10000)
break;
seg.setInputCloud (cloud.makeShared ());
pcl::ModelCoefficients coefficients;
// pcl::PointIndices inliers;
pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
seg.segment (*inliers, coefficients);
// Create the filtering object
pcl::ExtractIndices<pcl::PointXYZ> extract;
// Extract the inliers
ROS_INFO("extract the inliers");
pcl::PointCloud<pcl::PointXYZ> in_cloud;
extract.setInputCloud (cloud.makeShared ());
extract.setIndices (inliers);
extract.setNegative (false);
extract.filter (in_cloud);
extract.setNegative (true);
extract.filter (cloud);
}
// Algorithm
pcl::NormalEstimation<PointT, pcl::Normal> ne;
pcl::SACSegmentationFromNormals<PointT, pcl::Normal> seg2;
pcl::search::KdTree<PointT>::Ptr tree (new pcl::search::KdTree<PointT> ());
// Datasets
pcl::PointCloud<PointT>::Ptr cloud_filtered (new pcl::PointCloud<PointT>);
pcl::PointCloud<pcl::Normal>::Ptr cloud_normals (new pcl::PointCloud<pcl::Normal>);
pcl::ModelCoefficients::Ptr coefficients_cylinder (new pcl::ModelCoefficients);
pcl::PointIndices::Ptr inliers_cylinder (new pcl::PointIndices);
pcl::PointCloud<pcl::PointXYZI> TotalCloud;
sensor_msgs::PointCloud2 output;
for(int i = 0; i < 1; i++)
{
// Estimate point normals
ne.setSearchMethod (tree);
ne.setInputCloud (cloud.makeShared ());
ne.setKSearch (50);
ne.compute (*cloud_normals);
// Create the segmentation object for cylinder segmentation and set all the parameters
seg2.setOptimizeCoefficients (true);
seg2.setModelType (pcl::SACMODEL_CYLINDER);
seg2.setMethodType (pcl::SAC_RANSAC);
seg2.setNormalDistanceWeight (0.1);
seg2.setMaxIterations (1000);
seg2.setDistanceThreshold (0.05);
seg2.setRadiusLimits (0.05, 0.2);
seg2.setInputCloud (cloud.makeShared ());
seg2.setInputNormals (cloud_normals);
// Obtain the cylinder inliers and coefficients
seg2.segment (*inliers_cylinder, *coefficients_cylinder);
std::cerr << "Cylinder coefficients: " << *coefficients_cylinder << std::endl;
// Write the cylinder inliers to disk
pcl::PointCloud<PointT>::Ptr cloud_cylinder (new pcl::PointCloud<PointT> ());
pcl::ExtractIndices<PointT> extract;
extract.setInputCloud (cloud.makeShared ());
extract.setIndices (inliers_cylinder);
extract.setNegative (false);
extract.filter (*cloud_cylinder);
pcl::PointCloud<pcl::PointXYZ>::iterator index = cloud_cylinder->begin();
for(; index != cloud_cylinder->end(); index++)
{
pcl::PointXYZ pt = *index;
pcl::PointXYZI pt2;
pt2.x = pt.x, pt2.y = pt.y, pt2.z = pt.z;
pt2.intensity = (float)(i + 1);
TotalCloud.push_back(pt2);
}
ROS_INFO("%d. cylinder point cloud = %d", i, (int)TotalCloud.size());
}
// Convert To ROS data type
pcl::PCLPointCloud2 cloud_p;
pcl::toPCLPointCloud2(TotalCloud, cloud_p);
pcl_conversions::fromPCL(cloud_p, output);
output.header.frame_id = "camera_depth_optical_frame";
pub.publish(output);
ROS_INFO("published it.");
}
int
main (int argc, char** argv)
{
// Initialize ROS
ros::init (argc, argv, "my_pcl_tutorial");
ros::NodeHandle nh;
// Create a ROS subscriber for the input point cloud
ros::Subscriber sub = nh.subscribe ("camera/depth/points", 1, cloud_cb);
// Create a ROS publisher for the output model coefficients
// pub = nh.advertise<pcl_msgs::ModelCoefficients> ("pclplaneoutput", 1);
pub = nh.advertise<sensor_msgs::PointCloud2> ("pclplaneoutput", 1);
// Spin
ros::spin ();
}
3. 실행 결과
실행 결과는 다음과 같다. PCL에서 7개의 파라메터가 추출되는 데, 앞의 3개 값은 실린더 축의 첫번째 좌표 P1, 다음 3개 값은 두번째 좌표 P2, 마지막 값은 반경이다. 0.18 반경을 가진 실린더가 추출되었음을 알 수 있다.
ROS rviz로 확인해 보면, 실린더와 유사한 형상이 추출되었음을 확인할 수 있다.
4. 실험
이제 몇가지 실험을 해 본다. 실험을 위해 반경 0.05 m 인 파이프를 준비한다.
실행해 테스트해 보면, 다음과 같이 0.04 m 반경을 가진 파이프를 인식한 것을 확인할 수 있다. 인식 오차는 1 cm 정도이다.
rviz에서 확인해 보자. 우선 파이프를 추출하기 전 포인트 클라우드이다.
파이프를 인식해 보면, 앞의 포인트 클라우드에서 파이프 부분을 제대로 추출하였음을 확인할 수 있다. 다만, 일부 평면이 추출되었는 데, 이는 실린더 추출 시 설정 파라메터에서, 속도 문제로 반복횟수를 1000으로 낮추는 바람에, 허용오차가 제대로 고려되지 않은 문제로 보인다. 반경의 오차도 같은 이유로 높아진 원인이 있다. 물론, 불완전한 포인트 클라우드도 반경 오차에 영향을 미치고 있다.
이제 for(int i = 0; i < 1; i++) 부분을 두번 실행되도록 2로 수정한다. 그리고 빌드해 실행해 본다. 그럼 포인트 클라우드에서 설정된 파라메터 조건에 맞는 추출된 2개의 실런더 형상을 확인할 수 있을 것이다 .
5. 결론
앞의 시험 결과, 처리 성능만 개선한다면, 파이프 파라메터와 피팅(fitting)율을 높일 수 있기 때문에, 파이프 추출 시 정밀도 등을 높일 수 있다. 그럼에도 불구하고, 평면에 비해, 실린더는 형상 인식에 더 많은 계산 시간이 필요하여, 실시간성이 요구되는 응용에는 적합하지 않을 수 있다.
댓글 없음:
댓글 쓰기