Skip to content

Commit 12a1c94

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

File tree

2 files changed

+62
-2
lines changed

2 files changed

+62
-2
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: 52 additions & 2 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,33 @@ 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 uint32_t width = std::stoul(match[1].str());
167+
const uint32_t height = std::stoul(match[2].str());
168+
return libcamera::Size {width, height};
169+
}
170+
catch (const std::invalid_argument &) {
171+
throw std::runtime_error("Invalid size format! Expected [width]:[height] but got " + format_str);
172+
}
173+
catch (const std::out_of_range &) {
174+
throw std::runtime_error("Invalid size format! Expected [width]:[height] but got " + format_str);
175+
}
176+
}
177+
else {
178+
throw std::runtime_error("Invalid size format! Expected [width]:[height] but got " + format_str);
179+
}
180+
}
153181

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

252+
// Raw format dimensions
253+
rcl_interfaces::msg::ParameterDescriptor param_descr_sensor_mode;
254+
param_descr_sensor_mode.description = "raw mode of the sensor";
255+
param_descr_sensor_mode.additional_constraints = "string in format [width]:[height]";
256+
param_descr_sensor_mode.read_only = true;
257+
const libcamera::Size sensor_size = get_sensor_format(declare_parameter<std::string>("sensor_mode", {}, param_descr_sensor_mode));
258+
224259
// camera info file url
225260
rcl_interfaces::msg::ParameterDescriptor param_descr_camera_info_url;
226261
param_descr_camera_info_url.description = "camera calibration info file url";
@@ -299,14 +334,21 @@ CameraNode::CameraNode(const rclcpp::NodeOptions &options)
299334
if (camera->acquire())
300335
throw std::runtime_error("failed to acquire camera");
301336

337+
std::vector<libcamera::StreamRole> roles {role};
338+
339+
// Add the RAW role if the sensor_size is defined
340+
if (!sensor_size.isNull() && role != libcamera::StreamRole::Raw) {
341+
roles.push_back(libcamera::StreamRole::Raw);
342+
}
343+
302344
// configure camera stream
303345
std::unique_ptr<libcamera::CameraConfiguration> cfg =
304-
camera->generateConfiguration({role});
346+
camera->generateConfiguration(roles);
305347

306348
if (!cfg)
307349
throw std::runtime_error("failed to generate configuration");
308350

309-
assert(cfg->size() == 1);
351+
assert(cfg->size() >= 1);
310352
libcamera::StreamConfiguration &scfg = cfg->at(0);
311353

312354
// list all camera formats, including those not supported by the ROS message
@@ -357,6 +399,12 @@ CameraNode::CameraNode(const rclcpp::NodeOptions &options)
357399
scfg.size = size;
358400
}
359401

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+
360408
// store selected stream configuration
361409
const libcamera::StreamConfiguration selected_scfg = scfg;
362410

@@ -392,6 +440,8 @@ CameraNode::CameraNode(const rclcpp::NodeOptions &options)
392440
const std::optional<std::string> model = props.get(libcamera::properties::Model);
393441
if (model)
394442
cname = model.value() + '_' + cname;
443+
if (!sensor_size.isNull())
444+
cname = cname + '_' + cfg->at(1).toString();
395445

396446
// clean camera name of non-alphanumeric characters
397447
cname.erase(

0 commit comments

Comments
 (0)