求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
要资料
 
追随技术信仰

随时听讲座
每天看新闻
 
 
ROS教程
1.初级教程
1.1 安装和配置ROS环境
1.2 ROS文件系统导览
1.3 创建ROS软件包
1.4 构建ROS软件包
1.5 理解ROS节点
1.6 理解ROS话题
1.7 理解ROS服务和参数
1.8 使用rqt_console和roslaunch
1.9 使用rosed在ROS中编辑文件
1.10 创建ROS消息和服务
1.11 编写简单的发布者和订阅者(C++)
1.12 编写简单的发布者和订阅者(Python)
1.13 检验简单的发布者和订阅者
1.14 编写简单的服务和客户端(C++)
1.15 编写简单的服务和客户端(Python)
1.16 检验简单的服务和客户端
1.17 录制和回放数据
1.18 从bag文件中读取消息
1.19 roswtf入门
1.20 探索ROS维基
1.21 接下来做什么?
2.中级教程
2.1手动创建ROS包
2.2管理系统依赖项
2.3Roslaunch在大型项目中的使用技巧
2.4ROS在多机器人上的使用
2.5自定义消息
2.6在python中使用C++类
2.7如何编写教程
ROS标准
ROS开发者指南
标准计量单位和坐标约定
 
 
在python中使用C++类
来源: ros.org 在线教程    编辑:Alice(火龙果软件)
666 次浏览
5次  

描述: 本教程阐述一种在python中使用C++类的方法。

关键词: C++, Python, 绑定

教程级别: 高级

本教程演示了一种在 Python 中使用带有 ROS 消息的 C++ 类的方法。使用 Boost Python 库。难点在于将用纯 Python 编写的 ROS 消息的 Python 对象转换为等效的 C++ 实例。此转换将通过序列化/反序列化完成。源文件可在 https://github.com/galou/python_bindings_tutorial 中找到。

另一种解决方案是使用经典的ROS服务,用C++编写的服务器将是C++类的包装器,客户端C++或Python将调用该服务。这里提出的解决方案不会创建 ROS 节点,前提是要包装的类不使用 ros::NodeHandle。

另一种解决方案是为所有需要的 ROS 消息及其依赖项编写包装器。一些显然被弃用的软件包为这个任务的自动化提出了一些解决方案:genpybindings 和 boost_ python_ros。

1没有 NodeHandle 的类

因为 roscpp 在调用 rospy.init_node 时没有初始化。ros::NodeHandle 实例不能在 C++ 类中使用,而不会生成错误。如果 C++ 不使用 ros::NodeHandle,这不是问题。

1.1创建包并编写 C++ 类

创建一个包并创建 C++ 类,我们将要为其进行 Python 绑定。此类使用 ROS 消息作为参数和返回类型。

   1 catkin_create_pkg python_bindings_tutorial rospy roscpp std_msgs
   2 cd python_bindings_tutorial/include/python_bindings_tutorial
   3 touch add_two_ints.h
   4 rosed python_bindings_tutorial add_two_ints.h

include/python_bindings_tutorial/add_two_ints.h 的内容为:

   1 #ifndef PYTHON_BINDINGS_TUTORIAL_ADD_TWO_INTS_H
   2 #define PYTHON_BINDINGS_TUTORIAL_ADD_TWO_INTS_H
   3 
   4 #include <std_msgs/Int64.h>
   5 
   6 namespace python_bindings_tutorial {
   7 
   8 class AddTwoInts
   9 {
  10   public:
  11     std_msgs::Int64 add(const std_msgs::Int64& a, const std_msgs::Int64& b);
  12 };
  13 
  14 } // namespace python_bindings_tutorial
  15 
  16 #endif // PYTHON_BINDINGS_TUTORIAL_ADD_TWO_INTS_H
  17 

将类实现写入 。

roscd python_bindings_tutorial/src
touch add_two_ints.cpp
rosed python_bindings_tutorial add_two_ints.cpp

