Ошибка при попытке загрузить ONNX модель в OpenCV

У меня есть работающее приложение, которое выполняет обнаружение с помощью ssd_mobilenet_v2, работающее исключительно на OpenCV, версия 4.6.0. Моя система — Ubuntu 24, C++17, CLion. Я хочу перейти на v3, используя формат ONNX, но при попытке загрузить модель возникают фатальные ошибки:

Before load
[ERROR:[email protected]] global ./modules/dnn/src/onnx/onnx_importer.cpp (1018) handleNode DNN/ONNX: ERROR during processing node with 1 inputs and 1 outputs: [ReduceMax]:(onnx_node!/transform/ReduceMax) from domain='ai.onnx'
terminate called after throwing an instance of 'cv::Exception'
  what():  OpenCV(4.6.0) ./modules/dnn/src/onnx/onnx_importer.cpp:1040: error: (-2:Unspecified error) in function 'handleNode'
> Node [[email protected]]:(onnx_node!/transform/ReduceMax) parse error: OpenCV(4.6.0) ./modules/dnn/src/layers/reduce_layer.cpp:327: error: (-215:Assertion failed) inputs.size() > 0 in function 'getMemoryShapes'

Функция которая выбрасывает ошибку:

void SSDModel::loadModelFromONNX()
{
    std::cout << "Before load" << std::endl;
    net = cv::dnn::readNetFromONNX(model_path);
    std::cout << "After load" << std::endl;
    net.setPreferableBackend(cv::dnn::DNN_BACKEND_DEFAULT);
    net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);
}

И SSDModel класс целиком:

#include "SSDModel.h"
#include <opencv2/dnn.hpp>
#include <fstream>
#include <iostream>

SSDModel::SSDModel(const std::string& model_path,
                   const std::string& class_file_path,
                   float conf_threshold,
                   float nms_threshold)
    : conf_threshold(conf_threshold),
      nms_threshold(nms_threshold),
      model_path(model_path),
      class_file_path(class_file_path)
{
    loadModelFromONNX();
    readClassFile();
}

SSDModel::~SSDModel() = default;

void SSDModel::detectObjects(const cv::Mat& image,
                             std::vector<int>& classIds,
                             std::vector<std::string>& classNames,
                             std::vector<float>& confidences,
                             std::vector<cv::Rect>& boxes)
{
    std::vector<int> indices = detect(image, classIds, confidences, boxes);

    std::vector<int> filteredClassIds;
    std::vector<std::string> filteredClassNames;
    std::vector<float> filteredConfidences;
    std::vector<cv::Rect> filteredBoxes;

    for (int index : indices) {
        filteredClassIds.push_back(classIds[index]);
        filteredClassNames.push_back(classes[classIds[index]]);
        filteredConfidences.push_back(confidences[index]);
        filteredBoxes.push_back(boxes[index]);
    }

    classIds = std::move(filteredClassIds);
    classNames = std::move(filteredClassNames);
    confidences = std::move(filteredConfidences);
    boxes = std::move(filteredBoxes);
}

int SSDModel::getClassNumber() const
{
    return classes.size();
}

std::vector<int> SSDModel::detect(const cv::Mat &image,
                                  std::vector<int> &classIds,
                                  std::vector<float> &confidences,
                                  std::vector<cv::Rect> &boxes)
{
    cv::Mat blob = cv::dnn::blobFromImage(image, 1.0, cv::Size(300, 300), cv::Scalar(), true, false);
    net.setInput(blob);

    cv::Mat output = net.forward();

    cv::Mat detections(output.size[2], output.size[3], CV_32F, output.ptr<float>());

    for (int i = 0; i < detections.rows; ++i)
    {
        float confidence = detections.at<float>(i, 2);

        if (confidence > conf_threshold)
        {
            int classId = static_cast<int>(detections.at<float>(i, 1)) - 1;
            int left = static_cast<int>(detections.at<float>(i, 3) * image.cols);
            int top = static_cast<int>(detections.at<float>(i, 4) * image.rows);
            int right = static_cast<int>(detections.at<float>(i, 5) * image.cols);
            int bottom = static_cast<int>(detections.at<float>(i, 6) * image.rows);

            classIds.push_back(classId);
            confidences.push_back(confidence);
            boxes.push_back(cv::Rect(left, top, right - left + 1, bottom - top + 1));
        }
    }

    std::vector<int> indices;
    cv::dnn::NMSBoxes(boxes, confidences, conf_threshold, nms_threshold, indices);

    return indices;
}

void SSDModel::readClassFile()
{
    std::ifstream ifs(class_file_path);
    if (!ifs.is_open())
        CV_Error(cv::Error::StsError, "Class File not found: " + class_file_path);

    std::string line;
    while (std::getline(ifs, line))
    {
        classes.push_back(line);
    }
}

void SSDModel::loadModelFromONNX()
{
    std::cout << "Before load" << std::endl;
    net = cv::dnn::readNetFromONNX(model_path);
    std::cout << "After load" << std::endl;
    net.setPreferableBackend(cv::dnn::DNN_BACKEND_DEFAULT);
    net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);
}

void SSDModel::loadModelFromTf()
{
    try {
        net = cv::dnn::readNetFromTensorflow("../resources/frozen_inference_graph.pb",
                                         "../resources/ssd_mobilenet_v2_coco_2018_03_29.pbtxt");
        setupNetwork();
    } catch (const cv::Exception& e) {
        std::cerr << "Error loading ONNX model: " << e.what() << std::endl;
        throw;
    }


}

void SSDModel::setupNetwork()
{
    net.setPreferableBackend(cv::dnn::DNN_BACKEND_DEFAULT);
    net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);

    std::vector<int> outLayers = net.getUnconnectedOutLayers();
    std::string outLayerType = net.getLayer(outLayers[0])->type;
    if (outLayerType != "DetectionOutput")
        CV_Error(cv::Error::StsNotImplemented, "Unexpected output layer type: " + outLayerType);
}

При использовании loadModelFromTf() вместо loadModelFromONNX() все работает отлично. Я знаю, что у меня неправильный размер blob для v3, но суть не в этом, т.к. исполнение до этой строки не доходит. Я пробовал разные файлы ONNX для ssd_v3, некоторые выдавали ту же ошибку, некоторые - другую, но каждый файл приводил к ошибке в функции загрузки.

Вот папка google drive с файлами, которые я попробовал. Файл, который я сгенерировал сам, называется ssdlite320_mobilenet_v3_large.onnx

EDIT: обновление до версии 4.10 не помогло, текст ошибки изменился, суть осталась прежней:

[ERROR:[email protected]] global onnx_importer.cpp:1036 handleNode DNN/ONNX: ERROR during processing node with 6 inputs and 1 outputs: [Concat]:(onnx_node!/transform/Concat_2) from domain='ai.onnx'
terminate called after throwing an instance of 'cv::Exception'
  what():  OpenCV(4.10.0-dev) /home/kuver/Downloads/opencv-4.x/modules/dnn/src/onnx/onnx_importer.cpp:1058: error: (-2:Unspecified error) in function 'handleNode'
> Node [[email protected]]:(onnx_node!/transform/Concat_2) parse error: OpenCV(4.10.0-dev) /home/kuver/Downloads/opencv-4.x/modules/dnn/src/layers/concat_layer.cpp:104: error: (-215:Assertion failed) curShape.size() == outputs[0].size() in function 'getMemoryShapes'

Ответы (0 шт):