PR #23697 opened by Steven Xiao (younengxiao)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23697
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23697.patch

This patch extends the existing ONNX Runtime DNN backend with object
detection support via dnn_detect and removes the single-output restriction
in dnn_processing.

Example usage:
  - SSD detection (single output, DirectML)
  ffmpeg -i input.mp4 -vf 
"scale=300:300,format=rgb24,dnn_detect=dnn_backend=onnx:model=ssd.onnx:model_type=ssd:device=dml:device_id=0"
 -f null -

  - YOLOv3 detection (two outputs, CPU)
  ffmpeg -i input.mp4 -vf 
"scale=416:416,format=rgb24,dnn_detect=dnn_backend=onnx:model=yolov3.onnx:output=yolo_13&yolo_26:model_type=yolov3:nb_classes=80:anchors=116&90&156&198"
 -f null -

Signed-off-by: younengxiao <[email protected]>



From df9ad604eb58e366a13fe10e9530667c7df43444 Mon Sep 17 00:00:00 2001
From: younengxiao <[email protected]>
Date: Fri, 3 Jul 2026 22:50:48 -0400
Subject: [PATCH] avfilter/dnn: extend ONNX Runtime backend with dnn_detect and
 multi-output support

This patch extends the existing ONNX Runtime DNN backend with object
detection support via dnn_detect and removes the single-output restriction
in dnn_processing.

Example usage:
  # SSD detection (single output, DirectML)
  ffmpeg -i input.mp4 -vf 
"scale=300:300,format=rgb24,dnn_detect=dnn_backend=onnx:model=ssd.onnx:model_type=ssd:device=dml:device_id=0"
 -f null -

  # YOLOv3 detection (two outputs, CPU)
  ffmpeg -i input.mp4 -vf 
"scale=416:416,format=rgb24,dnn_detect=dnn_backend=onnx:model=yolov3.onnx:output=yolo_13&yolo_26:model_type=yolov3:nb_classes=80:anchors=116&90&156&198&373&326&30&61&62&45&59&119"
 -f null -

Signed-off-by: younengxiao <[email protected]>
---
 doc/filters.texi                   |  55 +++++-
 libavfilter/dnn/dnn_backend_onnx.c | 307 +++++++++++++++++++----------
 libavfilter/dnn_filter_common.c    |   8 +-
 libavfilter/vf_dnn_detect.c        |  14 +-
 4 files changed, 270 insertions(+), 114 deletions(-)

diff --git a/doc/filters.texi b/doc/filters.texi
index aa0059f9cc..204e31f862 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -12160,7 +12160,15 @@ The filter accepts the following options:
 @table @option
 @item dnn_backend
 Specify which DNN backend to use for model loading and execution. This option 
accepts
-only openvino now, tensorflow backends will be added.
+the following values:
+@table @samp
+@item tensorflow
+TensorFlow backend.
+@item openvino
+OpenVINO backend.
+@item onnx
+ONNX Runtime backend.
+@end table
 
 @item model
 Set path to model file specifying network architecture and its parameters.
@@ -12170,7 +12178,9 @@ Note that different backends use different file formats.
 Set the input name of the dnn network.
 
 @item output
-Set the output name of the dnn network.
+Set the output name of the dnn network. For the ONNX Runtime backend multiple
+output names may be specified separated by @samp{&} (e.g
+@option{output=yolo_13&yolo_26})
 
 @item confidence
 Set the confidence threshold (default: 0.5).
@@ -12182,12 +12192,48 @@ The first line is the name of label id 0 (usually it 
is 'background'),
 and the second line is the name of label id 1, etc.
 The label id is considered as name if the label file is not provided.
 
+@item model_type
+Set the detection model output format. The following values are accepted:
+
+@table @samp
+@item ssd
+Single-stage detector (default).
+@item yolo
+YOLO v1/v2.
+@item yolov3
+YOLOv3.
+@item yolov4
+YOLOv4.
+@end table
+@item anchors
+Anchor box dimensions, separated by @samp{&}. Required for YOLO-family models.
+@item nb_classes
+Number of detection classes. Required for YOLO-family models.
 @item backend_configs
 Set the configs to be passed into backend. To use async execution, set async 