src/add_two_ints.cpp的内容将是:

   1 #include <python_bindings_tutorial/add_two_ints.h>
   2 
   3 using namespace python_bindings_tutorial;
   4 
   5 std_msgs::Int64 AddTwoInts::add(const std_msgs::Int64& a, const std_msgs::Int64& b)
   6 {
   7   std_msgs::Int64 sum;
   8   sum.data = a.data + b.data;
   9   return sum;
  10 }

1.2绑定,C++ 部分

绑定通过两个包装类进行,一个在 C++ 中,一个在 Python 中。C++ 包装将序列化内容的输入转换为 C++ 消息实例,并将 C++ 消息实例的输出转换为序列化内容。

   1 roscd python_bindings_tutorial/src
   2 touch add_two_ints_wrapper.cpp
   3 rosed python_bindings_tutorial add_two_ints_wrapper.cpp

src/add_two_ints_wrapper.cpp的内容将是:

   1 #include <boost/python.hpp>
   2 
   3 #include <string>
   4 
   5 #include <ros/serialization.h>
   6 #include <std_msgs/Int64.h>
   7 
   8 #include <python_bindings_tutorial/add_two_ints.h>
   9 
  10 /* Read a ROS message from a serialized string.
  11   */
  12 template <typename M>
  13 M from_python(const std::string str_msg)
  14 {
  15   size_t serial_size = str_msg.size();
  16   boost::shared_array<uint8_t> buffer(new uint8_t[serial_size]);
  17   for (size_t i = 0; i < serial_size; ++i)
  18   {
  19     buffer[i] = str_msg[i];
  20   }
  21   ros::serialization::IStream stream(buffer.get(), serial_size);
  22   M msg;
  23   ros::serialization::Serializer<M>::read(stream, msg);
  24   return msg;
  25 }
  26 
  27 /* Write a ROS message into a serialized string.
  28 */
  29 template <typename M>
  30 std::string to_python(const M& msg)
  31 {
  32   size_t serial_size = ros::serialization::serializationLength(msg);
  33   boost::shared_array<uint8_t> buffer(new uint8_t[serial_size]);
  34   ros::serialization::OStream stream(buffer.get(), serial_size);
  35   ros::serialization::serialize(stream, msg);
  36   std::string str_msg;
  37   str_msg.reserve(serial_size);
  38   for (size_t i = 0; i < serial_size; ++i)
  39   {
  40     str_msg.push_back(buffer[i]);
  41   }
  42   return str_msg;
  43 }
  44 
  45 class AddTwoIntsWrapper : public python_bindings_tutorial::AddTwoInts
  46 {
  47   public:
  48     AddTwoIntsWrapper() : AddTwoInts() {}
  49 
  50     std::string add(const std::string& str_a, const std::string& str_b)
  51     {
  52       std_msgs::Int64 a = from_python<std_msgs::Int64>(str_a);
  53       std_msgs::Int64 b = from_python<std_msgs::Int64>(str_b);
  54       std_msgs::Int64 sum = AddTwoInts::add(a, b);
  55 
  56       return to_python(sum);
  57     }
  58 };
  59 
  60 BOOST_PYTHON_MODULE(_add_two_ints_wrapper_cpp)
  61 {
  62   boost::python::class_<AddTwoIntsWrapper>("AddTwoIntsWrapper", boost::python::init<>())
  63     .def("add", &AddTwoIntsWrapper::add)
  64     ;
  65 }

No code_block found 行以动态库的形式创建 Python 模块。模块的名称必须是 CMakeLists.txt 中导出的库的名称。

1.3绑定,Python 部分

Python 包装器将 Python 消息实例的输入转换为序列化内容,并将序列化内容的输出转换为 Python 消息实例。从 Python 序列化内容 (str) 到 C++ 序列化内容 (std::string) 的转换是在 Boost Python 库中构建的。

roscd python_bindings_tutorial/src
mkdir python_bindings_tutorial
roscd python_bindings_tutorial/src/python_bindings_tutorial
touch _add_two_ints_wrapper_py.py
rosed python_bindings_tutorial _add_two_ints_wrapper_py.py

