Skip to content

Added reinterpret() method to Mat to convert meta-data without actual data conversion#25394

Merged
asmorkalov merged 1 commit intoopencv:4.xfrom
HaoYuan-Gao:in_place_convertTo
Apr 4, 2025
Merged

Added reinterpret() method to Mat to convert meta-data without actual data conversion#25394
asmorkalov merged 1 commit intoopencv:4.xfrom
HaoYuan-Gao:in_place_convertTo

Conversation

@HaoYuan-Gao
Copy link
Copy Markdown
Contributor

@HaoYuan-Gao HaoYuan-Gao commented Apr 11, 2024

I have an idea to optimize the convertTo operation for a Mat object. When src and dst are the same variable and their elemSize are equal, it could perform the operation in-place. Based on the suggestion of vpisarev, I added an reinterpret API for reinterpreting the Mat type. Now, We can perform the following operations to reuse memory.

origin : (This will allocate new memory)

    cv::Mat src(3, 5, CV_8UC1, cv::Scalar(1));
    src.convertTo(src, CV_8S);

optimization: (dst and src point to the same address, and the convertTo operation will not allocate new memory.)

    cv::Mat src(3, 5, CV_8UC1, cv::Scalar(1));
    cv::Mat dst = src.reinterpret(CV_8SC1);
    src.convertTo(dst, CV_8S);

In the convertTo scenario, leaving the decision of whether to reuse memory to the user is a more reasonable approach.

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

@dkurt
Copy link
Copy Markdown
Member

dkurt commented Apr 11, 2024

Mat src = *this; won't let reuse memory because of references counter. So in case of similar buffer, Mat src = *this; should be called after .create. Make a test to verify

@HaoYuan-Gao
Copy link
Copy Markdown
Contributor Author

Mat src = *this; won't let reuse memory because of references counter. So in case of similar buffer, Mat src = *this; should be called after .create. Make a test to verify

The condition-based modification changed the way dst is created, and under the specified conditions, it does not allocate new memory. And I don't understand why the reference counter affects memory reuse.

@dkurt
Copy link
Copy Markdown
Member

dkurt commented Apr 11, 2024

@Gao-HaoYuan, ok, please add a test anyway.

@HaoYuan-Gao
Copy link
Copy Markdown
Contributor Author

@dkurt you are right, I need to add a test to verify the result. Thanks for your review. If there are still issues, please help me identify them.

