MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VisionExecutor.cpp
Go to the documentation of this file.
1#include "VisionExecutor.hpp"
2
3#include "Contours.hpp"
4#include "Gradient.hpp"
5#include "Harris.hpp"
6#include "ImageFilter.hpp"
7#include "Morphology.hpp"
8#include "OpticalFlow.hpp"
9#include "PixelOps.hpp"
10
12
13namespace P = MayaFlux::Parallel;
14
16
17namespace {
18
19 template <typename T>
20 const T& get_params(const VisionParams& p, VisionOp op)
21 {
22 const T* ptr = std::get_if<T>(&p);
23 if (!ptr) {
24 error<std::logic_error>(
26 std::source_location::current(),
27 "VisionExecutor: parameter type mismatch for op {}",
29 }
30 return *ptr;
31 }
32
33} // namespace
34
35// =============================================================================
36// Slot management
37// =============================================================================
38
39void VisionExecutor::ensure_slots(uint32_t w, uint32_t h)
40{
41 if (m_slot_w == w && m_slot_h == h)
42 return;
43
44 const size_t n = static_cast<size_t>(w) * h;
45
46 for (size_t i = 0; i < k_slot_count; ++i)
47 m_slots[i] = std::vector<float>(n, 0.0F);
48
49 m_slot_w = w;
50 m_slot_h = h;
51}
52
53// =============================================================================
54// Kernel cache
55// =============================================================================
56
57const std::vector<float>& VisionExecutor::gaussian_kernel(float sigma)
58{
59 const auto key = std::bit_cast<uint32_t>(sigma);
60 auto it = m_kernel_cache.find(key);
61 if (it != m_kernel_cache.end())
62 return it->second;
63
64 const auto radius = static_cast<int32_t>(std::ceil(3.0F * sigma));
65 const int32_t size = 2 * radius + 1;
66 const float inv_2s2 = 1.0F / (2.0F * sigma * sigma);
67
68 std::vector<float> k(static_cast<size_t>(size));
69 float sum = 0.0F;
70 for (int32_t i = -radius; i <= radius; ++i) {
71 const float v = std::exp(-static_cast<float>(i * i) * inv_2s2);
72 k[static_cast<size_t>(i + radius)] = v;
73 sum += v;
74 }
75
76 auto kmap = Eigen::Map<Eigen::ArrayXf>(k.data(), static_cast<Eigen::Index>(k.size()));
77 kmap /= sum;
78
79 return m_kernel_cache.emplace(key, std::move(k)).first->second;
80}
81
82// =============================================================================
83// reset
84// =============================================================================
85
87{
88 std::get<std::vector<float>>(m_prev_gray).clear();
89 m_curr_gray_cache.clear();
90 m_prev_keypoints.clear();
91}
92
93// =============================================================================
94// run
95// =============================================================================
96
98 const VisionSequence& sequence,
99 std::span<const float> frame,
100 uint32_t w, uint32_t h)
101{
102 ensure_slots(w, h);
103 const size_t slot_min = static_cast<size_t>(m_slot_w) * m_slot_h * 4;
104 if (slot_vec(k_slot_nxt).size() < slot_min)
105 slot_vec(k_slot_nxt).resize(slot_min);
106 if (slot_vec(k_slot_cur).capacity() < slot_min)
107 slot_vec(k_slot_cur).reserve(slot_min);
108
109 uint32_t channels = 4;
110
111 auto& cur_vec = slot_vec(k_slot_cur);
112 cur_vec.assign(frame.begin(), frame.end());
113
114 size_t cur = k_slot_cur;
115 size_t nxt = k_slot_nxt;
116 auto en = static_cast<Eigen::Index>(static_cast<size_t>(w) * h);
117
118 VisionResult result;
119 result.w = w;
120 result.h = h;
121
122 for (const auto& step : sequence.steps) {
123 switch (step.op) {
124
126 uint32_t new_w = 0, new_h = 0;
127 downsample_2x(slot_vec(cur), slot_vec(nxt), w, h, channels, new_w, new_h);
128 w = new_w;
129 h = new_h;
130 result.w = w;
131 result.h = h;
132 en = static_cast<Eigen::Index>(static_cast<size_t>(w) * h);
133 std::swap(cur, nxt);
134 result.structured = std::monostate {};
135 break;
136 }
137
139 rgba_to_gray(slot_vec(cur), slot_vec(nxt), w, h);
140 channels = 1;
141 std::swap(cur, nxt);
142
143 if (sequence.tracks_keypoints && !sequence.track_follows_peaks) {
144 m_curr_gray_cache.assign(slot_vec(cur).begin(),
145 slot_vec(cur).begin() + static_cast<size_t>(w) * h);
146 }
147
148 result.structured = std::monostate {};
149 break;
150 }
151
152 case VisionOp::RgbaToHsv: {
153 rgba_to_hsv(slot_vec(cur), slot_vec(nxt), w, h);
154 channels = 3;
155 std::swap(cur, nxt);
156 result.structured = std::monostate {};
157 break;
158 }
159
161 gray_to_rgba(slot_vec(cur), slot_vec(nxt), w, h);
162 channels = 4;
163 std::swap(cur, nxt);
164 result.structured = std::monostate {};
165 break;
166 }
167
168 case VisionOp::Threshold: {
169 const auto& p = get_params<ThresholdParams>(step.params, step.op);
170 slot_map_mut(cur, en) = (slot_map(cur, en) >= p.value).cast<float>();
171 result.structured = std::monostate {};
172 break;
173 }
174
176 const auto& p = get_params<ThresholdAdaptiveParams>(step.params, step.op);
177 threshold_adaptive(slot_vec(cur), slot_vec(nxt), w, h, p.block_size, p.offset);
178 std::swap(cur, nxt);
179 result.structured = std::monostate {};
180 break;
181 }
182
184 threshold_otsu(slot_vec(cur), slot_vec(nxt));
185 std::swap(cur, nxt);
186 result.structured = std::monostate {};
187 break;
188 }
189
191 auto m = slot_map_mut(cur, en);
192 const float mn = m.minCoeff();
193 const float mx = m.maxCoeff();
194 if (mx > mn)
195 m = (m - mn) / (mx - mn);
196 result.structured = std::monostate {};
197 break;
198 }
199
201 const auto& p = get_params<NormalizeRangeParams>(step.params, step.op);
202 if (p.hi > p.lo) {
203 auto m = slot_map_mut(cur, en);
204 m = ((m - p.lo) / (p.hi - p.lo)).max(0.0F).min(1.0F);
205 }
206 result.structured = std::monostate {};
207 break;
208 }
209
211 const auto& p = get_params<GaussianBlurParams>(step.params, step.op);
212 const auto& kern = gaussian_kernel(p.sigma);
213 filter_separable(slot_vec(cur), slot_vec(k_slot_tmp), slot_vec(nxt), w, h, kern, kern);
214 std::swap(cur, nxt);
215 result.structured = std::monostate {};
216 break;
217 }
218
220 const auto& p = get_params<FilterSeparableParams>(step.params, step.op);
221 filter_separable(slot_vec(cur), slot_vec(k_slot_tmp), slot_vec(nxt), w, h, p.kernel_x, p.kernel_y);
222 std::swap(cur, nxt);
223 result.structured = std::monostate {};
224 break;
225 }
226
227 case VisionOp::Sobel: {
228 GradientResult grad;
230 slot_vec(k_slot_tmp), w, h);
231
232 auto dx = slot_map(k_slot_dx, en);
233 auto dy = slot_map(k_slot_dy, en);
234 slot_map_mut(nxt, en) = (dx.square() + dy.square()).sqrt();
235 const float peak = slot_map(nxt, en).maxCoeff();
236
237 if (peak > 0.0F)
238 slot_map_mut(nxt, en) /= peak;
239
240 grad.dx.assign(slot_vec(k_slot_dx).begin(), slot_vec(k_slot_dx).begin() + en);
241 grad.dy.assign(slot_vec(k_slot_dy).begin(), slot_vec(k_slot_dy).begin() + en);
242 grad.magnitude.assign(slot_vec(nxt).begin(), slot_vec(nxt).begin() + en);
243 grad.angle.resize(static_cast<size_t>(en));
244
245 P::transform(P::par_unseq,
246 slot_vec(k_slot_dx).begin(), slot_vec(k_slot_dx).begin() + en,
247 slot_vec(k_slot_dy).begin(), grad.angle.begin(),
248 [](float gx, float gy) { return std::atan2(gy, gx); });
249
250 result.structured = std::move(grad);
251 std::swap(cur, nxt);
252 break;
253 }
254
255 case VisionOp::Scharr: {
256 GradientResult grad;
258 slot_vec(k_slot_tmp), w, h);
259
260 auto dx = slot_map(k_slot_dx, en);
261 auto dy = slot_map(k_slot_dy, en);
262 slot_map_mut(nxt, en) = (dx.square() + dy.square()).sqrt();
263
264 const float peak = slot_map(nxt, en).maxCoeff();
265 if (peak > 0.0F)
266 slot_map_mut(nxt, en) /= peak;
267
268 grad.dx.assign(slot_vec(k_slot_dx).begin(), slot_vec(k_slot_dx).begin() + en);
269 grad.dy.assign(slot_vec(k_slot_dy).begin(), slot_vec(k_slot_dy).begin() + en);
270 grad.magnitude.assign(slot_vec(nxt).begin(), slot_vec(nxt).begin() + en);
271 grad.angle.resize(static_cast<size_t>(en));
272
273 P::transform(P::par_unseq,
274 slot_vec(k_slot_dx).begin(), slot_vec(k_slot_dx).begin() + en,
275 slot_vec(k_slot_dy).begin(), grad.angle.begin(),
276 [](float gx, float gy) { return std::atan2(gy, gx); });
277
278 result.structured = std::move(grad);
279 std::swap(cur, nxt);
280 break;
281 }
282
283 case VisionOp::Canny: {
284 const auto& p = get_params<CannyParams>(step.params, step.op);
285 canny(slot_vec(cur), slot_vec(nxt), w, h, p.sigma, p.low_threshold, p.high_threshold);
286 std::swap(cur, nxt);
287 result.structured = std::monostate {};
288 break;
289 }
290
291 case VisionOp::Erode: {
292 const auto& p = get_params<MorphParams>(step.params, step.op);
293 erode(slot_vec(cur), slot_vec(nxt), w, h, p.radius);
294 std::swap(cur, nxt);
295 result.structured = std::monostate {};
296 break;
297 }
298
299 case VisionOp::Dilate: {
300 const auto& p = get_params<MorphParams>(step.params, step.op);
301 dilate(slot_vec(cur), slot_vec(nxt), w, h, p.radius);
302 std::swap(cur, nxt);
303 result.structured = std::monostate {};
304 break;
305 }
306
307 case VisionOp::Open: {
308 const auto& p = get_params<MorphParams>(step.params, step.op);
309 open(slot_vec(cur), slot_vec(k_slot_tmp), slot_vec(nxt), w, h, p.radius);
310 std::swap(cur, nxt);
311 result.structured = std::monostate {};
312 break;
313 }
314
315 case VisionOp::Close: {
316 const auto& p = get_params<MorphParams>(step.params, step.op);
317 close(slot_vec(cur), slot_vec(k_slot_tmp), slot_vec(nxt), w, h, p.radius);
318 std::swap(cur, nxt);
319 result.structured = std::monostate {};
320 break;
321 }
322
324 const auto& p = get_params<MorphParams>(step.params, step.op);
325 morph_gradient(slot_vec(cur), slot_vec(k_slot_tmp), slot_vec(nxt), w, h, p.radius);
326 std::swap(cur, nxt);
327 result.structured = std::monostate {};
328 break;
329 }
330
332 const size_t n = static_cast<size_t>(w) * h;
334 std::span<const float>(slot_vec(cur)).subspan(0, n),
335 w, h);
336 slot_vec(cur).clear();
337 result.w = 0;
338 result.h = 0;
339 break;
340 }
341
343 const auto& p = get_params<FindContoursParams>(step.params, step.op);
344 const size_t n = static_cast<size_t>(w) * h;
345 result.structured = find_contours(
346 std::span<const float>(slot_vec(cur)).subspan(0, n),
347 w, h, p.min_area, p.max_contours);
348 slot_vec(cur).clear();
349 result.w = 0;
350 result.h = 0;
351 break;
352 }
353
355 const auto& p = get_params<HarrisParams>(step.params, step.op);
357 slot_vec(cur),
361 slot_vec(nxt),
362 w, h, p.k, gaussian_kernel(p.sigma));
363 std::swap(cur, nxt);
364 result.structured = std::monostate {};
365 break;
366 }
367
369 const auto& p = get_params<ExtractPeaksParams>(step.params, step.op);
370 auto kpts = extract_peaks(slot_vec(cur), w, h, p.threshold, p.nms_radius);
371 m_prev_keypoints = kpts;
372
373 if (sequence.tracks_keypoints && !sequence.track_follows_peaks)
374 std::swap(std::get<std::vector<float>>(m_prev_gray), m_curr_gray_cache);
375
376 result.structured = std::move(kpts);
377 if (!sequence.track_follows_peaks) {
378 slot_vec(cur).clear();
379 result.w = 0;
380 result.h = 0;
381 }
382 break;
383 }
384
386 const auto& p = get_params<TrackKeypointsParams>(step.params, step.op);
387
388 auto& prev_vec = std::get<std::vector<float>>(m_prev_gray);
389 if (prev_vec.empty() || m_prev_keypoints.empty()) {
390 std::swap(prev_vec, slot_vec(cur));
391 result.structured = std::vector<TrackResult> {};
392 slot_vec(cur).clear();
393 result.w = 0;
394 result.h = 0;
395 break;
396 }
397
398 std::vector<glm::vec2> prev_pos;
399 prev_pos.reserve(m_prev_keypoints.size());
400 for (const auto& kp : m_prev_keypoints)
401 prev_pos.push_back(kp.position);
402
403 auto tracked = track_keypoints(
404 prev_vec, slot_vec(cur),
405 w, h, prev_pos,
406 p.window_radius, p.max_iterations,
407 p.eigen_threshold, p.error_threshold);
408
409 std::swap(prev_vec, slot_vec(cur));
410 result.structured = std::move(tracked);
411 slot_vec(cur).clear();
412 result.w = 0;
413 result.h = 0;
414 break;
415 }
416
417 case VisionOp::Snapshot: {
418 if (slot_vec(cur).empty())
419 break;
420 const size_t n = static_cast<size_t>(w) * h * channels;
421 SnapshotEntry entry;
422 entry.pixels.assign(slot_vec(cur).begin(), slot_vec(cur).begin() + n);
423 entry.w = w;
424 entry.h = h;
425 entry.channels = channels;
426 result.snapshots.push_back(std::move(entry));
427 break;
428 }
429 } // switch
430 }
431
432 result.pixel_image = std::move(m_slots[cur]);
433 slot_vec(cur).reserve(static_cast<size_t>(m_slot_w) * m_slot_h * 4);
434 return result;
435}
436
437} // namespace MayaFlux::Kinesis::Vision
Contour extraction from binary float masks.
Gradient and edge detection on normalised float image spans.
Harris corner detection and non-maximum suppression.
2D spatial filtering on normalised float image spans.
uint32_t h
Definition InkPress.cpp:28
Sparse optical flow via Lucas-Kanade tracker.
Pointwise pixel operations for MayaFlux::Kinesis::Vision.
const uint8_t * ptr
Dispatch engine for VisionSequence execution.
uint32_t radius
float k
float sigma
Binary morphological operations on normalised float masks.
std::array< Kakshya::DataVariant, k_slot_count > m_slots
Eigen::Map< Eigen::ArrayXf > slot_map_mut(size_t i, Eigen::Index n) noexcept
Zero-copy mutable Eigen::Map<ArrayXf> over slot i.
std::vector< float > & slot_vec(size_t i) noexcept
Mutable reference to the vector<float> inside slot i.
void reset()
Clear stored inter-frame state.
std::unordered_map< uint32_t, std::vector< float > > m_kernel_cache
VisionResult run(const VisionSequence &sequence, std::span< const float > frame, uint32_t w, uint32_t h)
Execute a VisionSequence on one frame.
void ensure_slots(uint32_t w, uint32_t h)
Ensure all slots are sized to n_pixels floats.
const std::vector< float > & gaussian_kernel(float sigma)
Return a reference to the precomputed 1D Gaussian kernel for sigma.
Eigen::Map< const Eigen::ArrayXf > slot_map(size_t i, Eigen::Index n) const noexcept
Zero-copy read-only Eigen::Map<const ArrayXf> over slot i.
@ Runtime
General runtime operations (default fallback)
@ Kinesis
General mathematical and physics algorithns.
void rgba_to_gray(std::span< const float > rgba, std::span< float > dst, uint32_t w, uint32_t h)
Convert RGBA to luminance gray using BT.601 coefficients.
Definition PixelOps.cpp:18
std::vector< float > filter_separable(std::span< const float > src, uint32_t w, uint32_t h, std::span< const float > kernel_x, std::span< const float > kernel_y)
Apply a separable 2D filter via two 1D passes.
std::vector< float > canny(std::span< const float > gray, uint32_t w, uint32_t h, float sigma, float low_threshold, float high_threshold)
Canny edge detector.
Definition Gradient.cpp:132
void gray_to_rgba(std::span< const float > gray, std::span< float > dst, uint32_t w, uint32_t h)
Global threshold writing into caller-supplied buffer.
Definition PixelOps.cpp:138
std::vector< Keypoint > extract_peaks(std::span< const float > response, uint32_t w, uint32_t h, float threshold, uint32_t nms_radius)
Extract peaks from a response map via non-maximum suppression.
Definition Harris.cpp:184
void erode(std::span< const float > mask, std::span< float > dst, uint32_t w, uint32_t h, uint32_t radius)
Erosion writing into dst.
std::vector< float > harris_response(std::span< const float > gray, uint32_t w, uint32_t h, float k, float sigma)
Compute the Harris corner response map.
Definition Harris.cpp:19
ComponentResult connected_components(std::span< const float > mask, uint32_t w, uint32_t h)
Label connected foreground components in a binary mask.
void rgba_to_hsv(std::span< const float > rgba, std::span< float > dst, uint32_t w, uint32_t h)
Convert RGBA to HSV writing into caller-supplied buffer.
Definition PixelOps.cpp:94
std::vector< Contour > find_contours(std::span< const float > mask, uint32_t w, uint32_t h, float min_area, uint32_t max_contours)
Extract outer contours from a binary mask.
Definition Contours.cpp:70
void close(std::span< const float > mask, std::span< float > tmp, std::span< float > dst, uint32_t w, uint32_t h, uint32_t radius)
Morphological closing writing into dst.
void sobel(std::span< const float > gray, std::span< float > dx, std::span< float > dy, std::span< float > tmp, uint32_t w, uint32_t h)
Sobel gradient writing dx and dy into caller-supplied buffers.
Definition Gradient.cpp:66
std::variant< std::monostate, ThresholdParams, ThresholdAdaptiveParams, NormalizeRangeParams, GaussianBlurParams, FilterSeparableParams, CannyParams, MorphParams, HarrisParams, ExtractPeaksParams, TrackKeypointsParams, ConnectedComponentsParams, FindContoursParams > VisionParams
Parameter variant covering all ops that carry parameters.
Definition VisionOp.hpp:148
void dilate(std::span< const float > mask, std::span< float > dst, uint32_t w, uint32_t h, uint32_t radius)
Dilation writing into dst.
VisionOp
Named operations available in a VisionSequence.
Definition VisionOp.hpp:29
std::vector< TrackResult > track_keypoints(std::span< const float > prev_gray, std::span< const float > curr_gray, uint32_t w, uint32_t h, std::span< const glm::vec2 > prev_points, uint32_t window_radius, uint32_t max_iterations, float eigen_threshold, float error_threshold)
Track keypoints from prev_gray to curr_gray via Lucas-Kanade.
void scharr(std::span< const float > gray, std::span< float > dx, std::span< float > dy, std::span< float > tmp, uint32_t w, uint32_t h)
Scharr gradient writing dx and dy into caller-supplied buffers.
Definition Gradient.cpp:99
void threshold_otsu(std::span< const float > gray, std::span< float > dst)
Otsu threshold writing into caller-supplied buffer.
Definition PixelOps.cpp:299
void threshold_adaptive(std::span< const float > gray, std::span< float > dst, uint32_t w, uint32_t h, uint32_t block_size, float offset)
Adaptive threshold writing into caller-supplied buffer.
Definition PixelOps.cpp:258
void downsample_2x(std::span< const float > src, std::span< float > dst, uint32_t w, uint32_t h, uint32_t &new_w, uint32_t &new_h)
2x box-filter downsample writing into a caller-supplied buffer.
Definition PixelOps.cpp:443
void morph_gradient(std::span< const float > mask, std::span< float > tmp, std::span< float > dst, uint32_t w, uint32_t h, uint32_t radius)
Morphological gradient (dilate - erode) writing into dst.
void open(std::span< const float > mask, std::span< float > tmp, std::span< float > dst, uint32_t w, uint32_t h, uint32_t radius)
Morphological opening writing into dst.
constexpr std::string_view enum_to_string(EnumType value) noexcept
Universal enum to string converter using magic_enum (original case)
double peak(const std::vector< double > &data)
Find peak amplitude in single-channel data.
Definition Yantra.cpp:268
std::vector< float > angle
Gradient angle in radians, [-pi, pi].
Definition Gradient.hpp:26
std::vector< float > magnitude
Gradient magnitude, normalised to [0, 1].
Definition Gradient.hpp:25
std::vector< float > dx
Horizontal gradient component.
Definition Gradient.hpp:23
std::vector< float > dy
Vertical gradient component.
Definition Gradient.hpp:24
Gradient maps produced by Sobel and Scharr operators.
Definition Gradient.hpp:22
Pixel buffer captured mid-pipeline by a Snapshot step.
Definition Features.hpp:74
std::vector< SnapshotEntry > snapshots
Result of executing a VisionSequence on one frame.
Ordered sequence of VisionSteps describing a complete vision pipeline.
Definition VisionOp.hpp:163