Ошибка при попытке загрузить 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'