This is an automated email from the git hooks/post-receive script.
Git pushed a commit to branch master
in repository ffmpeg.
The following commit(s) were added to refs/heads/master by this push:
new 1ae4048218 avfilter/dnn: extend ONNX Runtime backend with dnn_detect
and multi-output support
1ae4048218 is described below
commit 1ae40482188283a7f955d001abb08c5d865cdae0
Author: younengxiao <[email protected]>
AuthorDate: Fri Jul 3 22:50:48 2026 -0400
Commit: guoyejun <[email protected]>
CommitDate: Sat Aug 29 10:18:28 2026 +0000
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 | 58 ++++++-
libavfilter/dnn/dnn_backend_onnx.c | 322 +++++++++++++++++++++++++------------
libavfilter/dnn/dnn_io_proc.c | 155 +++++++++++++++---
libavfilter/dnn_filter_common.c | 8 +-
libavfilter/vf_dnn_detect.c | 272 +++++++++++++++++++++++++------
5 files changed, 631 insertions(+), 184 deletions(-)
diff --git a/doc/filters.texi b/doc/filters.texi
index 488ecdf9c5..9f2c41bf44 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -12176,7 +12176,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.
@@ -12186,7 +12194,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. The TensorFlow and ONNX Runtime
+backends accept multiple output names separated by @samp{&}, e.g.
+@option{output=yolo_13&yolo_26}.
@item confidence
Set the confidence threshold (default: 0.5).
@@ -12198,12 +12208,51 @@ 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.
+The list is consumed in output order, so the anchors of the first output come
+first: with @option{output=yolo_13&yolo_26} the leading pairs belong to
+@code{yolo_13}.
+@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
@@ -12265,7 +12314,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 6c75d6eb24..16b95cc55c 100644
--- a/libavfilter/dnn/dnn_backend_onnx.c
+++ b/libavfilter/dnn/dnn_backend_onnx.c
@@ -25,6 +25,7 @@
#include "libavutil/opt.h"
#include "libavutil/avassert.h"
+#include "libavutil/imgutils.h"
#include "libavutil/mem.h"
#include "libavutil/avstring.h"
#include "libavutil/thread.h"
@@ -55,9 +56,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 +127,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)
@@ -286,6 +294,17 @@ static int get_input_onnx(DNNModel *model, DNNData *input,
const char *input_nam
return AVERROR(ENOSYS);
}
+ for (size_t i = 1; i < num_dims; i++) {
+ if (dims[i] > INT_MAX) {
+ av_log(ctx, AV_LOG_ERROR,
+ "ONNX model input dimension %zu (%"PRId64") is too large to
be represented\n",
+ i, dims[i]);
+ av_free(dims);
+ g_ort->ReleaseTypeInfo(type_info);
+ return AVERROR(ENOSYS);
+ }
+ }
+
/*
* The ONNX backend assumes a 4-D NCHW input tensor (the rank check
* above already rejects anything else).
@@ -351,16 +370,44 @@ static int fill_model_input_onnx(ONNXModel *onnx_model,
ONNXRequestItem *request
height_idx = dnn_get_height_idx_by_layout(input.layout);
channel_idx = dnn_get_channel_idx_by_layout(input.layout);
- input.dims[height_idx] = task->in_frame->height;
- input.dims[width_idx] = task->in_frame->width;
+ if (input.dims[height_idx] < 0)
+ input.dims[height_idx] = task->in_frame->height;
+ if (input.dims[width_idx] < 0)
+ input.dims[width_idx] = task->in_frame->width;
+
+ if (input.dims[0] <= 0 || input.dims[channel_idx] <= 0 ||
+ input.dims[height_idx] <= 0 || input.dims[width_idx] <= 0) {
+ av_log(ctx, AV_LOG_ERROR, "ONNX input tensor has a non-positive
dimension\n");
+ ret = AVERROR(EINVAL);
+ goto err;
+ }
+
+ ret = av_image_check_size((unsigned)input.dims[width_idx],
+ (unsigned)input.dims[height_idx], 0, ctx);
+ if (ret < 0) {
+ av_log(ctx, AV_LOG_ERROR, "ONNX input image dimensions %dx%d are not
supported\n",
+ input.dims[width_idx], input.dims[height_idx]);
+ goto err;
+ }
input_shape[0] = input.dims[0];
input_shape[1] = input.dims[channel_idx];
input_shape[2] = input.dims[height_idx];
input_shape[3] = input.dims[width_idx];
- input_tensor_size = input_shape[0] * input_shape[1] * input_shape[2] *
input_shape[3];
- input_tensor_size *= sizeof(float);
+ /*
+ * Build the byte count with checked size_t multiplications instead of
+ * multiplying four int64_t shape values in one expression.
+ */
+ input_tensor_size = sizeof(float);
+ if (av_size_mult(input_tensor_size, (size_t)input_shape[0],
&input_tensor_size) < 0 ||
+ av_size_mult(input_tensor_size, (size_t)input_shape[1],
&input_tensor_size) < 0 ||
+ av_size_mult(input_tensor_size, (size_t)input_shape[2],
&input_tensor_size) < 0 ||
+ av_size_mult(input_tensor_size, (size_t)input_shape[3],
&input_tensor_size) < 0) {
+ av_log(ctx, AV_LOG_ERROR, "ONNX input tensor size overflows\n");
+ ret = AVERROR(EINVAL);
+ goto err;
+ }
input.data = av_malloc(input_tensor_size);
if (!input.data) {
@@ -374,14 +421,19 @@ static int fill_model_input_onnx(ONNXModel *onnx_model,
ONNXRequestItem *request
input.scale = 255;
if (task->do_ioproc) {
if (onnx_model->model.frame_pre_proc != NULL) {
- onnx_model->model.frame_pre_proc(task->in_frame, &input,
onnx_model->model.filter_ctx);
+ ret = onnx_model->model.frame_pre_proc(task->in_frame, &input,
+
onnx_model->model.filter_ctx);
} else {
- ff_proc_from_frame_to_dnn(task->in_frame, &input, ctx);
+ ret = ff_proc_from_frame_to_dnn(task->in_frame, &input, ctx);
}
+ if (ret < 0)
+ goto err;
}
break;
case DFT_ANALYTICS_DETECT:
- ff_frame_to_dnn_detect(task->in_frame, &input, ctx);
+ ret = ff_frame_to_dnn_detect(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);
@@ -426,8 +478,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");
@@ -440,12 +492,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");
@@ -469,46 +515,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)
@@ -516,7 +579,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;
@@ -524,93 +587,143 @@ static void infer_completion_callback(void *args)
ONNXTensorElementDataType tensor_type;
size_t num_dims;
int64_t *dims;
- void *output_data;
OrtStatus *status;
+ int ret;
- 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);
+ status = g_ort->GetDimensionsCount(tensor_info, &num_dims);
+ if (status != NULL) {
+ av_log(ctx, AV_LOG_ERROR, "Failed to get output tensor[%u]
dimension count\n", i);
+ g_ort->ReleaseStatus(status);
+ g_ort->ReleaseTensorTypeAndShapeInfo(tensor_info);
+ goto err;
+ }
- /* Output is interpreted as NCHW, matching the input assumption. */
- outputs.layout = DL_NCHW;
- outputs.order = DCO_RGB;
+ 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->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;
- }
+ status = g_ort->GetDimensions(tensor_info, dims, num_dims);
+ if (status != NULL) {
+ av_log(ctx, AV_LOG_ERROR, "Failed to get output tensor[%u]
dimensions\n", i);
+ g_ort->ReleaseStatus(status);
+ 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;
- }
+ for (size_t d = 0; d < num_dims; d++) {
+ if (dims[d] < 0 || dims[d] > INT_MAX) {
+ av_log(ctx, AV_LOG_ERROR,
+ "Output tensor[%u] dimension %zu (%"PRId64") is out of
representable range\n",
+ i, d, dims[d]);
+ av_free(dims);
+ g_ort->ReleaseTensorTypeAndShapeInfo(tensor_info);
+ goto err;
+ }
+ }
+
+ status = g_ort->GetTensorElementType(tensor_info, &tensor_type);
+ if (status != NULL) {
+ av_log(ctx, AV_LOG_ERROR, "Failed to get output tensor[%u] element
type\n", i);
+ g_ort->ReleaseStatus(status);
+ av_free(dims);
+ g_ort->ReleaseTensorTypeAndShapeInfo(tensor_info);
+ goto err;
+ }
+ 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 models output [1, N, D]; promote it to [1, 1, N,
D]. */
+ 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;
+ }
- 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:
+ ret = onnx_model->model.detect_post_proc(task->in_frame, outputs,
+ infer_request->nb_outputs,
+ onnx_model->model.filter_ctx);
+ if (ret < 0)
+ goto err;
+ 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) {
@@ -713,12 +826,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/dnn_io_proc.c b/libavfilter/dnn/dnn_io_proc.c
index 826110dab0..0a18c56b84 100644
--- a/libavfilter/dnn/dnn_io_proc.c
+++ b/libavfilter/dnn/dnn_io_proc.c
@@ -420,12 +420,60 @@ int ff_frame_to_dnn_classify(AVFrame *frame, DNNData
*input, uint32_t bbox_index
return ret;
}
+/*
+ * Write packed 24bit RGB/BGR samples into the tensor type/layout the model
expects.
+ */
+static void detect_write_tensor(DNNData *input, const uint8_t *src,
+ int src_linesize, int w, int h)
+{
+ const size_t plane_size = (size_t)w * h;
+
+ if (input->dt == DNN_FLOAT) {
+ float *dst = input->data;
+ if (input->layout == DL_NCHW) {
+ /* FLOAT + NCHW: deinterleave into planes while widening uint8 ->
float. */
+ for (int c = 0; c < 3; c++)
+ for (int y = 0; y < h; y++) {
+ const uint8_t *row = src + (ptrdiff_t)y * src_linesize;
+ for (int x = 0; x < w; x++)
+ dst[c * plane_size + (size_t)y * w + x] = row[x * 3 +
c];
+ }
+ } else {
+ /* FLOAT + NHWC : keep it packed, only widen uint8 -> float byte
by byte. */
+ for (int y = 0; y < h; y++) {
+ const uint8_t *row = src + (ptrdiff_t)y * src_linesize;
+ for (int x = 0; x < w * 3; x++)
+ dst[(size_t)y * w * 3 + x] = row[x];
+ }
+ }
+ } else {
+ /* UINT8 + NCHW: deinterleave into 3 uint8 planes. */
+ uint8_t *dst = input->data;
+ for (int c = 0; c < 3; c++)
+ for (int y = 0; y < h; y++) {
+ const uint8_t *row = src + (ptrdiff_t)y * src_linesize;
+ for (int x = 0; x < w; x++)
+ dst[c * plane_size + (size_t)y * w + x] = row[x * 3 + c];
+ }
+ }
+}
+
int ff_frame_to_dnn_detect(AVFrame *frame, DNNData *input, void *log_ctx)
{
- struct SwsContext *sws_ctx;
+ struct SwsContext *sws_ctx = NULL;
+ uint8_t *tmp_buf = NULL;
+ const uint8_t *src;
+ int src_linesize;
int linesizes[4];
- int ret = 0, width_idx, height_idx;
- enum AVPixelFormat fmt = get_pixel_format(input);
+ int ret = 0, width_idx, height_idx, channel_idx;
+ int packed_u8, w, h;
+ enum AVPixelFormat fmt;
+
+ switch (input->order) {
+ case DCO_BGR: fmt = AV_PIX_FMT_BGR24; break;
+ case DCO_RGB: fmt = AV_PIX_FMT_RGB24; break;
+ default: fmt = AV_PIX_FMT_NONE; break;
+ }
/* (scale != 1 and scale != 0) or mean != 0 */
if ((fabsf(input->scale - 1) > 1e-6f && fabsf(input->scale) > 1e-6f) ||
@@ -435,37 +483,100 @@ int ff_frame_to_dnn_detect(AVFrame *frame, DNNData
*input, void *log_ctx)
return AVERROR(ENOSYS);
}
- if (input->layout == DL_NCHW) {
- av_log(log_ctx, AV_LOG_ERROR, "dnn_detect input data doesn't support
layout: NCHW\n");
+ if (fmt == AV_PIX_FMT_NONE) {
+ av_log(log_ctx, AV_LOG_ERROR, "dnn_detect input data doesn't support "
+ "color order: %d\n", input->order);
return AVERROR(ENOSYS);
}
- width_idx = dnn_get_width_idx_by_layout(input->layout);
- height_idx = dnn_get_height_idx_by_layout(input->layout);
+ if (input->dt != DNN_UINT8 && input->dt != DNN_FLOAT) {
+ av_log(log_ctx, AV_LOG_ERROR, "dnn_detect input data doesn't support "
+ "data type: %d\n", input->dt);
+ return AVERROR(ENOSYS);
+ }
- sws_ctx = sws_getContext(frame->width, frame->height, frame->format,
- input->dims[width_idx],
- input->dims[height_idx], fmt,
- SWS_FAST_BILINEAR, NULL, NULL, NULL);
- if (!sws_ctx) {
- av_log(log_ctx, AV_LOG_ERROR, "Impossible to create scale context for
the conversion "
- "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), input->dims[width_idx],
- input->dims[height_idx]);
+ width_idx = dnn_get_width_idx_by_layout(input->layout);
+ height_idx = dnn_get_height_idx_by_layout(input->layout);
+ channel_idx = dnn_get_channel_idx_by_layout(input->layout);
+
+ w = input->dims[width_idx];
+ h = input->dims[height_idx];
+ if (w <= 0 || h <= 0) {
+ av_log(log_ctx, AV_LOG_ERROR, "dnn_detect input data has invalid "
+ "dimensions: %dx%d\n", w, h);
return AVERROR(EINVAL);
}
- ret = av_image_fill_linesizes(linesizes, fmt, input->dims[width_idx]);
+ packed_u8 = (input->dt == DNN_UINT8) && (input->layout != DL_NCHW);
+ if (!packed_u8 && input->dims[channel_idx] != 3) {
+ av_log(log_ctx, AV_LOG_ERROR, "dnn_detect requires a 3-channel input, "
+ "but the model has %d channels\n",
+ input->dims[channel_idx]);
+ return AVERROR(ENOSYS);
+ }
+
+ ret = av_image_fill_linesizes(linesizes, fmt, w);
if (ret < 0) {
av_log(log_ctx, AV_LOG_ERROR, "unable to get linesizes with
av_image_fill_linesizes");
- sws_freeContext(sws_ctx);
return ret;
}
- sws_scale(sws_ctx, (const uint8_t *const *)frame->data, frame->linesize,
0, frame->height,
- (uint8_t *const [4]){input->data, 0, 0, 0}, linesizes);
+ if (packed_u8) {
+ /* UINT8 + NHWC: sws_scale() writes straight into input->data in one
pass. */
+ 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, "Impossible to create scale context
for the conversion "
+ "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);
+ return AVERROR(EINVAL);
+ }
- sws_freeContext(sws_ctx);
+ sws_scale(sws_ctx, (const uint8_t *const *)frame->data,
frame->linesize, 0, frame->height,
+ (uint8_t *const [4]){input->data, 0, 0, 0},
linesizes);
+ sws_freeContext(sws_ctx);
+ return 0;
+ }
+
+ if (frame->format == fmt && frame->width == w && frame->height == h) {
+ src = frame->data[0];
+ src_linesize = frame->linesize[0];
+ } else {
+ size_t tmp_size;
+
+ if (av_size_mult((size_t)linesizes[0], (size_t)h, &tmp_size) < 0) {
+ av_log(log_ctx, AV_LOG_ERROR, "dnn_detect temporary buffer size
overflows\n");
+ return AVERROR(EINVAL);
+ }
+
+ tmp_buf = av_malloc(tmp_size);
+ if (!tmp_buf)
+ return AVERROR(ENOMEM);
+
+ 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, "Impossible to create scale context
for the conversion "
+ "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 end;
+ }
+
+ sws_scale(sws_ctx, (const uint8_t *const *)frame->data,
frame->linesize, 0, frame->height,
+ (uint8_t *const [4]){tmp_buf, 0, 0, 0}, linesizes);
+ sws_freeContext(sws_ctx);
+ sws_ctx = NULL;
+
+ src = tmp_buf;
+ src_linesize = linesizes[0];
+ }
+
+ detect_write_tensor(input, src, src_linesize, w, h);
+
+end:
+ av_freep(&tmp_buf);
return ret;
}
diff --git a/libavfilter/dnn_filter_common.c b/libavfilter/dnn_filter_common.c
index 1e2bbd776e..392cca4d38 100644
--- a/libavfilter/dnn_filter_common.c
+++ b/libavfilter/dnn_filter_common.c
@@ -109,18 +109,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 8ce51bf07d..99dca8546c 100644
--- a/libavfilter/vf_dnn_detect.c
+++ b/libavfilter/vf_dnn_detect.c
@@ -21,6 +21,7 @@
* implementing an object detecting filter using deep learning networks.
*/
+#include "libavutil/common.h"
#include "libavutil/file_open.h"
#include "libavutil/mem.h"
#include "libavutil/opt.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));
@@ -108,19 +112,80 @@ static int dnn_detect_get_label_id(int nb_classes, int
cell_size, float *label_d
return label_id;
}
+static int dnn_detect_label_id_from_float(float f)
+{
+ if (!isfinite(f) || f < 0 || (double)f > INT_MAX)
+ return -1;
+ return (int)f;
+}
+
+static int dnn_detect_float_to_coord(double v)
+{
+ if (!isfinite(v))
+ return 0;
+ if (v > INT_MAX)
+ return INT_MAX;
+ if (v < INT_MIN)
+ return INT_MIN;
+ return (int)v;
+}
+
+static int dnn_detect_confidence_num(double conf)
+{
+ double scaled = conf * 10000.0;
+ if (!isfinite(scaled))
+ return 0;
+ if (scaled > INT_MAX)
+ return INT_MAX;
+ if (scaled < 0)
+ return 0;
+ return (int)scaled;
+}
+
+static void dnn_detect_set_bbox_edges(AVDetectionBBox *bbox, double x0, double
y0,
+ double x1, double y1, int frame_w, int
frame_h)
+{
+ double lo_x = -1.0 * frame_w, hi_x = 2.0 * frame_w;
+ double lo_y = -1.0 * frame_h, hi_y = 2.0 * frame_h;
+
+ x0 = av_clipd(x0, lo_x, hi_x);
+ x1 = av_clipd(x1, lo_x, hi_x);
+ y0 = av_clipd(y0, lo_y, hi_y);
+ y1 = av_clipd(y1, lo_y, hi_y);
+
+ if (x1 < x0)
+ x1 = x0;
+ if (y1 < y0)
+ y1 = y0;
+
+ bbox->x = dnn_detect_float_to_coord(x0);
+ bbox->y = dnn_detect_float_to_coord(y0);
+ bbox->w = dnn_detect_float_to_coord(x1 - x0);
+ bbox->h = dnn_detect_float_to_coord(y1 - y0);
+}
+
/* Calculate Intersection Over Union */
static float dnn_detect_IOU(AVDetectionBBox *bbox1, AVDetectionBBox *bbox2)
{
- float overlapping_width = FFMIN(bbox1->x + bbox1->w, bbox2->x + bbox2->w)
- FFMAX(bbox1->x, bbox2->x);
- float overlapping_height = FFMIN(bbox1->y + bbox1->h, bbox2->y + bbox2->h)
- FFMAX(bbox1->y, bbox2->y);
- float intersection_area =
+ double x1_min = bbox1->x, y1_min = bbox1->y;
+ double x1_max = (double)bbox1->x + bbox1->w, y1_max = (double)bbox1->y +
bbox1->h;
+ double x2_min = bbox2->x, y2_min = bbox2->y;
+ double x2_max = (double)bbox2->x + bbox2->w, y2_max = (double)bbox2->y +
bbox2->h;
+ double overlapping_width = FFMIN(x1_max, x2_max) - FFMAX(x1_min, x2_min);
+ double overlapping_height = FFMIN(y1_max, y2_max) - FFMAX(y1_min, y2_min);
+ double intersection_area =
(overlapping_width < 0 || overlapping_height < 0) ? 0 :
overlapping_height * overlapping_width;
- float union_area = bbox1->w * bbox1->h + bbox2->w * bbox2->h -
intersection_area;
- return intersection_area / union_area;
+ double area1 = (double)bbox1->w * bbox1->h;
+ double area2 = (double)bbox2->w * bbox2->h;
+ double union_area = area1 + area2 - intersection_area;
+
+ if (area1 <= 0 || area2 <= 0 || union_area <= 0)
+ return 0.f;
+ return (float)(intersection_area / union_area);
}
static int dnn_detect_parse_yolo_output(AVFrame *frame, DNNData *output, int
output_index,
- AVFilterContext *filter_ctx)
+ AVFilterContext *filter_ctx, int64_t
*anchor_used)
{
DnnDetectContext *ctx = filter_ctx->priv;
float conf_threshold = ctx->confidence;
@@ -128,11 +193,19 @@ static int dnn_detect_parse_yolo_output(AVFrame *frame,
DNNData *output, int out
int cell_w = 0, cell_h = 0, scale_w = 0, scale_h = 0;
int nb_classes = ctx->nb_classes;
float *output_data = output[output_index].data;
- float *anchors = ctx->anchors;
+ float *anchors;
+ int64_t anchors_needed;
AVDetectionBBox *bbox;
float (*post_process_raw_data)(float x) = linear;
int is_NHWC = 0;
+ if (output[output_index].dims[0] != 1) {
+ av_log(filter_ctx, AV_LOG_ERROR,
+ "YOLO output batch dimension must be 1, got %d\n",
+ output[output_index].dims[0]);
+ return AVERROR_INVALIDDATA;
+ }
+
if (ctx->model_type == DDMT_YOLOV1V2) {
cell_w = ctx->cell_w;
cell_h = ctx->cell_h;
@@ -151,8 +224,6 @@ static int dnn_detect_parse_yolo_output(AVFrame *frame,
DNNData *output, int out
scale_w = ctx->scale_width;
scale_h = ctx->scale_height;
}
- box_size = nb_classes + 5;
-
switch (ctx->model_type) {
case DDMT_YOLOV1V2:
case DDMT_YOLOV3:
@@ -163,31 +234,66 @@ static int dnn_detect_parse_yolo_output(AVFrame *frame,
DNNData *output, int out
break;
}
- if (!cell_h || !cell_w) {
+ if (cell_h <= 0 || cell_w <= 0) {
av_log(filter_ctx, AV_LOG_ERROR, "cell_w and cell_h are detected\n");
return AVERROR(EINVAL);
}
- if (!nb_classes) {
+ if (nb_classes <= 0) {
av_log(filter_ctx, AV_LOG_ERROR, "nb_classes is not set\n");
return AVERROR(EINVAL);
}
- if (output[output_index].dims[1] * output[output_index].dims[2] *
- output[output_index].dims[3] % (box_size * cell_w * cell_h)) {
+ if (output[output_index].dims[1] <= 0 || output[output_index].dims[2] <= 0
||
+ output[output_index].dims[3] <= 0) {
+ av_log(filter_ctx, AV_LOG_ERROR, "invalid output tensor dimensions\n");
+ return AVERROR_INVALIDDATA;
+ }
+
+ size_t box_size_sz = (size_t)nb_classes + 5;
+ size_t cell_area, elems_per_box, tmp, total_elems, detection_boxes_sz;
+
+ if (av_size_mult((size_t)cell_w, (size_t)cell_h, &cell_area) < 0 ||
+ av_size_mult(box_size_sz, cell_area, &elems_per_box) < 0 ||
+ elems_per_box == 0 || elems_per_box > INT_MAX) {
+ av_log(filter_ctx, AV_LOG_ERROR, "wrong cell_w, cell_h or
nb_classes\n");
+ return AVERROR(EINVAL);
+ }
+
+ if (av_size_mult((size_t)output[output_index].dims[1],
+ (size_t)output[output_index].dims[2], &tmp) < 0 ||
+ av_size_mult(tmp, (size_t)output[output_index].dims[3], &total_elems)
< 0 ||
+ total_elems > INT_MAX) {
+ av_log(filter_ctx, AV_LOG_ERROR, "output tensor is too large\n");
+ return AVERROR_INVALIDDATA;
+ }
+
+ if (total_elems % elems_per_box) {
+ av_log(filter_ctx, AV_LOG_ERROR, "wrong cell_w, cell_h or
nb_classes\n");
+ return AVERROR(EINVAL);
+ }
+
+ detection_boxes_sz = total_elems / elems_per_box;
+ if (detection_boxes_sz == 0 || detection_boxes_sz > INT_MAX) {
av_log(filter_ctx, AV_LOG_ERROR, "wrong cell_w, cell_h or
nb_classes\n");
return AVERROR(EINVAL);
}
- detection_boxes = output[output_index].dims[1] *
- output[output_index].dims[2] *
- output[output_index].dims[3] / box_size / cell_w /
cell_h;
- anchors = anchors + (detection_boxes * output_index * 2);
- /**
- * find all candidate bbox
- * yolo output can be reshaped to [B, N*D, Cx, Cy]
- * Detection box 'D' has format [`x`, `y`, `h`, `w`, `box_score`,
`class_no_1`, ...,]
- **/
+ box_size = (int)box_size_sz;
+ detection_boxes = (int)detection_boxes_sz;
+
+ anchors_needed = (int64_t)detection_boxes * 2;
+ if (anchors_needed < 0 || *anchor_used < 0 ||
+ *anchor_used + anchors_needed > ctx->nb_anchor) {
+ av_log(filter_ctx, AV_LOG_ERROR,
+ "anchors array (%d floats) is too small for %d detection
box(es) "
+ "in output %d (needs %"PRId64" floats starting at offset
%"PRId64")\n",
+ ctx->nb_anchor, detection_boxes, output_index, anchors_needed,
*anchor_used);
+ return AVERROR_INVALIDDATA;
+ }
+ anchors = ctx->anchors + *anchor_used;
+ *anchor_used += anchors_needed;
+
for (int box_id = 0; box_id < detection_boxes; box_id++) {
for (int cx = 0; cx < cell_w; cx++)
for (int cy = 0; cy < cell_h; cy++) {
@@ -222,7 +328,7 @@ static int dnn_detect_parse_yolo_output(AVFrame *frame,
DNNData *output, int out
conf = conf * post_process_raw_data(
detection_boxes_data[cy * cell_w + cx +
(label_id + 5) * cell_w * cell_h]);
}
- if (conf < conf_threshold) {
+ if (!isfinite(conf) || conf < conf_threshold) {
continue;
}
@@ -230,11 +336,15 @@ static int dnn_detect_parse_yolo_output(AVFrame *frame,
DNNData *output, int out
if (!bbox)
return AVERROR(ENOMEM);
- bbox->w = exp(w) * anchors[box_id * 2] * frame->width /
scale_w;
- bbox->h = exp(h) * anchors[box_id * 2 + 1] * frame->height /
scale_h;
- bbox->x = (cx + x) / cell_w * frame->width - bbox->w / 2;
- bbox->y = (cy + y) / cell_h * frame->height - bbox->h / 2;
- bbox->detect_confidence = av_make_q((int)(conf * 10000),
10000);
+ double w_px = exp((double)w) * anchors[box_id * 2] *
frame->width / scale_w;
+ double h_px = exp((double)h) * anchors[box_id * 2 + 1] *
frame->height / scale_h;
+ double x_px = (cx + (double)x) / cell_w * frame->width - w_px
/ 2;
+ double y_px = (cy + (double)y) / cell_h * frame->height - h_px
/ 2;
+
+ dnn_detect_set_bbox_edges(bbox, x_px, y_px, x_px + w_px, y_px
+ h_px,
+ frame->width, frame->height);
+
+ bbox->detect_confidence =
av_make_q(dnn_detect_confidence_num(conf), 10000);
if (ctx->labels && label_id < ctx->label_count) {
av_strlcpy(bbox->detect_label, ctx->labels[label_id],
sizeof(bbox->detect_label));
} else {
@@ -303,7 +413,8 @@ static int dnn_detect_fill_side_data(AVFrame *frame,
AVFilterContext *filter_ctx
static int dnn_detect_post_proc_yolo(AVFrame *frame, DNNData *output,
AVFilterContext *filter_ctx)
{
int ret = 0;
- ret = dnn_detect_parse_yolo_output(frame, output, 0, filter_ctx);
+ int64_t anchor_used = 0;
+ ret = dnn_detect_parse_yolo_output(frame, output, 0, filter_ctx,
&anchor_used);
if (ret < 0)
return ret;
ret = dnn_detect_fill_side_data(frame, filter_ctx);
@@ -316,8 +427,9 @@ static int dnn_detect_post_proc_yolov3(AVFrame *frame,
DNNData *output,
AVFilterContext *filter_ctx, int
nb_outputs)
{
int ret = 0;
+ int64_t anchor_used = 0;
for (int i = 0; i < nb_outputs; i++) {
- ret = dnn_detect_parse_yolo_output(frame, output, i, filter_ctx);
+ ret = dnn_detect_parse_yolo_output(frame, output, i, filter_ctx,
&anchor_used);
if (ret < 0)
return ret;
}
@@ -334,42 +446,98 @@ static int dnn_detect_post_proc_ssd(AVFrame *frame,
DNNData *output, int nb_outp
float conf_threshold = ctx->confidence;
int proposal_count = 0;
int detect_size = 0;
+ int detect_output_idx = 0;
+ int label_output_idx = -1;
float *detections = NULL, *labels = NULL;
int nb_bboxes = 0;
AVDetectionBBoxHeader *header;
AVDetectionBBox *bbox;
int scale_w = ctx->scale_width;
int scale_h = ctx->scale_height;
+ size_t detect_elems, needed_elems;
if (nb_outputs == 1 && output->dims[3] == 7) {
+ detect_output_idx = 0;
proposal_count = output->dims[2];
detect_size = output->dims[3];
detections = output->data;
} else if (nb_outputs == 2 && output[0].dims[3] == 5) {
+ detect_output_idx = 0;
proposal_count = output[0].dims[2];
detect_size = output[0].dims[3];
detections = output[0].data;
labels = output[1].data;
+ label_output_idx = 1;
} else if (nb_outputs == 2 && output[1].dims[3] == 5) {
+ detect_output_idx = 1;
proposal_count = output[1].dims[2];
detect_size = output[1].dims[3];
detections = output[1].data;
labels = output[0].data;
+ label_output_idx = 0;
} else {
av_log(filter_ctx, AV_LOG_ERROR, "Model output shape doesn't match ssd
requirement.\n");
return AVERROR(EINVAL);
}
+ if (proposal_count < 0) {
+ av_log(filter_ctx, AV_LOG_ERROR, "Invalid negative proposal count
%d.\n", proposal_count);
+ return AVERROR_INVALIDDATA;
+ }
+
if (proposal_count == 0)
return 0;
+ if (av_size_mult((size_t)proposal_count, (size_t)detect_size,
+ &needed_elems) < 0) {
+ av_log(filter_ctx, AV_LOG_ERROR, "detection tensor element count
overflows\n");
+ return AVERROR_INVALIDDATA;
+ }
+ if (needed_elems > INT_MAX) {
+ av_log(filter_ctx, AV_LOG_ERROR,
+ "detection tensor has %zu elements, more than the supported
maximum\n",
+ needed_elems);
+ return AVERROR_INVALIDDATA;
+ }
+
+ if (av_size_mult((size_t)output[detect_output_idx].dims[0],
+ (size_t)output[detect_output_idx].dims[1],
&detect_elems) < 0 ||
+ av_size_mult(detect_elems, (size_t)output[detect_output_idx].dims[2],
&detect_elems) < 0 ||
+ av_size_mult(detect_elems, (size_t)output[detect_output_idx].dims[3],
&detect_elems) < 0) {
+ av_log(filter_ctx, AV_LOG_ERROR, "detection tensor element count
overflows\n");
+ return AVERROR_INVALIDDATA;
+ }
+ if (detect_elems != needed_elems) {
+ av_log(filter_ctx, AV_LOG_ERROR,
+ "detection tensor has %zu elements, expected %zu\n",
+ detect_elems, needed_elems);
+ return AVERROR_INVALIDDATA;
+ }
+
+ if (label_output_idx >= 0) {
+ size_t label_count;
+ if (av_size_mult((size_t)output[label_output_idx].dims[0],
+ (size_t)output[label_output_idx].dims[1],
&label_count) < 0 ||
+ av_size_mult(label_count,
(size_t)output[label_output_idx].dims[2], &label_count) < 0 ||
+ av_size_mult(label_count,
(size_t)output[label_output_idx].dims[3], &label_count) < 0) {
+ av_log(filter_ctx, AV_LOG_ERROR, "labels tensor element count
overflows\n");
+ return AVERROR_INVALIDDATA;
+ }
+ if (label_count < (size_t)proposal_count) {
+ av_log(filter_ctx, AV_LOG_ERROR,
+ "labels tensor has %zu element(s), too small for %d
proposal(s)\n",
+ label_count, proposal_count);
+ return AVERROR_INVALIDDATA;
+ }
+ }
+
for (int i = 0; i < proposal_count; ++i) {
float conf;
if (nb_outputs == 1)
conf = detections[i * detect_size + 2];
else
conf = detections[i * detect_size + 4];
- if (conf < conf_threshold) {
+ if (!isfinite(conf) || conf < conf_threshold) {
continue;
}
nb_bboxes++;
@@ -389,19 +557,19 @@ static int dnn_detect_post_proc_ssd(AVFrame *frame,
DNNData *output, int nb_outp
av_strlcpy(header->source, ctx->dnnctx.model_filename,
sizeof(header->source));
for (int i = 0; i < proposal_count; ++i) {
- av_unused int image_id = (int)detections[i * detect_size + 0];
int label_id;
float conf, x0, y0, x1, y1;
+ double x0_px, y0_px, x1_px, y1_px;
if (nb_outputs == 1) {
- label_id = (int)detections[i * detect_size + 1];
+ label_id = dnn_detect_label_id_from_float(detections[i *
detect_size + 1]);
conf = detections[i * detect_size + 2];
x0 = detections[i * detect_size + 3];
y0 = detections[i * detect_size + 4];
x1 = detections[i * detect_size + 5];
y1 = detections[i * detect_size + 6];
} else {
- label_id = (int)labels[i];
+ label_id = dnn_detect_label_id_from_float(labels[i]);
x0 = detections[i * detect_size] / scale_w;
y0 = detections[i * detect_size + 1] / scale_h;
x1 = detections[i * detect_size + 2] / scale_w;
@@ -409,20 +577,24 @@ static int dnn_detect_post_proc_ssd(AVFrame *frame,
DNNData *output, int nb_outp
conf = detections[i * detect_size + 4];
}
- if (conf < conf_threshold) {
+ if (!isfinite(conf) || conf < conf_threshold) {
continue;
}
bbox = av_get_detection_bbox(header, header->nb_bboxes - nb_bboxes);
- bbox->x = (int)(x0 * frame->width);
- bbox->w = (int)(x1 * frame->width) - bbox->x;
- bbox->y = (int)(y0 * frame->height);
- bbox->h = (int)(y1 * frame->height) - bbox->y;
- bbox->detect_confidence = av_make_q((int)(conf * 10000), 10000);
+ x0_px = (double)x0 * frame->width;
+ y0_px = (double)y0 * frame->height;
+ x1_px = (double)x1 * frame->width;
+ y1_px = (double)y1 * frame->height;
+
+ dnn_detect_set_bbox_edges(bbox, x0_px, y0_px, x1_px, y1_px,
+ frame->width, frame->height);
+
+ bbox->detect_confidence = av_make_q(dnn_detect_confidence_num(conf),
10000);
bbox->classify_count = 0;
- if (ctx->labels && label_id < ctx->label_count) {
+ if (ctx->labels && label_id >= 0 && label_id < ctx->label_count) {
av_strlcpy(bbox->detect_label, ctx->labels[label_id],
sizeof(bbox->detect_label));
} else {
snprintf(bbox->detect_label, sizeof(bbox->detect_label), "%d",
label_id);
@@ -436,7 +608,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;
@@ -512,6 +684,7 @@ static int dnn_detect_post_proc_tf(AVFrame *frame, DNNData
*output, AVFilterCont
av_strlcpy(header->source, ctx->dnnctx.model_filename,
sizeof(header->source));
for (int i = 0; i < proposal_count; ++i) {
+ int label_id_i;
y0 = position[i * 4];
x0 = position[i * 4 + 1];
y1 = position[i * 4 + 2];
@@ -531,10 +704,11 @@ static int dnn_detect_post_proc_tf(AVFrame *frame,
DNNData *output, AVFilterCont
bbox->detect_confidence = av_make_q((int)(conf[i] * 10000), 10000);
bbox->classify_count = 0;
- if (ctx->labels && label_id[i] < ctx->label_count) {
- av_strlcpy(bbox->detect_label, ctx->labels[(int)label_id[i]],
sizeof(bbox->detect_label));
+ label_id_i = dnn_detect_label_id_from_float(label_id[i]);
+ if (ctx->labels && label_id_i >= 0 && label_id_i < ctx->label_count) {
+ av_strlcpy(bbox->detect_label, ctx->labels[label_id_i],
sizeof(bbox->detect_label));
} else {
- snprintf(bbox->detect_label, sizeof(bbox->detect_label), "%d",
(int)label_id[i]);
+ snprintf(bbox->detect_label, sizeof(bbox->detect_label), "%d",
label_id_i);
}
nb_bboxes--;
@@ -551,9 +725,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 +815,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);
--
To stop receiving notification emails like this one, please contact
[email protected].
_______________________________________________
ffmpeg-cvslog mailing list -- [email protected]
To unsubscribe send an email to [email protected]