From b2efa6f8601069bbc8327c767137ee843416457d Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Mon, 24 Aug 2026 15:22:33 +0500 Subject: [PATCH] Merge pull request #29762 from lazerg:fix/issue-29761-sgbm-3way-uniqueness-div-by-zero calib3d: fix division by zero in SGBM 3-way mode when uniquenessRatio is 100 - #29762 Fixes #29761. `SGBM3WayMainLoop` derives its uniqueness threshold in the SIMD path as `(100*min_cost)/(100-uniquenessRatio)`, so a `uniquenessRatio` of 100 divides by zero and the process dies with SIGFPE. The scalar fallback right below it, and the other SGBM modes, express the same test as `cost*(100 - uniquenessRatio) < min_cost*100`, which needs no division and copes with the value fine. `MODE_HH4` goes through `CalcHorizontalSums`, which never divides, so only `MODE_SGBM_3WAY` reproduces. The SIMD shortcut is now skipped once `uniquenessRatio` reaches 100 and the scalar loop decides on its own, which is exactly what a build without SIMD already does. Ratios below 100 keep the fast path and produce identical output. The diff looks long because the existing block is indented one level, `?w=1` shows the real change. The second commit fixes the neighbouring case: `thresh` grows to `100*min_cost` as the ratio approaches 100, well past `SHRT_MAX`, and `(short)(thresh+1)` wraps. On the reporter's image pair at ratio 99, 3-way marked 48723 pixels valid while `MODE_SGBM`, `MODE_HH` and `MODE_HH4` all landed near 48370; saturating brings it to 48371. Verified with opencv_extra test data: `Calib3d_StereoSGBM.regression`, `Calib3d_StereoSGBM.deterministic`, `Calib3d_StereoSGBM_HH4.regression` and `Calib3d_StereoBM.regression` still pass. The new `Calib3d_StereoSGBM.regression_29761` aborts on unpatched 4.x under `-fsanitize=integer-divide-by-zero` and passes with the fix. The same code sits at `modules/stereo/src/stereosgbm.cpp` on 5.x, which is the path the reporter cited. The module move means the merge will not apply cleanly, so tell me if you would rather have a separate 5.x PR. ### 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. - [ ] The feature is well documented and sample code can be built with the project CMake --- modules/calib3d/src/stereosgbm.cpp | 61 ++++++++++---------- modules/calib3d/test/test_stereomatching.cpp | 12 ++++ 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/modules/calib3d/src/stereosgbm.cpp b/modules/calib3d/src/stereosgbm.cpp index 74d5068ac5..ff033e7a0c 100644 --- a/modules/calib3d/src/stereosgbm.cpp +++ b/modules/calib3d/src/stereosgbm.cpp @@ -2074,41 +2074,44 @@ void SGBM3WayMainLoop::impl(const Range& range) const { d = 0; #if (CV_SIMD || CV_SIMD_SCALABLE) - horPassCostVolume+=x; - int thresh = (100*min_cost)/(100-uniquenessRatio); - v_int16 thresh_reg = vx_setall_s16((short)(thresh+1)); - v_int16 d1 = vx_setall_s16((short)(best_d-1)); - v_int16 d2 = vx_setall_s16((short)(best_d+1)); - v_int16 eight_reg = vx_setall_s16((short)VTraits::vlanes()); - v_int16 cur_d = vx_load(idx_row); - v_int16 mask; + if(uniquenessRatio<100) + { + horPassCostVolume+=x; + int thresh = (100*min_cost)/(100-uniquenessRatio); + v_int16 thresh_reg = vx_setall_s16(saturate_cast(thresh+1)); + v_int16 d1 = vx_setall_s16((short)(best_d-1)); + v_int16 d2 = vx_setall_s16((short)(best_d+1)); + v_int16 eight_reg = vx_setall_s16((short)VTraits::vlanes()); + v_int16 cur_d = vx_load(idx_row); + v_int16 mask; - for( ; d <= D - 2*VTraits::vlanes(); d+=2*VTraits::vlanes() ) - { - mask = v_and(v_lt(vx_load_aligned(horPassCostVolume + d), thresh_reg), v_or(v_lt(cur_d, d1), v_gt(cur_d, d2))); - cur_d = v_add(cur_d, eight_reg); - if( v_check_any(mask) ) - break; - mask = v_and(v_lt(vx_load_aligned(horPassCostVolume + d + VTraits::vlanes()), thresh_reg), v_or(v_lt(cur_d, d1), v_gt(cur_d, d2))); - cur_d = v_add(cur_d, eight_reg); - if( v_check_any(mask) ) - break; - } - if( d <= D - 2*VTraits::vlanes() ) - { - horPassCostVolume-=x; - continue; - } - if( d <= D - VTraits::vlanes() ) - { - if( v_check_any(v_and(v_lt(vx_load_aligned(horPassCostVolume + d), thresh_reg), v_or(v_lt(cur_d, d1), v_gt(cur_d, d2)))) ) + for( ; d <= D - 2*VTraits::vlanes(); d+=2*VTraits::vlanes() ) + { + mask = v_and(v_lt(vx_load_aligned(horPassCostVolume + d), thresh_reg), v_or(v_lt(cur_d, d1), v_gt(cur_d, d2))); + cur_d = v_add(cur_d, eight_reg); + if( v_check_any(mask) ) + break; + mask = v_and(v_lt(vx_load_aligned(horPassCostVolume + d + VTraits::vlanes()), thresh_reg), v_or(v_lt(cur_d, d1), v_gt(cur_d, d2))); + cur_d = v_add(cur_d, eight_reg); + if( v_check_any(mask) ) + break; + } + if( d <= D - 2*VTraits::vlanes() ) { horPassCostVolume-=x; continue; } - d+=VTraits::vlanes(); + if( d <= D - VTraits::vlanes() ) + { + if( v_check_any(v_and(v_lt(vx_load_aligned(horPassCostVolume + d), thresh_reg), v_or(v_lt(cur_d, d1), v_gt(cur_d, d2)))) ) + { + horPassCostVolume-=x; + continue; + } + d+=VTraits::vlanes(); + } + horPassCostVolume-=x; } - horPassCostVolume-=x; #endif for( ; d < D; d++ ) { diff --git a/modules/calib3d/test/test_stereomatching.cpp b/modules/calib3d/test/test_stereomatching.cpp index 9ea16110b1..910dd50a12 100644 --- a/modules/calib3d/test/test_stereomatching.cpp +++ b/modules/calib3d/test/test_stereomatching.cpp @@ -952,6 +952,18 @@ TEST(Calib3d_StereoSGBM, deterministic) { } +TEST(Calib3d_StereoSGBM, regression_29761) +{ + Mat leftImg(256, 256, CV_8UC1), rightImg(256, 256, CV_8UC1); + randu(leftImg, Scalar(0), Scalar(255)); + randu(rightImg, Scalar(0), Scalar(255)); + + Ptr sgbm = StereoSGBM::create( 0, 64, 5, 100, 200, 1, 31, 100, 0, 0, StereoSGBM::MODE_SGBM_3WAY); + Mat leftDisp; + sgbm->compute( leftImg, rightImg, leftDisp); + EXPECT_EQ(countNonZero(leftDisp != -StereoMatcher::DISP_SCALE), 0); +} + TEST(Calib3d_StereoSGBM_HH4, regression) { String path = cvtest::TS::ptr()->get_data_path() + "cv/stereomatching/datasets/teddy/";