MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches

◆ op_track_keypoints()

bool MayaFlux::Yantra::VisionGpuExecutor::op_track_keypoints ( VisionGpuContexts &  contexts,
const Kinesis::Vision::VisionStep &  step 
)
private

Track the previous frame's keypoints into the current frame.

Adjacency mirrors ConnectedComponents into FindContours: ExtractPeaks must immediately precede, and its work is folded into this step so the detections are written straight into the flow context's buffers. The whole step is one dependency sequence: peaks, one Lucas-Kanade dispatch per pyramid level from coarse to fine, and the selection phases that build the next point list. Tracks persist: survivors carry over with their id and age, and new detections fill only the freed capacity, strongest first, one per grid cell. The first frame after a reset runs peaks and selection only.

Returns
True when the step was deferred and the run must suspend.

Definition at line 311 of file VisionFlowDispatch.cpp.

312{
313 auto& state = contexts.flow_state;
314 auto& flow = contexts.flow;
315 auto& foundry = Portal::Graphics::get_shader_foundry();
316
317 contexts.pass.result.structured = std::vector<Kinesis::Vision::TrackResult> {};
318 contexts.pass.result.w = 0;
319 contexts.pass.result.h = 0;
320
321 const auto* peaks = contexts.pass.behind();
322 if (!peaks || peaks->op != VisionOp::ExtractPeaks) {
324 "run_gpu: TrackKeypoints requires ExtractPeaks as the immediately preceding step");
325 return false;
326 }
327
328 if (!state.curr_ready || !state.atlas[0]) {
330 "run_gpu: TrackKeypoints requires an earlier RgbaToGray step to supply the gray frame");
331 return false;
332 }
333
334 const auto& p = std::get<TrackKeypointsParams>(step.params);
335 const auto& pk = std::get<ExtractPeaksParams>(peaks->params);
336 const auto& layout = state.layout;
337 const uint32_t w = contexts.pass.w;
338 const uint32_t h = contexts.pass.h;
339 const uint32_t max_points = std::clamp(p.max_points, 1U, k_flow_max_points);
340
341 if (w != layout.level[0].w || h != layout.level[0].h) {
343 "run_gpu: TrackKeypoints image is {}x{} but the gray frame was captured at {}x{}",
344 w, h, layout.level[0].w, layout.level[0].h);
345 return false;
346 }
347
348 const bool export_tracks = p.export_tracks;
349 const uint32_t duplicate_cutoff = (state.last_track_count > 0 && state.last_export)
351 : 0U;
352
353 const auto response = contexts.pass.current;
354 const auto atlas_a = state.atlas[0];
355 const auto atlas_b = state.atlas[1];
356
357 const ExtractPeaksPC peaks_pc {
358 .threshold = pk.threshold,
359 .nms_radius = pk.nms_radius,
360 .width = w,
361 .height = h,
362 .max_keypoints = k_flow_max_points,
363 .rect = region_rect(pk.region, w, h),
364 };
365
366 std::vector<DependencyStage> stages;
367 stages.reserve(layout.levels * (p.forward_backward_threshold > 0.0F ? 2U : 1U) + 8U);
368
369 stages.push_back({
370 .config = { .shader_path = "extract_peaks.comp.spv", .workgroup_size = k_wg2d, .push_constant_size = sizeof(ExtractPeaksPC) },
371 .stage_fn = [response, peaks_pc](GpuDispatchCore& ctx) {
372 ctx.stage_image_at(0, response, GpuBufferBinding::ElementType::IMAGE_STORAGE);
373 ctx.set_push_constants(peaks_pc); },
374 .hazard_fn = [](GpuDispatchCore& ctx) { return flow_hazards(ctx, { k_flow_det_count, k_flow_det_points }); },
375 .explicit_groups = std::array<uint32_t, 3> { (w + k_wg2d[0] - 1U) / k_wg2d[0], (h + k_wg2d[1] - 1U) / k_wg2d[1], 1U },
376 });
377
378 const auto base_w = static_cast<float>(layout.level[0].w);
379 const auto base_h = static_cast<float>(layout.level[0].h);
380 float cell = std::max(p.min_distance, 1.0F);
381 auto grid_w = static_cast<uint32_t>(std::ceil(base_w / cell));
382 auto grid_h = static_cast<uint32_t>(std::ceil(base_h / cell));
383 while (static_cast<uint64_t>(grid_w) * grid_h > k_flow_grid_capacity) {
384 cell *= 1.1F;
385 grid_w = static_cast<uint32_t>(std::ceil(base_w / cell));
386 grid_h = static_cast<uint32_t>(std::ceil(base_h / cell));
387 }
388 const uint32_t grid_cells = grid_w * grid_h;
389 const uint32_t clear_groups = std::max(1U, (grid_cells + k_flow_select_local - 1U) / k_flow_select_local);
390 const uint32_t candidate_groups = (k_flow_max_points + k_flow_select_local - 1U) / k_flow_select_local;
391 const uint32_t have_prev = state.have_prev ? 1U : 0U;
392
393 const auto add_select = [&](FlowSelectPhase phase, uint32_t groups, std::vector<FlowBufferSpec> touched, std::optional<FlowArgs> args = std::nullopt) {
394 const FlowSelectPC pc {
395 .phase = static_cast<uint32_t>(phase),
396 .max_points = max_points,
397 .have_prev = have_prev,
398 .grid_w = grid_w,
399 .grid_h = grid_h,
400 .grid_cells = grid_cells,
401 .cell = cell,
402 .base_w = base_w,
403 .base_h = base_h,
404 .publish_slot = state.export_slot,
405 .duplicate_cutoff = duplicate_cutoff,
406 };
407 stages.push_back({
408 .config = { .shader_path = "flow_select.comp.spv", .workgroup_size = { k_flow_select_local, 1, 1 }, .push_constant_size = sizeof(FlowSelectPC) },
409 .stage_fn = [pc](GpuDispatchCore& ctx) { ctx.set_push_constants(pc); },
410 .hazard_fn = [touched = std::move(touched)](GpuDispatchCore& ctx) { return flow_hazards(ctx, touched); },
411 .explicit_groups = std::array<uint32_t, 3> { groups, 1U, 1U },
412 .indirect_groups = args ? std::optional<IndirectGroupsSource> { flow_args_source(*args) } : std::nullopt,
413 });
414 };
415
416 add_select(FlowSelectPhase::ARGS, 1U, {});
417
418 const auto add_lk = [&](bool backward) {
419 for (uint32_t level = layout.levels; level-- > 0;) {
420 const PyramidLevel lv = layout.level[level];
421 const bool finest = level == 0;
422
423 const FlowLkPC pc {
424 .curr_atlas = state.curr,
425 .level = level,
426 .coarsest = level + 1U == layout.levels ? 1U : 0U,
427 .pad0 = 0,
428 .lvl_ox = lv.ox,
429 .lvl_oy = lv.oy,
430 .lvl_w = lv.w,
431 .lvl_h = lv.h,
432 .base_w = layout.level[0].w,
433 .base_h = layout.level[0].h,
434 .window_radius = std::min(p.window_radius, k_flow_max_radius),
435 .max_iterations = p.max_iterations,
436 .eigen_threshold = p.eigen_threshold,
437 .error_threshold = p.error_threshold,
438 .max_points = max_points,
439 .pad1 = 0,
440 .backward = backward ? 1U : 0U,
441 .forward_backward_threshold = p.forward_backward_threshold,
442 };
443
444 stages.push_back({
445 .config = { .shader_path = "flow_lk.comp.spv", .workgroup_size = { 64, 1, 1 }, .push_constant_size = sizeof(FlowLkPC) },
446 .stage_fn = [atlas_a, atlas_b, pc](GpuDispatchCore& ctx) {
447 ctx.stage_image_at(3, atlas_a, GpuBufferBinding::ElementType::IMAGE_STORAGE);
448 ctx.stage_image_at(4, atlas_b, GpuBufferBinding::ElementType::IMAGE_STORAGE);
449 ctx.set_push_constants(pc); },
450 .hazard_fn = [finest, backward](GpuDispatchCore& ctx) {
451 if (finest || backward)
452 return flow_hazards(ctx, { k_flow_tracks, k_flow_meta, k_flow_prev_count, k_flow_prev_points });
453 return flow_hazards(ctx, { k_flow_tracks, k_flow_meta }); },
454 .explicit_groups = std::array<uint32_t, 3> { k_flow_max_points, 1U, 1U },
455 .indirect_groups = flow_args_source(FlowArgs::LK),
456 });
457 }
458 };
459 if (state.have_prev) {
460 add_lk(false);
461 if (p.forward_backward_threshold > 0.0F)
462 add_lk(true);
463 }
464
465 add_select(FlowSelectPhase::CLEAR, clear_groups, { k_flow_grid, k_flow_histogram, k_flow_counters });
466 add_select(FlowSelectPhase::SURVIVORS, candidate_groups, { k_flow_next_points, k_flow_counters, k_flow_grid }, FlowArgs::RETAINED);
467 add_select(FlowSelectPhase::HISTOGRAM, candidate_groups, { k_flow_histogram }, FlowArgs::DETECTED);
468 add_select(FlowSelectPhase::THRESHOLD, 1U, { k_flow_counters });
469 add_select(FlowSelectPhase::ADD_STRONG, candidate_groups, { k_flow_next_points, k_flow_counters, k_flow_grid }, FlowArgs::DETECTED);
470 add_select(FlowSelectPhase::ADD_MARGINAL, candidate_groups, { k_flow_next_points, k_flow_counters, k_flow_grid }, FlowArgs::DETECTED);
471 add_select(FlowSelectPhase::COMMIT, 1U, {});
472 if (export_tracks)
473 add_select(FlowSelectPhase::PUBLISH, candidate_groups, { k_flow_export[0], k_flow_export[1] }, FlowArgs::PUBLISH);
474
475 ExecutionContext track_ctx;
476 track_ctx.mode = ExecutionMode::DEPENDENCY;
477 DependencyParams track_params;
478 track_params.stages = stages;
479 track_params.async = true;
480 track_ctx.parameters = track_params;
481 const auto track_dispatch_result = flow.execute(Datum<> {}, track_ctx);
482 const auto fence = track_dispatch_result.get_metadata<Portal::Graphics::FenceID>("gpu_fence").value_or(Portal::Graphics::INVALID_FENCE);
485 "run_gpu: TrackKeypoints failed to submit its dispatch sequence");
486 return false;
487 }
488 state.export_pending = export_tracks;
489 state.host_pending = p.host_tracks;
490
491 if (step.deferred) {
492 contexts.suspended.fence = fence;
493 contexts.suspended.finalize = [](VisionGpuContexts& c) { finish_track(c); };
494 return true;
495 }
496
497 foundry.wait_for_fence(fence);
498 foundry.release_fence(fence);
499 finish_track(contexts);
500 return false;
501}
#define MF_ERROR(comp, ctx,...)
vk::Fence fence
uint32_t h
Definition InkPress.cpp:29
@ ComputeMatrix
Compute operations (Yantra - algorithms, matrices, DSP)
@ Yantra
DSP algorithms, computational units, matrix operations, Grammar.
constexpr FenceID INVALID_FENCE
MAYAFLUX_API ShaderFoundry & get_shader_foundry()
Get the global shader compiler instance.
constexpr uint32_t k_flow_duplicate_energy
Frame change, in 1/1024 intensity summed over pixels, at or below which a frame counts as a repeat.
constexpr uint32_t k_flow_max_radius
Largest window radius the tracker's shared template cache holds.
constexpr uint32_t k_flow_grid_capacity
Occupancy grid capacity in cells; the cell size grows to fit large frames.
PixelRect region_rect(const std::optional< Kinesis::Vision::BoundingBox > &region, uint32_t w, uint32_t h)
Pixel rectangle of a normalised region over a w x h image, clamped to the image.
constexpr uint32_t k_flow_max_points
Upper bound on tracked keypoints, matching extract_peaks' buffer capacity.
constexpr std::array< uint32_t, 3 > k_wg2d
Standard 2D workgroup used by all pixel-to-pixel vision shaders.
@ DEPENDENCY
Part of dependency graph.