src/python_bindings_tutorial/_add_two_ints_wrapper_py.py 的内容将是

   1 from StringIO import StringIO
   2 
   3 import rospy
   4 from std_msgs.msg import Int64
   5 
   6 from python_bindings_tutorial._add_two_ints_wrapper_cpp import AddTwoIntsWrapper
   7 
   8 
   9 class AddTwoInts(object):
  10     def __init__(self):
  11         self._add_two_ints = AddTwoIntsWrapper()
  12 
  13     def _to_cpp(self, msg):
  14         """Return a serialized string from a ROS message
  15 
  16         Parameters
  17         ----------
  18         - msg: a ROS message instance.
  19         """
  20         buf = StringIO()
  21         msg.serialize(buf)
  22         return buf.getvalue()
  23 
  24     def _from_cpp(self, str_msg, cls):
  25         """Return a ROS message from a serialized string
  26 
  27         Parameters
  28         ----------
  29         - str_msg: str, serialized message
  30         - cls: ROS message class, e.g. sensor_msgs.msg.LaserScan.
  31         """
  32         msg = cls()
  33         return msg.deserialize(str_msg)
  34 
  35     def add(self, a, b):
  36         """Add two std_mgs/Int64 messages
  37 
  38         Return a std_msgs/Int64 instance.
  39 
  40         Parameters
  41         ----------
  42         - a: a std_msgs/Int64 instance.
  43         - b: a std_msgs/Int64 instance.
  44         """
  45         if not isinstance(a, Int64):
  46             rospy.ROSException('Argument 1 is not a std_msgs/Int64')
  47         if not isinstance(b, Int64):
  48             rospy.ROSException('Argument 2 is not a std_msgs/Int64')
  49         str_a = self._to_cpp(a)
  50         str_b = self._to_cpp(b)
  51         str_sum = self._add_two_ints.add(str_a, str_b)
  52         return self._from_cpp(str_sum, Int64)

 

以便能够将类导入为python_bindings_tutorial。AddTwoInts,我们导入 __init__.py 中的符号。首先,我们创建文件:

   1 roscd python_bindings_tutorial/src/python_bindings_tutorial
   2 touch __init__.py
   3 rosed python_bindings_tutorial __init__.py

 

src/python_bindings_tutorial/__init__.py的内容为:

   1 from python_bindings_tutorial._add_two_ints_wrapper_py import AddTwoInts

 

1.4将所有东西粘合在一起

编辑 CMakeLists.txt (rosed python_bindings_tutorial CmakeLists.txt) 如下所示:

cmake_minimum_required(VERSION 2.8.3)
project(python_bindings_tutorial)

find_package(catkin REQUIRED COMPONENTS
  roscpp
  roscpp_serialization
  std_msgs
)

## Both Boost.python and Python libs are required.
find_package(Boost REQUIRED COMPONENTS python)
find_package(PythonLibs 2.7 REQUIRED)


## Uncomment this if the package has a setup.py. This macro ensures
## modules and global scripts declared therein get installed
## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html
catkin_python_setup()

###################################
## catkin specific configuration ##
###################################
catkin_package(
        INCLUDE_DIRS include
        LIBRARIES add_two_ints _add_two_ints_wrapper_cpp
        CATKIN_DEPENDS roscpp
        #  DEPENDS system_lib
)

###########
## Build ##
###########

# include Boost and Python.
include_directories(
        include
        ${catkin_INCLUDE_DIRS}
        ${Boost_INCLUDE_DIRS}
        ${PYTHON_INCLUDE_DIRS}
        )

## Declare a cpp library
add_library(add_two_ints src/add_two_ints.cpp)
add_library(_add_two_ints_wrapper_cpp src/add_two_ints_wrapper.cpp)

## Specify libraries to link a library or executable target against
target_link_libraries(add_two_ints ${catkin_LIBRARIES})
target_link_libraries(_add_two_ints_wrapper_cpp add_two_ints ${catkin_LIBRARIES} ${Boost_LIBRARIES})