dst.create(dims, size, dtype);
Mat dstMat = dst.getMat();
Mat dstMat;
if (this->data == dst.getMat().data && this->elemSize() == CV_ELEM_SIZE(dtype)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There is no check for size/dims match. For example, ROI will have the same data ptr:

Mat m (10, 10, CV_8U);
Mat roi = m.rowRange(0, 2);
m.convertTo(roi, CV_8S);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I had through !dst.getMat().isSubmatrix() to determine this case.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about the following case? .data matched but not the shapes.

Mat m (10, 10, CV_8U);
Mat m2(5, 10, CV_8U, m.ptr<uint8_t>());
m.convertTo(m2, CV_8S);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Firstly, if roi is a submatrix, the refcount will add 1.
if (this->data == dst.getMat().data && this->elemSize() == CV_ELEM_SIZE(dtype) && refcount == 2)
This condition can recognize the first case.
Mat m (10, 10, CV_8U); Mat roi = m.rowRange(0, 2); m.convertTo(roi, CV_8S);

BUt, I have come up with a new question. Like,

Mat m (10, 10, CV_8U); Mat m2(10, 10, CV_8U, m.ptr<uint8_t>()); m.convertTo(m2, CV_8S);

In this case, the member 'u' is NULL. By checking whether 'u' is null, we can solve this problem.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Mat m (10, 10, CV_8U); Mat m2(10, 10, CV_8U, m.ptr<uint8_t>()); m.convertTo(m, CV_8S);

And, in this case, the original data of 'm' will be released, and 'm2' may encounter unpredictable errors. I believe such risky behavior should be noted by OpenCV users.

int dtype = CV_MAKE_TYPE(depth, cn);
A.convertTo(A, depth);
EXPECT_EQ(p, A.data);
EXPECT_EQ(A.type(), dtype);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Add a check for resulting values:

EXPECT_EQ(0, countNonZero(A != cv::Scalar(1, 2, 3)));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I had add a function to replace countNonZero, because countNonZero only support cn == 1.

@asmorkalov
Copy link
Copy Markdown
Contributor

@opencv-alalek could you take a look?

Mat dstMat = dst.getMat();
Mat dstMat;
int refcount = this->u->refcount;
if (this->data == dst.getMat().data && dst.getMat().u != NULL && this->elemSize() == CV_ELEM_SIZE(dtype) && refcount == 2) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

it's quite bad idea to do it at such a high-level with such unsafe low-level details: 1) refcount==2, 2) 3 calls to getMat(), which can be rather expensive. 3) dst.assign() call, which is not that safe.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@vpisarev
I have added a reinterpretType API to reinterpret the data type of Mat. However, it seems that these checks are unavoidable. Instead, I have changed it to refcount == 1 and reduced the number of dst.getMat calls.

@vpisarev
Copy link
Copy Markdown
Contributor

@Gao-HaoYuan, thanks for the patch. This could be a useful feature, but I don't like implementation, it has many drawbacks. I would rather suggest to add Mat::reinterpret_cast() method to return a new header pointing to the same data.

@asmorkalov
Copy link
Copy Markdown
Contributor

2024-04-16T12:20:57.4320646Z /home/ci/opencv/modules/core/src/convert.dispatch.cpp:284:87: warning: comparison of integer expressions of different signedness: 'size_t' {aka 'long unsigned int'} and 'int' [-Wsign-compare]

@asmorkalov
Copy link
Copy Markdown
Contributor

The patch breaks a lot of existing tests, e.g.:

Details
2024-04-16T12:28:18.9261297Z [  PASSED  ] 90 tests.
2024-04-16T12:28:18.9261708Z [  FAILED  ] 69 tests, listed below:
2024-04-16T12:28:18.9262216Z [  FAILED  ] Calib3d_Affine3f.accuracy_rvec
2024-04-16T12:28:18.9262824Z [  FAILED  ] Calib3d_CalibrateCamera_CPP.regression
2024-04-16T12:28:18.9263466Z [  FAILED  ] Calib3d_CalibrateCamera_CPP.badarg
2024-04-16T12:28:18.9264075Z [  FAILED  ] Calib3d_ProjectPoints_CPP.regression
2024-04-16T12:28:18.9264705Z [  FAILED  ] Calib3d_ProjectPoints_CPP.inputShape
2024-04-16T12:28:18.9265339Z [  FAILED  ] Calib3d_ProjectPoints_CPP.outputShape
2024-04-16T12:28:18.9265943Z [  FAILED  ] Calib3d_ProjectPoints_CPP.badarg
2024-04-16T12:28:18.9266565Z [  FAILED  ] Calib3d_StereoCalibrate_CPP.regression
2024-04-16T12:28:18.9267600Z [  FAILED  ] Calib3d_StereoCalibrate_CPP.extended
2024-04-16T12:28:18.9268289Z [  FAILED  ] Calib3d_StereoCalibrate.regression_10791
2024-04-16T12:28:18.9268990Z [  FAILED  ] Calib3d_StereoCalibrate.regression_11131
2024-04-16T12:28:18.9269716Z [  FAILED  ] Calib3d_StereoCalibrate.regression_23305
2024-04-16T12:28:18.9270912Z [  FAILED  ] CV_RecoverPoseTest.regression_15341
2024-04-16T12:28:18.9272242Z [  FAILED  ] cameraCalibrationTiltTest.calibrateCamera
2024-04-16T12:28:18.9273558Z [  FAILED  ] Calib3d_ChessboardDetector.accuracy
2024-04-16T12:28:18.9274534Z [  FAILED  ] Calib3d_ChessboardDetector.badarg
2024-04-16T12:28:18.9275147Z [  FAILED  ] Calib3d_ChessboardDetector2.accuracy
2024-04-16T12:28:18.9275901Z [  FAILED  ] Calib3d_ChessboardDetector3.accuracy
2024-04-16T12:28:18.9276747Z [  FAILED  ] Calib3d_AsymmetricCirclesPatternDetector.regression_18713
2024-04-16T12:28:18.9277756Z [  FAILED  ] Calib3d_AsymmetricCirclesPatternDetectorWithClustering.accuracy
2024-04-16T12:28:18.9278844Z [  FAILED  ] Calib3d_CirclesPatternDetectorWithClustering.accuracy
2024-04-16T12:28:18.9279729Z [  FAILED  ] Calib3d_ChessboardSubPixDetector.accuracy
2024-04-16T12:28:18.9280331Z [  FAILED  ] fisheyeTest.solvePnP
2024-04-16T12:28:18.9280848Z [  FAILED  ] fisheyeTest.jacobians
2024-04-16T12:28:18.9281420Z [  FAILED  ] Calib3d_Rodrigues.accuracy
2024-04-16T12:28:18.9282087Z [  FAILED  ] Calib3d_FindFundamentalMat.accuracy
2024-04-16T12:28:18.9282830Z [  FAILED  ] Calib3d_ConvertHomogeneoous.accuracy
2024-04-16T12:28:18.9283499Z [  FAILED  ] Calib3d_ComputeEpilines.accuracy
2024-04-16T12:28:18.9284085Z [  FAILED  ] Calib3d_FindEssentialMat.accuracy
2024-04-16T12:28:18.9284653Z [  FAILED  ] Calib3d_Homography.accuracy
2024-04-16T12:28:18.9285173Z [  FAILED  ] Calib3d_Homography.EKcase
2024-04-16T12:28:18.9285727Z [  FAILED  ] Calib3d_Homography.fromImages
2024-04-16T12:28:18.9286340Z [  FAILED  ] Calib3d_SolveP3P.accuracy
2024-04-16T12:28:18.9286874Z [  FAILED  ] Calib3d_SolvePnPRansac.accuracy
2024-04-16T12:28:18.9287508Z [  FAILED  ] Calib3d_SolvePnPRansac.concurrency
2024-04-16T12:28:18.9288201Z [  FAILED  ] Calib3d_SolvePnPRansac.input_type
2024-04-16T12:28:18.9288823Z [  FAILED  ] Calib3d_SolvePnPRansac.double_support
2024-04-16T12:28:18.9289562Z [  FAILED  ] Calib3d_SolvePnPRansac.bad_input_points_19253
2024-04-16T12:28:18.9290367Z [  FAILED  ] Calib3d_SolvePnPRansac.minPoints
2024-04-16T12:28:18.9291087Z [  FAILED  ] Calib3d_SolvePnPRansac.inputShape
2024-04-16T12:28:18.9291688Z [  FAILED  ] Calib3d_SolvePnP.accuracy
2024-04-16T12:28:18.9292585Z [  FAILED  ] Calib3d_SolvePnP.accuracy_planar
2024-04-16T12:28:18.9293190Z [  FAILED  ] Calib3d_SolvePnP.accuracy_planar_tag
2024-04-16T12:28:18.9293775Z [  FAILED  ] Calib3d_SolvePnP.input_type
2024-04-16T12:28:18.9294345Z [  FAILED  ] Calib3d_SolvePnP.translation
2024-04-16T12:28:18.9295136Z [  FAILED  ] Calib3d_SolvePnP.iterativeInitialGuess3pts
2024-04-16T12:28:18.9296421Z [  FAILED  ] Calib3d_SolvePnP.iterativeInitialGuess
2024-04-16T12:28:18.9297607Z [  FAILED  ] Calib3d_SolvePnP.generic
2024-04-16T12:28:18.9298647Z [  FAILED  ] Calib3d_SolvePnP.refine3pts
2024-04-16T12:28:18.9299666Z [  FAILED  ] Calib3d_SolvePnP.refine
2024-04-16T12:28:18.9300246Z [  FAILED  ] Calib3d_SolvePnP.inputShape
2024-04-16T12:28:18.9300819Z [  FAILED  ] Calib3d_StereoBM.regression
2024-04-16T12:28:18.9301350Z [  FAILED  ] Calib3d_StereoSGBM.regression
2024-04-16T12:28:18.9301992Z [  FAILED  ] Calib3d_DefaultNewCameraMatrix.accuracy
2024-04-16T12:28:18.9302807Z [  FAILED  ] Calib3d_GetOptimalNewCameraMatrixNoDistortion.accuracy
2024-04-16T12:28:18.9303573Z [  FAILED  ] Calib3d_UndistortPoints.accuracy
2024-04-16T12:28:18.9304207Z [  FAILED  ] Calib3d_InitUndistortRectifyMap.accuracy
2024-04-16T12:28:18.9304871Z [  FAILED  ] Calib3d_UndistortImgproc.accuracy
2024-04-16T12:28:18.9305525Z [  FAILED  ] Calib3d_InitUndistortMap.accuracy
2024-04-16T12:28:18.9306254Z [  FAILED  ] Calib3d_initUndistortRectifyMap.regression_14467
2024-04-16T12:28:18.9307384Z [  FAILED  ] Calib3d_initInverseRectificationMap.regression_20165
2024-04-16T12:28:18.9308108Z [  FAILED  ] UndistortPointsTest.accuracy
2024-04-16T12:28:18.9308814Z [  FAILED  ] UndistortPointsTest.undistortImagePointsAccuracy
2024-04-16T12:28:18.9309568Z [  FAILED  ] UndistortPointsTest.regression_14583
2024-04-16T12:28:18.9310198Z [  FAILED  ] usac_Fundamental.regression_19639
2024-04-16T12:28:18.9311069Z [  FAILED  ] Calib3d/EstimateAffine2D.testConversion/0, where GetParam() = RANSAC
2024-04-16T12:28:18.9312215Z [  FAILED  ] Calib3d/EstimateAffine2D.testConversion/1, where GetParam() = LMEDS
2024-04-16T12:28:18.9313429Z [  FAILED  ] Calib3d/EstimateAffinePartial2D.testConversion/0, where GetParam() = RANSAC
2024-04-16T12:28:18.9314660Z [  FAILED  ] Calib3d/EstimateAffinePartial2D.testConversion/1, where GetParam() = LMEDS

@HaoYuan-Gao
Copy link
Copy Markdown
Contributor Author

@vpisarev Thank you for your suggestion. Indeed, this implementation is not suitable for a high-level framework, and I need to consider more cases. I will think about a better implementation. Your ideas are very good. I need to learn coding strategies for high-level frameworks.

@HaoYuan-Gao
Copy link
Copy Markdown
Contributor Author

@asmorkalov I will check and fix these failing tests. Thank you.

@asmorkalov
Copy link
Copy Markdown
Contributor

Your PR contains severan not relevant commits now. Please rebase your branch on top of 4.x.

@HaoYuan-Gao
Copy link
Copy Markdown
Contributor Author

@asmorkalov Sorry, I've been busy the past few days. Today, I'm trying to investigate the cause of the error. This isn't a formal submission. I'm wondering if there is a platform for test code.

@asmorkalov asmorkalov modified the milestones: 4.10.0, 4.11.0 May 20, 2024
@asmorkalov
Copy link
Copy Markdown
Contributor

@Gao-HaoYuan friendly reminder

@HaoYuan-Gao
Copy link
Copy Markdown
Contributor Author

HaoYuan-Gao commented Jul 11, 2024

@asmorkalov

Sorry for the delay in addressing this issue. I've just updated the code.

@HaoYuan-Gao HaoYuan-Gao reopened this Jul 15, 2024
@HaoYuan-Gao
Copy link
Copy Markdown
Contributor Author

HaoYuan-Gao commented Jul 15, 2024

@Gao-HaoYuan friendly reminder

@asmorkalov Hello, I have solved the crash with the tests. Please review and let me know if there are any areas that need improvement.

type = CV_MAT_TYPE(type);
CV_Assert(CV_ELEM_SIZE(this->type()) == CV_ELEM_SIZE(type));
Mat m = *this;
m.flags = (type & CV_MAT_TYPE_MASK) | MAGIC_VAL;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

it's safer to use m.flags = (m.flags & ~CV_MAT_TYPE_MASK) | type; here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@vpisarev I had done

Mat _OutputArray::reinterpretType(int mtype) const
{
mtype = CV_MAT_TYPE(mtype);
Mat& m = *(Mat*)obj;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

don't silently assume that obj is Mat*. Do
return getMat().reinterpretType(mtype); instead

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@vpisarev I had done

void release() const;
void clear() const;
void setTo(const _InputArray& value, const _InputArray & mask = _InputArray()) const;
Mat reinterpretType( int type ) const;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we skip Type suffix in the name? (Mat reinterpret(int type) const;?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@vpisarev I had done

Mat src;
Mat dstMat;
if (u != NULL && u->refcount == 1 && data == dst.getMat().data
&& CV_ELEM_SIZE(stype) == CV_ELEM_SIZE(dtype) && isContinuous())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is wrong. When 8u is converted to 8s, for example, 255 should map to 127. In the case of opposite conversion (8s to 8u) all negative numbers should be converted to 0's. You also don't check noScale flag. I suggest to just retain reinterpret() and don't try to patch convertTo(). If reinterpret() works for you, just make the proper test that demonstrates the use of reinterpret().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@vpisarev The function aims to reuse memory while performing data type conversion within the conversion function. such as BinaryFunc func = noScale ? getConvertFunc(sdepth, ddepth) : getConvertScaleFunc(sdepth, ddepth);

However, I believe your suggestion is correct: if memory reuse is needed, it's more reasonable to first call the reinterpret function. This is a more correct approach compared to modifying the convertTo function.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@vpisarev My understanding and use of OpenCV, as well as my coding skills, indeed have some shortcomings. I am very grateful for your guidance, which has been very beneficial to me.

@asmorkalov asmorkalov modified the milestones: 4.11.0, 4.12.0 Dec 18, 2024
@asmorkalov asmorkalov changed the title in place convertTo Added reinterpret() method to Mat to convert meta-data without actual data conversion Mar 24, 2025
@vpisarev vpisarev self-requested a review April 4, 2025 07:10
@asmorkalov asmorkalov merged commit 0b31559 into opencv:4.x Apr 4, 2025

Mat Mat::reinterpret(int type) const
{
type = CV_MAT_TYPE(type);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why do we need CV_MAT_TYPE operation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I want to retrieve the actual type of the cv::Mat to prevent users from passing incorrect parameters, and then perform an assertion check.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Need to start with "bad param" test then.
Silent ignoring of invalid input doesn't look good.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I had added assert checks CV_Assert(CV_ELEM_SIZE(this->type()) == CV_ELEM_SIZE(type));

Surely, I will add test cases, and should I create a new PR to update this issue?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It is still not clear why do we need type = CV_MAT_TYPE(type) operation:

  • it is useless for valid MatType (works as no-op).
  • it silently ignores invalid out-of-range input converting/truncating it to some value (which user may not expect).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

My initial thought was that types with the same element size could perform a reinterpret operation, and for any other type, an assert operation would be executed. Your point seems to be:

When newtype < this->type, a reinterpret operation (truncate) can also be performed.
When newtype > this->type, a new mat (convert) should be created.

The CV_MAT_TYPE operation is indeed redundant, as these operations are actually the responsibility of the convertTo function. CV_MAT_TYPE is indeed unnecessary.

@asmorkalov asmorkalov mentioned this pull request Apr 29, 2025
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.

5 participants