求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 


业务架构设计
4月18-19日 在线直播



基于UML和EA进行系统分析设计
4月25-26日 北京+在线



AI 智能化软件测试方法与实践
5月23-24日 上海+在线
 
追随技术信仰

随时听讲座
每天看新闻
 
 
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(火龙果软件)
793 次浏览
12次  

描述: 本教程阐述一种在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元





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



793 次浏览
12次