Auterion App SDK
Auterion SDK is a library that can be used by AuterionOS apps to communicate with the system.
Loading...
Searching...
No Matches
camera.hpp
1/****************************************************************************
2 *
3 * Copyright 2023-2026 Auterion AG. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * 3. Neither the name of the copyright holder nor the names of its contributors
16 * may be used to endorse or promote products derived from this software without
17 * specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 *
31 ****************************************************************************/
32
33#pragma once
34
35#ifdef ROS_DISTRO_IS_ROLLING
36#include <tf2_ros/transform_listener.hpp>
37#else
38#include <tf2_ros/transform_listener.h>
39#endif
40
41#include <Eigen/Eigen>
42#include <auterion_core_msgs/msg/camera_info.hpp>
43#include <auterion_core_msgs/msg/image8m.hpp>
44#include <chrono>
45#include <functional>
46#include <opencv2/core/mat.hpp>
47#include <rclcpp/rclcpp.hpp>
48#include <sensor_msgs/msg/camera_info.hpp>
49#include <string>
50#include <utility>
51
52#include "camera_control.hpp"
53
54namespace auterion {
58using namespace std::chrono_literals;
59
60class SDK;
61class CameraMonitorImpl;
62class CameraImpl;
63
69 ImageHeader() = default;
70
72 enum class Encoding {
73 Unknown,
74 Y800,
75 Y16,
76 UYVY,
77 YUY2,
78 I420,
79 NV12,
80 RGB8,
81 BGR8,
83 BA81,
84 GBRG,
85 GRBG,
86 RGGB,
87 RGBA
88 };
89
90 ImageHeader(int width_, int height_, Encoding encoding_, int step_ = 0);
91
92 template <typename ImageT = auterion_core_msgs::msg::Image8m>
93 explicit ImageHeader(const ImageT& image)
94 : width(image.width), height(image.height), step(image.step), timestamp(image.stamp) {
95 const int encoding_len =
96 strnlen(reinterpret_cast<const char*>(image.encoding.data()), image.encoding.size());
97 const std::string encoding_str(reinterpret_cast<const char*>(image.encoding.data()),
98 encoding_len);
99 if (encoding_str == "mono8") {
100 encoding = Encoding::Y800;
101 } else if (encoding_str == "mono16") {
102 encoding = Encoding::Y16;
103 } else if (encoding_str == "uyvy") {
104 encoding = Encoding::UYVY;
105 } else if (encoding_str == "yuyv") {
106 encoding = Encoding::YUY2;
107 } else if (encoding_str == "i420") {
108 // (This does not exist in ROS:
109 // https://github.com/ros2/common_interfaces/blob/master/sensor_msgs/include/sensor_msgs/image_encodings.hpp)
110 encoding = Encoding::I420;
111 } else if (encoding_str == "nv12") {
112 // Does not exist in ROS 2
113 encoding = Encoding::NV12;
114 } else if (encoding_str == "rgb8") {
115 encoding = Encoding::RGB8;
116 } else if (encoding_str == "rgba8") {
117 encoding = Encoding::RGBA;
118 } else if (encoding_str == "bgr8") {
119 encoding = Encoding::BGR8;
120 } else if (encoding_str == "bayer_bggr8") {
121 encoding = Encoding::BA81;
122 } else if (encoding_str == "bayer_gbrg8") {
123 encoding = Encoding::GBRG;
124 } else if (encoding_str == "bayer_grbg8") {
125 encoding = Encoding::GRBG;
126 } else if (encoding_str == "bayer_rggb8") {
127 encoding = Encoding::RGGB;
128 }
129 }
130
131 template <typename ImageT = auterion_core_msgs::msg::Image8m>
132 void toImage(ImageT& image) {
133 image.width = width;
134 image.height = height;
135 image.step = step;
136 image.stamp = timestamp;
137 switch (encoding) {
138 case Encoding::Unknown:
139 strncpy(reinterpret_cast<char*>(image.encoding.data()), "unknown",
140 image.encoding.size());
141 break;
142 case Encoding::Y800:
143 strncpy(reinterpret_cast<char*>(image.encoding.data()), "mono8",
144 image.encoding.size());
145 break;
146 case Encoding::Y16:
147 strncpy(reinterpret_cast<char*>(image.encoding.data()), "mono16",
148 image.encoding.size());
149 break;
150 case Encoding::UYVY:
151 strncpy(reinterpret_cast<char*>(image.encoding.data()), "uyvy",
152 image.encoding.size());
153 break;
154 case Encoding::YUY2:
155 strncpy(reinterpret_cast<char*>(image.encoding.data()), "yuyv",
156 image.encoding.size());
157 break;
158 case Encoding::I420:
159 strncpy(reinterpret_cast<char*>(image.encoding.data()), "i420",
160 image.encoding.size());
161 break;
162 case Encoding::NV12:
163 strncpy(reinterpret_cast<char*>(image.encoding.data()), "nv12",
164 image.encoding.size());
165 break;
166 case Encoding::RGB8:
167 strncpy(reinterpret_cast<char*>(image.encoding.data()), "rgb8",
168 image.encoding.size());
169 break;
170 case Encoding::BGR8:
171 strncpy(reinterpret_cast<char*>(image.encoding.data()), "bgr8",
172 image.encoding.size());
173 break;
174 case Encoding::RGBA:
175 strncpy(reinterpret_cast<char*>(image.encoding.data()), "rgba8",
176 image.encoding.size());
177 break;
178 case Encoding::BA81:
179 strncpy(reinterpret_cast<char*>(image.encoding.data()), "bayer_bggr8",
180 image.encoding.size());
181 break;
182 case Encoding::GBRG:
183 strncpy(reinterpret_cast<char*>(image.encoding.data()), "bayer_gbrg8",
184 image.encoding.size());
185 break;
186 case Encoding::GRBG:
187 strncpy(reinterpret_cast<char*>(image.encoding.data()), "bayer_grbg8",
188 image.encoding.size());
189 break;
190 case Encoding::RGGB:
191 strncpy(reinterpret_cast<char*>(image.encoding.data()), "bayer_rggb8",
192 image.encoding.size());
193 break;
194 }
195 }
196
197 size_t planeOffset(int plane) const;
198 size_t planeSizeBytes(int plane) const;
199 size_t imageSizeBytes() const;
200
201 int width{};
202 int height{};
203 int step{};
204 Encoding encoding{Encoding::Unknown};
205
206 rclcpp::Time timestamp{};
207 std::optional<uint64_t> monotonic_timestamp_us{std::nullopt};
208 uint32_t frame_id{};
209
210 float framerate{0.f};
211
212 sensor_msgs::msg::CameraInfo camera_info{};
213};
214
215std::ostream& operator<<(std::ostream& os, const ImageHeader::Encoding& encoding);
216
221class Image {
222 public:
223 Image(ImageHeader header, uint8_t* data) : _header(std::move(header)), _data(data) {}
224
225 const ImageHeader& header() const { return _header; }
226
227 uint8_t* data() { return _data; }
228 const uint8_t* data() const { return _data; }
229
230 int width() const { return _header.width; }
231 int height() const { return _header.height; }
232
237 cv::Mat asOpenCVMat(int plane = -1) const;
238
239 private:
240 ImageHeader _header{};
241 uint8_t* _data{nullptr};
242};
243
251class ImageFD {
252 public:
253 ImageFD(ImageHeader header, int fd, uint64_t buffer_size,
254 std::function<void()> release_function, uint8_t* buffer = nullptr,
255 std::function<void()> discard_function = nullptr)
256 : _header(std::move(header)),
257 _fd(fd),
258 _buffer_size(buffer_size),
259 _buffer(buffer),
260 _release_function(std::move(release_function)),
261 _discard_function(std::move(discard_function)) {}
262
263 ~ImageFD() { release(); }
264
265 // Delete copy operations
266 // Copying an ImageFD is not allowed since it contains an FD that should be uniquely owned.
267 ImageFD(const ImageFD&) = delete;
268 ImageFD& operator=(const ImageFD&) = delete;
269
270 // Move constructor
271 ImageFD(ImageFD&& other) noexcept
272 : _header(std::move(other._header)),
273 _fd(other._fd),
274 _buffer_size(other._buffer_size),
275 _buffer(other.buffer()),
276 _release_function(std::move(other._release_function)),
277 _discard_function(std::move(other._discard_function)),
278 _was_released(other._was_released) {
279 other._fd = -1;
280 other._buffer_size = 0;
281 other._buffer = nullptr;
282 other._release_function = nullptr;
283 other._discard_function = nullptr;
284 other._was_released = true;
285 }
286
287 // Move assignment operator
288 ImageFD& operator=(ImageFD&& other) noexcept {
289 if (this != &other) {
290 // Release the current object first to avoid leaking the old fd/callback
291 release();
292 _header = std::move(other._header);
293 _fd = other._fd;
294 _buffer_size = other._buffer_size;
295 _buffer = other._buffer;
296 _release_function = std::move(other._release_function);
297 _discard_function = std::move(other._discard_function);
298 _was_released = other._was_released;
299 other._fd = -1;
300 other._buffer_size = 0;
301 other._buffer = nullptr;
302 other._release_function = nullptr;
303 other._discard_function = nullptr;
304 other._was_released = true;
305 }
306 return *this;
307 }
308
309 const ImageHeader& header() const { return _header; }
310
311 int fd() const { return _fd; }
312
313 uint64_t bufferSize() const { return _buffer_size; }
314
315 uint8_t* buffer() const { return _buffer; }
316
317 /*
318 * Release the FD, signalling that the buffer is no longer in use.
319 */
320 void release() {
321 if (_release_function) {
322 _release_function();
323 _release_function = nullptr;
324 }
325 _fd = -1;
326 _buffer = nullptr;
327 _was_released = true;
328 }
329
330 bool wasReleased() const { return _was_released; }
331
332 /*
333 * Mark a replacement buffer to be reclaimed without rendering (the compose never happened). The
334 * buffer is still forwarded on release; the producer just discards it. No-op for
335 * non-replacement images.
336 */
337 void discard() {
338 if (_discard_function) {
339 _discard_function();
340 _discard_function = nullptr;
341 }
342 }
343
344 private:
345 ImageHeader _header{};
346 int _fd{-1};
347 uint64_t _buffer_size{0};
348 uint8_t* _buffer{nullptr};
349
356 std::function<void()> _release_function{nullptr};
357
359 std::function<void()> _discard_function{nullptr};
360
361 bool _was_released{false};
362};
363
369 enum class SensorType {
370 Unknown = 0,
371 Monochrome = 1,
372 RGB = 2,
373 Depth = 3,
374 RGBD = 4,
375 IR = 5,
376 };
377 enum class MountOrientation {
378 Unknown = 0,
379 Dynamic = 1,
380 Forward = 2,
381 Backward = 3,
382 Upward = 4,
383 Downward = 5,
384 Leftward = 6,
385 Rightward = 7,
386 };
387 enum class MountOrientationDetail {
388 Unknown = 0,
389 Left = 1,
390 Right = 2,
391 Upper = 3,
392 Lower = 4,
393 };
394 enum class PrimaryPurpose {
395 Unknown = 0,
396 Generic = 1,
397 OpticalFlow = 2,
398 VIO = 3,
399 FPV = 4,
400 Survey = 5,
401 Tracking = 6,
402 };
403 enum class ImageRotation : uint8_t {
404 None = 0,
405 Rotation90Clockwise = 1,
406 Rotation180 = 2,
407 Rotation270Clockwise = 3,
408 };
410 Eigen::Quaterniond rotation{-0.5, 0.5, -0.5, 0.5}; // {q, x, y, z} default front facing
411 Eigen::Vector3d translation{0.0, 0.0, 0.0};
412 };
413
414 CameraDescriptor() = default;
415 explicit CameraDescriptor(const auterion_core_msgs::msg::CameraInfo& camera_info);
416
417 operator auterion_core_msgs::msg::CameraInfo() const;
418
419 std::string device_driver;
420 std::string device_identifier;
421
422 std::string unique_hash;
423 std::string unique_name;
424
425 std::string camera_model;
426
427 SensorType sensor_type{SensorType::Unknown};
428 MountOrientation mount_orientation{MountOrientation::Unknown};
429 MountOrientationDetail mount_orientation_detail{MountOrientationDetail::Unknown};
430 PrimaryPurpose primary_purpose{PrimaryPurpose::Unknown};
431 ImageRotation image_rotation{ImageRotation::None};
432 ExtrinsicCalibration extrinsic_calibration;
433};
434
441 enum class Mode { None, Point, Rectangle };
442
443 bool active{false};
444 bool coasting{false};
445 bool error{false};
446 Mode mode{Mode::None};
447 float point_x{0.F};
448 float point_y{0.F};
449 float radius{0.F};
450 float rectangle_top_x{0.F};
451 float rectangle_top_y{0.F};
452 float rectangle_bottom_x{0.F};
453 float rectangle_bottom_y{0.F};
454 unsigned resolution_x{0};
455 unsigned resolution_y{0};
456 unsigned hfov_deg{0};
457};
458
465 uint64_t session_id{0};
466};
467
475 public:
476 using NotificationCallback = std::function<void(const CameraDescriptor&)>;
477
478 CameraMonitor(SDK& sdk, const NotificationCallback& on_camera_added,
479 const NotificationCallback& on_camera_removed = nullptr);
480
481 ~CameraMonitor() = default;
482
483 private:
484 std::shared_ptr<CameraMonitorImpl> _impl;
485};
486
491class Camera {
492 public:
493 Camera(SDK& sdk, const CameraDescriptor& camera_descriptor);
494 Camera(std::shared_ptr<CameraImpl> impl);
495 ~Camera() = default;
496
501 const std::function<bool(const CameraDescriptor&)>& find_callback);
502
503 [[deprecated(
504 "waitForCamera with 'camera_descriptor' not used anymore. This will be removed in some "
505 "later release.")]] static Camera
506 waitForCamera(SDK& sdk,
507 std::optional<auterion::CameraDescriptor> camera_descriptor = std::nullopt);
508
509 static std::optional<Camera> open(SDK& sdk, const std::string& unique_name,
510 const std::chrono::seconds& timeout = 30s);
511 static std::optional<Camera> open(
512 SDK& sdk, const std::function<bool(const CameraDescriptor&)>& find_callback,
513 const std::chrono::seconds& timeout = 30s);
514 static std::optional<Camera> openFirst(SDK& sdk, const std::chrono::seconds& timeout = 30s);
515
516 using ImageCallback = std::function<void(const Image& image)>;
517 using ImageFDCallback = std::function<void(const std::shared_ptr<ImageFD>& image_fd)>;
518 using CameraInfoAfterSwitchCallback =
519 std::function<void(const sensor_msgs::msg::CameraInfo::SharedPtr& camera_info)>;
520 using TrackingStatusCallback = std::function<void(const TrackingStatus& status)>;
521 using ServiceStatusCallback = std::function<void(const CameraServiceStatus& status)>;
522
528 void subscribeImage(const ImageCallback& callback);
529
545 void subscribeImageFD(const ImageFDCallback& callback, float max_rate_hz = 0.0F);
546
564 void setImagesPaused(bool paused);
565
567 enum class PixelFormat {
568 SameAsImage,
569 NV12,
570 RGBA,
571 YUYV,
572 };
573
575
576 std::chrono::milliseconds timeout =
577 300ms;
582 PixelFormat pixel_format{PixelFormat::SameAsImage};
587
588 ImageReplacementOptions& withTimeout(std::chrono::milliseconds timeout_val) {
589 timeout = timeout_val;
590 return *this;
591 }
592
593 ImageReplacementOptions& withPixelFormat(PixelFormat format) {
594 pixel_format = format;
595 return *this;
596 }
597
598 ImageReplacementOptions& withUseOutputImageSize(bool use) {
600 return *this;
601 }
602 };
603
613 void deactivateImageReplacement();
629 std::optional<ImageFD> getReplacementImage(const ImageFD& image);
630
642 std::optional<ImageFD> getReplacementImage(uint32_t original_frame_id);
643
655 std::chrono::steady_clock::time_point lastReplacementImageOffer() const;
656
657 [[deprecated(
658 "subscribeCameraInfo() is deprecated and will be removed in a future release. The "
659 "CameraInfo is instead included in the image header obtained with subscribeImage(), and "
660 "can also be queried with getCameraInfo().")]] void
661 subscribeCameraInfo(
662 std::function<void(const sensor_msgs::msg::CameraInfo::SharedPtr)> callback);
663
669 void subscribeCameraInfoAfterSwitch(const CameraInfoAfterSwitchCallback& callback);
670
678 void subscribeTrackingStatus(TrackingStatusCallback callback);
679
686 void subscribeServiceStatus(ServiceStatusCallback callback);
687
696 bool cameraInfoValid() const;
697
706 std::optional<cv::Size> getLastImageSize() const;
707
714 Eigen::Vector3d unprojectImagePoint(const cv::Point& image_point) const;
715
730 bool getPoseBodyCamera(Eigen::Quaterniond& q_body_camera, Eigen::Vector3d& t_body_camera) const;
731
750 bool getPoseWorldCamera(Eigen::Quaterniond& q_enu_camera, Eigen::Vector3d& t_enu_camera,
751 const tf2::TimePoint& time = tf2::TimePointZero) const;
752
785 bool getDeltaRotationWorldCamera(Eigen::Quaterniond& q_delta_world_camera,
786 tf2::TimePoint& time_start, tf2::TimePoint& time_end);
787
796 bool getCameraInfo(sensor_msgs::msg::CameraInfo& camera_info) const;
797
808 bool getCameraInfoDirect(sensor_msgs::msg::CameraInfo& camera_info) const;
809
819 bool switchTo(const std::function<bool(const CameraDescriptor&)>& find_callback);
820
825
826 CameraControl& control();
827
831 bool usesImageSession() const;
832
833 private:
834 std::shared_ptr<CameraImpl> _impl;
835};
836
838} // namespace auterion
Definition camera_control.hpp:17
Monitor for new or removed cameras, of both camera protocols. Existing cameras are reported from the ...
Definition camera.hpp:474
Camera client to subscribe to a camera image stream.
Definition camera.hpp:491
void setImagesPaused(bool paused)
Stop or resume image delivery without giving up the subscription.
std::optional< ImageFD > getReplacementImage(const ImageFD &image)
Get a replacement image buffer.
const CameraDescriptor & descriptor() const
Get the descriptor of the active camera source.
bool cameraInfoValid() const
Determine whether a camera info message contains valid data.
void subscribeServiceStatus(ServiceStatusCallback callback)
Subscribe to the status of the service providing this camera.
void subscribeCameraInfoAfterSwitch(const CameraInfoAfterSwitchCallback &callback)
Subscribe to camera info updates emitted once after each successful stream switch.
bool getCameraInfo(sensor_msgs::msg::CameraInfo &camera_info) const
Get the camera information data for the camera.
bool getPoseBodyCamera(Eigen::Quaterniond &q_body_camera, Eigen::Vector3d &t_body_camera) const
Get the pose of the camera with respect to the body frame.
bool usesImageSession() const
void subscribeImage(const ImageCallback &callback)
std::optional< ImageFD > getReplacementImage(uint32_t original_frame_id)
Get a replacement image buffer by the original frame's id.
Eigen::Vector3d unprojectImagePoint(const cv::Point &image_point) const
Determine the (relative) camera-frame 3D position of a point in an image.
bool getDeltaRotationWorldCamera(Eigen::Quaterniond &q_delta_world_camera, tf2::TimePoint &time_start, tf2::TimePoint &time_end)
Calculates how much the camera has rotated between two points in time based only on gyro data.
std::optional< cv::Size > getLastImageSize() const
Returns the size of the last received image, if available.
bool getCameraInfoDirect(sensor_msgs::msg::CameraInfo &camera_info) const
get the camera info directly from the camera
static Camera waitForCamera(SDK &sdk, const std::function< bool(const CameraDescriptor &)> &find_callback)
void subscribeTrackingStatus(TrackingStatusCallback callback)
Subscribe to the status of the tracking on this camera.
bool switchTo(const std::function< bool(const CameraDescriptor &)> &find_callback)
Switch active UnixFD input to a discovered camera selected by callback.
bool getPoseWorldCamera(Eigen::Quaterniond &q_enu_camera, Eigen::Vector3d &t_enu_camera, const tf2::TimePoint &time=tf2::TimePointZero) const
Get the pose of the camera with respect to the ENU world frame.
std::chrono::steady_clock::time_point lastReplacementImageOffer() const
When the camera service last offered a replacement image buffer.
void activateImageReplacement(const ImageReplacementOptions &options=ImageReplacementOptions())
Activate image replacement.
Definition camera.hpp:251
Contains image data with a specific encoding.
Definition camera.hpp:221
cv::Mat asOpenCVMat(int plane=-1) const
SDK execution class. All callbacks are called on the same thread.
Definition auterion.hpp:133
Struct describing a camera configuration.
Definition camera.hpp:368
The status the service providing a camera (e.g. video-service) reports for it, about once a second.
Definition camera.hpp:464
uint64_t session_id
changes when the service (re)starts the camera
Definition camera.hpp:465
bool use_output_image_size
or equal to the subscription image size)
Definition camera.hpp:584
PixelFormat pixel_format
Definition camera.hpp:582
PixelFormat
Definition camera.hpp:567
std::chrono::milliseconds timeout
Definition camera.hpp:576
Contains image metadata.
Definition camera.hpp:68
int step
full row size in bytes
Definition camera.hpp:203
uint32_t frame_id
incremental frame id
Definition camera.hpp:208
std::optional< uint64_t > monotonic_timestamp_us
hardware timestamp [μs]
Definition camera.hpp:207
Encoding
Image encoding, following FourCC.
Definition camera.hpp:72
@ NV12
YUV 4:2:0, https://fourcc.org/pixel-format/yuv-nv12/.
@ Y800
8 bit monochrome image (GREY), https://fourcc.org/pixel-format/yuv-y800/
@ Y16
16 bit monochrome image, https://fourcc.org/pixel-format/yuv-y16/
@ BA81
8-bit Bayer (https://docs.kernel.org/userspace-api/media/v4l/pixfmt-srggb8.html)
@ UYVY
YUV 4:2:2, https://fourcc.org/pixel-format/yuv-uyvy/.
@ YUY2
YUV 4:2:2 (YUYV), https://fourcc.org/pixel-format/yuv-yuy2/.
@ I420
YUV 4:2:0, https://fourcc.org/pixel-format/yuv-i420/.
@ RGBA
32-bit RGBA used for rendering
The status of the tracking on a camera, as the app tracking on it reports it (see TrackingInterface)....
Definition camera.hpp:440
float rectangle_top_x
Mode::Rectangle.
Definition camera.hpp:450
unsigned resolution_x
the displayed resolution, 0 if unknown
Definition camera.hpp:454
bool coasting
the target is obscured, its position predicted
Definition camera.hpp:444
float point_x
Mode::Point.
Definition camera.hpp:447
bool active
a target is tracked (or coasting)
Definition camera.hpp:443
unsigned hfov_deg
the displayed horizontal field of view, 0 if unknown
Definition camera.hpp:456