Skip to content

Fix Memory Issues in Convex Hull algorithm #26975

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

Closed
wants to merge 0 commits into from

Conversation

kallaballa
Copy link
Contributor

Pull Request Readiness Checklist

See: #26952

  • 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

@kallaballa
Copy link
Contributor Author

It is now a bit faster and finally seems to be correct.

without fixes:
0m14.239s

with fixes:
0m13.314s

@kallaballa
Copy link
Contributor Author

kallaballa commented Feb 25, 2025

Found one more issue. My own tests pass but some imgproc accuracy tests, reproducers and regression tests still fail. going through it case by case now.

@kallaballa
Copy link
Contributor Author

imgproc performance tests pass

@kallaballa kallaballa marked this pull request as ready for review February 25, 2025 07:15
@kallaballa
Copy link
Contributor Author

I'm still investigating test differences but i think it's ready for a look.

@kallaballa
Copy link
Contributor Author

getPointVec is not complete yet.

Calib3d_ChessboardDetector.accuracy (among others) throws:

Unsupported format or combination of formats (InputArray is not formatted correctly) in getPointVec, file /build/precommit_linux64/4.x/opencv/modules/imgproc/src/convhull.cpp, line 80

@asmorkalov
Copy link
Contributor

@kallaballa Thanks a lot for the issue investigation and fix. Couple of thoughts:
If I understood correctly, the major issue is related to buffers casting: the same data is casted as Point* and Point2f* and used as integer of floating point.
You do not need to invent your own getPointVec, just use the following code snippet:

std::vector<Point> points;
std::vector<Point2f> points2f;
if(..)
  _points.copyTo(points2f);
else
  Mat tmp = _points.getMat();
  tmp.convertTo(points);

The similar case for output array. Just use copyTo method of Mat/InputArray/OutputArray. It handles the shapes and std::vectors automatically.

@asmorkalov asmorkalov self-requested a review February 25, 2025 11:45
@asmorkalov asmorkalov self-assigned this Feb 25, 2025
@asmorkalov
Copy link
Contributor

Warnings:

/Users/opencv-cn/GHA-OCV-3/_work/opencv/opencv/opencv/modules/imgproc/src/convhull.cpp:201:20: warning: variable 'i' is uninitialized when used here [-Wuninitialized]

@kallaballa
Copy link
Contributor Author

Warnings:

/Users/opencv-cn/GHA-OCV-3/_work/opencv/opencv/opencv/modules/imgproc/src/convhull.cpp:201:20: warning: variable 'i' is uninitialized when used here [-Wuninitialized]

thx!

@kallaballa
Copy link
Contributor Author

@kallaballa Thanks a lot for the issue investigation and fix. Couple of thoughts: If I understood correctly, the major issue is related to buffers casting: the same data is casted as Point* and Point2f* and used as integer of floating point. You do not need to invent your own getPointVec, just use the following code snippet:

std::vector<Point> points;
std::vector<Point2f> points2f;
if(..)
  _points.copyTo(points2f);
else
  Mat tmp = _points.getMat();
  tmp.convertTo(points);

The similar case for output array. Just use copyTo method of Mat/InputArray/OutputArray. It handles the shapes and std::vectors automatically.

good to know! but those this also cover cases where numerical values are converted to Points? Also i found copyTo to be quite slow in comparision.

@kallaballa
Copy link
Contributor Author

oh my. i think i misunderstood something. convexHull isn't supposed to be callable with numerical values instead of points which make getPointVec rubbish.

@kallaballa
Copy link
Contributor Author

Simplified the implementation. Less tests fail.

@kallaballa
Copy link
Contributor Author

kallaballa commented Feb 26, 2025

If I am not wrong some regression tests e.g.:

contour = Mat(N, 1, CV_32SC2);
make the same invalid assumption about object layouts.

@kallaballa
Copy link
Contributor Author

kallaballa commented Feb 26, 2025

and I am not sure what an implicit conversion from bool to Point2i should do:

contour.at<Point2i>(i) = (!clockwise) // image and convexHull coordinate systems are different

@kallaballa
Copy link
Contributor Author

am i right about some tests being invalid? because then this means i have to fix them one by one.

@kallaballa
Copy link
Contributor Author

kallaballa commented Feb 27, 2025

oh my. i think i misunderstood something. convexHull isn't supposed to be callable with numerical values instead of points which make getPointVec rubbish.

@kallaballa
Copy link
Contributor Author

kallaballa commented Feb 27, 2025

The tests assume that int[][2] should work as input - the docs say otherwise. What should i do?

@kallaballa
Copy link
Contributor Author

So i fixed Imgproc_ConvexityDefects.ordering_4539 (
82474ac) by using vector and doing manual conversions. It doesn't crash anymore but there are accuracy problems:

[ RUN      ] Imgproc_ConvexityDefects.ordering_4539
/home/elchaschab/devel/opencv/modules/imgproc/test/test_convhull.cpp:302: Failure
Expected equality of these values:
  pt
    Which is: [801, 179]
  hull[i]
    Which is: [921, 261]

