Merge pull request #29883 from lazerg:fix/issue-29880-addweighted-null-kernel

core: fix addWeighted null kernel crash for f64 dtype and bool inputs - #29883

Fixes #29880.

`cv::addWeighted` segfaults for `CV_8U`, `CV_8S`, `CV_16U`, `CV_16S`, `CV_16F`, `CV_16BF` and `CV_32F` inputs with `dtype=CV_64F`, and for `CV_Bool` inputs with any dtype. When no direct `T -> rdepth` kernel exists, `TExpr::emitBinary()` picks a wide work type and looks the kernel up again, but for those input types only `T -> T` and `T -> f32` kernels are generated, so the second lookup returns a null function pointer too. The `addInsn()` overload that takes an already resolved kernel stores it without checking, and `runInsn()` then calls through the null pointer.

Cast the operands to the work type when there is no kernel for them either, so the f64 (or f32) kernel runs on widened inputs. That is also what 4.x did, it converted the sources to the working type before computing, so an f64 destination keeps full precision instead of going through an f32 intermediate. Added the `CV_Assert` on the resolved kernel that the other emit paths already carry.

### 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 (`5.x`, the element-wise engine this regressed in does not exist on `4.x`)
- [x] There is a reference to the original bug report and related work (#29880, regressed by #29426)
- [x] There is an accuracy test (`Core_Arithm.addWeighted_dtype_29880`, which segfaults without the fix); not applicable: performance test and opencv_extra test data
- [x] N/A: this is a bug fix, no new public API or documentation needed
This commit is contained in:
Lazizbek Ergashev
2026-09-08 22:03:15 +05:00
committed by GitHub
parent 7bf005dd05
commit 24c45b01ab
2 changed files with 60 additions and 5 deletions

View File

@@ -314,8 +314,9 @@ int TExpr::emitBinary(TOp op, int a, int b, int rdepth, const Scalar& params)
{
// addWeighted a*alpha + b*beta + gamma (params = {alpha, beta, gamma}): ONE fused kernel (two v_fma).
// Inputs are the same type T (cast to a common type if not). The kernel outputs T/f32 (small ints,
// f16/bf16, f32) or f64 directly; for any other requested rdepth it computes in the work type W and a
// final cast narrows it.
// f16/bf16, f32) or f64 directly, at its own natural work precision; for any other requested rdepth
// it computes at that natural precision and casts the RESULT to rdepth afterward (never the inputs
// up before the op, that would spend an extra cast and run the op at needlessly high precision).
if (op == OP_ADDW)
{
int Tt = arginfo[a].depth;
@@ -324,14 +325,17 @@ int TExpr::emitBinary(TOp op, int a, int b, int rdepth, const Scalar& params)
Tt = promoteArith(arginfo[a].depth, arginfo[b].depth);
a = maybeAddCast(a, Tt); b = maybeAddCast(b, Tt);
}
if (Tt == CV_Bool)
CV_Error(Error::StsNotImplemented, "addWeighted: CV_Bool inputs are not supported, cast explicitly "
"first, e.g. cv::texpr(\"uint8({0})*{1} + uint8({2})*{3}\", {a, alpha, b, beta})");
if (rdepth == EW_DEPTH_NONE) rdepth = Tt; // default dtype = input depth
TKernel k = getElemwiseFunc(OP_ADDW, Tt, Tt, EW_DEPTH_NONE, rdepth);
int outD = rdepth;
if (!k.fptr) // no direct T->rdepth kernel: compute in W, cast
if (!k.fptr) // no direct T->rdepth kernel: compute at T's natural work depth, cast after
{
outD = (Tt==CV_32U || Tt==CV_32S || Tt==CV_64U || Tt==CV_64S || Tt==CV_64F || rdepth==CV_64F)
? CV_64F : CV_32F;
outD = (Tt==CV_32U || Tt==CV_32S || Tt==CV_64U || Tt==CV_64S || Tt==CV_64F) ? CV_64F : CV_32F;
k = getElemwiseFunc(OP_ADDW, Tt, Tt, EW_DEPTH_NONE, outD);
CV_Assert(k.fptr && "ew: no kernel for this op/type combination");
}
const int out = addTemp(outD);
addInsn(OP_ADDW, a, b, 0, out, k, Scalar(params[0], params[1], params[2]));

View File

@@ -4276,6 +4276,57 @@ TEST(Core_Arithm, DISABLED_mul_overflow_28557)
}
// https://github.com/opencv/opencv/issues/29880
typedef testing::TestWithParam< tuple<perf::MatDepth, int> > Core_AddWeighted_regression29880;
TEST_P(Core_AddWeighted_regression29880, dtype)
{
const int sdepth = get<0>(GetParam());
const int dtype = get<1>(GetParam());
const int ddepth = dtype < 0 ? sdepth : dtype;
cv::Mat src(4, 4, CV_MAKETYPE(sdepth, 1), cv::Scalar::all(1)), dst, dst64f;
cv::addWeighted(src, 2.0, src, 3.0, 4.0, dst, dtype);
ASSERT_EQ(ddepth, dst.depth());
dst.convertTo(dst64f, CV_64F);
EXPECT_EQ(0, cv::countNonZero(dst64f != (ddepth == CV_Bool ? 1.0 : 9.0)));
}
// sdepth excludes CV_Bool: addWeighted now rejects Bool sources outright, see below.
INSTANTIATE_TEST_CASE_P(/**/, Core_AddWeighted_regression29880, testing::Combine(
testing::Values(CV_8U, CV_8S, CV_16U, CV_16S, CV_16F, CV_16BF, CV_32F),
testing::Values(-1, CV_8U, CV_32F, CV_64F, CV_Bool)));
// CV_Bool sources are disabled per https://github.com/opencv/opencv/pull/29883#issuecomment-5569942015:
// the user should cast explicitly instead.
typedef testing::TestWithParam<int> Core_AddWeighted_boolInput_29880;
TEST_P(Core_AddWeighted_boolInput_29880, throws)
{
const int dtype = GetParam();
cv::Mat src(4, 4, CV_MAKETYPE(CV_Bool, 1), cv::Scalar::all(1)), dst;
ASSERT_THROW(cv::addWeighted(src, 2.0, src, 3.0, 4.0, dst, dtype), cv::Exception);
}
INSTANTIATE_TEST_CASE_P(/**/, Core_AddWeighted_boolInput_29880,
testing::Values(-1, CV_8U, CV_32F, CV_64F, CV_Bool));
// The dtype=CV_64F path computes at fp32 (addWeighted's native work precision for 8U..32F sources)
// and casts the RESULT up to fp64, rather than widening the sources to fp64 before the op; the two
// give different bit patterns for a generic alpha, so this pins down which one actually runs.
TEST(Core_Arithm, addWeighted_fp64_uses_fp32_intermediate_29880)
{
const float srcVal = 100.f, alpha = 1.f/3, beta = 0.f, gamma = 0.f;
cv::Mat src(1, 1, CV_8UC1, cv::Scalar(srcVal)), dst;
cv::addWeighted(src, (double)alpha, src, (double)beta, (double)gamma, dst, CV_64F);
const double fp32Then64 = (double)cv::saturate_cast<float>(srcVal*alpha + srcVal*beta + gamma);
const double fp64Only = (double)srcVal*(double)alpha + (double)srcVal*(double)beta + (double)gamma;
ASSERT_NE(fp32Then64, fp64Only) << "chosen alpha does not distinguish the two code paths";
EXPECT_EQ(fp32Then64, dst.at<double>(0, 0));
}
TEST(Core_Arithm, min_empty)
{
cv::Mat A, B, C;