References MayaFlux::Yantra::DependencyParams::async, MayaFlux::Kinesis::Vision::VisionPass< Handle >::behind(), MayaFlux::Journal::ComputeMatrix, MayaFlux::Yantra::VisionInternal::FlowLkPC::curr_atlas, MayaFlux::Kinesis::Vision::VisionPass< Handle >::current, MayaFlux::Kinesis::Vision::VisionStep::deferred, MayaFlux::Yantra::DEPENDENCY, fence, MayaFlux::Yantra::VisionGpuContexts::Suspension::fence, MayaFlux::Yantra::VisionGpuContexts::Suspension::finalize, MayaFlux::Yantra::VisionGpuContexts::flow, MayaFlux::Yantra::VisionGpuContexts::flow_state, MayaFlux::Portal::Graphics::get_shader_foundry(), MayaFlux::Kinesis::Vision::PyramidLevel::h, MayaFlux::Kinesis::Vision::VisionResult::h, MayaFlux::Kinesis::Vision::VisionPass< Handle >::h, h, MayaFlux::Portal::Graphics::GpuBufferBinding::IMAGE_STORAGE, MayaFlux::Portal::Graphics::INVALID_FENCE, MayaFlux::Yantra::VisionInternal::k_flow_duplicate_energy, MayaFlux::Yantra::VisionInternal::k_flow_grid_capacity, MayaFlux::Yantra::VisionInternal::k_flow_max_points, MayaFlux::Yantra::VisionInternal::k_flow_max_radius, MayaFlux::Yantra::VisionInternal::k_flow_select_local, MayaFlux::Yantra::VisionInternal::k_wg2d, MF_ERROR, MayaFlux::Yantra::ExecutionContext::mode, MayaFlux::Kinesis::Vision::PyramidLevel::ox, MayaFlux::Kinesis::Vision::PyramidLevel::oy, MayaFlux::Kinesis::Vision::VisionStep::params, MayaFlux::Yantra::VisionGpuContexts::pass, MayaFlux::Yantra::VisionInternal::FlowSelectPC::phase, MayaFlux::Yantra::VisionInternal::region_rect(), MayaFlux::Kinesis::Vision::VisionPass< Handle >::result, MayaFlux::Yantra::DependencyParams::stages, MayaFlux::Kinesis::Vision::VisionResult::structured, MayaFlux::Yantra::VisionGpuContexts::suspended, MayaFlux::Yantra::VisionInternal::ExtractPeaksPC::threshold, MayaFlux::Kinesis::Vision::PyramidLevel::w, MayaFlux::Kinesis::Vision::VisionResult::w, MayaFlux::Kinesis::Vision::VisionPass< Handle >::w, and MayaFlux::Journal::Yantra.

+ Here is the call graph for this function: