From cc2b7659b416eb9b3c7db6d33143ef1938f0d604 Mon Sep 17 00:00:00 2001 From: Abhishek Gola Date: Fri, 8 May 2026 19:27:24 +0530 Subject: [PATCH] Merge pull request #28966 from abhishek-gola:dequantized_issue_fix Fixed dequantizelinear slicing bug #28966 closes: https://github.com/opencv/opencv/issues/25999 ### 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 --- .../dnn/src/int8layers/quantization_utils.cpp | 14 ++--- .../dnn/src/layers/dequantizelinear_layer.cpp | 21 +++++++- modules/dnn/src/onnx/onnx_importer.cpp | 51 +++++++++++++++---- modules/dnn/test/test_onnx_conformance.cpp | 20 -------- ...e_layer_filter_opencv_cpu_denylist.inl.hpp | 0 ...mance_layer_filter_opencv_denylist.inl.hpp | 4 -- 6 files changed, 68 insertions(+), 42 deletions(-) delete mode 100644 modules/dnn/test/test_onnx_conformance_layer_filter_opencv_cpu_denylist.inl.hpp delete mode 100644 modules/dnn/test/test_onnx_conformance_layer_filter_opencv_denylist.inl.hpp diff --git a/modules/dnn/src/int8layers/quantization_utils.cpp b/modules/dnn/src/int8layers/quantization_utils.cpp index 10ca90a1f8..544cfc02be 100644 --- a/modules/dnn/src/int8layers/quantization_utils.cpp +++ b/modules/dnn/src/int8layers/quantization_utils.cpp @@ -138,6 +138,7 @@ class QuantizeLayerImpl CV_FINAL : public QuantizeLayer public: int axis; int block_size; + int outDepth; // CV_8S (legacy default) or CV_8U; driven by ONNX y_zero_point dtype bool is1D; Mat scalesMat, zeropointsMat; // Saving the broadcasted scales data. bool quantParamExternal = true; // Indicates if the quantization parameters (scale and zero point) are provided as inputs to the node. @@ -147,6 +148,7 @@ public: is1D = params.get("is1D", false); axis = params.get("axis", 1); block_size = params.get("block_size", 0); + outDepth = params.get("depth", CV_8S); if (!is1D) { @@ -196,7 +198,7 @@ public: std::vector& outputs, std::vector& internals) const CV_OVERRIDE { - outputs.assign(requiredOutputs, CV_8S); + outputs.assign(requiredOutputs, outDepth); } @@ -229,7 +231,7 @@ public: inputs[0] = inputFp32; // replace } - inputs[0].convertTo(outputs[0], CV_8S, 1.f/scales[0], zeropoints[0]); + inputs[0].convertTo(outputs[0], outDepth, 1.f/scales[0], zeropoints[0]); return true; } #endif @@ -263,8 +265,8 @@ public: } } - if (outputs[0].depth() != CV_8S) - outputs[0].convertTo(outputs[0], CV_8S); + if (outputs[0].depth() != outDepth) + outputs[0].convertTo(outputs[0], outDepth); } void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays internals_arr) CV_OVERRIDE @@ -287,10 +289,10 @@ public: divide(inputs[0], scalesMat, inputTmp); add(inputTmp, zeropointsMat, inputTmp); - inputTmp.convertTo(outputs[0], CV_8S); + inputTmp.convertTo(outputs[0], outDepth); } else - inputs[0].convertTo(outputs[0], CV_8S, 1.f/scales[0], zeropoints[0]); + inputs[0].convertTo(outputs[0], outDepth, 1.f/scales[0], zeropoints[0]); } #ifdef HAVE_DNN_NGRAPH diff --git a/modules/dnn/src/layers/dequantizelinear_layer.cpp b/modules/dnn/src/layers/dequantizelinear_layer.cpp index 5f6b7f499e..9626f7a3ea 100644 --- a/modules/dnn/src/layers/dequantizelinear_layer.cpp +++ b/modules/dnn/src/layers/dequantizelinear_layer.cpp @@ -100,7 +100,26 @@ static void dequantizeLinear(const _InpTp* inp_, const _ScaleTp* scale_, const _ScaleTp* sc = scale_ + scale_ofs; _OutTp* out = out_ + block_ofs; - if (slice_size > 1) { + if (slice_size > 1 && block_size > 0) { + // Blocked mode: each of `delta` blocks has `block_size` rows along the + // axis (clipped at sz_a), and within each row the scale/zp vary across + // the `slice_size` trailing positions. The same `slice_size` scales/zps + // are reused for every row inside one block. + for (int k = 0; k < delta; k++) { + int rows = std::min(block_size, sz_a - (block_idx + k)*block_size); + for (int b = 0; b < rows; b++) { + for (int64_t j = 0; j < slice_size; j++) { + float scval = (float)sc[j]; + _InpTp zpval = zp ? zp[j] : (_InpTp)0; + out[j] = _OutTp((inp[j] - zpval)*scval); + } + inp += slice_size; + out += slice_size; + } + sc += scale_step; + if (zp) zp += zp_step; + } + } else if (slice_size > 1) { for (int k = 0; k < delta; k++, inp += slice_size, out += slice_size, sc += scale_step, zp += zp_step) { float scval = (float)*sc; diff --git a/modules/dnn/src/onnx/onnx_importer.cpp b/modules/dnn/src/onnx/onnx_importer.cpp index 592a2cdb4a..6ef82d8b13 100644 --- a/modules/dnn/src/onnx/onnx_importer.cpp +++ b/modules/dnn/src/onnx/onnx_importer.cpp @@ -64,6 +64,21 @@ static T getScalarFromMat(Mat m) return m.at(0); } +// Read scalar zero-point from a Mat of any supported integer depth. +// `unshiftFromInt8` undoes the -128 offset that populateNet() applies when +// rewriting UINT8 initializers as INT8. +static int readZpScalar(const Mat& m, int i, bool unshiftFromInt8 = false) +{ + switch (m.depth()) { + case CV_8U: return (int)m.at(i); + case CV_8S: return (int)m.at(i) + (unshiftFromInt8 ? 128 : 0); + case CV_16U: return (int)m.at(i); + case CV_16S: return (int)m.at(i); + case CV_32S: return m.at(i); + default: CV_Error(Error::StsNotImplemented, "Unsupported zero_point depth"); + } +} + static int onnxDataTypeToCvDepth(int onnxType) { switch (onnxType) @@ -98,7 +113,9 @@ class ONNXImporter struct TensorInfo { int real_ndims; - TensorInfo(int _real_ndims = 0) : real_ndims(_real_ndims) {} + int onnx_dtype; + TensorInfo(int _real_ndims = 0, int _onnx_dtype = 0) + : real_ndims(_real_ndims), onnx_dtype(_onnx_dtype) {} }; std::map getGraphTensors( @@ -446,7 +463,7 @@ std::map ONNXImporter::getGraphTensors( continue; layers_weights.insert(std::make_pair(tensor_proto.name(), mat)); - constBlobsExtraInfo.insert(std::make_pair(tensor_proto.name(), TensorInfo(tensor_proto.dims_size()))); + constBlobsExtraInfo.insert(std::make_pair(tensor_proto.name(), TensorInfo(tensor_proto.dims_size(), tensor_proto.data_type()))); } return layers_weights; } @@ -867,6 +884,8 @@ void ONNXImporter::populateNet() const opencv_onnx::TypeProto::Tensor& tensor = typeProto.tensor_type(); CV_Assert(tensor.has_shape()); const opencv_onnx::TensorShapeProto& tensorShape = tensor.shape(); + if (constBlobsExtraInfo.find(name) == constBlobsExtraInfo.end()) + constBlobsExtraInfo.insert(std::make_pair(name, TensorInfo(tensor.shape().dim_size(), tensor.elem_type()))); int dim_size = tensorShape.dim_size(); CV_CheckGE(dim_size, 0, ""); // some inputs are scalars (dims=0), e.g. in Test_ONNX_nets.Resnet34_kinetics test @@ -3411,13 +3430,22 @@ void ONNXImporter::parseQuantDequant(LayerParams& layerParams, const opencv_onnx // or 1-D tensor (per-channel quantized). bool is1D = false; - if (layerParams.type == "Quantize") - layerParams.set("depth", CV_8S); - else // Dequantize - layerParams.set("depth", CV_32F); + int outDepth = (layerParams.type == "Quantize") ? CV_8U : CV_32F; + int zpOnnxDtype = 0; + if (node_proto.input_size() == 3) + { + auto it = constBlobsExtraInfo.find(node_proto.input(2)); + if (it != constBlobsExtraInfo.end()) + zpOnnxDtype = it->second.onnx_dtype; + } - // If scale is not defined as a constant blob, it is considered an external input. if(constBlobs.find(node_proto.input(1)) == constBlobs.end()){ + if (layerParams.type == "Quantize") + { + layerParams.set("depth", zpOnnxDtype == 0 ? CV_8U : onnxDataTypeToCvDepth(zpOnnxDtype)); + } + else + layerParams.set("depth", outDepth); addLayer(layerParams, node_proto); return; } @@ -3431,6 +3459,9 @@ void ONNXImporter::parseQuantDequant(LayerParams& layerParams, const opencv_onnx zpMat = getBlob(node_proto, 2); CV_Assert(zpMat.total() == scaleMat.total()); // zero point should has the same shape as scale. } + if (layerParams.type == "Quantize") + outDepth = CV_8S; + layerParams.set("depth", outDepth); if (is1D) { @@ -3443,8 +3474,7 @@ void ONNXImporter::parseQuantDequant(LayerParams& layerParams, const opencv_onnx { scales[i] = scaleMat.at(i); if (!zpMat.empty()) - zeropoints[i] = zpMat.depth() == CV_32S ? - zpMat.at(i) : (int)zpMat.at(i); + zeropoints[i] = readZpScalar(zpMat, i); } layerParams.set("is1D", true); @@ -3454,8 +3484,7 @@ void ONNXImporter::parseQuantDequant(LayerParams& layerParams, const opencv_onnx } else { - int zeropoint = zpMat.empty() ? 0 : zpMat.depth() == CV_32S ? - getScalarFromMat(zpMat) : (int)getScalarFromMat(zpMat); + int zeropoint = zpMat.empty() ? 0 : readZpScalar(zpMat, 0); float scale = getScalarFromMat(scaleMat); layerParams.set("is1D", false); diff --git a/modules/dnn/test/test_onnx_conformance.cpp b/modules/dnn/test/test_onnx_conformance.cpp index 275fa87723..d69323c09a 100644 --- a/modules/dnn/test/test_onnx_conformance.cpp +++ b/modules/dnn/test/test_onnx_conformance.cpp @@ -1711,10 +1711,8 @@ public: static std::set parser_deny_list; static std::set global_deny_list; - static std::set opencv_deny_list; static std::set opencl_fp16_deny_list; static std::set opencl_deny_list; - static std::set cpu_deny_list; static std::set classic_deny_list; #ifdef HAVE_HALIDE static std::set halide_deny_list; @@ -1778,10 +1776,6 @@ public: #include "test_onnx_conformance_layer_filter_opencv_all_denylist.inl.hpp" }; - opencv_deny_list = { - #include "test_onnx_conformance_layer_filter_opencv_denylist.inl.hpp" - }; - opencl_fp16_deny_list = { #include "test_onnx_conformance_layer_filter_opencv_ocl_fp16_denylist.inl.hpp" }; @@ -1790,10 +1784,6 @@ public: #include "test_onnx_conformance_layer_filter_opencv_ocl_fp32_denylist.inl.hpp" }; - cpu_deny_list = { - #include "test_onnx_conformance_layer_filter_opencv_cpu_denylist.inl.hpp" - }; - EngineType engine_forced = (EngineType)utils::getConfigurationParameterSizeT( "OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO); @@ -1832,10 +1822,8 @@ public: std::set Test_ONNX_conformance::parser_deny_list; std::set Test_ONNX_conformance::global_deny_list; -std::set Test_ONNX_conformance::opencv_deny_list; std::set Test_ONNX_conformance::opencl_fp16_deny_list; std::set Test_ONNX_conformance::opencl_deny_list; -std::set Test_ONNX_conformance::cpu_deny_list; std::set Test_ONNX_conformance::classic_deny_list; #ifdef HAVE_HALIDE std::set Test_ONNX_conformance::halide_deny_list; @@ -1876,10 +1864,6 @@ TEST_P(Test_ONNX_conformance, Layer_Test) if (backend == DNN_BACKEND_OPENCV) { - if (opencv_deny_list.find(name) != opencv_deny_list.end()) - { - applyTestTag(CV_TEST_TAG_DNN_SKIP_OPENCV_BACKEND, CV_TEST_TAG_DNN_SKIP_ONNX_CONFORMANCE); - } if ((target == DNN_TARGET_OPENCL_FP16) && (opencl_fp16_deny_list.find(name) != opencl_fp16_deny_list.end())) { applyTestTag(CV_TEST_TAG_DNN_SKIP_OPENCL_FP16, CV_TEST_TAG_DNN_SKIP_OPENCV_BACKEND, CV_TEST_TAG_DNN_SKIP_ONNX_CONFORMANCE); @@ -1888,10 +1872,6 @@ TEST_P(Test_ONNX_conformance, Layer_Test) { applyTestTag(CV_TEST_TAG_DNN_SKIP_OPENCL, CV_TEST_TAG_DNN_SKIP_OPENCV_BACKEND, CV_TEST_TAG_DNN_SKIP_ONNX_CONFORMANCE); } - if ((target == DNN_TARGET_CPU) && (cpu_deny_list.find(name) != cpu_deny_list.end())) - { - applyTestTag(CV_TEST_TAG_DNN_SKIP_CPU, CV_TEST_TAG_DNN_SKIP_OPENCV_BACKEND, CV_TEST_TAG_DNN_SKIP_ONNX_CONFORMANCE); - } if (name == "test_roialign_aligned_false" || name == "test_roialign_aligned_true") { default_l1 = std::max(default_l1, 3e-5); diff --git a/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_cpu_denylist.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_cpu_denylist.inl.hpp deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_denylist.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_denylist.inl.hpp deleted file mode 100644 index a47427eb65..0000000000 --- a/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_denylist.inl.hpp +++ /dev/null @@ -1,4 +0,0 @@ -"test_dequantizelinear_blocked", // Issue https://github.com/opencv/opencv/issues/25999 -"test_quantizelinear", // Issue https://github.com/opencv/opencv/issues/25999 -"test_quantizelinear_axis", // Issue https://github.com/opencv/opencv/issues/25999 -"test_quantizelinear_blocked", // Issue https://github.com/opencv/opencv/issues/25999