Detect edges in the current image with the Canny pipeline.
Applies smoothing, gradients, suppression, threshold classification, and hysteresis. The final edge image becomes the working image and is published as VisionResult::images.canny.
247{
248 auto& pixel_ctx = contexts.pixel;
249 auto& label_ctx = contexts.labels;
250 auto w = contexts.pass.w;
251 auto h = contexts.pass.h;
253
254 const auto canny_input = contexts.pass.current;
255
256 const auto radius =
static_cast<uint32_t
>(std::ceil(p.sigma * 3.0F));
259 pixel_ctx.swap_shader(blur_cfg);
260 pixel_ctx.stage_image(canny_input);
261 pixel_ctx.set_binding_data(2, std::span<const float>(weights));
262 pixel_ctx.set_push_constants(GaussianPC { .radius =
radius, .width = w, .height =
h });
263 pixel_ctx.prepare_output_image(w,
h);
264 {
265 const auto f = pixel_ctx.dispatch_async({});
266 foundry.wait_for_fence(f);
267 foundry.release_fence(f);
268 }
269 const auto blurred = pixel_ctx.get_output_image(0);
270
272 pixel_ctx.swap_shader(sobel_cfg);
273 pixel_ctx.stage_image(blurred);
274 pixel_ctx.prepare_output_image(w,
h);
275 {
276 const auto f = pixel_ctx.dispatch_async({});
277 foundry.wait_for_fence(f);
278 foundry.release_fence(f);
279 }
280 const auto grad = pixel_ctx.get_output_image(0);
281
285 };
286 pixel_ctx.swap_shader(nms_cfg);
287 pixel_ctx.stage_image(grad);
288 pixel_ctx.prepare_output_image(w,
h);
289 {
290 const auto f = pixel_ctx.dispatch_async({});
291 foundry.wait_for_fence(f);
292 foundry.release_fence(f);
293 }
294 auto suppressed = pixel_ctx.get_output_image(0);
295
297 pixel_ctx.swap_shader(classify_cfg);
298 pixel_ctx.stage_image(suppressed);
299 pixel_ctx.set_push_constants(ClassifyPC { .threshold = p.low_threshold, .value = 0.5F });
300 pixel_ctx.prepare_output_image(w,
h);
301 {
302 const auto f = pixel_ctx.dispatch_async({});
303 foundry.wait_for_fence(f);
304 foundry.release_fence(f);
305 }
306 auto classified_weak = pixel_ctx.get_output_image(0);
307
308 pixel_ctx.stage_image(classified_weak);
309 pixel_ctx.set_push_constants(ClassifyPC { .threshold = p.high_threshold, .value = 1.0F });
310 pixel_ctx.prepare_output_image(w,
h);
311 {
312 const auto f = pixel_ctx.dispatch_async({});
313 foundry.wait_for_fence(f);
314 foundry.release_fence(f);
315 }
316 auto classified = pixel_ctx.get_output_image(0);
317
318 constexpr uint32_t k_max_hysteresis_rounds = 64;
319 label_ctx.set_output_size(2, sizeof(uint32_t));
320 label_ctx.set_output_dimensions(w,
h);
321 label_ctx.swap_shader({
322 .shader_path = "canny_hysteresis.comp.spv",
324 .push_constant_size = sizeof(HysteresisPC),
325 });
328 {
329 uint32_t zero = 0;
330 label_ctx.set_binding_data(2, std::span<const uint32_t>(&zero, 1));
331 const HysteresisPC hpc { .width = w, .height =
h };
332 ExecutionContext chained_ctx;
334 chained_ctx.parameters = ChainedParams {
335 .pass_count = k_max_hysteresis_rounds,
336 .pc_updater = [hpc](uint32_t,
void*
dst) { std::memcpy(
dst, &hpc,
sizeof(HysteresisPC)); },
337 .passes_per_batch = k_max_hysteresis_rounds,
338 };
339 label_ctx.execute(Datum<> {}, chained_ctx);
340 }
342 auto hysteresis_result = classified;
343
345 ShaderSpec::Assemble {}
346 .storage_image("out", BindingDirection::Output)
347 .storage_image("src", BindingDirection::Input)
348 .pc("threshold")
349 .op(KernelOp::CompareGE)
351 .build());
352 pixel_ctx.swap_shader(finalize_cfg);
353 pixel_ctx.stage_image(hysteresis_result);
354 pixel_ctx.set_push_constants(FinalizePC { .threshold = 1.0F });
355 pixel_ctx.prepare_output_image(w,
h);
356 {
357 const auto f = pixel_ctx.dispatch_async({});
358 foundry.wait_for_fence(f);
359 foundry.release_fence(f);
360 }
361 auto finalized = pixel_ctx.get_output_image(0);
362
363 contexts.pass.result.images.canny = finalized;
364 contexts.pass.current = finalized;
365 contexts.pass.result.structured = std::monostate {};
366
367 contexts.bound_config = finalize_cfg;
368 contexts.bound_staged = hysteresis_result;
369
370 return { .output = finalized, .input = canny_input };
371}
const std::vector< float > & gaussian_kernel_2d(uint32_t radius, float sigma)
2D Gaussian kernel for convolution, cached by (radius, sigma bit pattern).
static GpuComputeConfig config(Kinesis::Vision::VisionOp op, const Kinesis::Vision::VisionParams ¶ms)
GpuComputeConfig for a given VisionOp and its parameters.
MAYAFLUX_API ShaderFoundry & get_shader_foundry()
Get the global shader compiler instance.
constexpr std::array< uint32_t, 3 > k_wg2d
Standard 2D workgroup used by all pixel-to-pixel vision shaders.
Portal::Graphics::GpuComputeConfig GpuComputeConfig
@ CHAINED
Part of a sequential chain.
GpuComputeConfig config_from_spec(const Portal::Graphics::ShaderSpec &spec)
Derive a GpuComputeConfig from a ShaderSpec.