Skip to content

Commit 9384f53

Browse files
Woojin-Crivelarsll
authored andcommitted
Enable selection of sensor resolution
1 parent d54147c commit 9384f53

File tree

2 files changed

+65
-4
lines changed

2 files changed

+65
-4
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,20 +123,30 @@ The camera stream is configured once when the node starts via the following stat
123123
| `role` | `string` | configures the camera with a `StreamRole` (possible choices: `raw`, `still`, `video`, `viewfinder`) [default: `viewfinder`] |
124124
| `format` | `string` | a `PixelFormat` that is supported by the camera [default: auto] |
125125
| `width`, `height` | `integer` | desired image resolution [default: auto] |
126+
| `sensor_mode` | `string` | desired raw sensor format resolution (format: `width:height`) [default: auto] |
126127

127128

128129
The configuration is done in the following order:
129130
1. select camera via `camera`
130131
2. configure camera stream via `role`
131132
3. set the pixel format for the stream via `format`
132133
4. set the image resolution for the stream via `width` and `height`
134+
5. set the sensor mode resolution for the raw feed from camera to GPU
133135

134136
Each stream role only supports a discrete set of data stream configurations as a combination of the image resolution and pixel format. The selected stream configuration is validated at the end of this sequence and adjusted to the closest valid stream configuration.
135137

136138
By default, the node will select the first available camera and configures it with the default pixel format and image resolution. If a parameter has not been set, the node will print the available options and inform the user about the default choice.
137139

138140
The node avoids memory copies of the image data by directly mapping from a camera pixel format to a ROS image format, with the exception of converting between "raw" and "compressed" image formats when requested by the user. As an effect, not all pixel formats that are supported by the camera may be supported by the ROS image message. Hence, the options for `format` are limited to pixel formats that are supported by the camera and the raw ROS image message.
139141

142+
### Sensor Modes and Cropping
143+
144+
Most camera modules provide different sensor modes, some of which provide a smaller resolution version of the full picture, whereas others have a limited field-of-view, e.g. by allowing for the selection of a 3:2 image from a 16:9 sensor, a more native 'digital zoom' effect by cropping the picture etc. The benefit of this is to perform compute intensive tasks at the source (the sensor), rather than downstream in the GPU or in application code. Unless we specify the sensor mode we want, libcamera will automatically select one. Unfortunately, it is not guaranteed that the selected mode will be a full sensor mode, leading to a potential cropping of the picture.
145+
146+
Example: When configuring a 640x480 output stream on a Raspberry Pi Camera Module v2, libcamera will automatically choose a heavily cropped 640x480 sensor mode, leading to a digital zoom of ~2.5x.
147+
148+
See the [PiCamera2 Documentation, section 4.2.2.3](https://datasheets.raspberrypi.com/camera/picamera2-manual.pdf) for a detailed explanation. The [PiCamera Documentation](https://picamera.readthedocs.io/en/release-1.13/fov.html#sensor-modes) has a good visualization of the issue. Read the documentation of your camera to see what modes are available, and which (if any) raw formats are cropped.
149+
140150
### Dynamic Frame Configuration (controls)
141151

142152
The dynamic parameters are created at runtime by inspecting the [controls](https://libcamera.org/api-html/namespacelibcamera_1_1controls.html) that are exposed by the camera. The set of exposed controls is camera-dependent. The ROS parameters are directly formatted from the exposed controls, hence the parameter names match the control names.

src/CameraNode.cpp

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
#include <rclcpp/publisher.hpp>
4848
#include <rclcpp/time.hpp>
4949
#include <rclcpp_components/register_node_macro.hpp>
50+
#include <regex>
5051
#include <sensor_msgs/image_encodings.hpp>
5152
#include <sensor_msgs/msg/camera_info.hpp>
5253
#include <sensor_msgs/msg/compressed_image.hpp>
@@ -150,6 +151,35 @@ get_role(const std::string &role)
150151
}
151152
}
152153

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("Invalid sensor_mode. Width or height exceeds maximum value of " +
173+
std::to_string(std::numeric_limits<int>::max()));
174+
}
175+
catch (const std::invalid_argument &) {
176+
// Unexpected - throw exception below
177+
}
178+
}
179+
180+
// Throw exception as it was not possible to parse the format string
181+
throw std::runtime_error("Invalid sensor_mode. Expected [width]:[height] but got " + format_str);
182+
}
153183

154184
// The following function "compressImageMsg" is adapted from "CvImage::toCompressedImageMsg"
155185
// (https://github.com/ros-perception/vision_opencv/blob/066793a23e5d06d76c78ca3d69824a501c3554fd/cv_bridge/src/cv_bridge.cpp#L512-L535)
@@ -221,6 +251,13 @@ CameraNode::CameraNode(const rclcpp::NodeOptions &options)
221251
const uint32_t h = declare_parameter<int64_t>("height", {}, param_descr_ro);
222252
const libcamera::Size size {w, h};
223253

254+
// Raw format dimensions
255+
rcl_interfaces::msg::ParameterDescriptor param_descr_sensor_mode;
256+
param_descr_sensor_mode.description = "raw mode of the sensor";
257+
param_descr_sensor_mode.additional_constraints = "string in format [width]:[height]";
258+
param_descr_sensor_mode.read_only = true;
259+
const libcamera::Size sensor_size = get_sensor_format(declare_parameter<std::string>("sensor_mode", {}, param_descr_sensor_mode));
260+
224261
// camera info file url
225262
rcl_interfaces::msg::ParameterDescriptor param_descr_camera_info_url;
226263
param_descr_camera_info_url.description = "camera calibration info file url";
@@ -299,14 +336,20 @@ CameraNode::CameraNode(const rclcpp::NodeOptions &options)
299336
if (camera->acquire())
300337
throw std::runtime_error("failed to acquire camera");
301338

339+
std::vector<libcamera::StreamRole> roles {role};
340+
341+
// Add the RAW role if the sensor_size is defined
342+
if (!sensor_size.isNull() && role != libcamera::StreamRole::Raw) {
343+
roles.push_back(libcamera::StreamRole::Raw);
344+
}
345+
302346
// configure camera stream
303347
std::unique_ptr<libcamera::CameraConfiguration> cfg =
304-
camera->generateConfiguration({role});
348+
camera->generateConfiguration(roles);
305349

306-
if (!cfg)
307-
throw std::runtime_error("failed to generate configuration");
350+
if (!cfg || cfg->size() != roles.size())
351+
throw std::runtime_error("failed to generate configuration for all roles");
308352

309-
assert(cfg->size() == 1);
310353
libcamera::StreamConfiguration &scfg = cfg->at(0);
311354

312355
// list all camera formats, including those not supported by the ROS message
@@ -357,6 +400,12 @@ CameraNode::CameraNode(const rclcpp::NodeOptions &options)
357400
scfg.size = size;
358401
}
359402

403+
if (!sensor_size.isNull() && role != libcamera::StreamRole::Raw) {
404+
libcamera::StreamConfiguration &modecfg = cfg->at(1);
405+
modecfg.size = sensor_size;
406+
RCLCPP_INFO_STREAM(get_logger(), "Sensor mode configuration: " << modecfg.toString());
407+
}
408+
360409
// store selected stream configuration
361410
const libcamera::StreamConfiguration selected_scfg = scfg;
362411

@@ -392,6 +441,8 @@ CameraNode::CameraNode(const rclcpp::NodeOptions &options)
392441
const std::optional<std::string> model = props.get(libcamera::properties::Model);
393442
if (model)
394443
cname = model.value() + '_' + cname;
444+
if (!sensor_size.isNull() && role != libcamera::StreamRole::Raw)
445+
cname = cname + '_' + cfg->at(1).toString();
395446

396447
// clean camera name of non-alphanumeric characters
397448
cname.erase(

0 commit comments

Comments
 (0)