VOXL OpenVINS Server 1.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 Zauberflote
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 {
89 if (img_type == voxl::ImageType::CL_MEM)
90 release_cl_mem(frame);
91 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 const bool vio_ready_for_throttling = vio_manager &&
116 vio_manager->initialized() &&
117 vio_state.load(std::memory_order_acquire) == VIO_STATE_OK;
118
119 // NEVER throttle during: INITIALIZING, FAILED, or first few seconds of OK state
120 if (!skip_jerk_detection && vio_ready_for_throttling)
121 {
122 // Only throttle when VIO is stable and running
123 const bool is_static = !(non_static.load(std::memory_order_acquire));
124 const bool acc_no_jerk = !(has_acc_jerk.load(std::memory_order_acquire));
125
126 if (en_debug)
127 {
128 printf("is_static: %d, acc_no_jerk: %d, drop_frames: %d\n",
129 is_static, acc_no_jerk, drop_frames);
130 }
131
132 if (is_static || acc_no_jerk)
133 {
134 if (!drop_frames)
135 {
136 drop_frames = true;
137 if (en_debug)
138 printf("normal frame\n");
139 }
140 else
141 {
142 // Drop this frame - platform is static
143 if (img_type == voxl::ImageType::CL_MEM)
144 release_cl_mem(frame);
145 drop_frames = false;
146 if (en_debug)
147 printf("dropping frame\n");
148 if (active_callbacks.fetch_sub(1, std::memory_order_release) == 1)
149 {
150 std::lock_guard<std::mutex> lk(reset_mtx);
151 reset_cv.notify_one();
152 }
153 return;
154 }
155 }
156 }
157 else if (!vio_ready_for_throttling && en_debug)
158 {
159 // During initialization or unstable state, process ALL frames
160 printf("[INIT] VIO not stable - processing ALL frames (no throttling)\n");
161 }
162 // Update dimensions
163 current_height = meta.height;
164 current_width = meta.width;
165
166 if (img_type == voxl::ImageType::CV_MAT)
167 {
168 // Process only supported formats with fast path
169 if (meta.format == IMAGE_FORMAT_RAW8)
170 {
171 process_raw8(meta, (char *)frame);
172 }
173 else
174 {
175 // Rare case, can be slower
176 fprintf(stderr, "Unsupported image format: %d\n", meta.format);
177 vio_error_codes |= ERROR_CODE_CAM_BAD_FORMAT;
178 }
179 }
180 if (img_type == voxl::ImageType::CL_MEM)
181 {
182 // Process only supported formats
183 if (meta.format == IMAGE_FORMAT_RAW8)
184 {
185 process_device_buf_raw8(meta, (cl_mem)frame);
186 }
187 else
188 {
189 // Rare case, can be slower
190 fprintf(stderr, "Unsupported image format: %d\n", meta.format);
191 vio_error_codes |= ERROR_CODE_CAM_BAD_FORMAT;
192 }
193 }
194 skip_jerk_detection = false;
195 // check if in flight processing count reaches zero and if a reset is requested
196 if (active_callbacks.fetch_sub(1, std::memory_order_release) == 1)
197 {
198 // Notify the reset thread to continue processing
199 std::lock_guard<std::mutex> lk(reset_mtx);
200 reset_cv.notify_one();
201 }
202
203 // Mark last processed timestamp (only when we actually processed)
204 last_processed_ts_ns_ = meta.timestamp_ns;
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 if (!camera_queue.push(message))
267 {
268 if (true)
269 {
270 // TODO: DROP OLDEST FRAME, ADD NEW FRAME --> RIGHT NOW WE JUST DROP THE NEW FRAME, NOT KOSHER
271 std::cerr << "Camera queue full — dropping frame from cam " << get_channel() << std::endl;
272 vio_error_codes |= ERROR_CODE_DROPPED_CAM;
273 }
274 }
275 else
276 {
277 // Notify fusion system that camera data is ready
279 }
280 }
281
282 void MonoCamera::process_device_buf_raw8(const camera_image_metadata_t &meta, cl_mem frame)
283 {
285 curr_message_.metadata = meta;
286
287 // Check if dimensions changed and update mask efficiently
288 const bool dimensions_changed = (use_mask_.rows != current_height || use_mask_.cols != current_width);
289 if (dimensions_changed)
290 {
291 mask_dimensions_changed_ = true;
292 }
293
294 // Determine if mask should be active based on occlusion and altitude
295 const bool altitude_below_threshold = std::abs(alt_z.load(std::memory_order_relaxed)) < takeoff_alt_threshold;
296 const bool should_mask = camera_info_.is_occluded_on_takeoff && altitude_below_threshold && !occlusion_threshold_passed_;
297
298 if (!altitude_below_threshold && camera_info_.is_occluded_on_takeoff) {
299 occlusion_threshold_passed_ = true;
300 }
301
302 // Update mask only when necessary
303 update_mask_if_needed(should_mask);
304
305 ov_core::CameraData message;
306 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
307 message.sensor_ids.push_back(get_id());
308
309 // modal_flow::ImageView ivA{{camA.width, camA.height, modal_flow::PixelFormat::R8, img_prev.step}, img_prev.data, modal_flow::ExternalType::None, 0};
310
311 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))};
312 modal_flow::Frame img_frame({get_id(), 0, iv});
313
314 message.cl_images.emplace_back(frame);
315
316 message.img_frames.push_back(img_frame);
317
318 message.images.emplace_back(cv::Mat::zeros(meta.height, meta.width, CV_8UC1));
319
320 message.masks.emplace_back(use_mask_);
321
322 if (!camera_queue.push(message))
323 {
324 if (true)
325 {
326 // TODO: DROP OLDEST FRAME, ADD NEW FRAME --> RIGHT NOW WE JUST DROP THE NEW FRAME, NOT KOSHER
327 std::cerr << "Camera queue full — dropping frame from cam " << get_channel() << std::endl;
328 vio_error_codes |= ERROR_CODE_DROPPED_CAM;
329 }
330 }
331 else
332 {
333 // Notify fusion system that camera data is ready
335 }
336 }
337
338 void MonoCamera::update_mask_if_needed(bool should_mask)
339 {
340 // Check if we need to update the mask
341 const bool needs_update = !current_mask_state_.has_value() ||
342 current_mask_state_.value() != should_mask ||
343 mask_dimensions_changed_;
344
345 if (!needs_update)
346 {
347 return;
348 }
349
350 // Update mask state
351 current_mask_state_ = should_mask;
352 mask_dimensions_changed_ = false;
353
354 // Create or update mask efficiently
355 if (should_mask)
356 {
357 // Use cv::Scalar constructor for better performance
358 use_mask_ = cv::Mat(current_height, current_width, CV_8UC1, cv::Scalar(255));
359 }
360 else
361 {
362 // Use cv::Scalar constructor for better performance
363 use_mask_ = cv::Mat(current_height, current_width, CV_8UC1, cv::Scalar(0));
364 }
365 }
366
367 /**
368 * @brief Check if system is in reset state
369 *
370 * Determines whether the VIO system is currently in a reset state,
371 * which affects how image processing should be handled.
372 *
373 * @return true if system is resetting, false otherwise
374 */
375 bool MonoCamera::is_system_resetting() const
376 {
377 return is_resetting;
378 }
379
380 /**
381 * @brief Check if system is ready to process images
382 *
383 * Determines whether the VIO system is ready to accept and process
384 * new image data. The system is considered ready when both the IMU
385 * is connected and the main process is running.
386 *
387 * @return true if system is ready, false otherwise
388 */
389 bool MonoCamera::is_system_ready() const
390 {
392 }
393
394} // namespace voxl
Monocular camera implementation for VOXL OpenVINS.
volatile int64_t last_cam_time
Timestamp of last camera data (nanoseconds)
Definition VoxlVars.cpp:199
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:56
std::mutex reset_mtx
Mutex used by reset thread.
Definition VoxlVars.cpp:59
float takeoff_alt_threshold
Takeoff altitude threshold.
Definition VoxlVars.cpp:208
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:148
std::condition_variable reset_cv
Reset conditional variable.
Definition VoxlVars.cpp:62
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.
static CameraQueueFusion & getInstance()
Get singleton instance.
void markCameraReady(size_t cam_id)
Mark a camera as ready with new data.
Base class for all camera implementations.
Definition CameraBase.h:57
bool drop_frames
Indicates if frames should be dropped.
Definition CameraBase.h:187
cv::Mat use_mask_
Per-instance reusable mask for feature tracking.
Definition CameraBase.h:184
int get_channel() const
Get the camera pipe channel.
Definition CameraBase.h:113
cam_info camera_info_
Camera configuration information.
Definition CameraBase.h:155
bool skip_jerk_detection
Indicates if jerk detection should be skipped.
Definition CameraBase.h:190
size_t get_id() const
Get the camera identifier.
Definition CameraBase.h:119
img_ringbuf_packet curr_message_
Instance-local buffer for image processing.
Definition CameraBase.h:181
boost::lockfree::spsc_queue< ov_core::CameraData, boost::lockfree::capacity< 64 > > camera_queue
Lock-free SPSC queue for camera data.
Definition CameraBase.h:178
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:198
bool is_occluded_on_takeoff
Flag indicating if camera is occluded on takeoff.
Definition VoxlCommon.h:208
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