Skip to content

Commit 0d68b4d

Browse files
add color image to backprojection (#24)
* add color image to backprojection * force input image encoding to match assumed ordering * add new field to signal out-of-view colors
1 parent c112464 commit 0d68b4d

File tree

4 files changed

+79
-34
lines changed

4 files changed

+79
-34
lines changed

semantic_inference_ros/include/semantic_inference_ros/pointcloud_projection.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ struct ProjectionConfig {
2929
std::set<uint32_t> allowed_labels;
3030
//! Input label remapping for input pointcloud
3131
std::unordered_map<uint32_t, uint32_t> input_remapping;
32+
//! Alpha value for out-of-view point colors
33+
uint16_t out_of_view_alpha = 0;
3234

3335
bool isOverride(uint32_t label) const { return override_labels.count(label); }
3436
bool isAllowed(uint32_t label) const { return allowed_labels.count(label); }
@@ -43,6 +45,7 @@ bool projectSemanticImage(const ProjectionConfig& config,
4345
const sensor_msgs::msg::PointCloud2& cloud,
4446
const Eigen::Isometry3f& image_T_cloud,
4547
sensor_msgs::msg::PointCloud2& output,
48+
const cv::Mat& color_image = cv::Mat(),
4649
const ImageRecolor* recolor = nullptr);
4750

4851
} // namespace semantic_inference

semantic_inference_ros/src/backprojection_nodelet.cpp

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
#include <message_filters/synchronizer.h>
88
#include <tf2_ros/transform_listener.h>
99

10-
#include <cstdint>
1110
#include <string>
1211

1312
#include <cv_bridge/cv_bridge.hpp>
@@ -28,7 +27,7 @@ using OptPose = std::optional<Eigen::Isometry3f>;
2827

2928
struct BackprojectionNode : public rclcpp::Node {
3029
public:
31-
using SyncPolicy = ApproximateTime<Image, CameraInfo, PointCloud2>;
30+
using SyncPolicy = ApproximateTime<Image, Image, CameraInfo, PointCloud2>;
3231
using Sync = message_filters::Synchronizer<SyncPolicy>;
3332

3433
struct Config {
@@ -48,11 +47,13 @@ struct BackprojectionNode : public rclcpp::Node {
4847
const std::string& child_link,
4948
const rclcpp::Time& stamp);
5049

51-
void callback(const Image::ConstSharedPtr& image_msg,
50+
void callback(const Image::ConstSharedPtr& label_msg,
51+
const Image::ConstSharedPtr& color_msg,
5252
const CameraInfo::ConstSharedPtr& info_msg,
5353
const PointCloud2::ConstSharedPtr& cloud_msg);
5454

55-
ianvs::ImageSubscription image_sub_;
55+
ianvs::ImageSubscription label_sub_;
56+
ianvs::ImageSubscription color_sub_;
5657
message_filters::Subscriber<CameraInfo> info_sub_;
5758
message_filters::Subscriber<PointCloud2> cloud_sub_;
5859
std::unique_ptr<Sync> sync_;
@@ -80,7 +81,8 @@ void declare_config(BackprojectionNode::Config& config) {
8081
BackprojectionNode::BackprojectionNode(const rclcpp::NodeOptions& options)
8182
: Node("backprojection_node", options),
8283
config(config::fromCLI<Config>(options.arguments())),
83-
image_sub_(*this),
84+
label_sub_(*this),
85+
color_sub_(*this),
8486
tf_buffer_(get_clock()),
8587
tf_listener_(tf_buffer_) {
8688
using namespace std::placeholders;
@@ -99,14 +101,16 @@ BackprojectionNode::BackprojectionNode(const rclcpp::NodeOptions& options)
99101

100102
pub_ = create_publisher<PointCloud2>("labeled_cloud", config.output_queue_size);
101103

104+
// these default to the same namespaces as the input to the inference node
102105
const rclcpp::QoS qos(config.input_queue_size);
103-
// these are designed to default to the same namespaces as the input to the inference
104-
// node
105-
image_sub_.subscribe("semantic/image_raw", config.input_queue_size);
106+
label_sub_.subscribe("semantic/image_raw", config.input_queue_size);
107+
color_sub_.subscribe("color/image_raw", config.input_queue_size);
106108
info_sub_.subscribe(this, "color/camera_info", qos.get_rmw_qos_profile());
107109
cloud_sub_.subscribe(this, "cloud", qos.get_rmw_qos_profile());
108-
sync_ = std::make_unique<Sync>(SyncPolicy(10), image_sub_, info_sub_, cloud_sub_);
109-
sync_->registerCallback(std::bind(&BackprojectionNode::callback, this, _1, _2, _3));
110+
sync_ = std::make_unique<Sync>(
111+
SyncPolicy(10), label_sub_, color_sub_, info_sub_, cloud_sub_);
112+
sync_->registerCallback(
113+
std::bind(&BackprojectionNode::callback, this, _1, _2, _3, _4));
110114
}
111115

112116
OptPose BackprojectionNode::getTransform(const std::string& parent_link,
@@ -124,23 +128,32 @@ OptPose BackprojectionNode::getTransform(const std::string& parent_link,
124128
return tf_double.cast<float>();
125129
}
126130

127-
void BackprojectionNode::callback(const Image::ConstSharedPtr& image_msg,
131+
void BackprojectionNode::callback(const Image::ConstSharedPtr& label_msg,
132+
const Image::ConstSharedPtr& color_msg,
128133
const CameraInfo::ConstSharedPtr& info_msg,
129134
const PointCloud2::ConstSharedPtr& cloud_msg) {
130135
// Find transform from cloud to image frame
131136
const rclcpp::Time stamp(cloud_msg->header.stamp);
132137
const auto image_T_cloud = getTransform(
133-
!config.camera_frame.empty() ? config.camera_frame : image_msg->header.frame_id,
138+
!config.camera_frame.empty() ? config.camera_frame : info_msg->header.frame_id,
134139
!config.lidar_frame.empty() ? config.lidar_frame : cloud_msg->header.frame_id,
135140
stamp);
136141
if (!image_T_cloud) {
137142
return;
138143
}
139144

140145
// Convert image
141-
cv_bridge::CvImageConstPtr img_ptr;
146+
cv_bridge::CvImageConstPtr label_ptr;
142147
try {
143-
img_ptr = cv_bridge::toCvShare(image_msg);
148+
label_ptr = cv_bridge::toCvShare(label_msg);
149+
} catch (const cv_bridge::Exception& e) {
150+
SLOG(ERROR) << "cv_bridge exception: " << e.what();
151+
return;
152+
}
153+
154+
cv_bridge::CvImageConstPtr color_ptr;
155+
try {
156+
color_ptr = cv_bridge::toCvShare(color_msg, "bgr8");
144157
} catch (const cv_bridge::Exception& e) {
145158
SLOG(ERROR) << "cv_bridge exception: " << e.what();
146159
return;
@@ -149,10 +162,11 @@ void BackprojectionNode::callback(const Image::ConstSharedPtr& image_msg,
149162
auto output = std::make_unique<PointCloud2>();
150163
const auto valid = projectSemanticImage(config.projection,
151164
*info_msg,
152-
img_ptr->image,
165+
label_ptr->image,
153166
*cloud_msg,
154167
image_T_cloud.value(),
155168
*output,
169+
color_ptr->image,
156170
recolor_.get());
157171
if (!valid) {
158172
return;
@@ -161,7 +175,7 @@ void BackprojectionNode::callback(const Image::ConstSharedPtr& image_msg,
161175
output->header = cloud_msg->header;
162176
output->header.frame_id = config.projection.use_lidar_frame
163177
? cloud_msg->header.frame_id
164-
: image_msg->header.frame_id;
178+
: info_msg->header.frame_id;
165179
pub_->publish(std::move(output));
166180
}
167181

semantic_inference_ros/src/pointcloud_projection.cpp

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "semantic_inference_ros/pointcloud_projection.h"
22

33
#include <config_utilities/config.h>
4+
#include <semantic_inference/logging.h>
45

56
#include <optional>
67

@@ -9,10 +10,10 @@
910
#include <rclcpp/node.hpp>
1011
#include <sensor_msgs/point_cloud2_iterator.hpp>
1112

12-
#include "semantic_inference_ros/ros_log_sink.h"
13-
1413
namespace semantic_inference {
1514

15+
using sensor_msgs::PointCloud2ConstIterator;
16+
using sensor_msgs::PointCloud2Iterator;
1617
using sensor_msgs::msg::CameraInfo;
1718
using sensor_msgs::msg::Image;
1819
using sensor_msgs::msg::PointCloud2;
@@ -49,9 +50,9 @@ struct OutputPosIter {
4950
private:
5051
// techinically we could probably just have one iterator, but (small) chance that
5152
// someone sends a non-contiguous pointcloud
52-
sensor_msgs::PointCloud2Iterator<float> x_iter_;
53-
sensor_msgs::PointCloud2Iterator<float> y_iter_;
54-
sensor_msgs::PointCloud2Iterator<float> z_iter_;
53+
PointCloud2Iterator<float> x_iter_;
54+
PointCloud2Iterator<float> y_iter_;
55+
PointCloud2Iterator<float> z_iter_;
5556
};
5657

5758
struct InputPosIter {
@@ -71,9 +72,9 @@ struct InputPosIter {
7172
private:
7273
// techinically we could probably just have one iterator, but (small) chance that
7374
// someone sends a non-contiguous pointcloud
74-
sensor_msgs::PointCloud2ConstIterator<float> x_iter_;
75-
sensor_msgs::PointCloud2ConstIterator<float> y_iter_;
76-
sensor_msgs::PointCloud2ConstIterator<float> z_iter_;
75+
PointCloud2ConstIterator<float> x_iter_;
76+
PointCloud2ConstIterator<float> y_iter_;
77+
PointCloud2ConstIterator<float> z_iter_;
7778
};
7879

7980
struct InputLabelIterBase {
@@ -93,7 +94,7 @@ struct InputLabelIter : InputLabelIterBase {
9394
uint32_t get() const override { return *iter_; }
9495

9596
private:
96-
sensor_msgs::PointCloud2ConstIterator<T> iter_;
97+
PointCloud2ConstIterator<T> iter_;
9798
};
9899

99100
std::unique_ptr<InputLabelIterBase> iterFromFields(const PointCloud2& cloud,
@@ -222,8 +223,8 @@ struct LabelImageAdapter {
222223
void recolorCloud(PointCloud2& output,
223224
const ImageRecolor& recolor,
224225
uint32_t unknown_label) {
225-
auto labels = sensor_msgs::PointCloud2ConstIterator<uint32_t>(output, "label");
226-
auto colors = sensor_msgs::PointCloud2Iterator<uint8_t>(output, "rgba");
226+
auto labels = PointCloud2ConstIterator<uint32_t>(output, "label");
227+
auto colors = PointCloud2Iterator<uint8_t>(output, "rgba");
227228
while (labels != labels.end()) {
228229
const auto unknown = static_cast<uint32_t>(*labels) == unknown_label;
229230
const auto& color = unknown ? recolor.default_color : recolor.getColor(*labels);
@@ -259,6 +260,9 @@ void declare_config(ProjectionConfig& config) {
259260
field(config.override_labels, "override_labels");
260261
field(config.allowed_labels, "allowed_labels");
261262
field(config.input_remapping, "input_remapping");
263+
field(config.out_of_view_alpha, "out_of_view_alpha");
264+
265+
checkInRange<uint16_t>(config.out_of_view_alpha, 0, 255, "out_of_view_alpha");
262266
}
263267

264268
std::optional<uint32_t> ProjectionConfig::remapInput(
@@ -274,22 +278,27 @@ std::optional<uint32_t> ProjectionConfig::remapInput(
274278

275279
bool projectSemanticImage(const ProjectionConfig& config,
276280
const CameraInfo& intrinsics,
277-
const cv::Mat& image,
281+
const cv::Mat& labels,
278282
const PointCloud2& cloud,
279283
const Eigen::Isometry3f& image_T_cloud,
280284
PointCloud2& output,
285+
const cv::Mat& color_image,
281286
const ImageRecolor* recolor) {
282287
image_geometry::PinholeCameraModel model;
283288
model.fromCameraInfo(intrinsics);
284289

285290
auto pos_in_iter = InputPosIter(cloud);
286-
const LabelImageAdapter img_wrapper(image);
291+
const LabelImageAdapter img_wrapper(labels);
287292
// iterator over label field in input pointcloud if it exists
288293
InputLabelAdapter label_in_iter(cloud, config.input_label_fieldname);
289294

290-
initOutput(cloud, output, recolor != nullptr);
295+
initOutput(cloud, output, recolor != nullptr || !color_image.empty());
291296
auto pos_out_iter = OutputPosIter(output);
292-
auto label_out_iter = sensor_msgs::PointCloud2Iterator<uint32_t>(output, "label");
297+
auto label_out_iter = PointCloud2Iterator<uint32_t>(output, "label");
298+
std::unique_ptr<PointCloud2Iterator<uint8_t>> color_out_iter;
299+
if (!color_image.empty()) {
300+
color_out_iter = std::make_unique<PointCloud2Iterator<uint8_t>>(output, "rgba");
301+
}
293302

294303
while (pos_in_iter) {
295304
const Eigen::Vector3f p_cloud = *pos_in_iter;
@@ -304,13 +313,28 @@ bool projectSemanticImage(const ProjectionConfig& config,
304313
v = std::round(pixel.y);
305314
}
306315

307-
const auto in_view = u >= 0 && u < image.cols && v >= 0 && v < image.rows;
316+
const auto in_view = u >= 0 && u < labels.cols && v >= 0 && v < labels.rows;
308317
const uint32_t label_in =
309318
config.remapInput(*label_in_iter).value_or(config.unknown_label);
310319
if (in_view) {
311320
*label_out_iter = config.isOverride(label_in) ? label_in : img_wrapper(v, u);
321+
if (color_out_iter) {
322+
auto& curr_color_out = *color_out_iter;
323+
const auto& pixel = color_image.at<cv::Vec3b>(v, u);
324+
curr_color_out[0] = pixel[0];
325+
curr_color_out[1] = pixel[1];
326+
curr_color_out[2] = pixel[2];
327+
curr_color_out[3] = 255u;
328+
}
312329
} else {
313330
*label_out_iter = config.isAllowed(label_in) ? label_in : config.unknown_label;
331+
if (color_out_iter) {
332+
auto& curr_color_out = *color_out_iter;
333+
curr_color_out[0] = 0;
334+
curr_color_out[1] = 0;
335+
curr_color_out[2] = 0;
336+
curr_color_out[3] = config.out_of_view_alpha;
337+
}
314338
}
315339

316340
if (!in_view && config.discard_out_of_view) {
@@ -323,9 +347,12 @@ bool projectSemanticImage(const ProjectionConfig& config,
323347
++pos_out_iter;
324348
++label_in_iter;
325349
++label_out_iter;
350+
if (color_out_iter) {
351+
++(*color_out_iter);
352+
}
326353
}
327354

328-
if (recolor) {
355+
if (recolor && color_image.empty()) {
329356
recolorCloud(output, *recolor, config.unknown_label);
330357
}
331358

semantic_inference_ros/test/test_pointcloud_projection.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ TEST(PointcloudProjection, ColorCorrect) {
135135
config.unknown_label = 5;
136136

137137
PointCloud2 labeled;
138-
projectSemanticImage(config, info, img, cloud, image_T_cloud, labeled, &recolor);
138+
projectSemanticImage(
139+
config, info, img, cloud, image_T_cloud, labeled, cv::Mat(), &recolor);
139140

140141
pcl::PointCloud<pcl::PointXYZRGBL> expected;
141142
expected.push_back(makePoint(1.0, 2.0, 1.0, 0, 255, 0, 2));

0 commit comments

Comments
 (0)