VOXL OpenVINS Server 0.7.0
Visual Inertial Odometry Server for VOXL Platform
Loading...
Searching...
No Matches
MonoCameraMinimal.cpp
Go to the documentation of this file.
1/**
2 * @file MonoCameraMinimal.cpp
3 * @brief Monocular camera implementation for VOXL OpenVINS
4 * @author Joao Leonardo Silva Cotta (@zauberflote1)
5 * @date 2025
6 * @version 1.0
7 *
8 * This file implements the MonoCamera class, which specializes the CameraBase
9 * for monocular camera configurations. It handles single camera image processing
10 * and integration with the VIO system.
11 *
12 * The implementation provides:
13 * - Single camera image processing for RAW8 format
14 * - ION buffer processing for efficient memory management
15 * - Queue-based data management for VIO integration
16 * - System state awareness and validation
17 * - Camera fusion system integration
18 * - Thread-safe data handling
19 */
20
21#include "MonoCameraMinimal.h"
22#include <CL/cl.h>
23#include <CL/cl_ext.h>
24
25namespace voxl
26{
27
28 /**
29 * @brief Constructor for MonoCamera
30 *
31 * Initializes a monocular camera instance by calling the base class
32 * constructor with the provided camera configuration information.
33 *
34 * @param camera_info Camera configuration and calibration information
35 */
36 MonoCamera::MonoCamera(const cam_info &camera_info)
37 : CameraBase(camera_info)
38 {
39 }
40
41 static void release_cl_mem(void *mem)
42 {
43 if (mem)
44 {
45 cl_int err = clReleaseMemObject((cl_mem)mem);
46 if (err != CL_SUCCESS)
47 {
48 fprintf(stderr, "Failed to release cl_mem object %p, err=%d\n", mem, err);
49 }
50 }
51 }
52
53 /**
54 * @brief Process incoming image data
55 *
56 * Overrides the base class method to handle monocular camera-specific
57 * image processing. This method is called by the pipe callback when
58 * new image data arrives.
59 *
60 * The processing includes:
61 * - Updating camera connection status and timestamps
62 * - Checking system readiness before processing
63 * - Routing to appropriate format-specific processing
64 * - Updating current image dimensions
65 *
66 * Currently supports RAW8 format with fast-path processing.
67 *
68 * @param meta Image metadata containing timestamp and format information
69 * @param frame Pointer to image data buffer
70 */
71 void MonoCamera::process_image(const camera_image_metadata_t &meta, voxl::ImageType img_type, void *frame)
72 {
73
74 // Update flags quickly
75 is_cam_connected = true;
76 last_cam_time = _apps_time_monotonic_ns();
77
78 // Early return if system not ready
79 if (!is_system_ready())
80 {
81 if (img_type == voxl::ImageType::CL_MEM)
82 release_cl_mem(frame);
83 return;
84 }
85
86 // if we are resetting, just return
87 if (is_resetting.load(std::memory_order_relaxed))
88 {
90 if (img_type == voxl::ImageType::CL_MEM)
91 release_cl_mem(frame);
92 return;
93 }
94
95 // indicate that we are processing IMU data
96 active_callbacks.fetch_add(1, std::memory_order_acquire);
97 if (is_resetting.load(std::memory_order_relaxed))
98 {
99
100 skip_jerk_detection = true;
101 if (img_type == voxl::ImageType::CL_MEM)
102 release_cl_mem(frame);
103
104 if (active_callbacks.fetch_sub(1, std::memory_order_release) == 1)
105 {
106 std::lock_guard<std::mutex> lk(reset_mtx);
107 reset_cv.notify_one();
108 }
109 return;
110 }
111 // CRITICAL FIX: DISABLE frame throttling during initialization completely
112 // User reported issues with initialization - process ALL frames until VIO is stable
113
114 // Check if VIO is fully initialized AND past grace period
115 // Multi-camera (unsynced) rigs must NOT decimate per-camera: the alternating drops are
116 // asymmetric across streams and break the epoch cadence the estimator anchors on
117 const bool vio_ready_for_throttling = vio_manager &&
118 vio_manager->initialized() &&
119 vio_manager_options.state_options.num_cameras <= 1 &&
120 vio_state.load(std::memory_order_acquire) == VIO_STATE_OK;
121
122 // NEVER throttle during: INITIALIZING, FAILED, or first few seconds of OK state
123 if (!skip_jerk_detection && vio_ready_for_throttling)
124 {
125 // Only throttle when VIO is stable and running
126 const bool is_static = !(non_static.load(std::memory_order_acquire));
127 const bool acc_no_jerk = !(has_acc_jerk.load(std::memory_order_acquire));
128
129 if (en_debug)
130 {
131 printf("is_static: %d, acc_no_jerk: %d, drop_frames: %d\n",
132 is_static, acc_no_jerk, drop_frames);
133 }
134
135 if (is_static || acc_no_jerk)
136 {
137 if (!drop_frames)
138 {
139 drop_frames = true;
140 if (en_debug)
141 printf("normal frame\n");
142 }
143 else
144 {
145 // Drop this frame - platform is static
146 if (img_type == voxl::ImageType::CL_MEM)
147 release_cl_mem(frame);
148 drop_frames = false;
149 if (en_debug)
150 printf("dropping frame\n");
151 if (active_callbacks.fetch_sub(1, std::memory_order_release) == 1)
152 {
153 std::lock_guard<std::mutex> lk(reset_mtx);
154 reset_cv.notify_one();
155 }
156 return;
157 }
158 }
159 }
160 else if (!vio_ready_for_throttling && en_debug)
161 {
162 // During initialization or unstable state, process ALL frames
163 printf("[INIT] VIO not stable - processing ALL frames (no throttling)\n");
164 }
165 // Update dimensions
166 current_height = meta.height;
167 current_width = meta.width;
168
169 if (img_type == voxl::ImageType::CV_MAT)
170 {
171 // Process only supported formats with fast path
172 if (meta.format == IMAGE_FORMAT_RAW8)
173 {
174 process_raw8(meta, (char *)frame);
175 }
176 else
177 {
178 // Rare case, can be slower
179 fprintf(stderr, "Unsupported image format: %d\n", meta.format);
180 vio_error_codes |= ERROR_CODE_CAM_BAD_FORMAT;
181 }
182 }
183 if (img_type == voxl::ImageType::CL_MEM)
184 {
185 // Process only supported formats
186 if (meta.format == IMAGE_FORMAT_RAW8)
187 {
188 process_device_buf_raw8(meta, (cl_mem)frame);
189 }
190 else
191 {
192 // Rare case, can be slower
193 fprintf(stderr, "Unsupported image format: %d\n", meta.format);
194 vio_error_codes |= ERROR_CODE_CAM_BAD_FORMAT;
195 }
196 }
197 skip_jerk_detection = false;
198 // check if in flight processing count reaches zero and if a reset is requested
199 if (active_callbacks.fetch_sub(1, std::memory_order_release) == 1)
200 {
201 // Notify the reset thread to continue processing
202 std::lock_guard<std::mutex> lk(reset_mtx);
203 reset_cv.notify_one();
204 }
205 }
206
207 /**
208 * @brief Process RAW8 image format
209 *
210 * Handles the processing of RAW8 format images, which is a common
211 * format for monochrome cameras used in VIO systems.
212 *
213 * The processing includes:
214 * - Setting up image ring buffer packet with metadata
215 * - Copying image data to internal buffer
216 * - Creating OpenCV Mat view of the image data
217 * - Setting up mask for feature tracking regions
218 * - Creating CameraData message for VIO processing
219 * - Pushing data to camera queue
220 * - Notifying fusion system of data availability
221 *
222 * @param meta Image metadata containing timestamp and dimensions
223 * @param frame Pointer to image data
224 */
225 void MonoCamera::process_raw8(const camera_image_metadata_t &meta, char *frame)
226 {
227 // TODO: FIX INTERCHANCHABLE META AND CURR_MESSAGE USAGE, KEEP IT CONSISTENT, RESPECT HALACHAH
228 // Use static buffer to avoid allocations in the hot path
230 curr_message_.metadata = meta;
231 memcpy(curr_message_.image_pixels, reinterpret_cast<uint8_t *>(frame), meta.size_bytes);
232
233 // OpenCV view (no copy)
234 cv::Mat image(current_height, current_width, CV_8UC1, curr_message_.image_pixels);
235
236 // Check if dimensions changed and update mask efficiently
237 const bool dimensions_changed = (use_mask_.rows != current_height || use_mask_.cols != current_width);
238 if (dimensions_changed)
239 {
240 mask_dimensions_changed_ = true;
241 }
242
243 // Determine if mask should be active based on occlusion and altitude
244 const bool altitude_below_threshold = std::abs(alt_z.load(std::memory_order_relaxed)) < takeoff_alt_threshold;
245 const bool should_mask = camera_info_.is_occluded_on_takeoff && altitude_below_threshold && !occlusion_threshold_passed_;
246
247 if (!altitude_below_threshold && camera_info_.is_occluded_on_takeoff) {
248 occlusion_threshold_passed_ = true;
249 }
250
251 // Update mask only when necessary
252 update_mask_if_needed(should_mask);
253
254 ov_core::CameraData message;
255 message.timestamp = (meta.timestamp_ns) * 1e-09; // TODO: check if we should consider adding exposure time/2 --> NAIVELY ADDING BRING CHAOS (Multi-cam) DO IT AT YOUR OWN RISK
256 message.sensor_ids.push_back(get_id());
257
258 // clone might be optional --> depends on consumer thread ownership guarantees TODO: CHECK THIS LATER ON
259 message.images.emplace_back(image.clone());
260 message.masks.emplace_back(use_mask_);
261
262 modal_flow::ImageView iv{{meta.width, meta.height, modal_flow::PixelFormat::R8, meta.stride}, message.images[0].data, modal_flow::ExternalType::None, 0};
263 modal_flow::Frame img_frame({get_id(), 0, iv});
264 message.img_frames.push_back(img_frame);
265
266 // Push straight into the estimator's lock-free per-camera ring (this pipe thread is the
267 // single producer for this stream); the IMU feed releases frames in global time order.
268 // Overflow/late drops are disposed + counted via the processed(false) callback.
269 if (vio_manager && frame_transform.is_initialized)
270 {
271 vio_manager->feed_measurement_camera(message);
272 }
273 }
274
275 void MonoCamera::process_device_buf_raw8(const camera_image_metadata_t &meta, cl_mem frame)
276 {
278 curr_message_.metadata = meta;
279
280 // Check if dimensions changed and update mask efficiently
281 const bool dimensions_changed = (use_mask_.rows != current_height || use_mask_.cols != current_width);
282 if (dimensions_changed)
283 {
284 mask_dimensions_changed_ = true;
285 }
286
287 // Determine if mask should be active based on occlusion and altitude
288 const bool altitude_below_threshold = std::abs(alt_z.load(std::memory_order_relaxed)) < takeoff_alt_threshold;
289 const bool should_mask = camera_info_.is_occluded_on_takeoff && altitude_below_threshold && !occlusion_threshold_passed_;
290
291 if (!altitude_below_threshold && camera_info_.is_occluded_on_takeoff) {
292 occlusion_threshold_passed_ = true;
293 }
294
295 // Update mask only when necessary
296 update_mask_if_needed(should_mask);
297
298 ov_core::CameraData message;
299 message.timestamp = (meta.timestamp_ns) * 1e-09; // TODO: check if we should consider adding exposure time/2 --> NAIVELY ADDING BRING CHAOS (Multi-cam) DO IT AT YOUR OWN RISK
300 message.sensor_ids.push_back(get_id());
301
302 // modal_flow::ImageView ivA{{camA.width, camA.height, modal_flow::PixelFormat::R8, img_prev.step}, img_prev.data, modal_flow::ExternalType::None, 0};
303
304 modal_flow::ImageView iv{{meta.width, meta.height, modal_flow::PixelFormat::R8, meta.stride}, nullptr, modal_flow::ExternalType::ClMem, static_cast<uint64_t>(reinterpret_cast<std::uintptr_t>(frame))};
305 modal_flow::Frame img_frame({get_id(), 0, iv});
306
307 message.cl_images.emplace_back(frame);
308
309 message.img_frames.push_back(img_frame);
310
311 message.images.emplace_back(cv::Mat::zeros(meta.height, meta.width, CV_8UC1));
312
313 message.masks.emplace_back(use_mask_);
314
315 // Push straight into the estimator's lock-free per-camera ring; drops (overflow/late) are
316 // disposed via the processed(false) callback, which releases the cl_mem handle exactly once
317 if (vio_manager && frame_transform.is_initialized)
318 {
319 vio_manager->feed_measurement_camera(message);
320 }
321 else
322 {
323 // No estimator yet: release the device buffer ourselves so it cannot leak
324 release_cl_mem(frame);
325 }
326 }
327
328 void MonoCamera::update_mask_if_needed(bool should_mask)
329 {
330 // Check if we need to update the mask
331 const bool needs_update = !current_mask_state_.has_value() ||
332 current_mask_state_.value() != should_mask ||
333 mask_dimensions_changed_;
334
335 if (!needs_update)
336 {
337 return;
338 }
339
340 // Update mask state
341 current_mask_state_ = should_mask;
342 mask_dimensions_changed_ = false;
343
344 // Create or update mask efficiently
345 if (should_mask)
346 {
347 // Use cv::Scalar constructor for better performance
348 use_mask_ = cv::Mat(current_height, current_width, CV_8UC1, cv::Scalar(255));
349 }
350 else
351 {
352 // Use cv::Scalar constructor for better performance
353 use_mask_ = cv::Mat(current_height, current_width, CV_8UC1, cv::Scalar(0));
354 }
355 }
356
357
358 /**
359 * @brief Check if system is ready to process images
360 *
361 * Determines whether the VIO system is ready to accept and process
362 * new image data. The system is considered ready when both the IMU
363 * is connected and the main process is running.
364 *
365 * @return true if system is ready, false otherwise
366 */
367 bool MonoCamera::is_system_ready() const
368 {
370 }
371
372} // namespace voxl
Monocular camera implementation for VOXL OpenVINS.
volatile int64_t last_cam_time
Timestamp of last camera data (nanoseconds)
Definition VoxlVars.cpp:188
volatile int main_running
Main process running flag.
Definition VoxlVars.cpp:38
std::atomic< uint32_t > active_callbacks
Number of callbacks inside the system.
Definition VoxlVars.cpp:63
ov_msckf::VioManagerOptions vio_manager_options
VIO manager options.
Definition VoxlVars.cpp:28
voxl::FrameTransform frame_transform
Global frame transform instance.
Definition VoxlVars.cpp:181
std::mutex reset_mtx
Mutex used by reset thread.
Definition VoxlVars.cpp:66
float takeoff_alt_threshold
Takeoff altitude threshold.
Definition VoxlVars.cpp:197
std::unique_ptr< ov_msckf::VioManager > vio_manager
Main VIO manager instance.
Definition VoxlVars.cpp:31
int en_debug
Enable debug output.
Definition VoxlVars.cpp:149
std::condition_variable reset_cv
Reset conditional variable.
Definition VoxlVars.cpp:69
std::atomic< bool > non_static
Non-static flag for jerk detection.
std::atomic< bool > is_resetting
VIO reset state flag.
std::atomic< uint8_t > vio_state
Current VIO system state.
std::atomic< float > alt_z
Altitude z.
std::atomic< bool > has_acc_jerk
Flag indicating if accelerometer jerk is detected.
std::atomic< uint32_t > vio_error_codes
VIO error codes.
std::atomic< bool > is_imu_connected
IMU connection state.
std::atomic< bool > is_cam_connected
Camera connection state.
Base class for all camera implementations.
Definition CameraBase.h:57
bool drop_frames
Indicates if frames should be dropped.
Definition CameraBase.h:185
cv::Mat use_mask_
Per-instance reusable mask for feature tracking.
Definition CameraBase.h:182
int get_channel() const
Pop camera data from the queue.
Definition CameraBase.h:112
cam_info camera_info_
Camera configuration information.
Definition CameraBase.h:154
bool skip_jerk_detection
Indicates if jerk detection should be skipped.
Definition CameraBase.h:188
size_t get_id() const
Get the camera identifier.
Definition CameraBase.h:118
img_ringbuf_packet curr_message_
Lock-free SPSC queue for camera data.
Definition CameraBase.h:179
MonoCamera(const cam_info &camera_info)
Constructor.
void process_image(const camera_image_metadata_t &meta, voxl::ImageType type, void *frame) override
Process incoming image data.
Main namespace for VOXL OpenVINS server components.
Camera information and calibration data.
Definition VoxlCommon.h:146
bool is_occluded_on_takeoff
Flag indicating if camera is occluded on takeoff.
Definition VoxlCommon.h:150
uint8_t image_pixels[MAX_IMAGE_SIZE]
Raw image pixel data.
Definition VoxlVars.h:64
camera_image_metadata_t metadata
Image metadata (timestamp, format, etc.)
Definition VoxlVars.h:63
int camid
Camera identifier.
Definition VoxlVars.h:62