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

◆ op_canny()

GpuVisionPass::Completed MayaFlux::Yantra::VisionGpuExecutor::op_canny ( VisionGpuContexts &  contexts,
const Kinesis::Vision::VisionParams &  params,
const Kinesis::Vision::CannyParams &  p 
)
private

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.

Returns
The resulting edge image and the original input image.

Definition at line 243 of file VisionImageDispatch.cpp.

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;
252 auto& foundry = Portal::Graphics::get_shader_foundry();
253
254 const auto canny_input = contexts.pass.current;
255
256 const auto radius = static_cast<uint32_t>(std::ceil(p.sigma * 3.0F));
257 const auto& weights = gaussian_kernel_2d(radius, p.sigma);
258 const auto blur_cfg = VisionGpuExecutor::config(VisionOp::GaussianBlur, GaussianBlurParams { .sigma = p.sigma });
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
271 const auto sobel_cfg = VisionGpuExecutor::config(VisionOp::Sobel, std::monostate {});
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
282 const GpuComputeConfig nms_cfg {
283 .shader_path = "canny_nms.comp.spv",
284 .workgroup_size = k_wg2d,
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
296 const auto classify_cfg = VisionGpuExecutor::config(VisionOp::Canny, params);
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",
323 .workgroup_size = k_wg2d,
324 .push_constant_size = sizeof(HysteresisPC),
325 });
326 label_ctx.stage_image_at(0, classified, GpuBufferBinding::ElementType::IMAGE_STORAGE);
327 label_ctx.slot_binding(0).direction = GpuBufferBinding::Direction::INPUT_OUTPUT;
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;
333 chained_ctx.mode = ExecutionMode::CHAINED;
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 }
341 label_ctx.slot_binding(0).direction = GpuBufferBinding::Direction::OUTPUT;
342 auto hysteresis_result = classified;
343
344 const auto finalize_cfg = config_from_spec(
345 ShaderSpec::Assemble {}
346 .storage_image("out", BindingDirection::Output)
347 .storage_image("src", BindingDirection::Input)
348 .pc("threshold")
349 .op(KernelOp::CompareGE)
350 .workgroup(k_wg2d[0], k_wg2d[1])
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}
float radius
uint32_t h
Definition InkPress.cpp:29
uint32_t * dst
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 &params)
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.

References MayaFlux::Yantra::VisionGpuContexts::bound_config, MayaFlux::Yantra::VisionGpuContexts::bound_staged, MayaFlux::Kinesis::Vision::ImageOutputs::canny, MayaFlux::Yantra::CHAINED, config(), MayaFlux::Yantra::config_from_spec(), MayaFlux::Kinesis::Vision::VisionPass< Handle >::current, dst, gaussian_kernel_2d(), MayaFlux::Portal::Graphics::get_shader_foundry(), MayaFlux::Kinesis::Vision::VisionPass< Handle >::h, h, MayaFlux::Kinesis::Vision::CannyParams::high_threshold, MayaFlux::Portal::Graphics::GpuBufferBinding::IMAGE_STORAGE, MayaFlux::Kinesis::Vision::VisionResult::images, MayaFlux::Portal::Graphics::GpuBufferBinding::INPUT_OUTPUT, MayaFlux::Yantra::VisionInternal::k_wg2d, MayaFlux::Yantra::VisionGpuContexts::labels, MayaFlux::Kinesis::Vision::CannyParams::low_threshold, MayaFlux::Yantra::ExecutionContext::mode, MayaFlux::Portal::Graphics::GpuBufferBinding::OUTPUT, MayaFlux::Yantra::ExecutionContext::parameters, MayaFlux::Yantra::VisionGpuContexts::pass, MayaFlux::Yantra::ChainedParams::pass_count, MayaFlux::Yantra::VisionGpuContexts::pixel, radius, MayaFlux::Yantra::VisionInternal::GaussianPC::radius, MayaFlux::Kinesis::Vision::VisionPass< Handle >::result, MayaFlux::Portal::Graphics::GpuComputeConfig::shader_path, MayaFlux::Kinesis::Vision::GaussianBlurParams::sigma, MayaFlux::Kinesis::Vision::CannyParams::sigma, MayaFlux::Portal::Graphics::ShaderSpec::Assemble::storage_image(), MayaFlux::Kinesis::Vision::VisionResult::structured, MayaFlux::Yantra::VisionInternal::ClassifyPC::threshold, MayaFlux::Yantra::VisionInternal::FinalizePC::threshold, MayaFlux::Kinesis::Vision::VisionPass< Handle >::w, and MayaFlux::Yantra::VisionInternal::HysteresisPC::width.

+ Here is the call graph for this function: