Skip to content

Commit fc212f4

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

File tree

2 files changed

+71
-3
lines changed

2 files changed

+71
-3
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,20 +123,32 @@ 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` | `string` in format `width:height` | desired raw sensor format resolution [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+
Some camera modules (e.g. Raspberry Pi Camera Modules) provides different sensor modes, some of which 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 'zoom' function etc. Unless we specify the sensor mode we want to use libcamera will automatically select one, and it is not guaranteed that this will be a full sensor mode, leading to cropping (which provides a zooming effect) of the picture.
145+
146+
As an example: Picking the commonly used 640x480 resolution with the Raspberry Pi Camera Module v2 gives a zooming effect of ~2.5x, greatly reducing the FoV. With the v1 Module there is no such zooming effect as the v1 camera only offers full FoV modes in the 4:3 aspect ratio.
147+
148+
With the Camera Module v2 it is suggested to use a sensor resolution of 1640x1232 when one needs lower resolution 4:3 images (e.g. 640x480, 480x320 etc.), and 1640x922 for lower resolution 16:9 images.
149+
150+
See the [PiCamera Documentation](https://picamera.readthedocs.io/en/release-1.13/fov.html#sensor-modes) for a visualization of the issue, and [section 4.2.2.3 in the PiCamera2 Documentation](https://datasheets.raspberrypi.com/camera/picamera2-manual.pdf) for further explanations.
151+
140152
### Dynamic Frame Configuration (controls)
141153

142154
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: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,43 @@ get_role(const std::string &role)
150150
}
151151
}
152152

153+
libcamera::Size
154+
get_sensor_format(const std::string &format_str)
155+
{
156+
if (format_str.empty()) {
157+
return libcamera::Size();
158+
}
159+
160+
size_t delimiter_pos = format_str.find(':');
161+
if (delimiter_pos == std::string::npos) {
162+
throw std::runtime_error("Invalid sensor parameter: '" + format_str +
163+
"'. Expected format 'width:height'");
164+
}
165+
166+
std::string width_str = format_str.substr(0, delimiter_pos);
167+
std::string height_str = format_str.substr(delimiter_pos + 1);
168+
169+
// Check that width contains only digits
170+
if (!std::all_of(width_str.begin(), width_str.end(), [](unsigned char c) { return std::isdigit(c); })) {
171+
throw std::runtime_error("Width in sensor parameter '" + format_str +
172+
"' must contain only digits");
173+
}
174+
175+
// Check that height contains only digits
176+
if (!std::all_of(height_str.begin(), height_str.end(), [](unsigned char c) { return std::isdigit(c); })) {
177+
throw std::runtime_error("Height in sensor parameter '" + format_str +
178+
"' must contain only digits");
179+
}
180+
181+
try {
182+
const uint32_t width = std::stoul(width_str);
183+
const uint32_t height = std::stoul(height_str);
184+
return libcamera::Size {width, height};
185+
}
186+
catch (const std::exception &e) {
187+
throw std::runtime_error("Failed to parse sensor parameter '" + format_str + "': " + e.what());
188+
}
189+
}
153190

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

261+
// Raw format dimensions
262+
libcamera::Size sensor_size = get_sensor_format(declare_parameter<std::string>("sensor", "", param_descr_ro));
263+
224264
// camera info file url
225265
rcl_interfaces::msg::ParameterDescriptor param_descr_camera_info_url;
226266
param_descr_camera_info_url.description = "camera calibration info file url";
@@ -299,18 +339,26 @@ CameraNode::CameraNode(const rclcpp::NodeOptions &options)
299339
if (camera->acquire())
300340
throw std::runtime_error("failed to acquire camera");
301341

342+
std::vector<libcamera::StreamRole> roles;
343+
roles.push_back(role);
344+
345+
// Add the RAW role if the sensor_size is defined
346+
if (!sensor_size.isNull() && roles.front() != libcamera::StreamRole::Raw) {
347+
roles.push_back(libcamera::StreamRole::Raw);
348+
}
349+
302350
// configure camera stream
303351
std::unique_ptr<libcamera::CameraConfiguration> cfg =
304-
camera->generateConfiguration({role});
352+
camera->generateConfiguration(roles);
305353

306354
if (!cfg)
307355
throw std::runtime_error("failed to generate configuration");
308356

309-
assert(cfg->size() == 1);
357+
assert(cfg->size() >= 1);
310358
libcamera::StreamConfiguration &scfg = cfg->at(0);
311359

312360
// list all camera formats, including those not supported by the ROS message
313-
RCLCPP_DEBUG_STREAM(get_logger(), "default " << role << " stream configuration: \"" << scfg.toString() << "\"");
361+
RCLCPP_DEBUG_STREAM(get_logger(), "default " << roles.front() << " stream configuration: \"" << scfg.toString() << "\"");
314362
RCLCPP_DEBUG_STREAM(get_logger(), scfg.formats());
315363

316364
// get common pixel formats that are supported by the camera and the node
@@ -357,6 +405,12 @@ CameraNode::CameraNode(const rclcpp::NodeOptions &options)
357405
scfg.size = size;
358406
}
359407

408+
if (!sensor_size.isNull() && roles.front() != libcamera::StreamRole::Raw) {
409+
libcamera::StreamConfiguration &modecfg = cfg->at(1);
410+
modecfg.size = sensor_size;
411+
RCLCPP_INFO_STREAM(get_logger(), "Sensor mode configuration: " << modecfg.toString());
412+
}
413+
360414
// store selected stream configuration
361415
const libcamera::StreamConfiguration selected_scfg = scfg;
362416

@@ -392,6 +446,8 @@ CameraNode::CameraNode(const rclcpp::NodeOptions &options)
392446
const std::optional<std::string> model = props.get(libcamera::properties::Model);
393447
if (model)
394448
cname = model.value() + '_' + cname;
449+
if (!sensor_size.isNull())
450+
cname = cname + '_' + cfg->at(1).toString();
395451

396452
// clean camera name of non-alphanumeric characters
397453
cname.erase(

0 commit comments

Comments
 (0)