点群の並進移動および回転移動の実装をご紹介します。
【ROS+PCL】点群の並進移動&回転
点群を扱うときに、形を崩さずに並進/回転移動させたいことはよくあります。
この記事では、ROSとPCLを使った実装例をご紹介します。
目次
仕様
Subscribeした点群に対して、並進/回転移動を適用してPublishする仕様とします。
ただし、回転の考え方は複数あり、本記事では、以下の2種類を実装します。
- ロール・ピッチ・ヨーをもとにする回転:
回転前の座標に固定されたx、y、z軸回りに回転させる - オイラー角をもとにする回転:
ロボット座標に固定されたx、y、z軸回りに回転させる(i.e. 各軸の回転ごとに回転軸は回転する)
以下に、実装における、入出力とパラメータを記載します。
入力(Subscribe)
- sensor_msgs::PointCloud2:入力点群
出力(Publish)
- sensor_msgs::PointCloud2:変換された点群
パラメータ
- double:x軸方向の移動距離 [m]
- double:y軸方向の移動距離 [m]
- double:z軸方向の移動距離 [m]
- double:ロール(またはx軸回りの)回転角度 [deg]
- double:ピッチ(またはy軸回りの)回転角度 [deg]
- double:ヨー(またはz軸回りの)回転角度 [deg]
環境構築
今回のプログラムを動かすためには、以下をインストールする必要があります。
- ROS
- PCL
(Dockerを使う場合)
Dockerを使う場合は、以下のDockerfileを例として参考にしてください。
########## Pull ########## FROM ros:noetic ########## ROS setup ########## RUN mkdir -p ~/catkin_ws/src && \ cd ~/catkin_ws && \ /bin/bash -c "source /opt/ros/noetic/setup.bash; catkin_make" && \ echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc && \ echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc && \ echo "export ROS_WORKSPACE=~/catkin_ws" >> ~/.bashrc ########## Requirements ########## RUN apt-get update && \ apt-get install -y \ libpcl-dev \ ros-noetic-pcl-conversions \ ros-noetic-rviz
実装
ディレクトリ構造
~/catkin_ws/src/pc_transform/
┣ src/
┃ ┣ rpy_transform.cpp
┃ ┗ eular_transform.cpp
┣ launch/
┃ ┗ transform.launch
┗ CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 2.8.3)
project(pc_transform)
add_compile_options(-std=c++14 -O2 -g -Wall)
find_package(catkin REQUIRED COMPONENTS
roscpp
tf
)
find_package(PCL)
catkin_package(
)
include_directories(
${catkin_INCLUDE_DIRS}
${PCL_INCLUDE_DIRS}
)
add_executable(tf_transform src/tf_transform.cpp)
target_link_libraries(tf_transform
${catkin_LIBRARIES}
)
add_executable(eular_transform src/eular_transform.cpp)
target_link_libraries(eular_transform
${catkin_LIBRARIES}
${PCL_LIBRARIES}
)
add_executable(rpy_transform src/rpy_transform.cpp)
target_link_libraries(rpy_transform
${catkin_LIBRARIES}
${PCL_LIBRARIES}
)
rpy_transform.cpp
ロール・ピッチ・ヨーをもとに回転させる実装です。
#include <ros/ros.h>
#include <sensor_msgs/PointCloud2.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl_conversions/pcl_conversions.h>
#include <pcl/common/transforms.h>
class PcRpyTransform{
private:
/*node handle*/
ros::NodeHandle nh_;
ros::NodeHandle nh_private_;
/*subscriber*/
ros::Subscriber sub_;
/*publisher*/
ros::Publisher pub_;
/*parameter*/
std::string publish_frame_;
double x_m_, y_m_, z_m_;
double r_deg_, p_deg_, y_deg_;
public:
PcRpyTransform();
void callbackPC(const sensor_msgs::PointCloud2ConstPtr& msg);
void transformPC(pcl::PointCloud<pcl::PointXYZI>::Ptr pc);
void publication(pcl::PointCloud<pcl::PointXYZI>::Ptr pc);
double degToRad(double deg);
};
PcRpyTransform::PcRpyTransform()
: nh_private_("~")
{
std::cout << "--- pc_rpy_transform ---" << std::endl;
/*parameter*/
nh_private_.param("publish_frame", publish_frame_, std::string(""));
std::cout << "publish_frame_ = " << publish_frame_ << std::endl;
nh_private_.param("x_m", x_m_, 0.0);
std::cout << "x_m_ = " << x_m_ << std::endl;
nh_private_.param("y_m", y_m_, 0.0);
std::cout << "y_m_ = " << y_m_ << std::endl;
nh_private_.param("z_m", z_m_, 0.0);
std::cout << "z_m_ = " << z_m_ << std::endl;
nh_private_.param("r_deg", r_deg_, 0.0);
std::cout << "r_deg_ = " << r_deg_ << std::endl;
nh_private_.param("p_deg", p_deg_, 0.0);
std::cout << "p_deg_ = " << p_deg_ << std::endl;
nh_private_.param("y_deg", y_deg_, 0.0);
std::cout << "y_deg_ = " << y_deg_ << std::endl;
/*subscriber*/
sub_ = nh_.subscribe("/point_cloud", 1, &PcRpyTransform::callbackPC, this);
/*publisher*/
pub_ = nh_.advertise<sensor_msgs::PointCloud2>("/point_cloud/transformed", 1);
}
void PcRpyTransform::callbackPC(const sensor_msgs::PointCloud2ConstPtr &msg)
{
pcl::PointCloud<pcl::PointXYZI>::Ptr pc (new pcl::PointCloud<pcl::PointXYZI>);
pcl::fromROSMsg(*msg, *pc);
transformPC(pc);
publication(pc);
}
void PcRpyTransform::transformPC(pcl::PointCloud<pcl::PointXYZI>::Ptr pc)
{
Eigen::Affine3f transformatoin = pcl::getTransformation(x_m_, y_m_, z_m_, degToRad(r_deg_), degToRad(p_deg_), degToRad(y_deg_));
pcl::transformPointCloud(*pc, *pc, transformatoin);
}
void PcRpyTransform::publication(pcl::PointCloud<pcl::PointXYZI>::Ptr pc)
{
sensor_msgs::PointCloud2 ros_pc;
pcl::toROSMsg(*pc, ros_pc);
if(publish_frame_ != "") ros_pc.header.frame_id = publish_frame_;
pub_.publish(ros_pc);
}
double PcRpyTransform::degToRad(double deg)
{
double rad = deg / 180.0 * M_PI;
rad = atan2(sin(rad), cos(rad));
return rad;
}
int main(int argc, char** argv)
{
ros::init(argc, argv, "pc_rpy_transform");
PcRpyTransform pc_rpy_transform;
ros::spin();
}
eular_transform.cpp
オイラー角をもとに回転させる実装です。
#include <ros/ros.h>
#include <sensor_msgs/PointCloud2.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl_conversions/pcl_conversions.h>
#include <pcl/common/transforms.h>
class PcEularTransform{
private:
/*node handle*/
ros::NodeHandle nh_;
ros::NodeHandle nh_private_;
/*subscriber*/
ros::Subscriber sub_;
/*publisher*/
ros::Publisher pub_;
/*parameter*/
std::string publish_frame_;
double x_m_, y_m_, z_m_;
double x_deg_, y_deg_, z_deg_;
public:
PcEularTransform();
void callbackPC(const sensor_msgs::PointCloud2ConstPtr& msg);
void transformPC(pcl::PointCloud<pcl::PointXYZI>::Ptr pc);
void publication(pcl::PointCloud<pcl::PointXYZI>::Ptr pc);
double degToRad(double deg);
};
PcEularTransform::PcEularTransform()
: nh_private_("~")
{
// rpy_transform.cppと同じ
}
void PcEularTransform::callbackPC(const sensor_msgs::PointCloud2ConstPtr &msg)
{
// rpy_transform.cppと同じ
}
void PcEularTransform::transformPC(pcl::PointCloud<pcl::PointXYZI>::Ptr pc)
{
Eigen::Quaternionf rotation =
Eigen::AngleAxisf(degToRad(x_deg_), Eigen::Vector3f::UnitX())
* Eigen::AngleAxisf(degToRad(y_deg_), Eigen::Vector3f::UnitY())
* Eigen::AngleAxisf(degToRad(z_deg_), Eigen::Vector3f::UnitZ());
Eigen::Vector3f offset(x_m_, y_m_, z_m_);
pcl::transformPointCloud(*pc, *pc, offset, rotation);
}
void PcEularTransform::publication(pcl::PointCloud<pcl::PointXYZI>::Ptr pc)
{
// rpy_transform.cppと同じ
}
double PcEularTransform::degToRad(double deg)
{
// rpy_transform.cppと同じ
}
int main(int argc, char** argv)
{
ros::init(argc, argv, "pc_eular_transform");
PcEularTransform pc_eular_transform;
ros::spin();
}
transform.launch
<launch> <!-- global rosparam --> <param name="use_sim_time" value="true"/> <!-- rosbag --> <node pkg="rosbag" type="play" name="player" args="--clock $(env HOME)/rosbag/demo.bag"/> <!-- rviz --> <node pkg="rviz" type="rviz" name="rviz" args="-d $(find pc_transform)/rviz_config/demo.rviz"/> <!-- main --> <node pkg="pc_transform" type="rpy_transform" name="rpy_transform" output="screen"> <!-- <node pkg="pc_transform" type="eular_transform" name="eular_transform" output="screen"> --> <remap from="/point_cloud" to="/demo_points"/> <remap from="/point_cloud/transformed" to="/demo_points/transformed"/> <param name="x_m" type="double" value="1.0"/> <param name="y_m" type="double" value="2.0"/> <param name="z_m" type="double" value="3.0"/> <param name="x_deg" type="double" value="10.0"/> <param name="y_deg" type="double" value="20.0"/> <param name="z_deg" type="double" value="30.0"/> </node> </launch>
使い方
ビルド
cd ~/catkin_ws catkin_make
実行
roslaunch pc_transform transform.launch
さいごに
ROSとPCLを使って、3次元点群の並進/回転移動を実装してみました。
参考になれば幸いです。
関連記事
本記事では指定した値で点群を並進移動/回転させました。
一方で、tfが設定されており、点群を座標変換したい場合は、tf::TransformListener::transformPointCloudを使うと便利です。
Ad.
コメント