From 22b747eae26e6724b1fd8ee82a004ced7425614a Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Thu, 22 Jun 2023 15:09:53 +0300 Subject: [PATCH] Merge pull request #23702 from dkurt:py_rotated_rect Python binding for RotatedRect #23702 ### Pull Request Readiness Checklist related: https://github.com/opencv/opencv/issues/23546#issuecomment-1562894602 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 --- modules/core/include/opencv2/core/types.hpp | 19 ++++++++------- modules/core/src/types.cpp | 5 ++++ modules/python/src2/cv2_convert.cpp | 26 +++++++++++++++++++++ modules/python/src2/pycompat.hpp | 10 ++++++-- modules/python/test/test_misc.py | 21 +++++++++++++++++ 5 files changed, 71 insertions(+), 10 deletions(-) diff --git a/modules/core/include/opencv2/core/types.hpp b/modules/core/include/opencv2/core/types.hpp index 73cb29f73a..844d8f8950 100644 --- a/modules/core/include/opencv2/core/types.hpp +++ b/modules/core/include/opencv2/core/types.hpp @@ -527,23 +527,23 @@ The sample below demonstrates how to use RotatedRect: @sa CamShift, fitEllipse, minAreaRect, CvBox2D */ -class CV_EXPORTS RotatedRect +class CV_EXPORTS_W_SIMPLE RotatedRect { public: //! default constructor - RotatedRect(); + CV_WRAP RotatedRect(); /** full constructor @param center The rectangle mass center. @param size Width and height of the rectangle. @param angle The rotation angle in a clockwise direction. When the angle is 0, 90, 180, 270 etc., the rectangle becomes an up-right rectangle. */ - RotatedRect(const Point2f& center, const Size2f& size, float angle); + CV_WRAP RotatedRect(const Point2f& center, const Size2f& size, float angle); /** Any 3 end points of the RotatedRect. They must be given in order (either clockwise or anticlockwise). */ - RotatedRect(const Point2f& point1, const Point2f& point2, const Point2f& point3); + CV_WRAP RotatedRect(const Point2f& point1, const Point2f& point2, const Point2f& point3); /** returns 4 vertices of the rotated rectangle @param pts The points array for storing rectangle vertices. The order is _bottomLeft_, _topLeft_, topRight, bottomRight. @@ -552,16 +552,19 @@ public: rectangle. */ void points(Point2f pts[]) const; + + CV_WRAP void points(CV_OUT std::vector& pts) const; + //! returns the minimal up-right integer rectangle containing the rotated rectangle - Rect boundingRect() const; + CV_WRAP Rect boundingRect() const; //! returns the minimal (exact) floating point rectangle containing the rotated rectangle, not intended for use with images Rect_ boundingRect2f() const; //! returns the rectangle mass center - Point2f center; + CV_PROP_RW Point2f center; //! returns width and height of the rectangle - Size2f size; + CV_PROP_RW Size2f size; //! returns the rotation angle. When the angle is 0, 90, 180, 270 etc., the rectangle becomes an up-right rectangle. - float angle; + CV_PROP_RW float angle; }; template<> class DataType< RotatedRect > diff --git a/modules/core/src/types.cpp b/modules/core/src/types.cpp index 15ba83a0f6..43e25ef045 100644 --- a/modules/core/src/types.cpp +++ b/modules/core/src/types.cpp @@ -186,6 +186,11 @@ void RotatedRect::points(Point2f pt[]) const pt[3].y = 2*center.y - pt[1].y; } +void RotatedRect::points(std::vector& pts) const { + pts.resize(4); + points(pts.data()); +} + Rect RotatedRect::boundingRect() const { Point2f pt[4]; diff --git a/modules/python/src2/cv2_convert.cpp b/modules/python/src2/cv2_convert.cpp index f03a2e2d86..2e69586f47 100644 --- a/modules/python/src2/cv2_convert.cpp +++ b/modules/python/src2/cv2_convert.cpp @@ -728,6 +728,26 @@ PyObject* pyopencv_from(const Rect2d& r) // --- RotatedRect +static inline bool convertToRotatedRect(PyObject* obj, RotatedRect& dst) +{ + PyObject* type = PyObject_Type(obj); + if (getPyObjectAttr(type, "__module__") == MODULESTR && + getPyObjectNameAttr(type) == "RotatedRect") + { + struct pyopencv_RotatedRect_t + { + PyObject_HEAD + cv::RotatedRect v; + }; + dst = reinterpret_cast(obj)->v; + + Py_DECREF(type); + return true; + } + Py_DECREF(type); + return false; +} + template<> bool pyopencv_to(PyObject* obj, RotatedRect& dst, const ArgInfo& info) { @@ -735,6 +755,12 @@ bool pyopencv_to(PyObject* obj, RotatedRect& dst, const ArgInfo& info) { return true; } + // This is a workaround for compatibility with an initialization from tuple. + // Allows import RotatedRect as an object. + if (convertToRotatedRect(obj, dst)) + { + return true; + } if (!PySequence_Check(obj)) { failmsg("Can't parse '%s' as RotatedRect." diff --git a/modules/python/src2/pycompat.hpp b/modules/python/src2/pycompat.hpp index bd3956dbc0..c8806dc812 100644 --- a/modules/python/src2/pycompat.hpp +++ b/modules/python/src2/pycompat.hpp @@ -98,10 +98,10 @@ static inline bool getUnicodeString(PyObject * obj, std::string &str) } static inline -std::string getPyObjectNameAttr(PyObject* obj) +std::string getPyObjectAttr(PyObject* obj, const char* attrName) { std::string obj_name; - PyObject* cls_name_obj = PyObject_GetAttrString(obj, "__name__"); + PyObject* cls_name_obj = PyObject_GetAttrString(obj, attrName); if (cls_name_obj && !getUnicodeString(cls_name_obj, obj_name)) { obj_name.clear(); } @@ -117,6 +117,12 @@ std::string getPyObjectNameAttr(PyObject* obj) return obj_name; } +static inline +std::string getPyObjectNameAttr(PyObject* obj) +{ + return getPyObjectAttr(obj, "__name__"); +} + //================================================================================================== #define CV_PY_FN_WITH_KW_(fn, flags) (PyCFunction)(void*)(PyCFunctionWithKeywords)(fn), (flags) | METH_VARARGS | METH_KEYWORDS diff --git a/modules/python/test/test_misc.py b/modules/python/test/test_misc.py index e8c4ab8849..ec7f44de0f 100644 --- a/modules/python/test/test_misc.py +++ b/modules/python/test/test_misc.py @@ -482,6 +482,27 @@ class Arguments(NewOpenCVTests): self.assertEqual(expected, actual, msg=get_conversion_error_msg(convertible, expected, actual)) + + def test_wrap_rotated_rect(self): + center = (34.5, 52.) + size = (565.0, 140.0) + angle = -177.5 + rect1 = cv.RotatedRect(center, size, angle) + self.assertEqual(rect1.center, center) + self.assertEqual(rect1.size, size) + self.assertEqual(rect1.angle, angle) + + pts = [[ 319.7845, -5.6109037], + [ 313.6778, 134.25586], + [-250.78448, 109.6109], + [-244.6778, -30.25586]] + self.assertLess(np.max(np.abs(rect1.points() - pts)), 1e-4) + + rect2 = cv.RotatedRect(pts[0], pts[1], pts[2]) + _, inter_pts = cv.rotatedRectangleIntersection(rect1, rect2) + self.assertLess(np.max(np.abs(inter_pts.reshape(-1, 2) - pts)), 1e-4) + + def test_parse_to_rotated_rect_not_convertible(self): for not_convertible in ([], (), np.array([]), (123, (45, 34), 1), {1: 2, 3: 4}, 123, np.array([[123, 123, 14], [1, 3], 56], dtype=object), '123'):