mirror of
https://github.com/opencv/opencv.git
synced 2026-07-31 08:13:04 +04:00
Merge pull request #28963 from abhishek-gola:custom_layers
Added custom layer support in new DNN engine #28963 Closes: https://github.com/opencv/opencv/issues/26200 Merge with: https://github.com/opencv/opencv_extra/pull/1358 ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
// Copyright (C) 2026, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Companion sample for tutorial:
|
||||
// doc/tutorials/dnn/dnn_custom_layers/dnn_custom_layers.md (ONNX section)
|
||||
//
|
||||
// Models used by this sample are generated by:
|
||||
// opencv_extra/testdata/dnn/onnx/generate_custom_layer_models.py
|
||||
// and live at:
|
||||
// opencv_extra/testdata/dnn/onnx/models/custom_layer_default_domain.onnx
|
||||
// opencv_extra/testdata/dnn/onnx/models/custom_layer_custom_domain.onnx
|
||||
|
||||
#include <opencv2/dnn.hpp>
|
||||
#include <opencv2/core.hpp>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
using namespace cv;
|
||||
using namespace cv::dnn;
|
||||
using namespace std;
|
||||
|
||||
//! [CustomScaleBiasLayer]
|
||||
// y = scale * x + bias, with scale/bias read from ONNX node attributes.
|
||||
class CustomScaleBiasLayer CV_FINAL : public Layer
|
||||
{
|
||||
public:
|
||||
CustomScaleBiasLayer(const LayerParams& params) : Layer(params)
|
||||
{
|
||||
scale = params.get<float>("scale", 1.f);
|
||||
bias = params.get<float>("bias", 0.f);
|
||||
}
|
||||
|
||||
static Ptr<Layer> create(LayerParams& params)
|
||||
{
|
||||
return makePtr<CustomScaleBiasLayer>(params);
|
||||
}
|
||||
|
||||
bool getMemoryShapes(const vector<MatShape>& inpts,
|
||||
const int /*requiredOutputs*/,
|
||||
vector<MatShape>& outShapes,
|
||||
vector<MatShape>& /*internals*/) const CV_OVERRIDE
|
||||
{
|
||||
outShapes.assign(1, inpts[0]);
|
||||
return false;
|
||||
}
|
||||
|
||||
void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr,
|
||||
OutputArrayOfArrays) CV_OVERRIDE
|
||||
{
|
||||
vector<Mat> inps, outs;
|
||||
inputs_arr.getMatVector(inps);
|
||||
outputs_arr.getMatVector(outs);
|
||||
inps[0].convertTo(outs[0], outs[0].type(), scale, bias);
|
||||
}
|
||||
|
||||
private:
|
||||
float scale, bias;
|
||||
};
|
||||
//! [CustomScaleBiasLayer]
|
||||
|
||||
static void printShape(const string& tag, const Mat& m)
|
||||
{
|
||||
cout << tag;
|
||||
for (int d = 0; d < m.dims; ++d)
|
||||
cout << (d ? "x" : "") << m.size[d];
|
||||
cout << "\n";
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
const string keys =
|
||||
"{ help h | | Print help message }"
|
||||
"{ model m | | Path to ONNX model containing the custom op }"
|
||||
"{ op o | MyCustomOp | Op key for registration. Default-domain: just the op_type "
|
||||
"(e.g. MyCustomOp). Custom-domain: <domain>.<op_type> "
|
||||
"(e.g. my.namespace.MyDomainOp) }";
|
||||
CommandLineParser parser(argc, argv, keys);
|
||||
parser.about("Demonstrates importing an ONNX model that contains a custom (non-standard) op "
|
||||
"by registering a user-defined layer with cv::dnn::LayerFactory.");
|
||||
if (parser.has("help") || !parser.has("model"))
|
||||
{
|
||||
parser.printMessage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
const string modelPath = parser.get<string>("model");
|
||||
const string opKey = parser.get<string>("op");
|
||||
|
||||
//! [Register CustomScaleBiasLayer]
|
||||
// ONNX op-type lookup: layers in the default `ai.onnx` domain are registered
|
||||
// under their op_type; layers in a non-default domain are registered under
|
||||
// "<domain>.<op_type>" (e.g. "my.namespace.MyDomainOp").
|
||||
LayerFactory::registerLayer(opKey, CustomScaleBiasLayer::create);
|
||||
//! [Register CustomScaleBiasLayer]
|
||||
|
||||
Net net = readNetFromONNX(modelPath);
|
||||
if (net.empty())
|
||||
{
|
||||
cerr << "Failed to load model: " << modelPath << "\n";
|
||||
LayerFactory::unregisterLayer(opKey);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// The companion ONNX models accept a 1x3x4x4 float input.
|
||||
Mat input(vector<int>{1, 3, 4, 4}, CV_32F, Scalar(1.0f));
|
||||
net.setInput(input);
|
||||
Mat out = net.forward();
|
||||
|
||||
cout << "Loaded " << modelPath << " using custom op key '" << opKey << "'.\n";
|
||||
printShape("Input shape: ", input);
|
||||
printShape("Output shape: ", out);
|
||||
cout << "Output[0,0,0,0] = " << out.ptr<float>()[0]
|
||||
<< " (= scale * 1.0 + bias for the registered op)\n";
|
||||
|
||||
LayerFactory::unregisterLayer(opKey);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user