|
47 | 47 | #include <rclcpp/publisher.hpp> |
48 | 48 | #include <rclcpp/time.hpp> |
49 | 49 | #include <rclcpp_components/register_node_macro.hpp> |
| 50 | +#include <regex> |
50 | 51 | #include <sensor_msgs/image_encodings.hpp> |
51 | 52 | #include <sensor_msgs/msg/camera_info.hpp> |
52 | 53 | #include <sensor_msgs/msg/compressed_image.hpp> |
@@ -150,6 +151,34 @@ get_role(const std::string &role) |
150 | 151 | } |
151 | 152 | } |
152 | 153 |
|
| 154 | +libcamera::Size |
| 155 | +get_sensor_format(const std::string &format_str) |
| 156 | +{ |
| 157 | + if (format_str.empty()) { |
| 158 | + return {}; |
| 159 | + } |
| 160 | + |
| 161 | + const std::regex pattern(R"((\d+):(\d+))"); |
| 162 | + std::smatch match; |
| 163 | + |
| 164 | + if (std::regex_match(format_str, match, pattern)) { |
| 165 | + try { |
| 166 | + const int width = std::stoi(match[1].str()); |
| 167 | + const int height = std::stoi(match[2].str()); |
| 168 | + |
| 169 | + return libcamera::Size {static_cast<unsigned int>(width), static_cast<unsigned int>(height)}; |
| 170 | + } |
| 171 | + catch (const std::out_of_range &) { |
| 172 | + throw std::runtime_error("Width or height exceeds maximum value of " + std::to_string(std::numeric_limits<int>::max())); |
| 173 | + } |
| 174 | + catch (const std::invalid_argument &) { |
| 175 | + // Unexpected - throw exception below |
| 176 | + } |
| 177 | + } |
| 178 | + |
| 179 | + // Throw exception as it was not possible to parse the format string |
| 180 | + throw std::runtime_error("Invalid size format! Expected [width]:[height] but got " + format_str); |
| 181 | +} |
153 | 182 |
|
154 | 183 | // The following function "compressImageMsg" is adapted from "CvImage::toCompressedImageMsg" |
155 | 184 | // (https://github.com/ros-perception/vision_opencv/blob/066793a23e5d06d76c78ca3d69824a501c3554fd/cv_bridge/src/cv_bridge.cpp#L512-L535) |
@@ -221,6 +250,13 @@ CameraNode::CameraNode(const rclcpp::NodeOptions &options) |
221 | 250 | const uint32_t h = declare_parameter<int64_t>("height", {}, param_descr_ro); |
222 | 251 | const libcamera::Size size {w, h}; |
223 | 252 |
|
| 253 | + // Raw format dimensions |
| 254 | + rcl_interfaces::msg::ParameterDescriptor param_descr_sensor_mode; |
| 255 | + param_descr_sensor_mode.description = "raw mode of the sensor"; |
| 256 | + param_descr_sensor_mode.additional_constraints = "string in format [width]:[height]"; |
| 257 | + param_descr_sensor_mode.read_only = true; |
| 258 | + const libcamera::Size sensor_size = get_sensor_format(declare_parameter<std::string>("sensor_mode", {}, param_descr_sensor_mode)); |
| 259 | + |
224 | 260 | // camera info file url |
225 | 261 | rcl_interfaces::msg::ParameterDescriptor param_descr_camera_info_url; |
226 | 262 | param_descr_camera_info_url.description = "camera calibration info file url"; |
@@ -299,14 +335,20 @@ CameraNode::CameraNode(const rclcpp::NodeOptions &options) |
299 | 335 | if (camera->acquire()) |
300 | 336 | throw std::runtime_error("failed to acquire camera"); |
301 | 337 |
|
| 338 | + std::vector<libcamera::StreamRole> roles {role}; |
| 339 | + |
| 340 | + // Add the RAW role if the sensor_size is defined |
| 341 | + if (!sensor_size.isNull() && role != libcamera::StreamRole::Raw) { |
| 342 | + roles.push_back(libcamera::StreamRole::Raw); |
| 343 | + } |
| 344 | + |
302 | 345 | // configure camera stream |
303 | 346 | std::unique_ptr<libcamera::CameraConfiguration> cfg = |
304 | | - camera->generateConfiguration({role}); |
| 347 | + camera->generateConfiguration(roles); |
305 | 348 |
|
306 | | - if (!cfg) |
307 | | - throw std::runtime_error("failed to generate configuration"); |
| 349 | + if (!cfg || cfg->size() != roles.size()) |
| 350 | + throw std::runtime_error("failed to generate configuration for all roles"); |
308 | 351 |
|
309 | | - assert(cfg->size() == 1); |
310 | 352 | libcamera::StreamConfiguration &scfg = cfg->at(0); |
311 | 353 |
|
312 | 354 | // list all camera formats, including those not supported by the ROS message |
@@ -357,6 +399,12 @@ CameraNode::CameraNode(const rclcpp::NodeOptions &options) |
357 | 399 | scfg.size = size; |
358 | 400 | } |
359 | 401 |
|
| 402 | + if (!sensor_size.isNull() && role != libcamera::StreamRole::Raw) { |
| 403 | + libcamera::StreamConfiguration &modecfg = cfg->at(1); |
| 404 | + modecfg.size = sensor_size; |
| 405 | + RCLCPP_INFO_STREAM(get_logger(), "Sensor mode configuration: " << modecfg.toString()); |
| 406 | + } |
| 407 | + |
360 | 408 | // store selected stream configuration |
361 | 409 | const libcamera::StreamConfiguration selected_scfg = scfg; |
362 | 410 |
|
@@ -392,6 +440,8 @@ CameraNode::CameraNode(const rclcpp::NodeOptions &options) |
392 | 440 | const std::optional<std::string> model = props.get(libcamera::properties::Model); |
393 | 441 | if (model) |
394 | 442 | cname = model.value() + '_' + cname; |
| 443 | + if (!sensor_size.isNull() && role != libcamera::StreamRole::Raw) |
| 444 | + cname = cname + '_' + cfg->at(1).toString(); |
395 | 445 |
|
396 | 446 | // clean camera name of non-alphanumeric characters |
397 | 447 | cname.erase( |
|
0 commit comments