最近有一个工作是需要把一组三维点以ROS PointCloud2 messge的形式进行publish。并且需要使用python环境。原始点云只有坐标数据,需要根据点距离坐标原点的距离对点云进行上色。
通过参考一些开源项目的源码,并通过NumPy的structured array功能实现了PointCloud2 message的生成。其中需要注意的是一点,PointCloud2 message 通过一个uint32类型的整数表达RGBA四个通道。虽然称作RGBA,但是在uint32的4个字节中,从高位向低位分别是B, G, R, A通道。另外,对2维的structured array进行索引,其方法和行为可能都和普通的高维array有些差异。
直接上源码。
import numpy as np
import rospy
from sensor_msgs.msg import PointCloud2, PointField
from std_msgs.msg import Header
from ColorMapping import color_map
DIST_COLORS = [\
"#2980b9",\
"#27ae60",\
"#f39c12",\
"#c0392b",\
]
DIST_COLOR_LEVELS = 20
def convert_numpy_2_pointcloud2_color(points, stamp=None, frame_id=None, maxDistColor=None):
'''
Create a sensor_msgs.PointCloud2 from an array
of points.
This function will automatically assign RGB values to each point. The RGB values are
determined by the distance of a point from the origin. Use maxDistColor to set the distance
at which the color corresponds to the farthest distance is used.
points: A NumPy array of Nx3.
stamp: An alternative ROS header stamp.
frame_id: The frame id. String.
maxDisColor: Should be positive if specified..
This function get inspired by
https://github.com/spillai/pybot/blob/master/pybot/externals/ros/pointclouds.py
https://gist.github.com/lucasw/ea04dcd65bc944daea07612314d114bb
(https://answers.ros.org/question/289576/understanding-the-bytes-in-a-pcl2-message/)
and expo_utility.xyz_array_to_point_cloud_msg() function of the AirSim package.
ROS sensor_msgs/PointField.
http://docs.ros.org/melodic/api/sensor_msgs/html/msg/PointField.html
More references on mixed-type NumPy array, structured array.
https://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html
https://stackoverflow.com/questions/37791134/merge-width-x-height-x-3-numpy-uint8-array-into-width-x-height-x-1-uint32-array
https://jakevdp.github.io/PythonDataScienceHandbook/02.09-structured-data-numpy.html
'''
# Clipping input.
dist = np.linalg.norm( points, axis=1 )
if ( maxDistColor is not None and maxDistColor > 0):
dist = np.clip(dist, 0, maxDistColor)
# Compose color.
cr, cg, cb = color_map( dist, DIST_COLORS, DIST_COLOR_LEVELS )
C = np.zeros((cr.size, 4), dtype=np.uint8) + 255
C[:, 0] = cb.astype(np.uint8)
C[:, 1] = cg.astype(np.uint8)
C[:, 2] = cr.astype(np.uint8)
C = C.view("uint32")
# Structured array.
pointsColor = np.zeros( (points.shape[0], 1), \
dtype={
"names": ( "x", "y", "z", "rgba" ),
"formats": ( "f4", "f4", "f4", "u4" )} )
points = points.astype(np.float32)
pointsColor["x"] = points[:, 0].reshape((-1, 1))
pointsColor["y"] = points[:, 1].reshape((-1, 1))
pointsColor["z"] = points[:, 2].reshape((-1, 1))
pointsColor["rgba"] = C
header = Header()
if stamp is None:
header.stamp = rospy.Time().now()
else:
header.stamp = stamp
if frame_id is None:
header.frame_id = "None"
else:
header.frame_id = frame_id
msg = PointCloud2()
msg.header = header
if len(points.shape) == 3:
msg.height = points.shape[1]
msg.width = points.shape[0]
else:
msg.height = 1
msg.width = points.shape[0]
msg.fields = [
PointField('x', 0, PointField.FLOAT32, 1),
PointField('y', 4, PointField.FLOAT32, 1),
PointField('z', 8, PointField.FLOAT32, 1),
PointField('rgb', 12, PointField.UINT32, 1),
]
msg.is_bigendian = False
msg.point_step = 16
msg.row_step = msg.point_step * points.shape[0]
msg.is_dense = int( np.isfinite(points).all() )
msg.data = pointsColor.tostring()
return msg
其中,ColorMapping
是我参考另外一个博客,在其源码基础上自己创建的一个生成线性梯度RGB值的辅助工具。源码在这里。
ROS PointCloud2 message生成
https://github.com/spillai/pybot/blob/master/pybot/externals/ros/pointclouds.py https://gist.github.com/lucasw/ea04dcd65bc944daea07612314d114bb
(https://answers.ros.org/question/289576/understanding-the-bytes-in-a-pcl2-message/)
ROS sensor_msgs/PointField
http://docs.ros.org/melodic/api/sensor_msgs/html/msg/PointField.html
NumPy structured array
https://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html
https://stackoverflow.com/questions/37791134/merge-width-x-height-x-3-numpy-uint8-array-into-width-x-height-x-1-uint32-array
https://jakevdp.github.io/PythonDataScienceHandbook/02.09-structured-data-numpy.html
Color mapping
https://bsou.io/posts/color-gradients-with-python