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

Merge pull request #27757 from vpisarev:matshape_inside_mat

Use MatShape instead of MatSize inside cv::Mat/cv::UMat #27757

**Merge together with https://github.com/opencv/opencv_contrib/pull/3996**
---

This PR continues cv::Mat/cv::UMat refactoring. See #26056, where `MatShape` was introduced. Now it's put inside cv::Mat/cv::UMat instead of a weird `MatSize`. MatSize is now an alias for MatShape:

**before:**

```
struct MatShape { ... };
struct MatSize { ... };

struct Mat {
    ...
    int dims;
    int rows;
    int cols;

    ...

    MatShape shape() const { ... /* constructs MatShape out of MatSize and returns it;
                                    layout is always 'unknown', because we don't store it */ }

    MatSize size; // size is not valid without the parent cv::Mat,
                  // because size.p may point to Mat::rows or to Mat::cols,
                  // depending on the dimensionality, and dims() returns Mat::dims.
    MatStep step; // may allocate memory, depending on the dimensionality.
    ...
};
```

**after:**

```
struct MatShape { ... };
typedef MatShape MatSize; // they are now synonyms

struct Mat {
    ...
    int dims;
    int rows;
    int cols;

    ...

    MatShape shape() const { return size; } // just return the embedded shape (including the proper layout information)

    MatSize size; // size is self-contained data structure that can be used without the parent cv::Mat.
                  // size.dims is now a copy of dims; size.p[*] contains copies of Mat::rows and Mat::cols when dims <= 2.
    MatStep step; // does not allocate extra memory buffers.
    ...
};
```

There are several reasons to do that:

1. the main reason is to be able to store data layout (MatShape::layout) inside each cv::Mat/cv::UMat. This is necessary for the proper shape inference in DNN module. In particular, it's necessary for the next step of DNN inference optimization where we introduce block-layout-optimized convolution and other operations. Later on, we can use layout information to support non-interleaved images (e.g. RRR...GGG...BBB...) or even batches of such images in core/imgproc modules.
2. the other reason is to represent 3D/4D/5D etc. tensors as cv::Mat/cv::UMat instances more conveniently, without extra dynamic memory allocation. Before this patch we allocated some memory buffers dynamically to store shape & steps for more than 2D arrays. Now the whole cv::Mat/cv::UMat header can be stored completely on stack/in a container. Creating another copy of Mat/UMat header is now done more efficiently.
3. the third reason is to introduce the new coding pattern: `dst.create(src.size, <dst_type>);`. The pattern is suitable for most of element-wise (including cloning) and filtering operations. This pattern does not only look crisp and self-documenting, it will also automatically copy shape (including layout) from the source tensor into the destination matrix/tensor.
4. in the future we might add `colorspace` member to MatShape that will allow to distinguish RGB from BGR or NV12. `dst.create(src.size, <dst_type>);` will then copy the colorspace information as well.

