mirror of
https://github.com/opencv/opencv.git
synced 2026-07-29 07:13:02 +04:00
Merge pull request #28522 from abhishek-gola:batchnorm_layer_add
Added Batch Normalization layer to new DNN engine
This commit is contained in:
@@ -1161,6 +1161,15 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
static Ptr<BatchNormLayer> create(const LayerParams ¶ms);
|
||||
};
|
||||
|
||||
class CV_EXPORTS BatchNorm2Layer : public Layer
|
||||
{
|
||||
public:
|
||||
float epsilon;
|
||||
bool useGlobalStats, hasWeights, hasBias;
|
||||
|
||||
static Ptr<BatchNorm2Layer> create(const LayerParams& params);
|
||||
};
|
||||
|
||||
class CV_EXPORTS BatchNormLayerInt8 : public BatchNormLayer
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -189,6 +189,7 @@ void initializeLayerFactory()
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Gelu, GeluLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(GeluApproximation, GeluApproximationLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(BatchNorm, BatchNormLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(BatchNorm2, BatchNorm2Layer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(MaxUnpool, MaxUnpoolLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Dropout, BlankLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Identity, BlankLayer);
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
// 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.
|
||||
|
||||
#include "../precomp.hpp"
|
||||
#include "layers_common.hpp"
|
||||
|
||||
namespace cv {
|
||||
namespace dnn {
|
||||
|
||||
class BatchNorm2LayerImpl CV_FINAL : public BatchNorm2Layer {
|
||||
public:
|
||||
BatchNorm2LayerImpl(const LayerParams& params) {
|
||||
setParamsFrom(params);
|
||||
|
||||
epsilon = params.get<float>("epsilon", params.get<float>("eps", 1e-5f));
|
||||
useGlobalStats = params.get<bool>("use_global_stats", true);
|
||||
hasWeights = params.get<bool>("has_weight", false);
|
||||
hasBias = params.get<bool>("has_bias", false);
|
||||
|
||||
if (blobs.size() >= 4) {
|
||||
dynamicInputs = false;
|
||||
|
||||
const Mat& mean = blobs[0];
|
||||
const Mat& var = blobs[1];
|
||||
const Mat& scale = blobs[2];
|
||||
const Mat& beta = blobs[3];
|
||||
|
||||
weights_.create(scale.size(), CV_32F);
|
||||
bias_.create(scale.size(), CV_32F);
|
||||
|
||||
cv::sqrt(var + epsilon, bias_);
|
||||
cv::divide(scale, bias_, weights_);
|
||||
bias_ = beta - mean.mul(weights_);
|
||||
} else {
|
||||
dynamicInputs = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool supportBackend(int backendId) CV_OVERRIDE
|
||||
{
|
||||
return backendId == DNN_BACKEND_OPENCV;
|
||||
}
|
||||
|
||||
bool dynamicOutputShapes() const CV_OVERRIDE
|
||||
{
|
||||
return dynamicInputs;
|
||||
}
|
||||
|
||||
bool getMemoryShapes(const std::vector<MatShape>& inputs,
|
||||
const int requiredOutputs,
|
||||
std::vector<MatShape>& outputs,
|
||||
std::vector<MatShape>& internals) const CV_OVERRIDE
|
||||
{
|
||||
CV_Assert(!inputs.empty());
|
||||
outputs.assign(requiredOutputs, inputs[0]);
|
||||
return false;
|
||||
}
|
||||
|
||||
void getTypes(const std::vector<MatType>& inputs,
|
||||
const int requiredOutputs,
|
||||
const int requiredInternals,
|
||||
std::vector<MatType>& outputs,
|
||||
std::vector<MatType>& internals) const CV_OVERRIDE
|
||||
{
|
||||
CV_Assert(!inputs.empty());
|
||||
outputs.assign(requiredOutputs, inputs[0]);
|
||||
}
|
||||
|
||||
void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays internals_arr) CV_OVERRIDE
|
||||
{
|
||||
if (inputs_arr.depth() == CV_16F)
|
||||
{
|
||||
forward_fallback(inputs_arr, outputs_arr, internals_arr);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<Mat> inputs;
|
||||
inputs_arr.getMatVector(inputs);
|
||||
|
||||
const Mat &X = inputs[0];
|
||||
Mat Y;
|
||||
Mat w, b;
|
||||
|
||||
if (dynamicInputs) {
|
||||
CV_Assert(inputs.size() == 5);
|
||||
|
||||
const Mat& scale = inputs[1];
|
||||
const Mat& beta = inputs[2];
|
||||
const Mat& mean = inputs[3];
|
||||
const Mat& var = inputs[4];
|
||||
|
||||
w.create(scale.size(), CV_32F);
|
||||
b.create(scale.size(), CV_32F);
|
||||
|
||||
cv::sqrt(var + epsilon, b);
|
||||
cv::divide(scale, b, w);
|
||||
b = beta - mean.mul(w);
|
||||
} else {
|
||||
w = weights_;
|
||||
b = bias_;
|
||||
}
|
||||
|
||||
if (w.empty() || b.empty())
|
||||
CV_Error(Error::StsBadArg, "BatchNorm2Layer: Weights not initialized");
|
||||
|
||||
MatShape outShape = shape(X);
|
||||
auto kind = outputs_arr.kind();
|
||||
if (kind == _InputArray::STD_VECTOR_MAT) {
|
||||
std::vector<Mat>& outs = outputs_arr.getMatVecRef();
|
||||
CV_Assert(outs.size() >= 1);
|
||||
outs[0].fit(outShape, X.type());
|
||||
Y = outs[0];
|
||||
} else if (kind == _InputArray::STD_VECTOR_UMAT) {
|
||||
std::vector<UMat>& uouts = outputs_arr.getUMatVecRef();
|
||||
CV_Assert(uouts.size() >= 1);
|
||||
uouts[0].fit(outShape, X.type());
|
||||
Y = uouts[0].getMat(ACCESS_WRITE);
|
||||
} else {
|
||||
CV_Error(Error::StsBadArg, "Unsupported output array kind");
|
||||
}
|
||||
|
||||
const int C = (X.dims >= 2) ? X.size[1] : 1;
|
||||
const int N = X.size[0];
|
||||
const size_t planeSize = X.total() / (N * C);
|
||||
|
||||
CV_Assert(w.total() == C);
|
||||
|
||||
parallel_for_(Range(0, N * C), [&](const Range& r) {
|
||||
for (int i = r.start; i < r.end; ++i) {
|
||||
int c = i % C;
|
||||
|
||||
float scale_val = w.ptr<float>()[c];
|
||||
float shift_val = b.ptr<float>()[c];
|
||||
|
||||
const float* srcPtr = X.ptr<float>() + i * planeSize;
|
||||
float* dstPtr = Y.ptr<float>() + i * planeSize;
|
||||
|
||||
int j = 0;
|
||||
#if CV_SIMD128
|
||||
v_float32x4 v_scale = v_setall_f32(scale_val);
|
||||
v_float32x4 v_shift = v_setall_f32(shift_val);
|
||||
for (; j <= (int)planeSize - 4; j += 4) {
|
||||
v_float32x4 v_src = v_load(srcPtr + j);
|
||||
v_float32x4 v_dst = v_muladd(v_src, v_scale, v_shift);
|
||||
v_store(dstPtr + j, v_dst);
|
||||
}
|
||||
#endif
|
||||
for (; j < (int)planeSize; ++j) {
|
||||
dstPtr[j] = srcPtr[j] * scale_val + shift_val;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
private:
|
||||
bool dynamicInputs;
|
||||
Mat weights_, bias_;
|
||||
};
|
||||
|
||||
Ptr<BatchNorm2Layer> BatchNorm2Layer::create(const LayerParams& params)
|
||||
{
|
||||
return makePtr<BatchNorm2LayerImpl>(params);
|
||||
}
|
||||
}} // namespace cv::dnn
|
||||
@@ -1321,38 +1321,31 @@ void ONNXImporter2::parseInstanceNormalization(LayerParams& layerParams, const o
|
||||
void ONNXImporter2::parseBatchNormalization(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
|
||||
{
|
||||
if (node_proto.input_size() != 5)
|
||||
CV_Error(Error::StsNotImplemented,
|
||||
"Expected input, scale, bias, mean and var");
|
||||
CV_Error(Error::StsNotImplemented, "Expected input, scale, bias, mean and var");
|
||||
|
||||
layerParams.type = "BatchNorm";
|
||||
replaceLayerParam(layerParams, "epsilon", "eps");
|
||||
replaceLayerParam(layerParams, "spatial", "use_global_stats");
|
||||
layerParams.type = "BatchNorm2";
|
||||
|
||||
CV_Assert(net.isConstArg(node_inputs[3]));
|
||||
CV_Assert(net.isConstArg(node_inputs[4]));
|
||||
float eps = 1e-5f;
|
||||
for (int i = 0; i < node_proto.attribute_size(); i++) {
|
||||
const opencv_onnx::AttributeProto& attr = node_proto.attribute(i);
|
||||
if (attr.name() == "epsilon") eps = attr.f();
|
||||
}
|
||||
layerParams.set("epsilon", eps);
|
||||
|
||||
Mat meanData = net.argTensor(node_inputs[3]);
|
||||
Mat stdData = net.argTensor(node_inputs[4]);
|
||||
bool isStatic = net.isConstArg(node_inputs[1]) && // scale
|
||||
net.isConstArg(node_inputs[2]) && // bias
|
||||
net.isConstArg(node_inputs[3]) && // mean
|
||||
net.isConstArg(node_inputs[4]); // var
|
||||
|
||||
layerParams.blobs.push_back(meanData);
|
||||
layerParams.blobs.push_back(stdData);
|
||||
|
||||
if (!node_proto.input(1).empty()) {
|
||||
layerParams.set("has_weight", true);
|
||||
CV_Assert(net.isConstArg(node_inputs[1]));
|
||||
layerParams.blobs.push_back(net.argTensor(node_inputs[1])); // weightData
|
||||
} else {
|
||||
layerParams.set("has_weight", false);
|
||||
if (isStatic) {
|
||||
layerParams.blobs.resize(4);
|
||||
layerParams.blobs[0] = net.argTensor(node_inputs[3]); // mean
|
||||
layerParams.blobs[1] = net.argTensor(node_inputs[4]); // var
|
||||
layerParams.blobs[2] = net.argTensor(node_inputs[1]); // scale
|
||||
layerParams.blobs[3] = net.argTensor(node_inputs[2]); // bias
|
||||
}
|
||||
|
||||
if (!node_proto.input(2).empty()) {
|
||||
layerParams.set("has_bias", true);
|
||||
CV_Assert(net.isConstArg(node_inputs[1]));
|
||||
layerParams.blobs.push_back(net.argTensor(node_inputs[2])); // biasData
|
||||
} else {
|
||||
layerParams.set("has_bias", false);
|
||||
}
|
||||
addLayer(layerParams, node_proto, 1);
|
||||
addLayer(layerParams, node_proto);
|
||||
}
|
||||
|
||||
void ONNXImporter2::parseGemm(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
|
||||
|
||||
@@ -261,11 +261,11 @@ CASE(test_basic_conv_without_padding)
|
||||
CASE(test_basic_convinteger)
|
||||
// no filter
|
||||
CASE(test_batchnorm_epsilon)
|
||||
// no filter
|
||||
SKIP;
|
||||
CASE(test_batchnorm_epsilon_training_mode)
|
||||
// no filter
|
||||
CASE(test_batchnorm_example)
|
||||
// no filter
|
||||
SKIP;
|
||||
CASE(test_batchnorm_example_training_mode)
|
||||
// no filter
|
||||
CASE(test_bernoulli)
|
||||
|
||||
@@ -712,3 +712,5 @@
|
||||
"test_roialign_aligned_false",
|
||||
"test_roialign_aligned_true",
|
||||
"test_roialign_mode_max",
|
||||
"test_batchnorm_example",
|
||||
"test_batchnorm_epsilon",
|
||||
|
||||
@@ -124,9 +124,7 @@
|
||||
"test_basic_convinteger", // Issues::Layer::Can't create layer "onnx_node_output_0!y" of type "ConvInteger" in function 'getLayerInstance'
|
||||
"test_basic_deform_conv_with_padding",
|
||||
"test_basic_deform_conv_without_padding",
|
||||
"test_batchnorm_epsilon", // Issue:: Unkonwn error::Blob mean not found in const blobs in function 'getBlob'
|
||||
"test_batchnorm_epsilon_training_mode", // ---- same as above ---
|
||||
"test_batchnorm_example", // ---- same as above ---
|
||||
"test_batchnorm_example_training_mode", // ---- same as above ---
|
||||
"test_bernoulli", // Issues::Layer::Can't create layer "onnx_node_output_0!y" of type "Bernoulli" in function 'getLayerInstance'
|
||||
"test_bernoulli_double", // ---- same as above ---
|
||||
|
||||
Reference in New Issue
Block a user