1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-29 15:23:05 +04:00

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
This commit is contained in:
Abhishek Gola
2026-05-08 19:27:24 +05:30
committed by GitHub
parent 642a7307c4
commit cc2b7659b4
6 changed files with 68 additions and 42 deletions
@@ -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<bool>("is1D", false);
axis = params.get<int>("axis", 1);
block_size = params.get<int>("block_size", 0);
outDepth = params.get<int>("depth", CV_8S);
if (!is1D)
{
@@ -196,7 +198,7 @@ public:
std::vector<MatType>& outputs,
std::vector<MatType>& 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
@@ -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;
+40 -11
View File
@@ -64,6 +64,21 @@ static T getScalarFromMat(Mat m)
return m.at<T>(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<uint8_t>(i);
case CV_8S: return (int)m.at<int8_t>(i) + (unshiftFromInt8 ? 128 : 0);
case CV_16U: return (int)m.at<uint16_t>(i);
case CV_16S: return (int)m.at<int16_t>(i);
case CV_32S: return m.at<int>(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<std::string, Mat> getGraphTensors(
@@ -446,7 +463,7 @@ std::map<std::string, Mat> 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<float>(i);
if (!zpMat.empty())
zeropoints[i] = zpMat.depth() == CV_32S ?
zpMat.at<int>(i) : (int)zpMat.at<int8_t>(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<int>(zpMat) : (int)getScalarFromMat<int8_t>(zpMat);
int zeropoint = zpMat.empty() ? 0 : readZpScalar(zpMat, 0);
float scale = getScalarFromMat<float>(scaleMat);
layerParams.set("is1D", false);
@@ -1711,10 +1711,8 @@ public:
static std::set<std::string> parser_deny_list;
static std::set<std::string> global_deny_list;
static std::set<std::string> opencv_deny_list;
static std::set<std::string> opencl_fp16_deny_list;
static std::set<std::string> opencl_deny_list;
static std::set<std::string> cpu_deny_list;
static std::set<std::string> classic_deny_list;
#ifdef HAVE_HALIDE
static std::set<std::string> 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<std::string> Test_ONNX_conformance::parser_deny_list;
std::set<std::string> Test_ONNX_conformance::global_deny_list;
std::set<std::string> Test_ONNX_conformance::opencv_deny_list;
std::set<std::string> Test_ONNX_conformance::opencl_fp16_deny_list;
std::set<std::string> Test_ONNX_conformance::opencl_deny_list;
std::set<std::string> Test_ONNX_conformance::cpu_deny_list;
std::set<std::string> Test_ONNX_conformance::classic_deny_list;
#ifdef HAVE_HALIDE
std::set<std::string> 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);
@@ -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