(default: set).
 Roll back to sync execution if the backend does not support async.
 
 @end table
 
+@subsection Examples
+
+@itemize
+@item
+Run SSD object detection with an ONNX model (single output, DirectML on 
Windows):
+@example
+ffmpeg -i input.mp4 -vf "scale=300:300,format=rgb24,dnn_detect=dnn_backend=onnx
+       :model=ssd.onnx:model_type=ssd:confidence=0.5:device=dml" -f null -
+@end example
+
+@item
+Run YOLOv3 object detection with an ONNX model (two outputs, CPU):
+@example
+ffmpeg -i input.mp4 -vf "scale=416:416,format=rgb24,dnn_detect=dnn_backend=onnx
+       
:model=yolov3.onnx:output=yolo_13&yolo_26:model_type=yolov3:nb_classes=80
+       :anchors=116&90&156&198&373&326&30&61&62&45&59&119" -f null -
+@end example
+@end itemize
+
 @anchor{dnn_processing}
 @section dnn_processing
 
@@ -12239,7 +12285,10 @@ exactly one input tensor when running the model.
 
 The @option{input} and @option{output} options are optional for the
 ONNX Runtime backend; when they are omitted the backend resolves the
-tensor names from the session.
+tensor names from the session.  Multiple output names may be supplied
+separated by @samp{&} (e.g. @option{output=out_a&out_b}); however for
+@code{dnn_processing} only the first output tensor is used for frame
+post-processing.
 
 The ONNX Runtime backend runs inference synchronously using a single
 inference request. The shared @option{async} and @option{nireq} options
diff --git a/libavfilter/dnn/dnn_backend_onnx.c 
b/libavfilter/dnn/dnn_backend_onnx.c
index 0ff0ffb285..559310ba24 100644
--- a/libavfilter/dnn/dnn_backend_onnx.c
+++ b/libavfilter/dnn/dnn_backend_onnx.c
@@ -29,6 +29,8 @@
 #include "libavutil/avstring.h"
 #include "libavutil/thread.h"
 #include "libavutil/wchar_filename.h"
+#include "libavutil/pixdesc.h"
+#include "libswscale/swscale.h"
 #include "../filters.h"
 #include "dnn_io_proc.h"
 #include "dnn_backend_common.h"
@@ -55,9 +57,10 @@ typedef struct ONNXModel {
 } ONNXModel;
 
 typedef struct ONNXInferRequest {
-    OrtValue *input_tensor;
-    OrtValue *output_tensor;
-    void     *input_data;
+    OrtValue  *input_tensor;
+    OrtValue **output_tensors;
+    uint32_t   nb_outputs;
+    void      *input_data;
 } ONNXInferRequest;
 
 typedef struct ONNXRequestItem {
@@ -125,10 +128,16 @@ static void onnx_free_request(ONNXInferRequest *request)
         request->input_tensor = NULL;
     }
     av_freep(&request->input_data);
-    if (request->output_tensor) {
-        g_ort->ReleaseValue(request->output_tensor);
-        request->output_tensor = NULL;
+    if (request->output_tensors) {
+        for (uint32_t i = 0; i < request->nb_outputs; i++) {
+            if (request->output_tensors[i]) {
+                g_ort->ReleaseValue(request->output_tensors[i]);
+                request->output_tensors[i] = NULL;
+            }
+        }
+        av_freep(&request->output_tensors);
     }
+    request->nb_outputs = 0;
 }
 
 static inline void destroy_request_item(ONNXRequestItem **arg)
@@ -319,6 +328,76 @@ static int get_input_onnx(DNNModel *model, DNNData *input, 
const char *input_nam
     return 0;
 }
 