# Don't prepend wrapper library name with lib and add to Python libs.
set_target_properties(_add_two_ints_wrapper_cpp PROPERTIES
        PREFIX ""
        LIBRARY_OUTPUT_DIRECTORY ${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_PYTHON_DESTINATION}
        )

 

c++ 包装库应与 Python 模块同名。如果由于某种原因需要不同目标名称,则可以使用 set_target_properties(_add_two_ints_wrapper_cpp PROPERTIES OUTPUT_NAME correct_library_name) 指定库名称。

生产线

catkin_python_setup()

用于导出 Python 模块,并与文件相关联 setup.py

   1 roscd python_bindings_tutorial
   2 touch setup.py

setup.py 的内容将是:

   1 # ! DO NOT MANUALLY INVOKE THIS setup.py, USE CATKIN INSTEAD
   2 
   3 from distutils.core import setup
   4 from catkin_pkg.python_setup import generate_distutils_setup
   5 
   6 # fetch values from package.xml
   7 setup_args = generate_distutils_setup(
   8     packages=['python_bindings_tutorial'],
   9     package_dir={'': 'src'})
  10 
  11 setup(**setup_args)

然后,我们使用 catkin_make 构建包。

1.5测试绑定

现在,您可以在 Python 脚本或 Python shell 中使用绑定

   1 from std_msgs.msg import Int64
   2 from python_bindings_tutorial import AddTwoInts
   3 a = Int64(4)
   4 b = Int64(2)
   5 addtwoints = AddTwoInts()
   6 sum = addtwoints.add(a, b)
   7 sum

2具有 NodeHandle 的类

如前所述,对 rospy.init_node 的 Python 调用不会初始化 roscpp。如果未初始化 roscpp,则实例化 ros::NodeHandle 将导致致命错误。moveit_ros_planning_interface为此提供了解决方案。在任何使用包装类的 Python 脚本中,在实例化 AddTwoInts 之前需要添加两行:

   1 from moveit_ros_planning_interface._moveit_roscpp_initializer import roscpp_init
   2 roscpp_init('node_name', [])

这将创建一个 ROS 节点。因此,与经典的 ROS 服务服务器/客户端实现相比,这种方法的优势并不像不需要 ros::NodeHandle 的情况那样明显。

3带有 ROS 消息容器的类

如果类使用 ROS 消息的容器,则必须添加额外的转换步骤。此步骤并非特定于 ROS,而是 Boost Python 库的一部分。

3.1“std::vector<M>”作为返回类型

在定义 C++ 包装类的文件中,添加以下行:

   1 // Extracted from https://gist.github.com/avli/b0bf77449b090b768663.
   2 template<class T>
   3 struct vector_to_python
   4 {
   5   static PyObject* convert(const std::vector<T>& vec)
   6   {
   7     boost::python::list* l = new boost::python::list();
   8     for(std::size_t i = 0; i < vec.size(); i++)
   9       (*l).append(vec[i]);
  10 
  11     return l->ptr();
  12   }
  13 };
  14 
  15 class Wrapper : public WrappedClass
  16 {
  17 /*
  18 ...
  19 */
  20     std::vector<std::string> wrapper_fun(const std::string str_msg)
  21     {
  22       /* ... */
  23     }
  24 
  25 };
  26 
  27 BOOST_PYTHON_MODULE(module_wrapper_cpp)
  28 {
  29   boost::python::class_<Wrapper>("Wrapper", bp::init</* ... */>())
  30     .def("fun", &Wrapper::wrapper_fun);
  31 
  32   boost::python::to_python_converter<std::vector<std::string, std::allocator<std::string> >, vector_to_python<std::string> >();
  33 }

3.2 'std::vector<M>' 作为参数类型

参见 Boost.Python 文档。


您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码: 验证码,看不清楚?请点击刷新验证码 必填



666 次浏览
5次