mirror of
https://github.com/opencv/opencv.git
synced 2026-10-28 08:35:30 +08:00
Merge pull request #29799 from lazerg:fix/issue-29798-dilate-bool
imgproc: accept CV_Bool input in morphological operations - #29799 ### Pull Request Readiness Checklist Fixes #29798 In 5.0 `cv::Mat_<bool>::depth()` returns `CV_Bool` instead of `CV_8U`, so the morphology filter factories in `morph.simd.hpp` no longer matched any branch and threw `Unsupported data type`. This worked in 4.x. `CV_Bool` is a 1-byte 0/1 type, so it is dispatched through the same `uchar` min/max filters as `CV_8U`, and the default border value follows the `CV_8U` case (1 for erode, 0 for dilate). The OpenCL path bails out for `CV_Bool` and falls back to the CPU implementation, since `ocl::typeToStr` has no mapping for it. Accepting `CV_Bool` also exposed a latent bug in `FilterEngine::init`: `borderElemSize` was computed from `CV_MAT_DEPTH(srcType) >= CV_32S`, which assumes the depth enum is ordered by element size. `CV_Bool` is 9, so a 1-byte type took the `sizeof(int)` branch and `borderElemSize` became 0, leaving `borderTab` empty and making every non-`BORDER_CONSTANT` erode/dilate read through a null pointer. It now keys off `CV_ELEM_SIZE1` instead. `CV_16F`/`CV_16BF` had the same latent miscomputation. Added a test comparing `Mat_<bool>` against the equivalent `CV_8UC1` mask for erode, dilate, open and close over the three structuring element shapes and four border types, plus in-place and multi-iteration calls. `MORPH_GRADIENT`/`TOPHAT`/`BLACKHAT` remain unsupported for `CV_Bool`: they subtract two masks, which has no meaning for a boolean type, and they throw rather than returning a wrong result. 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:
1 parent
7bd8de1f31
commit
7c3d04a044
4 files changed
+73
-10
No files matched your search
@@ -145,7 +145,12 @@ void FilterEngine::init( const Ptr<BaseFilter>& _filter2D,
|
||||
CV_Assert( 0 <= anchor.x && anchor.x < ksize.width &&
|
||||
0 <= anchor.y && anchor.y < ksize.height );
|
||||
|
||||
borderElemSize = srcElemSize/(CV_MAT_DEPTH(srcType) >= CV_32S ? sizeof(int) : 1);
|
||||
// borderTab keeps the source offsets of the border elements of one row. FilterEngine__proceed()
|
||||
// reads them as int offsets when borderElemSize*sizeof(int) is the element size, and as byte
|
||||
// offsets in all other cases. An element whose channel type is smaller than int uses the byte
|
||||
// form, so borderElemSize is the full element size in bytes there.
|
||||
borderElemSize = CV_ELEM_SIZE1(srcType) >= (int)sizeof(int) ?
|
||||
srcElemSize/(int)sizeof(int) : srcElemSize;
|
||||
int borderLength = std::max(ksize.width - 1, 1);
|
||||
borderTab.resize(borderLength*borderElemSize);
|
||||
|
||||
|
||||
@@ -113,15 +113,16 @@ Ptr<FilterEngine> createMorphologyFilter(
|
||||
borderValue == morphologyDefaultBorderValue() )
|
||||
{
|
||||
int depth = CV_MAT_DEPTH(type);
|
||||
CV_Assert( depth == CV_8U || depth == CV_16U || depth == CV_16S ||
|
||||
CV_Assert( depth == CV_8U || depth == CV_Bool || depth == CV_16U || depth == CV_16S ||
|
||||
depth == CV_32F || depth == CV_64F );
|
||||
if( op == MORPH_ERODE )
|
||||
borderValue = Scalar::all( depth == CV_8U ? (double)UCHAR_MAX :
|
||||
depth == CV_Bool ? 1. :
|
||||
depth == CV_16U ? (double)USHRT_MAX :
|
||||
depth == CV_16S ? (double)SHRT_MAX :
|
||||
depth == CV_32F ? (double)FLT_MAX : DBL_MAX);
|
||||
else
|
||||
borderValue = Scalar::all( depth == CV_8U || depth == CV_16U ?
|
||||
borderValue = Scalar::all( depth == CV_8U || depth == CV_Bool || depth == CV_16U ?
|
||||
0. :
|
||||
depth == CV_16S ? (double)SHRT_MIN :
|
||||
depth == CV_32F ? (double)-FLT_MAX : -DBL_MAX);
|
||||
@@ -797,7 +798,8 @@ static bool ocl_morphOp(InputArray _src, OutputArray _dst, InputArray _kernel,
|
||||
Size ksize = !kernel.empty() ? kernel.size() : Size(3, 3), ssize = _src.size();
|
||||
|
||||
bool doubleSupport = dev.doubleFPConfig() > 0;
|
||||
if ((depth == CV_64F && !doubleSupport) || borderType != BORDER_CONSTANT)
|
||||
// ocl::typeToStr() has no mapping for CV_Bool, so fall back to the CPU path
|
||||
if ((depth == CV_64F && !doubleSupport) || borderType != BORDER_CONSTANT || depth == CV_Bool)
|
||||
return false;
|
||||
|
||||
bool haveExtraMat = !_extraMat.empty();
|
||||
|
||||
@@ -724,7 +724,7 @@ Ptr<BaseRowFilter> getMorphologyRowFilter(int op, int type, int ksize, int ancho
|
||||
CV_Assert( op == MORPH_ERODE || op == MORPH_DILATE );
|
||||
if( op == MORPH_ERODE )
|
||||
{
|
||||
if( depth == CV_8U )
|
||||
if( depth == CV_8U || depth == CV_Bool )
|
||||
return makePtr<MorphRowFilter<MinOp<uchar>,
|
||||
ErodeRowVec8u> >(ksize, anchor);
|
||||
if( depth == CV_16U )
|
||||
@@ -742,7 +742,7 @@ Ptr<BaseRowFilter> getMorphologyRowFilter(int op, int type, int ksize, int ancho
|
||||
}
|
||||
else
|
||||
{
|
||||
if( depth == CV_8U )
|
||||
if( depth == CV_8U || depth == CV_Bool )
|
||||
return makePtr<MorphRowFilter<MaxOp<uchar>,
|
||||
DilateRowVec8u> >(ksize, anchor);
|
||||
if( depth == CV_16U )
|
||||
@@ -772,7 +772,7 @@ Ptr<BaseColumnFilter> getMorphologyColumnFilter(int op, int type, int ksize, int
|
||||
CV_Assert( op == MORPH_ERODE || op == MORPH_DILATE );
|
||||
if( op == MORPH_ERODE )
|
||||
{
|
||||
if( depth == CV_8U )
|
||||
if( depth == CV_8U || depth == CV_Bool )
|
||||
return makePtr<MorphColumnFilter<MinOp<uchar>,
|
||||
ErodeColumnVec8u> >(ksize, anchor);
|
||||
if( depth == CV_16U )
|
||||
@@ -790,7 +790,7 @@ Ptr<BaseColumnFilter> getMorphologyColumnFilter(int op, int type, int ksize, int
|
||||
}
|
||||
else
|
||||
{
|
||||
if( depth == CV_8U )
|
||||
if( depth == CV_8U || depth == CV_Bool )
|
||||
return makePtr<MorphColumnFilter<MaxOp<uchar>,
|
||||
DilateColumnVec8u> >(ksize, anchor);
|
||||
if( depth == CV_16U )
|
||||
@@ -819,7 +819,7 @@ Ptr<BaseFilter> getMorphologyFilter(int op, int type, const Mat& kernel, Point a
|
||||
CV_Assert( op == MORPH_ERODE || op == MORPH_DILATE );
|
||||
if( op == MORPH_ERODE )
|
||||
{
|
||||
if( depth == CV_8U )
|
||||
if( depth == CV_8U || depth == CV_Bool )
|
||||
return makePtr<MorphFilter<MinOp<uchar>, ErodeVec8u> >(kernel, anchor);
|
||||
if( depth == CV_16U )
|
||||
return makePtr<MorphFilter<MinOp<ushort>, ErodeVec16u> >(kernel, anchor);
|
||||
@@ -832,7 +832,7 @@ Ptr<BaseFilter> getMorphologyFilter(int op, int type, const Mat& kernel, Point a
|
||||
}
|
||||
else
|
||||
{
|
||||
if( depth == CV_8U )
|
||||
if( depth == CV_8U || depth == CV_Bool )
|
||||
return makePtr<MorphFilter<MaxOp<uchar>, DilateVec8u> >(kernel, anchor);
|
||||
if( depth == CV_16U )
|
||||
return makePtr<MorphFilter<MaxOp<ushort>, DilateVec16u> >(kernel, anchor);
|
||||
|
||||
@@ -1071,6 +1071,62 @@ TEST(Imgproc, morphologyEx_small_input_22893)
|
||||
ASSERT_EQ(0, cvtest::norm(result, gold, NORM_INF));
|
||||
}
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/29798
|
||||
// cv::Mat_<bool>::depth() returns CV_Bool in 5.0, where it was CV_8U in 4.x, so
|
||||
// morphological operations should keep accepting boolean masks as binary input.
|
||||
typedef TestWithParam< tuple<int, int, int> > Imgproc_Morphology_Bool;
|
||||
|
||||
TEST_P(Imgproc_Morphology_Bool, bool_mask_29798)
|
||||
{
|
||||
const int op = get<0>(GetParam());
|
||||
const int shape = get<1>(GetParam());
|
||||
const int borderType = get<2>(GetParam());
|
||||
|
||||
Mat_<bool> mask(7, 9, false);
|
||||
mask(1, 1) = true;
|
||||
mask(2, 2) = true;
|
||||
mask(2, 3) = true;
|
||||
mask(3, 3) = true;
|
||||
mask(5, 6) = true;
|
||||
mask(5, 7) = true;
|
||||
mask(6, 7) = true;
|
||||
|
||||
Mat_<uchar> ref;
|
||||
mask.convertTo(ref, CV_8U);
|
||||
|
||||
ASSERT_EQ(mask.depth(), CV_Bool);
|
||||
|
||||
Mat kernel = getStructuringElement(shape, Size(3, 3));
|
||||
|
||||
Mat result_bool, result_ref, result_8u;
|
||||
ASSERT_NO_THROW(morphologyEx(mask, result_bool, op, kernel, Point(-1, -1), 1, borderType));
|
||||
morphologyEx(ref, result_ref, op, kernel, Point(-1, -1), 1, borderType);
|
||||
|
||||
EXPECT_EQ(result_bool.depth(), CV_Bool);
|
||||
result_bool.convertTo(result_8u, CV_8U);
|
||||
EXPECT_EQ(0, cvtest::norm(result_8u, result_ref, NORM_INF));
|
||||
|
||||
// The issue reports cv::dilate directly, so cover it in-place and with several iterations too.
|
||||
Mat_<bool> inplace = mask.clone();
|
||||
Mat_<uchar> inplace_ref = ref.clone();
|
||||
ASSERT_NO_THROW(cv::dilate(inplace, inplace, kernel, Point(-1, -1), 2, borderType));
|
||||
cv::erode(inplace, inplace, kernel, Point(-1, -1), 2, borderType);
|
||||
cv::dilate(inplace_ref, inplace_ref, kernel, Point(-1, -1), 2, borderType);
|
||||
cv::erode(inplace_ref, inplace_ref, kernel, Point(-1, -1), 2, borderType);
|
||||
|
||||
Mat inplace_8u;
|
||||
inplace.convertTo(inplace_8u, CV_8U);
|
||||
EXPECT_EQ(0, cvtest::norm(inplace_8u, inplace_ref, NORM_INF));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Imgproc_Morphology_Bool,
|
||||
Combine(
|
||||
Values(MORPH_ERODE, MORPH_DILATE, MORPH_OPEN, MORPH_CLOSE),
|
||||
Values(MORPH_RECT, MORPH_CROSS, MORPH_ELLIPSE),
|
||||
Values(BORDER_CONSTANT, BORDER_REPLICATE, BORDER_REFLECT, BORDER_REFLECT_101)
|
||||
)
|
||||
);
|
||||
|
||||
TEST(Imgproc_sepFilter2D, identity)
|
||||
{
|
||||
std::vector<uint8_t> kernelX{0, 0, 0, 1, 0, 0, 0};
|
||||
|
||||
Reference in new issue
Block a user