i think those changes in behavior are legitimate but I'm gonna test some more.

How should i proceed with broken tests?

@kallaballa
Copy link
Contributor Author

kallaballa commented Feb 27, 2025

I understand that those changes effect a lot of users, but since it is about memory corruption i can't see a chance to guarantee behavior doesn't change. so i think it's best to enforce the documentation by error checking and fix up tests and depending algorithms.
Do you agree?

@kallaballa
Copy link
Contributor Author

kallaballa commented Feb 27, 2025

maybe convexityDefects suffers from the same problem. still have to check many tests.

@kallaballa
Copy link
Contributor Author

kallaballa commented Feb 27, 2025

I understand that those changes effect a lot of users, but since it is about memory corruption i can't see a chance to guarantee behavior doesn't change. so i think it's best to enforce the documentation by error checking and fix up tests and depending algorithms. Do you agree?

I also think by adding more error checks the change won't go unnoticed because user-code will break with a meaningful message. which i would like :)

@kallaballa
Copy link
Contributor Author

Afaics there is no legal way to find the element type (except for numerical opencv types) of a container which makes rather complete error handling impossible.

@kallaballa
Copy link
Contributor Author

Maybe deprecation (with a big warning) in favor of a new function would be an option.

@kallaballa
Copy link
Contributor Author

I still got spurious memory errors, so i changed getVec to use OutputArray::copyTo as suggested - didn't help. There is more in the bush i suspect. Investigating...

@kallaballa
Copy link
Contributor Author

kallaballa commented Mar 3, 2025

is this correct?

std::vector<Point2f> vec = {{2.0f,3.0f}};
std::vector<Point2f> vec2;
OutputArray arr = vec;
arr.copyTo(vec2)
CV_Assert(vec.size() == vec2.size())

@kallaballa
Copy link
Contributor Author

kallaballa commented Mar 3, 2025

I pushed the current version that uses OutputArray::copy. It spuriously fails but always at (with asan enabled):

CV_Assert(pointsf.size() == total);

@kallaballa
Copy link
Contributor Author

kallaballa commented Mar 3, 2025

Note that I removed undocumented behavior (returning points automatically depending on the OutputArray type)

@asmorkalov
Copy link
Contributor

  1. InputArray / OutputArray are not designed to be used as local variables, wrappers for function para,meters only.
  2. OutputArray should be pre-allocated before .copy() call. You need to calll OutputArray::create() first.

@kallaballa
Copy link
Contributor Author

kallaballa commented Mar 3, 2025

  1. InputArray / OutputArray are not designed to be used as local variables, wrappers for function para,meters only.

I understand now.

  1. OutputArray should be pre-allocated before .copy() call. You need to call OutputArray::create() first.

I saw that in the documentation and tried that but i can only create an OutputArray with numerical types. How to create an OutputArray that contains a std::vector<Point2f>?

@kallaballa
Copy link
Contributor Author

kallaballa commented Mar 3, 2025

is this correct?

std::vector<Point2f> vec = {{2.0f,3.0f}};
std::vector<Point2f> vec2;
OutputArray arr = vec;
arr.copyTo(vec2)
CV_Assert(vec.size() == vec2.size())

Should be InputArray...

void copyVec(InputArray _input, OutputArray _output) {
//  _output.create ?
 _input.copyTo(_output);
}

std::vector<Point2f> vec = {{2.0f,3.0f}};
std::vector<Point2f> vec2;
copyVec(vec, vec2)
CV_Assert(vec.size() == vec2.size())

So this should be correct?

@kallaballa
Copy link
Contributor Author

kallaballa commented Mar 3, 2025

neither ASAN nor valgrind complain about both version (copy/no-copy. running UBSAN and TSAN next.

@kallaballa
Copy link
Contributor Author

With lessons learned I am going back to the proverbial drawing board...

@kallaballa
Copy link
Contributor Author

I duplicated the original branch to https://github.com/kallaballa/opencv/tree/convexhullpointer_old and forcibly reset this one to 8e65075

@kallaballa
Copy link
Contributor Author

kallaballa commented Mar 4, 2025

how about this: to stick to the documentation as close as possible and catch all that did stick to the documentation, while making it obvious to others that they need to change something.

Preferred signature: easy to be implemented fast and correct.

template<typename Thull>
void convexHull(InputArray array, std::vector<Thull>& hull, clockwise>

Deprecated signature 1: does the same as the preferred signature, only deprecated because the last parameter should be removed

template<typename Thull>
void convexHull(InputArray array, std::vector<Thull>& hull, clockwise, bool ignored>

Deprecated signature 2: the original function fixed to the point where it retains behavior as closely as possible while preventing memory errors. probably considerably slower.

void convexHull( InputArray _points, OutputArray _hull, bool clockwise, bool returnPoints )

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.

2 participants