Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

attempt to add 0d/1d mat support to OpenCV #23473

Merged
merged 25 commits into from Sep 21, 2023
Merged

Conversation

vpisarev
Copy link
Contributor

@vpisarev vpisarev commented Apr 8, 2023

updated
This is another attempt to add 0D/1D Mat support to OpenCV. In the era of deep learning, ChatGPT etc. we want to support ONNX well, so we want Mat/UMat to be able to represent 1D and 0D data w/o 0D/1D=>2D hack.

The previous patch is here:
#18594. Unlike in 18594, here we don't add any special "1d" flag to the header. Instead, for 1D Mat the header looks like:

Mat::dims == 1
Mat::rows == 1
Mat::cols == number_of_elements
Mat::step[0] == Mat::step.buf[1] = Mat::elemSize()
Mat::step.buf[0] == number_of_elements*Mat::step.buf[1]

for 0D Mat (not tested yet) there should be

Mat::dims == 0
Mat::rows == Mat::cols == 1
Mat::step[0] = Mat::step.buf[0] == Mat::step.buf[1] == Mat::elemSize()

In other words, most of the functions that could process 2D Mat's by using their Mat::rows, Mat::cols, data and step's should handle 1D/0D Mat's just fine, but the check dims == 2 (if any) should be changed with dims <= 2. Also, the functions should create output array more carefully, via nD versions of Mat::create().

so far, OpenCV core tests seem to pass.
Let's see whether any other tests fail

Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

  • I agree to contribute to the project under Apache 2 License.
  • 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
  • The PR is proposed to the proper branch
  • There is a reference to the original bug report and related work
  • 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

@opencv-alalek
Copy link
Contributor

opencv-alalek commented Apr 10, 2023

For 1D case I (and other people) expect m.size(0) == total() instead of m.size(1) == total().

total() by definition is multiply of m.size(i) where i in [0, dims) (for any ND tensor). No need to break that.

Also accessing values >= dims is a mess and should be prohibited to avoid coding mistakes.

No need to design "something" just take a look on numpy.

a = np.array([1,2,3], dtype=int)
print(a)
print(a.shape)     # (3,)
print(a.shape[0])  # 3
print(a.shape[1])  # usage ERROR

Problem is here:

Mat::rows == number_of_elements
Mat::cols == 1

It should be vise versa.

@@ -515,7 +515,7 @@ void extract(const Mat& src, Mat& dst, int coi)
void transpose(const Mat& src, Mat& dst)
{
CV_Assert(src.data != dst.data && "Inplace is not support in cvtest::transpose");
CV_Assert(src.dims == 2);
CV_Assert(src.dims <= 2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transpose() for 1D/0D should be prohibited to avoid coding mistakes.

User should reshape mat to 2D first in explicit way.

CV_Assert(src.dims == 2);
dst.create(src.size(), src.type());
CV_Assert(src.dims <= 2);
dst.create(src.dims, src.size.p, src.type());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createSameSize()

Comment on lines +1759 to +1786
int n = 3;
Mat_<float> B(1, &n); B << 1, 2, 3;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

n

use self-described names.

int shapeB[1] = { 3 };

@@ -726,7 +727,6 @@ const _Tp* Mat::ptr(int y) const
inline
uchar* Mat::ptr(int i0, int i1)
{
CV_DbgAssert(dims >= 2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no sense to use both arguments for 1D/0D cases.

@asmorkalov asmorkalov added this to the 5.0 milestone May 5, 2023
@vrabaud
Copy link
Contributor

vrabaud commented May 10, 2023

I believe this supersedes #21059
Maybe the tests from that PR can be copied over ?

@dmatveev
Copy link
Contributor

👀

Copy link
Contributor

@dmatveev dmatveev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks invasive but will focus more deeply on the g-api changes.

@@ -2134,6 +2147,7 @@ class CV_EXPORTS Mat
int dims;
//! the number of rows and columns or (-1, -1) when the matrix has more than 2 dimensions
int rows, cols;
int dummy = 153;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will remove it in one of the subsequent commits in a different pull request;
Let it stay here for a few weeks/months to catch the situation when Mat.size.p (when it points to rows or cols) is accessed outside of those 2 fields

@vpisarev vpisarev merged commit 416bf32 into opencv:5.x Sep 21, 2023
23 checks passed
Copy link
Contributor

@opencv-alalek opencv-alalek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In comparison with #18594 there are too much changes in tests (equals to broken user code).

Comment on lines 550 to +553
Cv3d.undistortPoints(src, dst, cameraMatrix, distCoeffs);

assertEquals(src.size(), dst.size());
assertEquals(src.cols(), dst.rows());
assertEquals(src.rows(), dst.cols());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we want to swap rows/cols?
This doesn't look like a convenient behavior.

@@ -40,7 +40,8 @@ PERF_TEST_P(PointsNum_Algo, solvePnP,
projectPoints(points3d, rvec, tvec, intrinsics, distortion, points2d);

//add noise
Mat noise(1, (int)points2d.size(), CV_32FC2);
int sz = (int)points2d.size();
Mat noise(1, &sz, CV_32FC2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requirement of test modification means that we would broke user code.
We could add new tests easily, but not to modify it.

We need compatibility code to support both inputs (old and new).

@@ -2432,6 +2439,7 @@ TEST(Mat1D, DISABLED_basic)
Mat m(5, 100, CV_8UC1, Scalar::all(0));
const Mat row2D = m.row(2);
EXPECT_NO_THROW(m1.copyTo(row2D));
EXPECT_NO_THROW(row2D.copyTo(m1_copy));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idea is to write set of simple independent tests with common predefined variables.
This line violates that rule as it modifies common variable (result affects checks below).

{
std::string s;
s = cv::utils::dumpInputArray(noArray());
EXPECT_EQ(s, "InputArray: noArray()");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signature for _EQ is ASSERT_EQ(expected_reference, actual_result);.
Correct order of parameters are required to emit valid error messages.

Reference: https://github.com/opencv/opencv/blob/4.0.0/modules/ts/include/opencv2/ts/ts_gtest.h#L8196-L8200

GTEST_API_ AssertionResult EqFailure(const char* expected_expression,
                                     const char* actual_expression,
                                     const std::string& expected_value,
                                     const std::string& actual_value,
                                     bool ignoring_case);

if (shape.empty())
return 0;
//if (shape.empty())
// return 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just return 1 for 0D tensor?

axis = (axis < 0) ? (dims + axis) : axis;
CV_DbgCheck(axis, axis >= 0 && axis < dims, "");
CV_Assert(dims >= 0);
CV_Check(axis, axis >= -dims && axis <= dims, "");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

axis <= dims

We should not allow axis == dims.
It is not allowed in classic index/stride rules (e.g. in Python)

>>> a = [1, 2, 3]
>>> a[-3]
1
>>> a[-2]
2
>>> a[-1]
3
>>> a[0]
1
>>> a[1]
2
>>> a[2]
3
>>> a[3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>>

@@ -1899,7 +1905,7 @@ TEST_P(Test_ONNX_layers, Quantized_Convolution)

TEST_P(Test_ONNX_layers, Quantized_MatMul)
{
testONNXModels("quantized_matmul_uint8_weights", npy, 0.005, 0.007);
testONNXModels("quantized_matmul_uint8_weights", npy, 0.008, 0.015);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is happened?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants