├── CMakeLists.txt
├── README.md
├── config
└── param.yaml
├── image
├── robione_0.png
├── robione_1.png
├── slam_0.png
├── slam_1.png
├── vls128_0.png
└── vls128_1.png
├── include
└── color_point_cloud
│ ├── ColorPointCloud.hpp
│ ├── data_type
│ ├── Camera.hpp
│ └── PointCloudType.hpp
│ └── utils
│ └── TransformeProvider.hpp
├── launch
└── color_point_cloud.launch.xml
├── package.xml
└── src
├── ColorPointCloud.cpp
└── color_point_cloud_node.cpp
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.8)
2 | project(color_point_cloud)
3 |
4 | find_package(OpenCV REQUIRED)
5 | find_package(Eigen3 REQUIRED)
6 |
7 | find_package(ament_cmake REQUIRED)
8 | find_package(rclcpp REQUIRED)
9 | find_package(sensor_msgs REQUIRED)
10 | find_package(geometry_msgs REQUIRED)
11 | find_package(cv_bridge REQUIRED)
12 | find_package(tf2_ros REQUIRED)
13 | find_package(tf2_eigen REQUIRED)
14 |
15 | include_directories(include
16 | ${OpenCV_INCLUDE_DIRS}
17 | ${EIGEN3_INCLUDE_DIRS}
18 | )
19 |
20 | add_executable(color_point_cloud
21 | src/color_point_cloud_node.cpp
22 | src/ColorPointCloud.cpp)
23 |
24 | ament_target_dependencies(color_point_cloud
25 | rclcpp
26 | sensor_msgs
27 | geometry_msgs
28 | cv_bridge
29 | tf2_ros
30 | tf2_eigen
31 | )
32 |
33 | target_link_libraries(color_point_cloud
34 | ${OpenCV_LIBRARIES}
35 | ${EIGEN3_LIBRARIES}
36 | )
37 |
38 | install(DIRECTORY
39 | launch
40 | config
41 | DESTINATION share/${PROJECT_NAME}/
42 | )
43 |
44 | install(TARGETS color_point_cloud
45 | DESTINATION lib/${PROJECT_NAME})
46 |
47 | ament_package()
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # color-point-cloud
2 |
3 | Tool for colorize point cloud with images from camera. This tool takes a point cloud and
4 | dynamic number of images from camera via ROS2 and colorize the point cloud with the images.
5 |
6 | ## Installation
7 |
8 | ### Prerequisites
9 |
10 | - ROS2 (tested with Humble)
11 | - OpenCV
12 | - PCL
13 | - Eigen3
14 |
15 |
16 | ### Build
17 |
18 | ```bash
19 | mkdir -p ~/color-point-cloud_ws/src
20 | cd ~/color-point-cloud_ws/src
21 | git clone https://github.com/leo-drive/color-point-cloud
22 | cd ..
23 | colcon build --symlink-install
24 | ```
25 |
26 | ## Usage
27 |
28 | ### Run
29 |
30 | ```bash
31 | source ~/color-point-cloud_ws/install/setup.bash
32 | ros2 launch color_point_cloud color_point_cloud.launch.xml
33 | ```
34 |
35 | ### Parameters
36 |
37 | Change parameters in `color_point_cloud.launch.xml` to fit your environment.
38 |
39 | | Parameter | Description |
40 | | :-------- |:----------------------------------------|
41 | | `point_cloud_topic` | your lidar topic as `string` |
42 | | `point_cloud_frame_id` | your lidar topic as `string` |
43 | | `camera_topics` | array of your camera topics as `string` |
44 | | `image_type` | your image format `enum` |
45 |
46 | ### Result
47 |
48 | #### Projection Lucid Vision Triton 5.4 MP 120° camera onto Velodyne Alpha Prime LiDAR
49 |
50 |
51 |
52 |
53 |
54 |
55 | #### Projection AC-IMX390 camera onto RoboSense Helios 32 beam LiDAR
56 |
57 |
58 |
59 |
60 |
61 |
62 | #### SLAM results with LIO-SAM ([link](https://github.com/leo-drive/LIO-SAM-COLOR))
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/config/param.yaml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leo-drive/color-point-cloud/e431fb76a2ad68310de5251539fdca08f9dcd370/config/param.yaml
--------------------------------------------------------------------------------
/image/robione_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leo-drive/color-point-cloud/e431fb76a2ad68310de5251539fdca08f9dcd370/image/robione_0.png
--------------------------------------------------------------------------------
/image/robione_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leo-drive/color-point-cloud/e431fb76a2ad68310de5251539fdca08f9dcd370/image/robione_1.png
--------------------------------------------------------------------------------
/image/slam_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leo-drive/color-point-cloud/e431fb76a2ad68310de5251539fdca08f9dcd370/image/slam_0.png
--------------------------------------------------------------------------------
/image/slam_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leo-drive/color-point-cloud/e431fb76a2ad68310de5251539fdca08f9dcd370/image/slam_1.png
--------------------------------------------------------------------------------
/image/vls128_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leo-drive/color-point-cloud/e431fb76a2ad68310de5251539fdca08f9dcd370/image/vls128_0.png
--------------------------------------------------------------------------------
/image/vls128_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leo-drive/color-point-cloud/e431fb76a2ad68310de5251539fdca08f9dcd370/image/vls128_1.png
--------------------------------------------------------------------------------
/include/color_point_cloud/ColorPointCloud.hpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by bzeren on 05.10.2023.
3 | //
4 |
5 | #pragma once
6 |
7 | #include "color_point_cloud/data_type/Camera.hpp"
8 | #include "color_point_cloud/data_type/PointCloudType.hpp"
9 | #include "color_point_cloud/utils/TransformeProvider.hpp"
10 |
11 | #include
12 |
13 | #include
14 | #include
15 | #include
16 |
17 | #include
18 | #include
19 | #include
20 |
21 | #include "cv_bridge/cv_bridge.h"
22 |
23 | #include
24 |
25 | namespace color_point_cloud {
26 | class ColorPointCloud : public rclcpp::Node {
27 | public:
28 | explicit ColorPointCloud(const rclcpp::NodeOptions &options);
29 |
30 | private:
31 | rclcpp::TimerBase::SharedPtr timer_;
32 |
33 | rclcpp::Subscription::SharedPtr point_cloud_subscriber_;
34 | rclcpp::Publisher::SharedPtr point_cloud_publisher_;
35 |
36 | double timeout_sec_;
37 |
38 | std::string point_cloud_topic_;
39 |
40 | std::string point_cloud_frame_id_;
41 |
42 | ImageType image_type_;
43 |
44 | std::string image_topic_last_name_;
45 |
46 | std::string camera_info_topic_last_name_;
47 |
48 | std::vector camera_topics_;
49 |
50 | std::map camera_type_stdmap_;
51 |
52 | std::vector::SharedPtr> image_subscribers_;
53 | std::vector::SharedPtr> camera_info_subscribers_;
54 |
55 | void timer_callback();
56 |
57 | void point_cloud_callback(const sensor_msgs::msg::PointCloud2::ConstSharedPtr &msg);
58 |
59 | TransformProviderConstPtr transform_provider_ptr_;
60 | };
61 | } // namespace color_point_cloud
62 |
--------------------------------------------------------------------------------
/include/color_point_cloud/data_type/Camera.hpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by bzeren on 05.10.2023.
3 | //
4 |
5 | #pragma once
6 |
7 | #include
8 |
9 | #include
10 |
11 | #include
12 | #include
13 |
14 | #include
15 | #include
16 |
17 | #include
18 |
19 | #include
20 |
21 | #include
22 |
23 | #include
24 |
25 | enum class ImageType {
26 | RAW,
27 | RECTIFIED
28 | };
29 |
30 | namespace color_point_cloud {
31 | class CameraType {
32 | public:
33 | CameraType(std::string image_topic, std::string camera_info_topic) :
34 | image_topic_(std::move(image_topic)), camera_info_topic_(std::move(camera_info_topic)),
35 | is_info_initialized_(false), is_transform_initialized_(false), image_width_(0), image_height_(0) {
36 |
37 | }
38 |
39 | void set_cv_image(const sensor_msgs::msg::Image::ConstSharedPtr &msg, ImageType image_type) {
40 | cv_bridge::CvImageConstPtr cv_ptr;
41 | if (image_type == ImageType::RAW) {
42 | try {
43 | cv_ptr = cv_bridge::toCvShare(msg, get_image_msg()->encoding);
44 | } catch (cv_bridge::Exception &e) {
45 | // RCLCPP_ERROR(this->get_logger(), "cv_bridge exception: %s", e.what());
46 | return;
47 | }
48 | cv::undistort(cv_ptr->image, cv_image_, get_camera_matrix_cv(),
49 | get_distortion_matrix_cv());
50 | } else if (image_type == ImageType::RECTIFIED) {
51 | try {
52 | cv_ptr = cv_bridge::toCvShare(msg, get_image_msg()->encoding);
53 | } catch (cv_bridge::Exception &e) {
54 | // RCLCPP_ERROR(this->get_logger(), "cv_bridge exception: %s", e.what());
55 | return;
56 | }
57 | cv_image_ = cv_ptr->image;
58 | } else {
59 | // RCLCPP_ERROR(this->get_logger(), "Unknown image type");
60 | return;
61 | }
62 | }
63 |
64 | void set_image_msg(const sensor_msgs::msg::Image::ConstSharedPtr &msg) {
65 | image_msg_ = msg;
66 | }
67 |
68 | void set_camera_info(const sensor_msgs::msg::CameraInfo::ConstSharedPtr &msg) {
69 | camera_info_ = msg;
70 | }
71 |
72 | cv::Mat get_cv_image() {
73 | return cv_image_;
74 | }
75 |
76 | sensor_msgs::msg::Image::ConstSharedPtr get_image_msg() {
77 | return image_msg_;
78 | }
79 |
80 | sensor_msgs::msg::CameraInfo::ConstSharedPtr get_camera_info() {
81 | return camera_info_;
82 | }
83 |
84 | bool is_info_initialized() const {
85 | return is_info_initialized_;
86 | }
87 |
88 | bool is_transform_initialized() const {
89 | return is_transform_initialized_;
90 | }
91 |
92 | void set_camera_utils(const sensor_msgs::msg::CameraInfo::ConstSharedPtr &msg) {
93 | camera_frame_id_ = msg->header.frame_id;
94 | distortion_model_ = msg->distortion_model;
95 |
96 | image_width_ = msg->width;
97 | image_height_ = msg->height;
98 |
99 | camera_matrix_(0, 0) = msg->k[0];
100 | camera_matrix_(0, 2) = msg->k[2];
101 | camera_matrix_(1, 1) = msg->k[4];
102 | camera_matrix_(1, 2) = msg->k[5];
103 | camera_matrix_(2, 2) = 1;
104 |
105 | camera_matrix_cv_ = (cv::Mat_(3, 3)
106 | << msg->k[0], 0.000000, msg->k[2], 0.000000, msg->k[4], msg->k[5], 0.000000, 0.000000, 1.000000);
107 |
108 | rectification_matrix_(0, 0) = msg->r[0];
109 | rectification_matrix_(0, 1) = msg->r[1];
110 | rectification_matrix_(0, 2) = msg->r[2];
111 | rectification_matrix_(1, 0) = msg->r[3];
112 | rectification_matrix_(1, 1) = msg->r[4];
113 | rectification_matrix_(1, 2) = msg->r[5];
114 | rectification_matrix_(2, 0) = msg->r[6];
115 | rectification_matrix_(2, 1) = msg->r[7];
116 | rectification_matrix_(2, 2) = msg->r[8];
117 |
118 | projection_matrix_(0, 0) = msg->p[0];
119 | projection_matrix_(0, 2) = msg->p[2];
120 | projection_matrix_(1, 1) = msg->p[5];
121 | projection_matrix_(1, 2) = msg->p[6];
122 | projection_matrix_(2, 2) = 1;
123 |
124 | distortion_matrix_(0, 0) = msg->d[0];
125 | distortion_matrix_(0, 1) = msg->d[1];
126 | distortion_matrix_(0, 2) = msg->d[3];
127 | distortion_matrix_(0, 3) = msg->d[4];
128 | distortion_matrix_(0, 4) = msg->d[2];
129 |
130 | distortion_matrix_cv_ = (cv::Mat_(1, 5) << msg->d[0], msg->d[1], msg->d[3], msg->d[4], msg->d[2]);
131 |
132 | is_info_initialized_ = true;
133 | }
134 |
135 | void set_lidar_to_camera_matrix(geometry_msgs::msg::TransformStamped &msg) {
136 | Eigen::Affine3d affine_lidar_to_camera_matrix = tf2::transformToEigen(msg);
137 |
138 | lidar_to_camera_matrix_ = Eigen::Matrix4d::Identity();
139 | lidar_to_camera_matrix_ = affine_lidar_to_camera_matrix.matrix();
140 |
141 | is_transform_initialized_ = true;
142 | }
143 |
144 | void set_lidar_to_camera_projection_matrix() {
145 | lidar_to_camera_projection_matrix_ = projection_matrix_ * lidar_to_camera_matrix_.block<4, 4>(0, 0);
146 | }
147 |
148 | std::string get_image_topic() {
149 | return image_topic_;
150 | }
151 |
152 | std::string get_camera_info_topic() {
153 | return camera_info_topic_;
154 | }
155 |
156 | std::string get_camera_frame_id() {
157 | return camera_frame_id_;
158 | }
159 |
160 | std::string get_distortion_model() {
161 | return distortion_model_;
162 | }
163 |
164 | double get_image_width() const {
165 | return image_width_;
166 | }
167 |
168 | double get_image_height() const {
169 | return image_height_;
170 | }
171 |
172 | Eigen::Matrix get_camera_matrix() {
173 | return camera_matrix_;
174 | }
175 |
176 | cv::Mat get_camera_matrix_cv() {
177 | return camera_matrix_cv_;
178 | }
179 |
180 | Eigen::Matrix get_rectification_matrix() {
181 | return rectification_matrix_;
182 | }
183 |
184 | Eigen::Matrix get_projection_matrix() {
185 | return projection_matrix_;
186 | }
187 |
188 | Eigen::Matrix get_distortion_matrix() {
189 | return distortion_matrix_;
190 | }
191 |
192 | cv::Mat get_distortion_matrix_cv() {
193 | return distortion_matrix_cv_;
194 | }
195 |
196 | Eigen::Matrix4d get_lidar_to_camera_matrix() {
197 | return lidar_to_camera_matrix_;
198 | }
199 |
200 | Eigen::Matrix get_lidar_to_camera_projection_matrix() {
201 | return lidar_to_camera_projection_matrix_;
202 | }
203 |
204 |
205 |
206 | private:
207 | std::string image_topic_;
208 | std::string camera_info_topic_;
209 |
210 | cv::Mat cv_image_;
211 |
212 | sensor_msgs::msg::Image::ConstSharedPtr image_msg_;
213 | sensor_msgs::msg::CameraInfo::ConstSharedPtr camera_info_;
214 |
215 | bool is_info_initialized_;
216 | bool is_transform_initialized_;
217 |
218 | std::string camera_frame_id_;
219 | std::string distortion_model_;
220 |
221 | double image_width_;
222 | double image_height_;
223 |
224 | Eigen::Matrix camera_matrix_;
225 | Eigen::Matrix rectification_matrix_;
226 | Eigen::Matrix projection_matrix_;
227 | Eigen::Matrix distortion_matrix_;
228 |
229 | cv::Mat camera_matrix_cv_;
230 | cv::Mat distortion_matrix_cv_;
231 |
232 | Eigen::Matrix4d lidar_to_camera_matrix_;
233 |
234 | Eigen::Matrix lidar_to_camera_projection_matrix_;
235 | };
236 |
237 | typedef std::shared_ptr CameraTypePtr;
238 | typedef std::shared_ptr CameraTypeConstPtr;
239 |
240 | } // namespace color_point_cloud
241 |
242 |
--------------------------------------------------------------------------------
/include/color_point_cloud/data_type/PointCloudType.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include "sensor_msgs/msg/point_cloud2.hpp"
6 | #include "sensor_msgs/point_cloud2_iterator.hpp"
7 | #include "tf2/LinearMath/Transform.h"
8 |
9 | namespace pc2_combiner
10 | {
11 |
12 | struct Point
13 | {
14 | float x;
15 | float y;
16 | float z;
17 | float intensity;
18 | unsigned short ring;
19 |
20 | void transform(const geometry_msgs::msg::TransformStamped& t_transform)
21 | {
22 | const tf2::Quaternion rotation{ t_transform.transform.rotation.x,
23 | t_transform.transform.rotation.y,
24 | t_transform.transform.rotation.z,
25 | t_transform.transform.rotation.w };
26 | const tf2::Vector3 translation{ t_transform.transform.translation.x,
27 | t_transform.transform.translation.y,
28 | t_transform.transform.translation.z };
29 | const tf2::Transform transform{ rotation, translation };
30 |
31 | const tf2::Vector3 old_point{ x, y, z };
32 | const tf2::Vector3 new_point{ transform(old_point) };
33 |
34 | x = new_point.getX();
35 | y = new_point.getY();
36 | z = new_point.getZ();
37 | }
38 | };
39 |
40 | /**
41 | * @brief This class takes the reference of an existing const PointCloud2 message and enables
42 | * iterating its points.
43 | *
44 | */
45 | class PointCloudConst
46 | {
47 | public:
48 | /**
49 | * @brief Constructs a new PointCloud object with the reference of an existing PointCloud2 msg.
50 | *
51 | * @param t_pointcloud2 The PointCloud2 message.
52 | */
53 | PointCloudConst(const sensor_msgs::msg::PointCloud2& t_pointcloud2)
54 | : m_pointcloud2{ t_pointcloud2 }
55 | , m_point_count{ m_pointcloud2.height * m_pointcloud2.width }
56 | , m_current_index{ 0 }
57 | , m_iter_x{ m_pointcloud2, "x" }
58 | , m_iter_y{ m_pointcloud2, "y" }
59 | , m_iter_z{ m_pointcloud2, "z" }
60 | , m_iter_intensity{ m_pointcloud2, "intensity" }
61 | , m_iter_ring{ m_pointcloud2, "ring" }
62 | {
63 | }
64 |
65 | /**
66 | * @brief Get the point count of the cloud.
67 | *
68 | * @return Point count.
69 | */
70 | unsigned int getPointCount() const
71 | {
72 | return m_point_count;
73 | }
74 |
75 | /**
76 | * @brief Get the current point of the cloud.
77 | *
78 | * @return Current point.
79 | */
80 | Point getCurrentPoint() const
81 | {
82 | const Point point{ *m_iter_x, *m_iter_y, *m_iter_z, *m_iter_intensity, *m_iter_ring };
83 | return point;
84 | }
85 |
86 | /**
87 | * @brief Increases iterators by 1 to go to next point.
88 | *
89 | */
90 | void nextPoint()
91 | {
92 | if (m_current_index < m_point_count)
93 | {
94 | ++m_current_index;
95 | ++m_iter_x;
96 | ++m_iter_y;
97 | ++m_iter_z;
98 | ++m_iter_intensity;
99 | ++m_iter_ring;
100 | }
101 | }
102 |
103 | size_t getCurrentIndex() const
104 | {
105 | return m_current_index;
106 | }
107 |
108 | private:
109 | // Reference of the existing PointCloud2.
110 | const sensor_msgs::msg::PointCloud2& m_pointcloud2;
111 |
112 | // Number of the points in the cloud.
113 | const unsigned int m_point_count;
114 |
115 | // Index of the current point.
116 | size_t m_current_index;
117 |
118 | // PointCloud2 iterators.
119 | sensor_msgs::PointCloud2ConstIterator m_iter_x;
120 | sensor_msgs::PointCloud2ConstIterator m_iter_y;
121 | sensor_msgs::PointCloud2ConstIterator m_iter_z;
122 | sensor_msgs::PointCloud2ConstIterator m_iter_intensity;
123 | sensor_msgs::PointCloud2ConstIterator m_iter_ring;
124 | };
125 |
126 | /**
127 | * @brief This class takes the reference of an existing const PointCloud2 message and enables
128 | * iterating its points.
129 | *
130 | */
131 | class PointCloud
132 | {
133 | public:
134 | /**
135 | * @brief Constructs a new PointCloud object with the reference of an existing PointCloud2 msg.
136 | *
137 | * @param t_pointcloud2 The PointCloud2 message.
138 | */
139 | PointCloud(sensor_msgs::msg::PointCloud2& t_pointcloud2)
140 | : m_pointcloud2{ t_pointcloud2 }
141 | , m_point_count{ m_pointcloud2.height * m_pointcloud2.width }
142 | , m_current_index{ 0 }
143 | {
144 | }
145 |
146 | /**
147 | * @brief Get the point count of the cloud.
148 | *
149 | * @return Point count.
150 | */
151 | unsigned int getPointCount() const
152 | {
153 | return m_point_count;
154 | }
155 |
156 | /**
157 | * @brief Get the current point of the cloud.
158 | *
159 | * @return Current point.
160 | */
161 | Point getCurrentPoint() const
162 | {
163 | const Point point{ *(*m_iter_x), *(*m_iter_y), *(*m_iter_z), *(*m_iter_intensity),
164 | *(*m_iter_ring) };
165 | return point;
166 | }
167 |
168 | /**
169 | * @brief Increases iterators by 1 to go to next point.
170 | *
171 | */
172 | void nextPoint()
173 | {
174 | if (m_current_index < m_point_count)
175 | {
176 | ++*(m_iter_x);
177 | ++*(m_iter_y);
178 | ++*(m_iter_z);
179 | ++*(m_iter_intensity);
180 | ++*(m_iter_ring);
181 | }
182 | }
183 |
184 | /**
185 | * @brief Sets PointCloud2 fileds and resizes.
186 | *
187 | * @param t_point_count Number of points.
188 | *
189 | */
190 | void setFieldsAndResize(const size_t& t_point_count)
191 | {
192 | m_point_count = t_point_count;
193 | sensor_msgs::PointCloud2Modifier modifier{ m_pointcloud2 };
194 | modifier.resize(m_point_count);
195 | modifier.setPointCloud2Fields(5, "x", 1, sensor_msgs::msg::PointField::FLOAT32, "y", 1,
196 | sensor_msgs::msg::PointField::FLOAT32, "z", 1,
197 | sensor_msgs::msg::PointField::FLOAT32, "intensity", 1,
198 | sensor_msgs::msg::PointField::FLOAT32, "ring", 1,
199 | sensor_msgs::msg::PointField::UINT16);
200 |
201 | m_iter_x.emplace(m_pointcloud2, "x");
202 | m_iter_y.emplace(m_pointcloud2, "y");
203 | m_iter_z.emplace(m_pointcloud2, "z");
204 | m_iter_intensity.emplace(m_pointcloud2, "intensity");
205 | m_iter_ring.emplace(m_pointcloud2, "ring");
206 |
207 | m_pointcloud2.header.frame_id = "base_link";
208 | }
209 |
210 | /**
211 | * @brief Sets the current point.
212 | *
213 | * @param t_point The point to set.
214 | */
215 | void setCurrentPoint(const Point& t_point)
216 | {
217 | *(*m_iter_x) = t_point.x;
218 | *(*m_iter_y) = t_point.y;
219 | *(*m_iter_z) = t_point.z;
220 | *(*m_iter_intensity) = t_point.intensity;
221 | *(*m_iter_ring) = t_point.ring;
222 | }
223 |
224 | void append(const Point& t_point)
225 | {
226 | setCurrentPoint(t_point);
227 | nextPoint();
228 | }
229 |
230 | private:
231 | // Reference of the existing PointCloud2.
232 | sensor_msgs::msg::PointCloud2& m_pointcloud2;
233 |
234 | // Number of the points in the cloud.
235 | unsigned int m_point_count;
236 |
237 | // Index of the current point.
238 | size_t m_current_index;
239 |
240 | // PointCloud2 iterators.
241 | std::optional> m_iter_x;
242 | std::optional> m_iter_y;
243 | std::optional> m_iter_z;
244 | std::optional> m_iter_intensity;
245 | std::optional> m_iter_ring;
246 | };
247 |
248 | } // namespace pc2_combiner
--------------------------------------------------------------------------------
/include/color_point_cloud/utils/TransformeProvider.hpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by bzeren on 05.10.2023.
3 | //
4 |
5 | #pragma once
6 |
7 | #include
8 | #include
9 | #include
10 |
11 | #include
12 |
13 | namespace color_point_cloud {
14 | class TransformProvider {
15 | public:
16 | explicit TransformProvider(rclcpp::Clock::SharedPtr clock) {
17 | std::cout << "TransformProvider constructor" << std::endl;
18 |
19 | tf_buffer_ = std::make_unique(clock);
20 | tf_listener_ = std::make_unique(*tf_buffer_);
21 | }
22 |
23 | std::optional operator()(const std::string &target_frame,
24 | const std::string &source_frame) const {
25 | std::optional transform_stamped;
26 | try {
27 | transform_stamped = tf_buffer_->lookupTransform(target_frame, source_frame, tf2::TimePointZero);
28 | } catch (tf2::TransformException &ex) {
29 | // RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "%s", ex.what());
30 | return std::nullopt;
31 | }
32 | return transform_stamped;
33 | }
34 |
35 | private:
36 | std::unique_ptr tf_listener_;
37 | std::unique_ptr tf_buffer_;
38 | };
39 |
40 | typedef std::shared_ptr TransformProviderPtr;
41 | typedef std::shared_ptr TransformProviderConstPtr;
42 |
43 | } // namespace color_point_cloud
--------------------------------------------------------------------------------
/launch/color_point_cloud.launch.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | color_point_cloud
5 | 0.0.0
6 | TODO: Package description
7 | bzeren
8 | TODO: License declaration
9 |
10 | ament_cmake
11 |
12 | ament_lint_auto
13 | ament_lint_common
14 |
15 |
16 | ament_cmake
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/ColorPointCloud.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by bzeren on 05.10.2023.
3 | //
4 |
5 | #include "color_point_cloud/ColorPointCloud.hpp"
6 |
7 | namespace color_point_cloud {
8 | ColorPointCloud::ColorPointCloud(const rclcpp::NodeOptions &options) : Node("color_point_cloud_node", options),
9 | transform_provider_ptr_(
10 | std::make_shared(
11 | this->get_clock())) {
12 | RCLCPP_INFO(this->get_logger(), "ColorPointCloud node started");
13 |
14 | // Declare and get parameters
15 | {
16 | // timeout_sec
17 | this->declare_parameter("timeout_sec", 0.1);
18 | timeout_sec_ = this->get_parameter("timeout_sec").as_double();
19 |
20 | this->declare_parameter("point_cloud_topic", "/points_raw");
21 | point_cloud_topic_ = this->get_parameter("point_cloud_topic").as_string();
22 |
23 | this->declare_parameter("point_cloud_frame_id", "lidar");
24 | point_cloud_frame_id_ = this->get_parameter("point_cloud_frame_id").as_string();
25 |
26 | this->declare_parameter("image_type", 0);
27 | image_type_ = static_cast(this->get_parameter("image_type").as_int());
28 |
29 | this->declare_parameter("image_topic_last_name", "/image_raw");
30 | image_topic_last_name_ = this->get_parameter("image_topic_last_name").as_string();
31 |
32 | this->declare_parameter("camera_info_topic_last_name", "/camera_info");
33 | camera_info_topic_last_name_ = this->get_parameter("camera_info_topic_last_name").as_string();
34 |
35 | // camera_topics
36 | this->declare_parameter>("camera_topics", std::vector());
37 | camera_topics_ = this->get_parameter("camera_topics").as_string_array();
38 |
39 | for (auto &camera_topic: camera_topics_) {
40 | RCLCPP_INFO(this->get_logger(), "camera_topic: %s", camera_topic.c_str());
41 | }
42 | }
43 |
44 | // Create camera object, create subscriber to image and camera_info
45 | {
46 | for (const auto &camera_topic: camera_topics_) {
47 | std::string image_topic = camera_topic + image_topic_last_name_;
48 | std::string camera_info_topic = camera_topic + camera_info_topic_last_name_;
49 |
50 | CameraTypePtr camera_type_ptr = std::make_shared(image_topic, camera_info_topic);
51 | camera_type_stdmap_[camera_topic] = camera_type_ptr;
52 |
53 | this->image_subscribers_.push_back(this->create_subscription(
54 | image_topic, rclcpp::SensorDataQoS(),
55 | [this, image_topic, camera_topic](const sensor_msgs::msg::Image::ConstSharedPtr &msg) {
56 | // RCLCPP_INFO(this->get_logger(), "Received image on topic %s", camera_topic.c_str());
57 | camera_type_stdmap_[camera_topic]->set_image_msg(msg);
58 | }));
59 |
60 | this->camera_info_subscribers_.push_back(this->create_subscription(
61 | camera_info_topic, rclcpp::SensorDataQoS(), [this, camera_info_topic, camera_topic](
62 | const sensor_msgs::msg::CameraInfo::ConstSharedPtr &msg) {
63 | // RCLCPP_INFO(this->get_logger(), "Received camera_info on topic %s", camera_topic.c_str());
64 | camera_type_stdmap_[camera_topic]->set_camera_info(msg);
65 | }));
66 | }
67 | }
68 |
69 | {
70 | point_cloud_subscriber_ = this->create_subscription(
71 | point_cloud_topic_, rclcpp::SensorDataQoS(),
72 | std::bind(&ColorPointCloud::point_cloud_callback, this, std::placeholders::_1));
73 |
74 | point_cloud_publisher_ = this->create_publisher(
75 | "/points_color", rclcpp::SensorDataQoS());
76 | }
77 |
78 | // Set timer
79 | {
80 | const auto period_ns = std::chrono::duration_cast(
81 | std::chrono::duration(timeout_sec_));
82 | timer_ = rclcpp::create_timer(this, this->get_clock(), period_ns,
83 | std::bind(&ColorPointCloud::timer_callback, this));
84 | }
85 |
86 | }
87 |
88 | void ColorPointCloud::timer_callback() {
89 | for (const auto &pair: camera_type_stdmap_) {
90 | if (pair.second->get_image_msg() == nullptr || pair.second->get_camera_info() == nullptr) {
91 | // RCLCPP_INFO(this->get_logger(), "Image or camera info is null for topic: %s", pair.first.c_str());
92 | continue;
93 | }
94 |
95 | if (!pair.second->is_info_initialized()) {
96 | RCLCPP_INFO(this->get_logger(), "Camera info is setting: %s", pair.first.c_str());
97 | pair.second->set_camera_utils(pair.second->get_camera_info());
98 | }
99 |
100 | if (!pair.second->is_transform_initialized() && pair.second->is_info_initialized()) {
101 | std::optional transform = (*transform_provider_ptr_)(
102 | pair.second->get_camera_frame_id(), point_cloud_frame_id_);
103 | if (!transform.has_value()) {
104 | continue;
105 | }
106 | pair.second->set_lidar_to_camera_matrix(transform.value());
107 | pair.second->set_lidar_to_camera_projection_matrix();
108 | }
109 | }
110 | }
111 |
112 | void ColorPointCloud::point_cloud_callback(const sensor_msgs::msg::PointCloud2::ConstSharedPtr &msg) {
113 |
114 | sensor_msgs::msg::PointCloud2 cloud_color_msg;
115 | std::for_each(camera_type_stdmap_.begin(), camera_type_stdmap_.end(),
116 | [this, &cloud_color_msg, msg](std::pair pair) {
117 | if (pair.second->get_image_msg() == nullptr || pair.second->get_camera_info() == nullptr ||
118 | !pair.second->is_info_initialized() || !pair.second->is_transform_initialized()) {
119 | RCLCPP_INFO(this->get_logger(),
120 | "Cant project camera: %s \n\n Expected reasons: \n don't receive image \n don't receive \n can't get transforme \n can't init camera utils",
121 | pair.first.c_str());
122 | return;
123 | }
124 | pair.second->set_cv_image(pair.second->get_image_msg(), image_type_);
125 |
126 | sensor_msgs::PointCloud2Modifier modifier(cloud_color_msg);
127 | modifier.setPointCloud2Fields(6, "x", 1, sensor_msgs::msg::PointField::FLOAT32, "y", 1,
128 | sensor_msgs::msg::PointField::FLOAT32, "z", 1,
129 | sensor_msgs::msg::PointField::FLOAT32, "rgb", 1,
130 | sensor_msgs::msg::PointField::FLOAT32, "ring", 1,
131 | sensor_msgs::msg::PointField::UINT16, "intensity", 1,
132 | sensor_msgs::msg::PointField::FLOAT32);
133 | modifier.resize(msg->width * msg->height);
134 |
135 | sensor_msgs::PointCloud2Iterator iter_x(cloud_color_msg, "x");
136 | sensor_msgs::PointCloud2Iterator iter_y(cloud_color_msg, "y");
137 | sensor_msgs::PointCloud2Iterator iter_z(cloud_color_msg, "z");
138 | sensor_msgs::PointCloud2Iterator iter_r(cloud_color_msg, "r");
139 | sensor_msgs::PointCloud2Iterator iter_g(cloud_color_msg, "g");
140 | sensor_msgs::PointCloud2Iterator iter_b(cloud_color_msg, "b");
141 | sensor_msgs::PointCloud2Iterator iter_ring(cloud_color_msg, "ring");
142 | sensor_msgs::PointCloud2Iterator iter_intensity(cloud_color_msg, "intensity");
143 |
144 | pc2_combiner::PointCloudConst cloud{*msg};
145 | for (size_t i = 0; i < cloud.getPointCount(); ++i) {
146 | pc2_combiner::Point point{cloud.getCurrentPoint()};
147 |
148 | Eigen::Vector4d point4d(point.x, point.y, point.z, 1.0);
149 |
150 | Eigen::Vector3d point3d_transformed_camera =
151 | pair.second->get_lidar_to_camera_projection_matrix() * point4d;
152 |
153 | Eigen::Vector2d point2d_transformed_camera = Eigen::Vector2d(
154 | point3d_transformed_camera[0] / point3d_transformed_camera[2],
155 | point3d_transformed_camera[1] / point3d_transformed_camera[2]);
156 |
157 | double x = point2d_transformed_camera[0];
158 | double y = point2d_transformed_camera[1];
159 |
160 | if (x < 0 || x > pair.second->get_image_width() || y < 0 ||
161 | y > pair.second->get_image_height() ||
162 | point3d_transformed_camera[2] < 0) {
163 |
164 | iter_x[0] = point.x;
165 | iter_y[0] = point.y;
166 | iter_z[0] = point.z;
167 | iter_ring[0] = point.ring;
168 | iter_intensity[0] = point.intensity;
169 |
170 | } else {
171 | cv::Vec3d color = pair.second->get_cv_image().at(cv::Point(x, y));
172 | cv::Scalar color_scalar(color[0], color[1], color[2]);
173 |
174 | iter_x[0] = point.x;
175 | iter_y[0] = point.y;
176 | iter_z[0] = point.z;
177 | iter_ring[0] = point.ring;
178 | iter_intensity[0] = point.intensity;
179 |
180 | if (pair.second->get_image_msg()->encoding == "rgb8") {
181 | iter_r[0] = color[0];
182 | iter_g[0] = color[1];
183 | iter_b[0] = color[2];
184 | } else if (pair.second->get_image_msg()->encoding == "bgr8") {
185 | iter_r[0] = color[2];
186 | iter_g[0] = color[1];
187 | iter_b[0] = color[0];
188 | } else {
189 | iter_r[0] = color[2];
190 | iter_g[0] = color[1];
191 | iter_b[0] = color[0];
192 | }
193 | }
194 |
195 | ++iter_x;
196 | ++iter_y;
197 | ++iter_z;
198 | ++iter_r;
199 | ++iter_g;
200 | ++iter_b;
201 | ++iter_ring;
202 | ++iter_intensity;
203 |
204 | cloud.nextPoint();
205 | }
206 | // cv::imshow(pair.first, pair.second->get_cv_image());
207 | // cv::waitKey(1);
208 | });
209 |
210 | cloud_color_msg.header = msg->header;
211 | cloud_color_msg.height = 1;
212 | cloud_color_msg.width = msg->width * msg->height;
213 | point_cloud_publisher_->publish(cloud_color_msg);
214 | }
215 |
216 |
217 | } // namespace color_point_cloud
--------------------------------------------------------------------------------
/src/color_point_cloud_node.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by bzeren on 05.10.2023.
3 | //
4 |
5 | #include "color_point_cloud/ColorPointCloud.hpp"
6 |
7 | int main(int argc, char **argv) {
8 | rclcpp::init(argc, argv);
9 | rclcpp::NodeOptions options;
10 | rclcpp::spin(std::make_shared(options));
11 | rclcpp::shutdown();
12 | return 0;
13 | }
--------------------------------------------------------------------------------