+static int onnx_fill_detect_input(const AVFrame *frame, DNNData *input,
+                                  void *log_ctx)
+{
+    struct SwsContext *sws_ctx;
+    uint8_t *uint8_buf = NULL;
+    float *dst = (float *)input->data;
+    enum AVPixelFormat fmt;
+    int w, h, ret = 0;
+    int linesizes[4] = { 0, 0, 0, 0 };
+
+    switch (input->order) {
+        case DCO_BGR: fmt = AV_PIX_FMT_BGR24; break;
+        case DCO_RGB: fmt = AV_PIX_FMT_RGB24; break;
+        default:
+            av_log(log_ctx, AV_LOG_ERROR,
+                "onnx detect: unsupported channel order %d\n", input->order);
+            return AVERROR(ENOSYS);
+    }
+
+    w = input->dims[dnn_get_width_idx_by_layout(input->layout)];
+    h = input->dims[dnn_get_height_idx_by_layout(input->layout)];
+
+    const uint8_t *src;
+    int src_stride;
+
+    if (frame->format == fmt && frame->width == w && frame->height == h) {
+        src        = frame->data[0];
+        src_stride = frame->linesize[0];
+    } else {
+        uint8_buf = av_malloc((size_t)w * h * 3);
+        if (!uint8_buf)
+            return AVERROR(ENOMEM);
+
+        linesizes[0] = w * 3;
+
+        sws_ctx = sws_getContext(frame->width, frame->height, frame->format,
+                                 w, h, fmt,
+                                 SWS_FAST_BILINEAR, NULL, NULL, NULL);
+        if (!sws_ctx) {
+            av_log(log_ctx, AV_LOG_ERROR,
+                   "onnx detect: failed to create sws context "
+                   "fmt:%s s:%dx%d -> fmt:%s s:%dx%d\n",
+                   av_get_pix_fmt_name(frame->format), frame->width, 
frame->height,
+                   av_get_pix_fmt_name(fmt), w, h);
+            ret = AVERROR(EINVAL);
+            goto free_buf;
+        }
+
+        sws_scale(sws_ctx,
+                  (const uint8_t *const *)frame->data, frame->linesize,
+                  0, frame->height,
+                  (uint8_t *const [4]){uint8_buf, 0, 0, 0}, linesizes);
+        sws_freeContext(sws_ctx);
+
+        src        = uint8_buf;
+        src_stride = w * 3;
+    }
+
+    /* Transpose packed HWC uint8 -> planar NCHW float32. */
+    for (int c = 0; c < 3; c++)
+        for (int hi = 0; hi < h; hi++)
+            for (int wi = 0; wi < w; wi++)
+                dst[c * h * w + hi * w + wi] =
+                    (float)src[hi * src_stride + wi * 3 + c];
+
+free_buf:
+    av_freep(&uint8_buf);
+    return ret;
+}
+
 static int fill_model_input_onnx(ONNXModel *onnx_model, ONNXRequestItem 
*request)
 {
     LastLevelTaskItem       *lltask = NULL;
@@ -380,7 +459,9 @@ static int fill_model_input_onnx(ONNXModel *onnx_model, 
ONNXRequestItem *request
         }
         break;
     case DFT_ANALYTICS_DETECT:
-        ff_frame_to_dnn_detect(task->in_frame, &input, ctx);
+        ret = onnx_fill_detect_input(task->in_frame, &input, ctx);
+        if (ret < 0)
+            goto err;
         break;
     default:
         avpriv_report_missing_feature(ctx, "model function type %d", 
onnx_model->model.func_type);
@@ -425,8 +506,8 @@ static int onnx_start_inference(void *args)
     ONNXModel           *onnx_model = NULL;
     DnnContext                 *ctx = NULL;
     OrtStatus *status;
-    const char  *input_names[1];
-    const char *output_names[1];
+    const char *input_names[1];
+    int ret = DNN_GENERIC_ERROR;
 
     if (!request) {
         av_log(NULL, AV_LOG_ERROR, "ONNXRequestItem is NULL\n");
@@ -439,12 +520,6 @@ static int onnx_start_inference(void *args)
     onnx_model = (ONNXModel *)task->model;
     ctx = onnx_model->ctx;
 
-    if (task->nb_output > 1) {
-        avpriv_report_missing_feature(ctx,
-            "Multiple output tensors (%u) for ONNX backend", task->nb_output);
-        return AVERROR(ENOSYS);
-    }
-
     if (!task->input_name || !task->output_names || !task->output_names[0]) {
         av_log(ctx, AV_LOG_ERROR,
                "ONNX backend: input/output tensor name was not resolved at 
load time\n");
@@ -468,46 +543,63 @@ static int onnx_start_inference(void *args)
             return AVERROR(EINVAL);
         }
 
-        for (size_t i = 0; i < output_count; i++) {
-            char *name = NULL;
-            status = g_ort->SessionGetOutputName(onnx_model->session, i,
-                                                 onnx_model->allocator, &name);
-            if (status != NULL) {
-                g_ort->ReleaseStatus(status);
-                continue;
+        for (uint32_t req = 0; req < task->nb_output; req++) {
+            found_output = 0;
+            for (size_t i = 0; i < output_count; i++) {
+                char *name = NULL;
+                status = g_ort->SessionGetOutputName(onnx_model->session, i,
+                                                     onnx_model->allocator, 
&name);
+                if (status != NULL) {
+                    g_ort->ReleaseStatus(status);
+                    continue;
+                }
+                if (!strcmp(name, task->output_names[req]))
+                    found_output = 1;
+                onnx_model->allocator->Free(onnx_model->allocator, name);
+                if (found_output)
+                    break;
+            }
+            if (!found_output) {
+                av_log(ctx, AV_LOG_ERROR,
+                       "Output name '%s' not found in ONNX model\n",
+                       task->output_names[req]);
+                return AVERROR(EINVAL);
             }
-            if (!strcmp(name, task->output_names[0]))
-                found_output = 1;
-            onnx_model->allocator->Free(onnx_model->allocator, name);
-            if (found_output)
-                break;
-        }
-
-        if (!found_output) {
-            av_log(ctx, AV_LOG_ERROR,
-                   "Output name '%s' not found in ONNX model\n",
-                   task->output_names[0]);
-            return AVERROR(EINVAL);
         }
 
         onnx_model->output_resolved = 1;
     }
 
-    input_names[0]  = task->input_name;
-    output_names[0] = task->output_names[0];
+    input_names[0] = task->input_name;
+
+    /* ORT writes task->nb_output result handles into this array; it must be
+     * allocated (and NULL-initialised) before Run() so ORT owns each slot. */
+    av_freep(&infer_request->output_tensors);
+    infer_request->output_tensors = av_calloc(task->nb_output,
+                                              
sizeof(*infer_request->output_tensors));
+    if (!infer_request->output_tensors) {
+        infer_request->nb_outputs = 0;
+        return AVERROR(ENOMEM);
+    }
+    infer_request->nb_outputs = task->nb_output;
 
     status = g_ort->Run(onnx_model->session, NULL,
-                        input_names, (const OrtValue *const 
*)&infer_request->input_tensor, 1,
-                        output_names, 1, &infer_request->output_tensor);
+        input_names, (const OrtValue *const *)&infer_request->input_tensor, 1,
+        task->output_names, task->nb_output, infer_request->output_tensors);
 
     if (status != NULL) {
         const char *msg = g_ort->GetErrorMessage(status);
         av_log(ctx, AV_LOG_ERROR, "ONNX inference failed: %s\n", msg);
         g_ort->ReleaseStatus(status);
-        return DNN_GENERIC_ERROR;
+        goto err;
     }
 
     return 0;
+
+err:
+    av_freep(&infer_request->output_tensors);
+    infer_request->nb_outputs = 0;
+    return ret;
 }
 
 static void infer_completion_callback(void *args)
@@ -515,7 +607,7 @@ static void infer_completion_callback(void *args)
     ONNXRequestItem  *request = (ONNXRequestItem *)args;
     LastLevelTaskItem *lltask = request->lltask;
     TaskItem            *task = lltask->task;
-    DNNData           outputs = { 0 };
+    DNNData          *outputs = NULL;
     ONNXInferRequest *infer_request = request->infer_request;
     ONNXModel           *onnx_model = (ONNXModel *)task->model;
     DnnContext                 *ctx = onnx_model->ctx;
@@ -523,93 +615,107 @@ static void infer_completion_callback(void *args)
     ONNXTensorElementDataType tensor_type;
     size_t num_dims;
     int64_t *dims;
-    void *output_data;
     OrtStatus *status;
 
-    if (!infer_request->output_tensor) {
-        av_log(ctx, AV_LOG_ERROR, "Output tensor is NULL\n");
+    outputs = av_calloc(infer_request->nb_outputs, sizeof(*outputs));
+    if (!outputs) {
+        av_log(ctx, AV_LOG_ERROR, "Failed to allocate output DNNData array\n");
         goto err;
     }
 
-    status = g_ort->GetTensorTypeAndShape(infer_request->output_tensor, 
&tensor_info);
-    if (status != NULL) {
-        av_log(ctx, AV_LOG_ERROR, "Failed to get output tensor info\n");
-        g_ort->ReleaseStatus(status);
-        goto err;
-    }
+    for (uint32_t i = 0; i < infer_request->nb_outputs; i++) {
+        status = g_ort->GetTensorTypeAndShape(infer_request->output_tensors[i],
+                                              &tensor_info);
+        if (status != NULL) {
+            av_log(ctx, AV_LOG_ERROR, "Failed to get output tensor[%u] 
type/shape\n", i);
+            g_ort->ReleaseStatus(status);
+            goto err;
+        }
 
-    g_ort->GetDimensionsCount(tensor_info, &num_dims);
-    dims = av_malloc(num_dims * sizeof(int64_t));
-    if (!dims) {
-        av_log(ctx, AV_LOG_ERROR, "Failed to allocate memory for 
dimensions\n");
-        g_ort->ReleaseTensorTypeAndShapeInfo(tensor_info);
-        goto err;
-    }
-    g_ort->GetDimensions(tensor_info, dims, num_dims);
+        g_ort->GetDimensionsCount(tensor_info, &num_dims);
+        dims = av_malloc(num_dims * sizeof(int64_t));
+        if (!dims) {
+            av_log(ctx, AV_LOG_ERROR, "Failed to allocate dims array\n");
+            g_ort->ReleaseTensorTypeAndShapeInfo(tensor_info);
+            goto err;
+        }
+        g_ort->GetDimensions(tensor_info, dims, num_dims);
 
-    /* Output is interpreted as NCHW, matching the input assumption. */
-    outputs.layout = DL_NCHW;
-    outputs.order = DCO_RGB;
+        g_ort->GetTensorElementType(tensor_info, &tensor_type);
+        if (tensor_type == ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT) {
+            outputs[i].dt = DNN_FLOAT;
+        } else {
+            av_log(ctx, AV_LOG_ERROR,
+                   "Unsupported output tensor[%u] data type, only float 
supported\n", i);
+            av_free(dims);
+            g_ort->ReleaseTensorTypeAndShapeInfo(tensor_info);
+            goto err;
+        }
+
+        /* Output is interpreted as NCHW, matching the input assumption. */
+        outputs[i].layout = DL_NCHW;
+        outputs[i].order  = DCO_RGB;
+
+        if (num_dims == 4) {
+            outputs[i].dims[0] = dims[0];
+            outputs[i].dims[1] = dims[1];
+            outputs[i].dims[2] = dims[2];
+            outputs[i].dims[3] = dims[3];
+        } else if (num_dims == 3) {
+            /* Some detection model outputs are [1, N, D] — store in 
dims[0..2]. */
+            outputs[i].dims[0] = dims[0];
+            outputs[i].dims[1] = 1;
+            outputs[i].dims[2] = dims[1];
+            outputs[i].dims[3] = dims[2];
+        } else {
+            avpriv_report_missing_feature(ctx,
+                "Support for %zu-dimensional output (tensor[%u])", num_dims, 
i);
+            av_free(dims);
+            g_ort->ReleaseTensorTypeAndShapeInfo(tensor_info);
+            goto err;
+        }
+
+        status = g_ort->GetTensorMutableData(infer_request->output_tensors[i], 
&outputs[i].data);
+        if (status != NULL) {
+            av_log(ctx, AV_LOG_ERROR, "Failed to get tensor[%u] data 
pointer\n", i);
+            g_ort->ReleaseStatus(status);
+            av_free(dims);
+            g_ort->ReleaseTensorTypeAndShapeInfo(tensor_info);
+            goto err;
+        }
 
-    g_ort->GetTensorElementType(tensor_info, &tensor_type);
-    if (tensor_type == ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT) {
-        outputs.dt = DNN_FLOAT;
-    } else {
-        av_log(ctx, AV_LOG_ERROR, "Unsupported output tensor data type, only 
float is supported\n");
         av_free(dims);
         g_ort->ReleaseTensorTypeAndShapeInfo(tensor_info);
-        goto err;
     }
 
-    if (num_dims == 4) {
-        outputs.dims[0] = dims[0];
-        outputs.dims[1] = dims[1];
-        outputs.dims[2] = dims[2];
-        outputs.dims[3] = dims[3];
-    } else {
-        avpriv_report_missing_feature(ctx, "Support for %zu dimensional 
output", num_dims);
-        av_free(dims);
-        g_ort->ReleaseTensorTypeAndShapeInfo(tensor_info);
-        goto err;
-    }
-
-    status = g_ort->GetTensorMutableData(infer_request->output_tensor, 
&output_data);
-    if (status != NULL) {
-        av_log(ctx, AV_LOG_ERROR, "Failed to get tensor data\n");
-        g_ort->ReleaseStatus(status);
-        av_free(dims);
-        g_ort->ReleaseTensorTypeAndShapeInfo(tensor_info);
-        goto err;
-    }
-
-    outputs.data = output_data;
-
     switch (onnx_model->model.func_type) {
     case DFT_PROCESS_FRAME:
         if (task->do_ioproc) {
-            outputs.scale = 255;
+            outputs[0].scale = 255;
             if (onnx_model->model.frame_post_proc != NULL) {
-                onnx_model->model.frame_post_proc(task->out_frame, &outputs, 
onnx_model->model.filter_ctx);
+                onnx_model->model.frame_post_proc(task->out_frame, outputs, 
onnx_model->model.filter_ctx);
             } else {
-                ff_proc_from_dnn_to_frame(task->out_frame, &outputs, ctx);
+                ff_proc_from_dnn_to_frame(task->out_frame, outputs, ctx);
             }
         } else {
-            task->out_frame->width = 
outputs.dims[dnn_get_width_idx_by_layout(outputs.layout)];
-            task->out_frame->height = 
outputs.dims[dnn_get_height_idx_by_layout(outputs.layout)];
+            task->out_frame->width  = 
outputs[0].dims[dnn_get_width_idx_by_layout(outputs[0].layout)];
+            task->out_frame->height = 
outputs[0].dims[dnn_get_height_idx_by_layout(outputs[0].layout)];
         }
         break;
+    case DFT_ANALYTICS_DETECT:
+        onnx_model->model.detect_post_proc(task->in_frame, outputs,
+                                           infer_request->nb_outputs,
+                                           onnx_model->model.filter_ctx);
+        break;
     default:
         avpriv_report_missing_feature(ctx, "model function type %d", 
onnx_model->model.func_type);
-        av_free(dims);
-        g_ort->ReleaseTensorTypeAndShapeInfo(tensor_info);
         goto err;
     }
 
-    av_free(dims);
-    g_ort->ReleaseTensorTypeAndShapeInfo(tensor_info);
     task->inference_done++;
 
 err:
+    av_freep(&outputs);
     av_freep(&request->lltask);
     onnx_free_request(infer_request);
     if (ff_safe_queue_push_back(onnx_model->request_queue, request) < 0) {
@@ -712,12 +818,9 @@ err:
 
 static ONNXInferRequest *onnx_create_inference_request(void)
 {
-    ONNXInferRequest *request = av_malloc(sizeof(ONNXInferRequest));
+    ONNXInferRequest *request = av_mallocz(sizeof(ONNXInferRequest));
     if (!request)
         return NULL;
-    request->input_tensor  = NULL;
-    request->output_tensor = NULL;
-    request->input_data    = NULL;
     return request;
 }
 
diff --git a/libavfilter/dnn_filter_common.c b/libavfilter/dnn_filter_common.c
index 73c5e6b33c..25f985766c 100644
--- a/libavfilter/dnn_filter_common.c
+++ b/libavfilter/dnn_filter_common.c
@@ -107,18 +107,14 @@ int ff_dnn_init(DnnContext *ctx, DNNFunctionType 
func_type, AVFilterContext *fil
             return AVERROR(EINVAL);
         }
     } else if (backend == DNN_ONNX) {
-        /* ONNX: input and output tensor names are optional.*/
+        /* ONNX: input and output tensor names are optional.
+         * Multiple output names may be specified separated by '&'. */
         if (ctx->model_outputnames_string) {
             ctx->model_outputnames = 
separate_output_names(ctx->model_outputnames_string, "&", &ctx->nb_outputs);
             if (!ctx->model_outputnames) {
                 av_log(filter_ctx, AV_LOG_ERROR, "could not parse model output 
names\n");
                 return AVERROR(EINVAL);
             }
-            if (ctx->nb_outputs != 1) {
-                av_log(filter_ctx, AV_LOG_ERROR,
-                       "ONNX backend supports a single output name only\n");
-                return AVERROR(EINVAL);
-            }
         }
     }
 
diff --git a/libavfilter/vf_dnn_detect.c b/libavfilter/vf_dnn_detect.c
index 81487ba14b..63832984b5 100644
--- a/libavfilter/vf_dnn_detect.c
+++ b/libavfilter/vf_dnn_detect.c
@@ -22,6 +22,7 @@
  */
 
 #include "libavutil/file_open.h"
+#include "libavutil/internal.h"
 #include "libavutil/mem.h"
 #include "libavutil/opt.h"
 #include "filters.h"
@@ -70,6 +71,9 @@ static const AVOption dnn_detect_options[] = {
 #endif
 #if (CONFIG_LIBOPENVINO == 1)
     { "openvino",    "openvino backend flag",      0,                        
AV_OPT_TYPE_CONST,     { .i64 = DNN_OV },    0, 0, FLAGS, .unit = "backend" },
+#endif
+#if (CONFIG_LIBONNXRUNTIME == 1)
+    { "onnx",        "ONNX Runtime backend flag",  0,                        
AV_OPT_TYPE_CONST,     { .i64 = DNN_ONNX },  0, 0, FLAGS, .unit = "backend" },
 #endif
     { "confidence",  "threshold of confidence",    OFFSET2(confidence),      
AV_OPT_TYPE_FLOAT,     { .dbl = 0.5 },  0, 1, FLAGS},
     { "labels",      "path to labels file",        OFFSET2(labels_filename), 
AV_OPT_TYPE_STRING,    { .str = NULL }, 0, 0, FLAGS },
@@ -85,7 +89,7 @@ static const AVOption dnn_detect_options[] = {
     { NULL }
 };
 
-AVFILTER_DNN_DEFINE_CLASS(dnn_detect, DNN_TF | DNN_OV);
+AVFILTER_DNN_DEFINE_CLASS(dnn_detect, DNN_TF | DNN_OV | DNN_ONNX);
 
 static inline float sigmoid(float x) {
     return 1.f / (1.f + exp(-x));
@@ -436,7 +440,7 @@ static int dnn_detect_post_proc_ssd(AVFrame *frame, DNNData 
*output, int nb_outp
     return 0;
 }
 
-static int dnn_detect_post_proc_ov(AVFrame *frame, DNNData *output, int 
nb_outputs,
+static int dnn_detect_post_proc_anchored(AVFrame *frame, DNNData *output, int 
nb_outputs,
                                    AVFilterContext *filter_ctx)
 {
     AVFrameSideData *sd;
@@ -551,9 +555,11 @@ static int dnn_detect_post_proc(AVFrame *frame, DNNData 
*output, uint32_t nb, AV
     DnnContext *dnn_ctx = &ctx->dnnctx;
     switch (dnn_ctx->backend_type) {
     case DNN_OV:
-        return dnn_detect_post_proc_ov(frame, output, nb, filter_ctx);
+        return dnn_detect_post_proc_anchored(frame, output, nb, filter_ctx);
     case DNN_TF:
         return dnn_detect_post_proc_tf(frame, output, filter_ctx);
+    case DNN_ONNX:
+        return dnn_detect_post_proc_anchored(frame, output, nb, filter_ctx);
     default:
         avpriv_report_missing_feature(filter_ctx, "Current dnn backend does 
not support detect filter\n");
         return AVERROR(EINVAL);
@@ -639,6 +645,8 @@ static int check_output_nb(DnnDetectContext *ctx, 
DNNBackendType backend_type, i
         return 0;
     case DNN_OV:
         return 0;
+    case DNN_ONNX:
+        return 0;
     default:
         avpriv_report_missing_feature(ctx, "Dnn detect filter does not support 
current backend\n");
         return AVERROR(EINVAL);
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to