From b41cda2b93cfeca7752a5585e5ff6a49819ebc4c Mon Sep 17 00:00:00 2001 From: Trishanth Mellimi Date: Fri, 24 Jul 2026 23:48:00 +0530 Subject: [PATCH] imgproc: accept CV_Bool masks in distanceTransform cv::Mat_::depth() returns CV_Bool in 5.0 where it returned CV_8U in 4.x, so distanceTransform started rejecting boolean masks. CV_Bool is a 1-byte type whose values are only zero-tested here, so accept it wherever a CV_8UC1 mask was previously accepted, matching the 4.x behaviour and the 4->5 migration guide. Relaxes the type checks on all four reachable paths: the public wrapper, distanceTransform_L1_8U, trueDistTrans (DIST_MASK_PRECISE) and distanceATS_L1_8u (CV_8U output). Closes #29596 --- modules/imgproc/src/distransform.cpp | 12 +++-- .../imgproc/test/test_distancetransform.cpp | 45 +++++++++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/modules/imgproc/src/distransform.cpp b/modules/imgproc/src/distransform.cpp index e4a2b4eeac..f85581e34f 100644 --- a/modules/imgproc/src/distransform.cpp +++ b/modules/imgproc/src/distransform.cpp @@ -830,7 +830,7 @@ trueDistTrans( const Mat& src, Mat& dst ) CV_Assert( src.size() == dst.size() ); - CV_Assert( src.type() == CV_8UC1 && dst.type() == CV_32FC1 ); + CV_Assert( (src.type() == CV_8UC1 || src.type() == CV_BoolC1) && dst.type() == CV_32FC1 ); int i, m = src.rows, n = src.cols; cv::AutoBuffer _buf(std::max(m*2*sizeof(int) + (m*3+1)*sizeof(int), n*2*sizeof(float))); @@ -886,7 +886,7 @@ distanceATS_L1_8u( const Mat& src, Mat& dst ) int srcstep = (int)src.step; int dststep = (int)dst.step; - CV_Assert( src.type() == CV_8UC1 && dst.type() == CV_8UC1 ); + CV_Assert( (src.type() == CV_8UC1 || src.type() == CV_BoolC1) && dst.type() == CV_8UC1 ); CV_Assert( src.size() == dst.size() ); ////////////////////// forward scan //////////////////////// @@ -957,7 +957,9 @@ static void distanceTransform_L1_8U(InputArray _src, OutputArray _dst) Mat src = _src.getMat(); - CV_Assert( src.type() == CV_8UC1); + // CV_Bool is a 1-byte mask type (added in 5.0) and is only ever zero-tested + // here, so accept it wherever a CV_8UC1 mask was accepted. See #29596. + CV_Assert( src.type() == CV_8UC1 || src.type() == CV_BoolC1); _dst.create( src.size(), CV_8UC1); Mat dst = _dst.getMat(); @@ -978,7 +980,9 @@ void cv::distanceTransform( InputArray _src, OutputArray _dst, OutputArray _labe Mat src = _src.getMat(), labels; bool need_labels = _labels.needed(); - CV_Assert( src.type() == CV_8UC1); + // CV_Bool is a 1-byte mask type (added in 5.0) and is only ever zero-tested + // here, so accept it wherever a CV_8UC1 mask was accepted. See #29596. + CV_Assert( src.type() == CV_8UC1 || src.type() == CV_BoolC1); _dst.create( src.size(), CV_32F); Mat dst = _dst.getMat(); diff --git a/modules/imgproc/test/test_distancetransform.cpp b/modules/imgproc/test/test_distancetransform.cpp index c1d2e8c34a..6d73dec8fc 100644 --- a/modules/imgproc/test/test_distancetransform.cpp +++ b/modules/imgproc/test/test_distancetransform.cpp @@ -209,4 +209,49 @@ TEST(Imgproc_DistanceTransform, ipp_deterministic) } } +// See https://github.com/opencv/opencv/issues/29596 +// cv::Mat_::depth() returns CV_Bool in 5.0, where it was CV_8U in 4.x, so +// distanceTransform started rejecting boolean masks. CV_Bool is a 1-byte type +// whose values are only ever zero-tested here, so the result must match the one +// produced by an equivalent CV_8UC1 mask. +TEST(Imgproc_DistanceTransform, bool_mask_29596) +{ + Mat_ mask(7, 7, true); + mask(3, 3) = false; + mask(0, 6) = false; + + Mat_ ref(7, 7, (uchar)255); + ref(3, 3) = 0; + ref(0, 6) = 0; + + ASSERT_EQ(mask.depth(), CV_Bool); + + // CV_32F output: 3x3 and 5x5 kernels, plus the precise (trueDistTrans) path + const int maskSizes[] = { DIST_MASK_3, DIST_MASK_5, DIST_MASK_PRECISE }; + for (size_t i = 0; i < sizeof(maskSizes)/sizeof(maskSizes[0]); i++) + { + Mat dist_bool, dist_ref; + ASSERT_NO_THROW(distanceTransform(mask, dist_bool, DIST_L2, maskSizes[i])); + distanceTransform(ref, dist_ref, DIST_L2, maskSizes[i]); + EXPECT_EQ(cv::norm(dist_bool, dist_ref, NORM_INF), 0) << "maskSize = " << maskSizes[i]; + } + + // CV_8U output path (distanceTransform_L1_8U -> distanceATS_L1_8u) + { + Mat dist_bool, dist_ref; + ASSERT_NO_THROW(distanceTransform(mask, dist_bool, DIST_L1, DIST_MASK_3, CV_8U)); + distanceTransform(ref, dist_ref, DIST_L1, DIST_MASK_3, CV_8U); + EXPECT_EQ(cv::norm(dist_bool, dist_ref, NORM_INF), 0); + } + + // labels overload + { + Mat dist_bool, labels_bool, dist_ref, labels_ref; + ASSERT_NO_THROW(distanceTransform(mask, dist_bool, labels_bool, DIST_L2, DIST_MASK_5)); + distanceTransform(ref, dist_ref, labels_ref, DIST_L2, DIST_MASK_5); + EXPECT_EQ(cv::norm(dist_bool, dist_ref, NORM_INF), 0); + EXPECT_EQ(cv::norm(labels_bool, labels_ref, NORM_INF), 0); + } +} + }} // namespace