### 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:
Vadim Pisarevsky
2025-09-15 15:03:34 +03:00
committed by GitHub
parent abcfb3c1e7
commit bdab54f79e
52 changed files with 521 additions and 624 deletions
+10 -10
View File
@@ -614,7 +614,7 @@ static void inRange(const Mat& src, const Mat& lb, const Mat& rb, Mat& dst)
{
CV_Assert( src.type() == lb.type() && src.type() == rb.type() &&
src.size == lb.size && src.size == rb.size );
dst.create( src.dims, &src.size[0], CV_8U );
dst.create( src.size, CV_8U );
const Mat *arrays[]={&src, &lb, &rb, &dst, 0};
Mat planes[4];
@@ -678,7 +678,7 @@ static void inRange(const Mat& src, const Mat& lb, const Mat& rb, Mat& dst)
static void inRangeS(const Mat& src, const Scalar& lb, const Scalar& rb, Mat& dst)
{
dst.create( src.dims, &src.size[0], CV_8U );
dst.create( src.size, CV_8U );
const Mat *arrays[]={&src, &dst, 0};
Mat planes[2];
@@ -836,7 +836,7 @@ static void finiteMask_(const _Tp *src, uchar *dst, size_t total, int cn)
static void finiteMask(const Mat& src, Mat& dst)
{
dst.create(src.dims, &src.size[0], CV_8UC1);
dst.create(src.size, CV_8UC1);
const Mat *arrays[]={&src, &dst, 0};
Mat planes[2];
@@ -1019,7 +1019,7 @@ namespace reference {
static void flip(const Mat& src, Mat& dst, int flipcode)
{
CV_Assert(src.dims <= 2);
dst.createSameSize(src, src.type());
dst.create(src.size, src.type());
int i, j, k, esz = (int)src.elemSize(), width = src.cols*esz;
for( i = 0; i < dst.rows; i++ )
@@ -1191,7 +1191,7 @@ struct SetZeroOp : public BaseElemWiseOp
namespace reference {
static void exp(const Mat& src, Mat& dst)
{
dst.create( src.dims, &src.size[0], src.type() );
dst.create( src.size, src.type() );
const Mat *arrays[]={&src, &dst, 0};
Mat planes[2];
@@ -1220,7 +1220,7 @@ static void exp(const Mat& src, Mat& dst)
static void log(const Mat& src, Mat& dst)
{
dst.create( src.dims, &src.size[0], src.type() );
dst.create( src.size, src.type() );
const Mat *arrays[]={&src, &dst, 0};
Mat planes[2];
@@ -1312,8 +1312,8 @@ static void cartToPolar(const Mat& mx, const Mat& my, Mat& mmag, Mat& mangle, bo
{
CV_Assert( (mx.type() == CV_32F || mx.type() == CV_64F) &&
mx.type() == my.type() && mx.size == my.size );
mmag.create( mx.dims, &mx.size[0], mx.type() );
mangle.create( mx.dims, &mx.size[0], mx.type() );
mmag.create( mx.size, mx.type() );
mangle.create( mx.size, mx.type() );
const Mat *arrays[]={&mx, &my, &mmag, &mangle, 0};
Mat planes[4];
@@ -1380,7 +1380,7 @@ struct CartToPolarToCartOp : public BaseArithmOp
Mat msrc[] = {mag, angle, x, y};
int pairs[] = {0, 0, 1, 1, 2, 2, 3, 3};
dst.create(src[0].dims, src[0].size, CV_MAKETYPE(src[0].depth(), 4));
dst.create(src[0].size, CV_MAKETYPE(src[0].depth(), 4));
cv::mixChannels(msrc, 4, &dst, 1, pairs, 4);
}
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
@@ -1389,7 +1389,7 @@ struct CartToPolarToCartOp : public BaseArithmOp
reference::cartToPolar(src[0], src[1], mag, angle, angleInDegrees);
Mat msrc[] = {mag, angle, src[0], src[1]};
int pairs[] = {0, 0, 1, 1, 2, 2, 3, 3};
dst.create(src[0].dims, src[0].size, CV_MAKETYPE(src[0].depth(), 4));
dst.create(src[0].size, CV_MAKETYPE(src[0].depth(), 4));
cv::mixChannels(msrc, 4, &dst, 1, pairs, 4);
}
void generateScalars(int, RNG& rng)
+13 -6
View File
@@ -160,23 +160,26 @@ TEST_P(HasNonZeroNd, hasNonZeroNd)
std::vector<size_t> steps(ndims);
std::vector<int> sizes(ndims);
size_t totalBytes = 1;
for(int dim = 0 ; dim<ndims ; ++dim)
for(int dim = ndims-1; dim >= 0; --dim)
{
const bool isFirstDim = (dim == 0);
const bool isLastDim = (dim+1 == ndims);
const int length = rng.uniform(1, 64);
steps[dim] = (isLastDim ? 1 : static_cast<size_t>(length))*CV_ELEM_SIZE(type);
steps[dim] = isLastDim ? CV_ELEM_SIZE(type) : sizes[dim+1]*steps[dim+1];
sizes[dim] = (isFirstDim || continuous) ? length : rng.uniform(1, length);
totalBytes *= steps[dim]*static_cast<size_t>(sizes[dim]);
totalBytes *= steps[dim];
}
totalBytes *= sizes[0];
std::vector<unsigned char> buffer(totalBytes);
void* data = buffer.data();
unsigned char magicval = 153;
size_t border = 128;
std::vector<unsigned char> buffer(totalBytes+border*2, magicval);
void* data = buffer.data() + border;
Mat m = Mat(ndims, sizes.data(), type, data, steps.data());
std::vector<Range> nzRange(ndims);
for(int dim = 0 ; dim<ndims ; ++dim)
for(int dim = 0; dim < ndims ; ++dim)
{
const int pos = rng.uniform(0, sizes[dim]);
nzRange[dim] = Range(pos, pos+1);
@@ -187,6 +190,10 @@ TEST_P(HasNonZeroNd, hasNonZeroNd)
const int nzCount = countNonZero(m);
EXPECT_EQ((nzCount>0), hasNonZero(m));
for (size_t j = 0; j < border; j++) {
ASSERT_EQ(buffer[j], magicval);
ASSERT_EQ(buffer[border + totalBytes + j], magicval);
}
}
}
+5 -4
View File
@@ -131,13 +131,13 @@ protected:
static_cast<int>(cvtest::randInt(rng)%10+1),
static_cast<int>(cvtest::randInt(rng)%10+1),
};
MatND test_mat_nd(3, sz, CV_MAKETYPE(depth, cn));
Mat test_mat_nd(3, sz, CV_MAKETYPE(depth, cn));
rng0.fill(test_mat_nd, RNG::UNIFORM, Scalar::all(ranges[depth][0]), Scalar::all(ranges[depth][1]));
if( depth >= CV_32F )
{
exp(test_mat_nd, test_mat_nd);
MatND test_mat_scale(test_mat_nd.dims, test_mat_nd.size, test_mat_nd.type());
Mat test_mat_scale(test_mat_nd.size, test_mat_nd.type());
rng0.fill(test_mat_scale, RNG::UNIFORM, Scalar::all(-1), Scalar::all(1));
cv::multiply(test_mat_nd, test_mat_scale, test_mat_nd);
}
@@ -148,8 +148,9 @@ protected:
static_cast<int>(cvtest::randInt(rng)%10+1),
static_cast<int>(cvtest::randInt(rng)%10+1),
};
SparseMat test_sparse_mat = cvTsGetRandomSparseMat(4, ssz, cvtest::randInt(rng)%(CV_64F+1),
cvtest::randInt(rng) % 10000, 0, 100, rng);
SparseMat test_sparse_mat =
cvTsGetRandomSparseMat(4, ssz, cvtest::randInt(rng)%(CV_64F+1),
cvtest::randInt(rng) % 10000, 0, 100, rng);
fs << "test_int" << test_int << "test_real" << test_real << "test_string" << test_string;
fs << "test_mat" << test_mat;
+2 -2
View File
@@ -2411,7 +2411,7 @@ TEST(Mat1D, basic)
m1.at<uchar>(50) = 10;
EXPECT_FALSE(m1.empty());
ASSERT_EQ(1, m1.dims);
ASSERT_EQ(1, m1.size.dims()); // hack map on .rows
ASSERT_EQ(1, m1.size.dims); // hack map on .rows
EXPECT_EQ(Size(100, 1), m1.size());
{
@@ -2598,7 +2598,7 @@ TEST(Mat, Recreate1DMatWithSameMeta)
cv::Mat m(dims, depth);
// By default m has dims: [1, 100]
m.dims = 1;
m.size.dims = m.dims = 1;
EXPECT_NO_THROW(m.create(dims, depth));
}