diff --git a/modules/imgproc/doc/pics/minenclosingtriangle.png b/modules/imgproc/doc/pics/minenclosingtriangle.png new file mode 100644 index 0000000000..ec89c6aa25 Binary files /dev/null and b/modules/imgproc/doc/pics/minenclosingtriangle.png differ diff --git a/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst b/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst index f74671244f..f7cb69cf41 100644 --- a/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst +++ b/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst @@ -560,6 +560,41 @@ The function finds the four vertices of a rotated rectangle. This function is us +minEnclosingTriangle +---------------------- +Finds a triangle of minimum area enclosing a 2D point set and returns its area. + +.. ocv:function:: double minEnclosingTriangle( InputArray points, OutputArray triangle ) + +.. ocv:pyfunction:: cv2.minEnclosingTriangle(points[, triangle]) -> retval, triangle + + :param points: Input vector of 2D points with depth ``CV_32S`` or ``CV_32F``, stored in: + + * ``std::vector<>`` or ``Mat`` (C++ interface) + + * Nx2 numpy array (Python interface) + + :param triangle: Output vector of three 2D points defining the vertices of the triangle. The depth of the OutputArray must be ``CV_32F``. + +The function finds a triangle of minimum area enclosing the given set of 2D points and returns its area. The output for a given 2D point set is shown in the image below. 2D points are depicted in *red* and the enclosing triangle in *yellow*. + +.. image:: pics/minenclosingtriangle.png + :height: 250px + :width: 250px + :alt: Sample output of the minimum enclosing triangle function + +The implementation of the algorithm is based on O'Rourke's [ORourke86]_ and Klee and Laskowski's [KleeLaskowski85]_ papers. O'Rourke provides a +:math:`\theta(n)` +algorithm for finding the minimal enclosing triangle of a 2D convex polygon with ``n`` vertices. Since the :ocv:func:`minEnclosingTriangle` function takes a 2D point set as input an additional preprocessing step of computing the convex hull of the 2D point set is required. The complexity of the :ocv:func:`convexHull` function is +:math:`O(n log(n))` which is higher than +:math:`\theta(n)`. +Thus the overall complexity of the function is +:math:`O(n log(n))`. + +.. note:: See ``opencv_source/samples/cpp/minarea.cpp`` for a usage example. + + + minEnclosingCircle ---------------------- Finds a circle of the minimum area enclosing a 2D point set. @@ -672,6 +707,10 @@ See below a sample output of the function where each image pixel is tested again .. [Hu62] M. Hu. *Visual Pattern Recognition by Moment Invariants*, IRE Transactions on Information Theory, 8:2, pp. 179-187, 1962. +.. [KleeLaskowski85] Klee, V. and Laskowski, M.C., *Finding the smallest triangles containing a given convex polygon*, Journal of Algorithms, vol. 6, no. 3, pp. 359-375 (1985) + +.. [ORourke86] O’Rourke, J., Aggarwal, A., Maddila, S., and Baldwin, M., *An optimal algorithm for finding minimal enclosing triangles*, Journal of Algorithms, vol. 7, no. 2, pp. 258-269 (1986) + .. [Sklansky82] Sklansky, J., *Finding the Convex Hull of a Simple Polygon*. PRL 1 $number, pp 79-83 (1982) .. [Suzuki85] Suzuki, S. and Abe, K., *Topological Structural Analysis of Digitized Binary Images by Border Following*. CVGIP 30 1, pp 32-46 (1985) diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 1d207d835f..48b2ff2ec5 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -1459,6 +1459,9 @@ CV_EXPORTS_W void boxPoints(RotatedRect box, OutputArray points); CV_EXPORTS_W void minEnclosingCircle( InputArray points, CV_OUT Point2f& center, CV_OUT float& radius ); +//! computes the minimal enclosing triangle for a set of points and returns its area +CV_EXPORTS_W double minEnclosingTriangle( InputArray points, CV_OUT OutputArray triangle ); + //! matches two contours using one of the available algorithms CV_EXPORTS_W double matchShapes( InputArray contour1, InputArray contour2, int method, double parameter ); diff --git a/modules/imgproc/src/min_enclosing_triangle.cpp b/modules/imgproc/src/min_enclosing_triangle.cpp new file mode 100644 index 0000000000..98bfd46e5d --- /dev/null +++ b/modules/imgproc/src/min_enclosing_triangle.cpp @@ -0,0 +1,1563 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// INFORMATION REGARDING THE CONTRIBUTION: +// +// Author: Ovidiu Parvu +// Affiliation: Brunel University +// Created: 11.09.2013 +// E-mail: +// Web: http://people.brunel.ac.uk/~cspgoop +// +// These functions were implemented during Ovidiu Parvu's first year as a PhD student at +// Brunel University, London, UK. The PhD project is supervised by prof. David Gilbert (principal) +// and prof. Nigel Saunders (second). +// +// THE IMPLEMENTATION OF THE MODULES IS BASED ON THE FOLLOWING PAPERS: +// +// [1] V. Klee and M. C. Laskowski, "Finding the smallest triangles containing a given convex +// polygon", Journal of Algorithms, vol. 6, no. 3, pp. 359-375, Sep. 1985. +// [2] J. O'Rourke, A. Aggarwal, S. Maddila, and M. Baldwin, "An optimal algorithm for finding +// minimal enclosing triangles", Journal of Algorithms, vol. 7, no. 2, pp. 258-269, Jun. 1986. +// +// The overall complexity of the algorithm is theta(n) where "n" represents the number +// of vertices in the convex polygon. +// +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2000, Intel Corporation, all rights reserved. +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Copyright (C) 2013, Ovidiu Parvu, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +#include "precomp.hpp" + +#include +#include +#include +#include + + +///////////////////////////////// Constants definitions ////////////////////////////////// + + +// Intersection of line and polygon + +#define INTERSECTS_BELOW 1 +#define INTERSECTS_ABOVE 2 +#define INTERSECTS_CRITICAL 3 + +// Error messages + +#define ERR_SIDE_B_GAMMA "The position of side B could not be determined, because gamma(b) could not be computed." +#define ERR_VERTEX_C_ON_SIDE_B "The position of the vertex C on side B could not be determined, because the considered lines do not intersect." + +// Possible values for validation flag + +#define VALIDATION_SIDE_A_TANGENT 0 +#define VALIDATION_SIDE_B_TANGENT 1 +#define VALIDATION_SIDES_FLUSH 2 + +// Threshold value for comparisons + +#define EPSILON 1E-5 + + +////////////////////////////// Helper functions declarations ///////////////////////////// + + +namespace minEnclosingTriangle { + +static void advance(unsigned int &index, unsigned int nrOfPoints); + +static void advanceBToRightChain(const std::vector &polygon, + unsigned int nrOfPoints, unsigned int &b, + unsigned int c); + +static bool almostEqual(double number1, double number2); + +static double angleOfLineWrtOxAxis(const cv::Point2f &a, const cv::Point2f &b); + +static bool areEqualPoints(const cv::Point2f &point1, const cv::Point2f &point2); + +static bool areIdenticalLines(const std::vector &side1Params, + const std::vector &side2Params, double sideCExtraParam); + +static bool areIdenticalLines(double a1, double b1, double c1, double a2, double b2, double c2); + +static bool areIntersectingLines(const std::vector &side1Params, + const std::vector &side2Params, + double sideCExtraParam, cv::Point2f &intersectionPoint1, + cv::Point2f &intersectionPoint2); + +static bool areOnTheSameSideOfLine(const cv::Point2f &p1, const cv::Point2f &p2, + const cv::Point2f &a, const cv::Point2f &b); + +static double areaOfTriangle(const cv::Point2f &a, const cv::Point2f &b, const cv::Point2f &c); + +static void copyResultingTriangle(const std::vector &resultingTriangle, cv::OutputArray triangle); + +static void createConvexHull(cv::InputArray points, std::vector &polygon); + +static double distanceBtwPoints(const cv::Point2f &a, const cv::Point2f &b); + +static double distanceFromPointToLine(const cv::Point2f &a, const cv::Point2f &linePointB, + const cv::Point2f &linePointC); + +static bool findGammaIntersectionPoints(const std::vector &polygon, unsigned int nrOfPoints, + unsigned int c, unsigned int polygonPointIndex, + const cv::Point2f &side1StartVertex, const cv::Point2f &side1EndVertex, + const cv::Point2f &side2StartVertex, const cv::Point2f &side2EndVertex, + cv::Point2f &intersectionPoint1, cv::Point2f &intersectionPoint2); + +static void findMinEnclosingTriangle(cv::InputArray points, + CV_OUT cv::OutputArray triangle, CV_OUT double &area); + +static void findMinEnclosingTriangle(const std::vector &polygon, + std::vector &triangle, double &area); + +static void findMinimumAreaEnclosingTriangle(const std::vector &polygon, + std::vector &triangle, double &area); + +static cv::Point2f findVertexCOnSideB(const std::vector &polygon, unsigned int nrOfPoints, + unsigned int a, unsigned int c, + const cv::Point2f &sideBStartVertex, + const cv::Point2f &sideBEndVertex, + const cv::Point2f &sideCStartVertex, + const cv::Point2f &sideCEndVertex); + +static bool gamma(unsigned int polygonPointIndex, cv::Point2f &gammaPoint, + const std::vector &polygon, unsigned int nrOfPoints, + unsigned int a, unsigned int c); + +static bool greaterOrEqual(double number1, double number2); + +static double height(const cv::Point2f &polygonPoint, const std::vector &polygon, + unsigned int nrOfPoints, unsigned int c); + +static double height(unsigned int polygonPointIndex, const std::vector &polygon, + unsigned int nrOfPoints, unsigned int c); + +static void initialise(std::vector &triangle, double &area); + +static unsigned int intersects(double angleGammaAndPoint, unsigned int polygonPointIndex, + const std::vector &polygon, unsigned int nrOfPoints, + unsigned int c); + +static bool intersectsAbove(const cv::Point2f &gammaPoint, unsigned int polygonPointIndex, + const std::vector &polygon, unsigned int nrOfPoints, + unsigned int c); + +static unsigned int intersectsAboveOrBelow(unsigned int succPredIndex, unsigned int pointIndex, + const std::vector &polygon, + unsigned int nrOfPoints, unsigned int c); + +static bool intersectsBelow(const cv::Point2f &gammaPoint, unsigned int polygonPointIndex, + const std::vector &polygon, unsigned int nrOfPoints, + unsigned int c); + +static bool isAngleBetween(double angle1, double angle2, double angle3); + +static bool isAngleBetweenNonReflex(double angle1, double angle2, double angle3); + +static bool isFlushAngleBtwPredAndSucc(double &angleFlushEdge, double anglePred, double angleSucc); + +static bool isGammaAngleBtw(double &gammaAngle, double angle1, double angle2); + +static bool isGammaAngleEqualTo(double &gammaAngle, double angle); + +static bool isLocalMinimalTriangle(cv::Point2f &vertexA, cv::Point2f &vertexB, + cv::Point2f &vertexC, const std::vector &polygon, + unsigned int nrOfPoints, unsigned int a, unsigned int b, + unsigned int validationFlag, const cv::Point2f &sideAStartVertex, + const cv::Point2f &sideAEndVertex, const cv::Point2f &sideBStartVertex, + const cv::Point2f &sideBEndVertex, const cv::Point2f &sideCStartVertex, + const cv::Point2f &sideCEndVertex); + +static bool isNotBTangency(const std::vector &polygon, + unsigned int nrOfPoints, unsigned int a, unsigned int b, + unsigned int c); + +static bool isOppositeAngleBetweenNonReflex(double angle1, double angle2, double angle3); + +static bool isPointOnLineSegment(const cv::Point2f &point, const cv::Point2f &lineSegmentStart, + const cv::Point2f &lineSegmentEnd); + +static bool isValidMinimalTriangle(const cv::Point2f &vertexA, const cv::Point2f &vertexB, + const cv::Point2f &vertexC, const std::vector &polygon, + unsigned int nrOfPoints, unsigned int a, unsigned int b, + unsigned int validationFlag, const cv::Point2f &sideAStartVertex, + const cv::Point2f &sideAEndVertex, const cv::Point2f &sideBStartVertex, + const cv::Point2f &sideBEndVertex, const cv::Point2f &sideCStartVertex, + const cv::Point2f &sideCEndVertex); + +static bool lessOrEqual(double number1, double number2); + +static void lineEquationDeterminedByPoints(const cv::Point2f &p, const cv::Point2f &q, + double &a, double &b, double &c); + +static std::vector lineEquationParameters(const cv::Point2f& p, const cv::Point2f &q); + +static bool lineIntersection(const cv::Point2f &a1, const cv::Point2f &b1, const cv::Point2f &a2, + const cv::Point2f &b2, cv::Point2f &intersection); + +static bool lineIntersection(double a1, double b1, double c1, double a2, double b2, double c2, + cv::Point2f &intersection); + +static double maximum(double number1, double number2, double number3); + +static cv::Point2f middlePoint(const cv::Point2f &a, const cv::Point2f &b); + +static bool middlePointOfSideB(cv::Point2f &middlePointOfSideB, const cv::Point2f &sideAStartVertex, + const cv::Point2f &sideAEndVertex, const cv::Point2f &sideBStartVertex, + const cv::Point2f &sideBEndVertex, const cv::Point2f &sideCStartVertex, + const cv::Point2f &sideCEndVertex); + +static void moveAIfLowAndBIfHigh(const std::vector &polygon, + unsigned int nrOfPoints, unsigned int &a, unsigned int &b, + unsigned int c); + +static double oppositeAngle(double angle); + +static unsigned int predecessor(unsigned int index, unsigned int nrOfPoints); + +static void returnMinimumAreaEnclosingTriangle(const std::vector &polygon, + std::vector &triangle, double &area); + +static void searchForBTangency(const std::vector &polygon, + unsigned int nrOfPoints, unsigned int a, unsigned int &b, + unsigned int c); + +static int sign(double number); + +static unsigned int successor(unsigned int index, unsigned int nrOfPoints); + +static void updateMinimumAreaEnclosingTriangle(std::vector &triangle, double &area, + const cv::Point2f &vertexA, const cv::Point2f &vertexB, + const cv::Point2f &vertexC); + +static void updateSideB(const std::vector &polygon, + unsigned int nrOfPoints, unsigned int a, unsigned int b, + unsigned int c, unsigned int &validationFlag, + cv::Point2f &sideBStartVertex, cv::Point2f &sideBEndVertex); + +static void updateSidesBA(const std::vector &polygon, + unsigned int nrOfPoints, unsigned int a, unsigned int b, + unsigned int c, unsigned int &validationFlag, + cv::Point2f &sideAStartVertex, cv::Point2f &sideAEndVertex, + cv::Point2f &sideBStartVertex, cv::Point2f &sideBEndVertex, + const cv::Point2f &sideCStartVertex, const cv::Point2f &sideCEndVertex); + +static void updateSidesCA(const std::vector &polygon, + unsigned int nrOfPoints, unsigned int a, unsigned int c, + cv::Point2f &sideAStartVertex, cv::Point2f &sideAEndVertex, + cv::Point2f &sideCStartVertex, cv::Point2f &sideCEndVertex); + +}; + + +///////////////////////////////////// Main functions ///////////////////////////////////// + + +//! Find the minimum enclosing triangle for the given set of points and return its area +/*! +* @param points Set of points +* @param triangle Minimum area triangle enclosing the given set of points +*/ +double cv::minEnclosingTriangle(cv::InputArray points, CV_OUT cv::OutputArray triangle) { + double area; + + minEnclosingTriangle::findMinEnclosingTriangle(points, triangle, area); + + return area; +} + + +/////////////////////////////// Helper functions definition ////////////////////////////// + + +namespace minEnclosingTriangle { + +//! Find the minimum enclosing triangle and its area +/*! +* @param points Set of points +* @param triangle Minimum area triangle enclosing the given set of points +* @param area Area of the minimum area enclosing triangle +*/ +static void findMinEnclosingTriangle(cv::InputArray points, + CV_OUT cv::OutputArray triangle, CV_OUT double &area) { + std::vector resultingTriangle, polygon; + + createConvexHull(points, polygon); + findMinEnclosingTriangle(polygon, resultingTriangle, area); + copyResultingTriangle(resultingTriangle, triangle); +} + +//! Create the convex hull of the given set of points +/*! +* @param points The provided set of points +* @param polygon The polygon representing the convex hull of the points +*/ +static void createConvexHull(cv::InputArray points, std::vector &polygon) { + cv::Mat pointsMat = points.getMat(); + std::vector pointsVector; + + CV_Assert((pointsMat.checkVector(2) > 0) && + ((pointsMat.depth() == CV_32F) || (pointsMat.depth() == CV_32S))); + + pointsMat.convertTo(pointsVector, CV_32F); + + convexHull(pointsVector, polygon, true, true); +} + +//! Find the minimum enclosing triangle and its area +/*! +* The overall complexity of the algorithm is theta(n) where "n" represents the number +* of vertices in the convex polygon +* +* @param polygon The polygon representing the convex hull of the points +* @param triangle Minimum area triangle enclosing the given polygon +* @param area Area of the minimum area enclosing triangle +*/ +static void findMinEnclosingTriangle(const std::vector &polygon, + std::vector &triangle, double &area) { + initialise(triangle, area); + + if (polygon.size() > 3) { + findMinimumAreaEnclosingTriangle(polygon, triangle, area); + } else { + returnMinimumAreaEnclosingTriangle(polygon, triangle, area); + } +} + +//! Copy resultingTriangle to the OutputArray triangle +/*! +* @param resultingTriangle Minimum area triangle enclosing the given polygon found by the algorithm +* @param triangle Minimum area triangle enclosing the given polygon returned to the user +*/ +static void copyResultingTriangle(const std::vector &resultingTriangle, + cv::OutputArray triangle) { + cv::Mat(resultingTriangle).copyTo(triangle); +} + +//! Initialisation function +/*! +* @param triangle Minimum area triangle enclosing the given polygon +* @param area Area of the minimum area enclosing triangle +*/ +static void initialise(std::vector &triangle, double &area) { + area = std::numeric_limits::max(); + + // Clear all points previously stored in the vector + triangle.clear(); +} + +//! Find the minimum area enclosing triangle for the given polygon +/*! +* @param polygon The polygon representing the convex hull of the points +* @param triangle Minimum area triangle enclosing the given polygon +* @param area Area of the minimum area enclosing triangle +*/ +static void findMinimumAreaEnclosingTriangle(const std::vector &polygon, + std::vector &triangle, double &area) { + // Algorithm specific variables + + unsigned int validationFlag; + + cv::Point2f vertexA, vertexB, vertexC; + + cv::Point2f sideAStartVertex, sideAEndVertex; + cv::Point2f sideBStartVertex, sideBEndVertex; + cv::Point2f sideCStartVertex, sideCEndVertex; + + unsigned int a, b, c; + unsigned int nrOfPoints; + + // Variables initialisation + + nrOfPoints = static_cast(polygon.size()); + + a = 1; + b = 2; + c = 0; + + // Main algorithm steps + + for (c = 0; c < nrOfPoints; c++) { + advanceBToRightChain(polygon, nrOfPoints, b, c); + moveAIfLowAndBIfHigh(polygon, nrOfPoints, a, b, c); + searchForBTangency(polygon, nrOfPoints, a ,b, c); + + updateSidesCA(polygon, nrOfPoints, a, c, sideAStartVertex, sideAEndVertex, + sideCStartVertex, sideCEndVertex); + + if (isNotBTangency(polygon, nrOfPoints, a, b, c)) { + updateSidesBA(polygon, nrOfPoints, a, b, c, validationFlag, sideAStartVertex, + sideAEndVertex, sideBStartVertex, sideBEndVertex, + sideCStartVertex, sideCEndVertex); + } else { + updateSideB(polygon, nrOfPoints, a, b, c, validationFlag, + sideBStartVertex, sideBEndVertex); + } + + if (isLocalMinimalTriangle(vertexA, vertexB, vertexC, polygon, nrOfPoints, a, b, + validationFlag, sideAStartVertex, sideAEndVertex, + sideBStartVertex, sideBEndVertex, sideCStartVertex, + sideCEndVertex)) { + updateMinimumAreaEnclosingTriangle(triangle, area, vertexA, vertexB, vertexC); + } + } +} + +//! Return the minimum area enclosing (pseudo-)triangle in case the convex polygon has at most three points +/*! +* @param polygon The polygon representing the convex hull of the points +* @param triangle Minimum area triangle enclosing the given polygon +* @param area Area of the minimum area enclosing triangle +*/ +static void returnMinimumAreaEnclosingTriangle(const std::vector &polygon, + std::vector &triangle, double &area) { + unsigned int nrOfPoints = static_cast(polygon.size()); + + for (int i = 0; i < 3; i++) { + triangle.push_back(polygon[i % nrOfPoints]); + } + + area = areaOfTriangle(triangle[0], triangle[1], triangle[2]); +} + +//! Advance b to the right chain +/*! +* See paper [2] for more details +* +* @param polygon The polygon representing the convex hull of the points +* @param nrOfPoints Number of points defining the convex polygon +* @param b Index b +* @param c Index c +*/ +static void advanceBToRightChain(const std::vector &polygon, + unsigned int nrOfPoints, unsigned int &b, + unsigned int c) { + while (greaterOrEqual(height(successor(b, nrOfPoints), polygon, nrOfPoints, c), + height(b, polygon, nrOfPoints, c))) { + advance(b, nrOfPoints); + } +} + +//! Move "a" if it is low and "b" if it is high +/*! +* See paper [2] for more details +* +* @param polygon The polygon representing the convex hull of the points +* @param nrOfPoints Number of points defining the convex polygon +* @param a Index a +* @param b Index b +* @param c Index c +*/ +static void moveAIfLowAndBIfHigh(const std::vector &polygon, + unsigned int nrOfPoints, unsigned int &a, unsigned int &b, + unsigned int c) { + cv::Point2f gammaOfA; + + while(height(b, polygon, nrOfPoints, c) > height(a, polygon, nrOfPoints, c)) { + if ((gamma(a, gammaOfA, polygon, nrOfPoints, a, c)) && (intersectsBelow(gammaOfA, b, polygon, nrOfPoints, c))) { + advance(b, nrOfPoints); + } else { + advance(a, nrOfPoints); + } + } +} + +//! Search for the tangency of side B +/*! +* See paper [2] for more details +* +* @param polygon The polygon representing the convex hull of the points +* @param nrOfPoints Number of points defining the convex polygon +* @param a Index a +* @param b Index b +* @param c Index c +*/ +static void searchForBTangency(const std::vector &polygon, + unsigned int nrOfPoints, unsigned int a, unsigned int &b, + unsigned int c) { + cv::Point2f gammaOfB; + + while (((gamma(b, gammaOfB, polygon, nrOfPoints, a, c)) && + (intersectsBelow(gammaOfB, b, polygon, nrOfPoints, c))) && + (greaterOrEqual(height(b, polygon, nrOfPoints, c), + height(predecessor(a, nrOfPoints), polygon, nrOfPoints, c))) + ) { + advance(b, nrOfPoints); + } +} + +//! Check if tangency for side B was not obtained +/*! +* See paper [2] for more details +* +* @param polygon The polygon representing the convex hull of the points +* @param nrOfPoints Number of points defining the convex polygon +* @param a Index a +* @param b Index b +* @param c Index c +*/ +static bool isNotBTangency(const std::vector &polygon, + unsigned int nrOfPoints, unsigned int a, unsigned int b, + unsigned int c) { + cv::Point2f gammaOfB; + + if (((gamma(b, gammaOfB, polygon, nrOfPoints, a, c)) && + (intersectsAbove(gammaOfB, b, polygon, nrOfPoints, c))) || + (height(b, polygon, nrOfPoints, c) < height(predecessor(a, nrOfPoints), polygon, nrOfPoints, c))) { + return true; + } + + return false; +} + +//! Update sides A and C +/*! +* Side C will have as start and end vertices the polygon points "c" and "c-1" +* Side A will have as start and end vertices the polygon points "a" and "a-1" +* +* @param polygon The polygon representing the convex hull of the points +* @param nrOfPoints Number of points defining the convex polygon +* @param a Index a +* @param c Index c +* @param sideAStartVertex Start vertex for defining side A +* @param sideAEndVertex End vertex for defining side A +* @param sideCStartVertex Start vertex for defining side C +* @param sideCEndVertex End vertex for defining side C +*/ +static void updateSidesCA(const std::vector &polygon, + unsigned int nrOfPoints, unsigned int a, unsigned int c, + cv::Point2f &sideAStartVertex, cv::Point2f &sideAEndVertex, + cv::Point2f &sideCStartVertex, cv::Point2f &sideCEndVertex) { + sideCStartVertex = polygon[predecessor(c, nrOfPoints)]; + sideCEndVertex = polygon[c]; + + sideAStartVertex = polygon[predecessor(a, nrOfPoints)]; + sideAEndVertex = polygon[a]; +} + +//! Update sides B and possibly A if tangency for side B was not obtained +/*! +* See paper [2] for more details +* +* @param polygon The polygon representing the convex hull of the points +* @param nrOfPoints Number of points defining the convex polygon +* @param a Index a +* @param b Index b +* @param c Index c +* @param validationFlag Flag used for validation +* @param sideAStartVertex Start vertex for defining side A +* @param sideAEndVertex End vertex for defining side A +* @param sideBStartVertex Start vertex for defining side B +* @param sideBEndVertex End vertex for defining side B +* @param sideCStartVertex Start vertex for defining side C +* @param sideCEndVertex End vertex for defining side C +*/ +static void updateSidesBA(const std::vector &polygon, + unsigned int nrOfPoints, unsigned int a, unsigned int b, + unsigned int c, unsigned int &validationFlag, + cv::Point2f &sideAStartVertex, cv::Point2f &sideAEndVertex, + cv::Point2f &sideBStartVertex, cv::Point2f &sideBEndVertex, + const cv::Point2f &sideCStartVertex, const cv::Point2f &sideCEndVertex) { + // Side B is flush with edge [b, b-1] + sideBStartVertex = polygon[predecessor(b, nrOfPoints)]; + sideBEndVertex = polygon[b]; + + // Find middle point of side B + cv::Point2f sideBMiddlePoint; + + if ((middlePointOfSideB(sideBMiddlePoint, sideAStartVertex, sideAEndVertex, sideBStartVertex, + sideBEndVertex, sideCStartVertex, sideCEndVertex)) && + (height(sideBMiddlePoint, polygon, nrOfPoints, c) < + height(predecessor(a, nrOfPoints), polygon, nrOfPoints, c))) { + sideAStartVertex = polygon[predecessor(a, nrOfPoints)]; + sideAEndVertex = findVertexCOnSideB(polygon, nrOfPoints, a, c, + sideBStartVertex, sideBEndVertex, + sideCStartVertex, sideCEndVertex); + + validationFlag = VALIDATION_SIDE_A_TANGENT; + } else { + validationFlag = VALIDATION_SIDES_FLUSH; + } +} + +//! Set side B if tangency for side B was obtained +/*! +* See paper [2] for more details +* +* @param polygon The polygon representing the convex hull of the points +* @param nrOfPoints Number of points defining the convex polygon +* @param a Index a +* @param b Index b +* @param c Index c +* @param validationFlag Flag used for validation +* @param sideBStartVertex Start vertex for defining side B +* @param sideBEndVertex End vertex for defining side B +*/ +static void updateSideB(const std::vector &polygon, + unsigned int nrOfPoints, unsigned int a, unsigned int b, + unsigned int c, unsigned int &validationFlag, + cv::Point2f &sideBStartVertex, cv::Point2f &sideBEndVertex) { + if (!gamma(b, sideBStartVertex, polygon, nrOfPoints, a, c)) { + CV_Error(cv::Error::StsInternal, ERR_SIDE_B_GAMMA); + } + + sideBEndVertex = polygon[b]; + + validationFlag = VALIDATION_SIDE_B_TANGENT; +} + +//! Update the triangle vertices after all sides were set and check if a local minimal triangle was found or not +/*! +* See paper [2] for more details +* +* @param vertexA Vertex A of the enclosing triangle +* @param vertexB Vertex B of the enclosing triangle +* @param vertexC Vertex C of the enclosing triangle +* @param polygon The polygon representing the convex hull of the points +* @param nrOfPoints Number of points defining the convex polygon +* @param a Index a +* @param b Index b +* @param validationFlag Flag used for validation +* @param sideAStartVertex Start vertex for defining side A +* @param sideAEndVertex End vertex for defining side A +* @param sideBStartVertex Start vertex for defining side B +* @param sideBEndVertex End vertex for defining side B +* @param sideCStartVertex Start vertex for defining side C +* @param sideCEndVertex End vertex for defining side C +*/ +static bool isLocalMinimalTriangle(cv::Point2f &vertexA, cv::Point2f &vertexB, + cv::Point2f &vertexC, const std::vector &polygon, + unsigned int nrOfPoints, unsigned int a, unsigned int b, + unsigned int validationFlag, const cv::Point2f &sideAStartVertex, + const cv::Point2f &sideAEndVertex, const cv::Point2f &sideBStartVertex, + const cv::Point2f &sideBEndVertex, const cv::Point2f &sideCStartVertex, + const cv::Point2f &sideCEndVertex) { + if ((!lineIntersection(sideAStartVertex, sideAEndVertex, + sideBStartVertex, sideBEndVertex, vertexC)) || + (!lineIntersection(sideAStartVertex, sideAEndVertex, + sideCStartVertex, sideCEndVertex, vertexB)) || + (!lineIntersection(sideBStartVertex, sideBEndVertex, + sideCStartVertex, sideCEndVertex, vertexA))) { + return false; + } + + return isValidMinimalTriangle(vertexA, vertexB, vertexC, polygon, nrOfPoints, a, b, + validationFlag, sideAStartVertex, sideAEndVertex, + sideBStartVertex, sideBEndVertex, sideCStartVertex, + sideCEndVertex); +} + +//! Check if the found minimal triangle is valid +/*! +* This means that all midpoints of the triangle should touch the polygon +* +* See paper [2] for more details +* +* @param vertexA Vertex A of the enclosing triangle +* @param vertexB Vertex B of the enclosing triangle +* @param vertexC Vertex C of the enclosing triangle +* @param polygon The polygon representing the convex hull of the points +* @param nrOfPoints Number of points defining the convex polygon +* @param a Index a +* @param b Index b +* @param validationFlag Flag used for validation +* @param sideAStartVertex Start vertex for defining side A +* @param sideAEndVertex End vertex for defining side A +* @param sideBStartVertex Start vertex for defining side B +* @param sideBEndVertex End vertex for defining side B +* @param sideCStartVertex Start vertex for defining side C +* @param sideCEndVertex End vertex for defining side C +*/ +static bool isValidMinimalTriangle(const cv::Point2f &vertexA, const cv::Point2f &vertexB, + const cv::Point2f &vertexC, const std::vector &polygon, + unsigned int nrOfPoints, unsigned int a, unsigned int b, + unsigned int validationFlag, const cv::Point2f &sideAStartVertex, + const cv::Point2f &sideAEndVertex, const cv::Point2f &sideBStartVertex, + const cv::Point2f &sideBEndVertex, const cv::Point2f &sideCStartVertex, + const cv::Point2f &sideCEndVertex) { + cv::Point2f midpointSideA = middlePoint(vertexB, vertexC); + cv::Point2f midpointSideB = middlePoint(vertexA, vertexC); + cv::Point2f midpointSideC = middlePoint(vertexA, vertexB); + + bool sideAValid = (validationFlag == VALIDATION_SIDE_A_TANGENT) + ? (areEqualPoints(midpointSideA, polygon[predecessor(a, nrOfPoints)])) + : (isPointOnLineSegment(midpointSideA, sideAStartVertex, sideAEndVertex)); + + bool sideBValid = (validationFlag == VALIDATION_SIDE_B_TANGENT) + ? (areEqualPoints(midpointSideB, polygon[b])) + : (isPointOnLineSegment(midpointSideB, sideBStartVertex, sideBEndVertex)); + + bool sideCValid = isPointOnLineSegment(midpointSideC, sideCStartVertex, sideCEndVertex); + + return (sideAValid && sideBValid && sideCValid); +} + +//! Update the current minimum area enclosing triangle if the newly obtained one has a smaller area +/*! +* @param triangle Minimum area triangle enclosing the given polygon +* @param area Area of the minimum area triangle enclosing the given polygon +* @param vertexA Vertex A of the enclosing triangle +* @param vertexB Vertex B of the enclosing triangle +* @param vertexC Vertex C of the enclosing triangle +*/ +static void updateMinimumAreaEnclosingTriangle(std::vector &triangle, double &area, + const cv::Point2f &vertexA, const cv::Point2f &vertexB, + const cv::Point2f &vertexC) { + double triangleArea = areaOfTriangle(vertexA, vertexB, vertexC); + + if (triangleArea < area) { + triangle.clear(); + + triangle.push_back(vertexA); + triangle.push_back(vertexB); + triangle.push_back(vertexC); + + area = triangleArea; + } +} + +//! Return the middle point of side B +/*! +* @param middlePointOfSideB Middle point of side B +* @param sideAStartVertex Start vertex for defining side A +* @param sideAEndVertex End vertex for defining side A +* @param sideBStartVertex Start vertex for defining side B +* @param sideBEndVertex End vertex for defining side B +* @param sideCStartVertex Start vertex for defining side C +* @param sideCEndVertex End vertex for defining side C +*/ +static bool middlePointOfSideB(cv::Point2f &middlePointOfSideB, const cv::Point2f &sideAStartVertex, + const cv::Point2f &sideAEndVertex, const cv::Point2f &sideBStartVertex, + const cv::Point2f &sideBEndVertex, const cv::Point2f &sideCStartVertex, + const cv::Point2f &sideCEndVertex) { + cv::Point2f vertexA, vertexC; + + if ((!lineIntersection(sideBStartVertex, sideBEndVertex, sideCStartVertex, sideCEndVertex, vertexA)) || + (!lineIntersection(sideBStartVertex, sideBEndVertex, sideAStartVertex, sideAEndVertex, vertexC))) { + return false; + } + + middlePointOfSideB = middlePoint(vertexA, vertexC); + + return true; +} + +//! Check if the line intersects below +/*! +* Check if the line determined by gammaPoint and polygon[polygonPointIndex] intersects +* the polygon below the point polygon[polygonPointIndex] +* +* @param gammaPoint Gamma(p) +* @param polygonPointIndex Index of the polygon point which is considered when determining the line +* @param polygon The polygon representing the convex hull of the points +* @param nrOfPoints Number of points defining the convex polygon +* @param c Index c +*/ +static bool intersectsBelow(const cv::Point2f &gammaPoint, unsigned int polygonPointIndex, + const std::vector &polygon, unsigned int nrOfPoints, + unsigned int c) { + double angleOfGammaAndPoint = angleOfLineWrtOxAxis(polygon[polygonPointIndex], gammaPoint); + + return (intersects(angleOfGammaAndPoint, polygonPointIndex, polygon, nrOfPoints, c) == INTERSECTS_BELOW); +} + +//! Check if the line intersects above +/*! +* Check if the line determined by gammaPoint and polygon[polygonPointIndex] intersects +* the polygon above the point polygon[polygonPointIndex] +* +* @param gammaPoint Gamma(p) +* @param polygonPointIndex Index of the polygon point which is considered when determining the line +* @param polygon The polygon representing the convex hull of the points +* @param nrOfPoints Number of points defining the convex polygon +* @param c Index c +*/ +static bool intersectsAbove(const cv::Point2f &gammaPoint, unsigned int polygonPointIndex, + const std::vector &polygon, unsigned int nrOfPoints, + unsigned int c) { + double angleOfGammaAndPoint = angleOfLineWrtOxAxis(gammaPoint, polygon[polygonPointIndex]); + + return (intersects(angleOfGammaAndPoint, polygonPointIndex, polygon, nrOfPoints, c) == INTERSECTS_ABOVE); +} + +//! Check if/where the line determined by gammaPoint and polygon[polygonPointIndex] intersects the polygon +/*! +* @param angleGammaAndPoint Angle determined by gammaPoint and polygon[polygonPointIndex] wrt Ox axis +* @param polygonPointIndex Index of the polygon point which is considered when determining the line +* @param polygon The polygon representing the convex hull of the points +* @param nrOfPoints Number of points defining the convex polygon +* @param c Index c +*/ +static unsigned int intersects(double angleGammaAndPoint, unsigned int polygonPointIndex, + const std::vector &polygon, unsigned int nrOfPoints, + unsigned int c) { + double anglePointPredecessor = angleOfLineWrtOxAxis(polygon[predecessor(polygonPointIndex, nrOfPoints)], + polygon[polygonPointIndex]); + double anglePointSuccessor = angleOfLineWrtOxAxis(polygon[successor(polygonPointIndex, nrOfPoints)], + polygon[polygonPointIndex]); + double angleFlushEdge = angleOfLineWrtOxAxis(polygon[predecessor(c, nrOfPoints)], + polygon[c]); + + if (isFlushAngleBtwPredAndSucc(angleFlushEdge, anglePointPredecessor, anglePointSuccessor)) { + if ((isGammaAngleBtw(angleGammaAndPoint, anglePointPredecessor, angleFlushEdge)) || + (almostEqual(angleGammaAndPoint, anglePointPredecessor))) { + return intersectsAboveOrBelow(predecessor(polygonPointIndex, nrOfPoints), + polygonPointIndex, polygon, nrOfPoints, c); + } else if ((isGammaAngleBtw(angleGammaAndPoint, anglePointSuccessor, angleFlushEdge)) || + (almostEqual(angleGammaAndPoint, anglePointSuccessor))) { + return intersectsAboveOrBelow(successor(polygonPointIndex, nrOfPoints), + polygonPointIndex, polygon, nrOfPoints, c); + } + } else { + if ( + (isGammaAngleBtw(angleGammaAndPoint, anglePointPredecessor, anglePointSuccessor)) || + ( + (isGammaAngleEqualTo(angleGammaAndPoint, anglePointPredecessor)) && + (!isGammaAngleEqualTo(angleGammaAndPoint, angleFlushEdge)) + ) || + ( + (isGammaAngleEqualTo(angleGammaAndPoint, anglePointSuccessor)) && + (!isGammaAngleEqualTo(angleGammaAndPoint, angleFlushEdge)) + ) + ) { + return INTERSECTS_BELOW; + } + } + + return INTERSECTS_CRITICAL; +} + +//! If (gamma(x) x) intersects P between successorOrPredecessorIndex and pointIntex is it above/below? +/*! +* @param succPredIndex Index of the successor or predecessor +* @param pointIndex Index of the point x in the polygon +* @param polygon The polygon representing the convex hull of the points +* @param nrOfPoints Number of points defining the convex polygon +* @param c Index c +*/ +static unsigned int intersectsAboveOrBelow(unsigned int succPredIndex, unsigned int pointIndex, + const std::vector &polygon, + unsigned int nrOfPoints, unsigned int c) { + if (height(succPredIndex, polygon, nrOfPoints, c) > height(pointIndex, polygon, nrOfPoints, c)) { + return INTERSECTS_ABOVE; + } else { + return INTERSECTS_BELOW; + } +} + +//! Find gamma for a given point "p" specified by its index +/*! +* The function returns true if gamma exists i.e. if lines (a a-1) and (x y) intersect +* and false otherwise. In case the two lines intersect in point intersectionPoint, gamma is computed. +* +* Considering that line (x y) is a line parallel to (c c-1) and that the distance between the lines is equal +* to 2 * height(p), we can have two possible (x y) lines. +* +* Therefore, we will compute two intersection points between the lines (x y) and (a a-1) and take the +* point which is on the same side of line (c c-1) as the polygon. +* +* See paper [2] and formula for distance from point to a line for more details +* +* @param polygonPointIndex Index of the polygon point +* @param gammaPoint Point gamma(polygon[polygonPointIndex]) +* @param polygon The polygon representing the convex hull of the points +* @param nrOfPoints Number of points defining the convex polygon +* @param a Index a +* @param c Index c +*/ +static bool gamma(unsigned int polygonPointIndex, cv::Point2f &gammaPoint, + const std::vector &polygon, unsigned int nrOfPoints, + unsigned int a, unsigned int c) { + cv::Point2f intersectionPoint1, intersectionPoint2; + + // Get intersection points if they exist + if (!findGammaIntersectionPoints(polygon, nrOfPoints, c, polygonPointIndex, + polygon[a], polygon[predecessor(a, nrOfPoints)], + polygon[c], polygon[predecessor(c, nrOfPoints)], + intersectionPoint1, intersectionPoint2)) { + return false; + } + + // Select the point which is on the same side of line C as the polygon + if (areOnTheSameSideOfLine(intersectionPoint1, polygon[successor(c, nrOfPoints)], + polygon[c], polygon[predecessor(c, nrOfPoints)])) { + gammaPoint = intersectionPoint1; + } else { + gammaPoint = intersectionPoint2; + } + + return true; +} + +//! Find vertex C which lies on side B at a distance = 2 * height(a-1) from side C +/*! +* Considering that line (x y) is a line parallel to (c c-1) and that the distance between the lines is equal +* to 2 * height(a-1), we can have two possible (x y) lines. +* +* Therefore, we will compute two intersection points between the lines (x y) and (b b-1) and take the +* point which is on the same side of line (c c-1) as the polygon. +* +* See paper [2] and formula for distance from point to a line for more details +* +* @param polygon The polygon representing the convex hull of the points +* @param nrOfPoints Number of points defining the convex polygon +* @param a Index a +* @param c Index c +* @param sideBStartVertex Start vertex for defining side B +* @param sideBEndVertex End vertex for defining side B +* @param sideCStartVertex Start vertex for defining side C +* @param sideCEndVertex End vertex for defining side C +*/ +static cv::Point2f findVertexCOnSideB(const std::vector &polygon, unsigned int nrOfPoints, + unsigned int a, unsigned int c, + const cv::Point2f &sideBStartVertex, + const cv::Point2f &sideBEndVertex, + const cv::Point2f &sideCStartVertex, + const cv::Point2f &sideCEndVertex) { + cv::Point2f intersectionPoint1, intersectionPoint2; + + // Get intersection points if they exist + if (!findGammaIntersectionPoints(polygon, nrOfPoints, c, predecessor(a, nrOfPoints), + sideBStartVertex, sideBEndVertex, + sideCStartVertex, sideCEndVertex, + intersectionPoint1, intersectionPoint2)) { + CV_Error(cv::Error::StsInternal, ERR_VERTEX_C_ON_SIDE_B); + } + + // Select the point which is on the same side of line C as the polygon + if (areOnTheSameSideOfLine(intersectionPoint1, polygon[successor(c, nrOfPoints)], + polygon[c], polygon[predecessor(c, nrOfPoints)])) { + return intersectionPoint1; + } else { + return intersectionPoint2; + } +} + +//! Find the intersection points to compute gamma(point) +/*! +* @param polygon The polygon representing the convex hull of the points +* @param nrOfPoints Number of points defining the convex polygon +* @param c Index c +* @param polygonPointIndex Index of the polygon point for which the distance is known +* @param side1StartVertex Start vertex for side 1 +* @param side1EndVertex End vertex for side 1 +* @param side2StartVertex Start vertex for side 2 +* @param side2EndVertex End vertex for side 2 +* @param intersectionPoint1 First intersection point between one pair of lines +* @param intersectionPoint2 Second intersection point between other pair of lines +*/ +static bool findGammaIntersectionPoints(const std::vector &polygon, unsigned int nrOfPoints, + unsigned int c, unsigned int polygonPointIndex, + const cv::Point2f &side1StartVertex, const cv::Point2f &side1EndVertex, + const cv::Point2f &side2StartVertex, const cv::Point2f &side2EndVertex, + cv::Point2f &intersectionPoint1, cv::Point2f &intersectionPoint2) { + std::vector side1Params = lineEquationParameters(side1StartVertex, side1EndVertex); + std::vector side2Params = lineEquationParameters(side2StartVertex, side2EndVertex); + + // Compute side C extra parameter using the formula for distance from a point to a line + double polygonPointHeight = height(polygonPointIndex, polygon, nrOfPoints, c); + double distFormulaDenom = sqrt((side2Params[0] * side2Params[0]) + (side2Params[1] * side2Params[1])); + double sideCExtraParam = 2 * polygonPointHeight * distFormulaDenom; + + // Get intersection points if they exist or if lines are identical + if (!areIntersectingLines(side1Params, side2Params, sideCExtraParam, intersectionPoint1, intersectionPoint2)) { + return false; + } else if (areIdenticalLines(side1Params, side2Params, sideCExtraParam)) { + intersectionPoint1 = side1StartVertex; + intersectionPoint2 = side1EndVertex; + } + + return true; +} + +//! Check if the given lines are identical or not +/*! +* The lines are specified as: +* ax + by + c = 0 +* OR +* ax + by + c (+/-) sideCExtraParam = 0 +* +* @param side1Params Vector containing the values of a, b and c for side 1 +* @param side2Params Vector containing the values of a, b and c for side 2 +* @param sideCExtraParam Extra parameter for the flush edge C +*/ +static bool areIdenticalLines(const std::vector &side1Params, + const std::vector &side2Params, double sideCExtraParam) { + return ( + (areIdenticalLines(side1Params[0], side1Params[1], -(side1Params[2]), + side2Params[0], side2Params[1], -(side2Params[2]) - sideCExtraParam)) || + (areIdenticalLines(side1Params[0], side1Params[1], -(side1Params[2]), + side2Params[0], side2Params[1], -(side2Params[2]) + sideCExtraParam)) + ); +} + +//! Check if the given lines intersect or not. If the lines intersect find their intersection points. +/*! +* The lines are specified as: +* ax + by + c = 0 +* OR +* ax + by + c (+/-) sideCExtraParam = 0 +* +* @param side1Params Vector containing the values of a, b and c for side 1 +* @param side2Params Vector containing the values of a, b and c for side 2 +* @param sideCExtraParam Extra parameter for the flush edge C +* @param intersectionPoint1 The first intersection point, if it exists +* @param intersectionPoint2 The second intersection point, if it exists +*/ +static bool areIntersectingLines(const std::vector &side1Params, + const std::vector &side2Params, + double sideCExtraParam, cv::Point2f &intersectionPoint1, + cv::Point2f &intersectionPoint2) { + return ( + (lineIntersection(side1Params[0], side1Params[1], -(side1Params[2]), + side2Params[0], side2Params[1], -(side2Params[2]) - sideCExtraParam, + intersectionPoint1)) && + (lineIntersection(side1Params[0], side1Params[1], -(side1Params[2]), + side2Params[0], side2Params[1], -(side2Params[2]) + sideCExtraParam, + intersectionPoint2)) + ); +} + +//! Get the line equation parameters "a", "b" and "c" for the line determined by points "p" and "q" +/*! +* The equation of the line is considered in the general form: +* ax + by + c = 0 +* +* @param p One point for defining the equation of the line +* @param q Second point for defining the equation of the line +*/ +static std::vector lineEquationParameters(const cv::Point2f& p, const cv::Point2f &q) { + std::vector lineEquationParameters; + double a, b, c; + + lineEquationDeterminedByPoints(p, q, a, b, c); + + lineEquationParameters.push_back(a); + lineEquationParameters.push_back(b); + lineEquationParameters.push_back(c); + + return lineEquationParameters; +} + +//! Compute the height of the point +/*! +* See paper [2] for more details +* +* @param polygonPoint Polygon point +* @param polygon The polygon representing the convex hull of the points +* @param nrOfPoints Number of points defining the convex polygon +* @param c Index c +*/ +static double height(const cv::Point2f &polygonPoint, const std::vector &polygon, + unsigned int nrOfPoints, unsigned int c) { + cv::Point2f pointC = polygon[c]; + cv::Point2f pointCPredecessor = polygon[predecessor(c, nrOfPoints)]; + + return distanceFromPointToLine(polygonPoint, pointC, pointCPredecessor); +} + +//! Compute the height of the point specified by the given index +/*! +* See paper [2] for more details +* +* @param polygonPointIndex Index of the polygon point +* @param polygon The polygon representing the convex hull of the points +* @param nrOfPoints Number of points defining the convex polygon +* @param c Index c +*/ +static double height(unsigned int polygonPointIndex, const std::vector &polygon, + unsigned int nrOfPoints, unsigned int c) { + cv::Point2f pointC = polygon[c]; + cv::Point2f pointCPredecessor = polygon[predecessor(c, nrOfPoints)]; + + cv::Point2f polygonPoint = polygon[polygonPointIndex]; + + return distanceFromPointToLine(polygonPoint, pointC, pointCPredecessor); +} + +//! Advance the given index with one position +/*! +* @param index Index of the point +* @param nrOfPoints Number of points defining the convex polygon +*/ +static void advance(unsigned int &index, unsigned int nrOfPoints) { + index = successor(index, nrOfPoints); +} + +//! Return the succesor of the provided point index +/*! +* The succesor of the last polygon point is the first polygon point +* (circular referencing) +* +* @param index Index of the point +* @param nrOfPoints Number of points defining the convex polygon +*/ +static unsigned int successor(unsigned int index, unsigned int nrOfPoints) { + return ((index + 1) % nrOfPoints); +} + +//! Return the predecessor of the provided point index +/*! +* The predecessor of the first polygon point is the last polygon point +* (circular referencing) +* +* @param index Index of the point +* @param nrOfPoints Number of points defining the convex polygon +*/ +static unsigned int predecessor(unsigned int index, unsigned int nrOfPoints) { + return (index == 0) ? (nrOfPoints - 1) + : (index - 1); +} + +//! Check if the flush edge angle/opposite angle lie between the predecessor and successor angle +/*! +* Check if the angle of the flush edge or its opposite angle lie between the angle of +* the predecessor and successor +* +* @param angleFlushEdge Angle of the flush edge +* @param anglePred Angle of the predecessor +* @param angleSucc Angle of the successor +*/ +static bool isFlushAngleBtwPredAndSucc(double &angleFlushEdge, double anglePred, double angleSucc) { + if (isAngleBetweenNonReflex(angleFlushEdge, anglePred, angleSucc)) { + return true; + } else if (isOppositeAngleBetweenNonReflex(angleFlushEdge, anglePred, angleSucc)) { + angleFlushEdge = oppositeAngle(angleFlushEdge); + + return true; + } + + return false; +} + +//! Check if the angle of the line (gamma(p) p) or its opposite angle is equal to the given angle +/*! +* @param gammaAngle Angle of the line (gamma(p) p) +* @param angle Angle to compare against +*/ +static bool isGammaAngleEqualTo(double &gammaAngle, double angle) { + return (almostEqual(gammaAngle, angle)); +} + +//! Check if the angle of the line (gamma(p) p) or its opposite angle lie between angle1 and angle2 +/*! +* @param gammaAngle Angle of the line (gamma(p) p) +* @param angle1 One of the boundary angles +* @param angle2 Another boundary angle +*/ +static bool isGammaAngleBtw(double &gammaAngle, double angle1, double angle2) { + return (isAngleBetweenNonReflex(gammaAngle, angle1, angle2)); +} + +//! Get the angle of the line measured from the Ox axis in counterclockwise direction +/*! +* The line is specified by points "a" and "b". The value of the angle is expressed in degrees. +* +* @param a Point a +* @param b Point b +*/ +static double angleOfLineWrtOxAxis(const cv::Point2f &a, const cv::Point2f &b) { + double y = b.y - a.y; + double x = b.x - a.x; + + double angle = (std::atan2(y, x) * 180 / CV_PI); + + return (angle < 0) ? (angle + 360) + : angle; +} + +//! Check if angle1 lies between non reflex angle determined by angles 2 and 3 +/*! +* @param angle1 The angle which lies between angle2 and angle3 or not +* @param angle2 One of the boundary angles +* @param angle3 The other boundary angle +*/ +static bool isAngleBetweenNonReflex(double angle1, double angle2, double angle3) { + if (std::abs(angle2 - angle3) > 180) { + if (angle2 > angle3) { + return (((angle2 < angle1) && (lessOrEqual(angle1, 360))) || + ((lessOrEqual(0, angle1)) && (angle1 < angle3))); + } else { + return (((angle3 < angle1) && (lessOrEqual(angle1, 360))) || + ((lessOrEqual(0, angle1)) && (angle1 < angle2))); + } + } else { + return isAngleBetween(angle1, angle2, angle3); + } +} + +//! Check if the opposite of angle1, ((angle1 + 180) % 360), lies between non reflex angle determined by angles 2 and 3 +/*! +* @param angle1 The angle which lies between angle2 and angle3 or not +* @param angle2 One of the boundary angles +* @param angle3 The other boundary angle +*/ +static bool isOppositeAngleBetweenNonReflex(double angle1, double angle2, double angle3) { + double angle1Opposite = oppositeAngle(angle1); + + return (isAngleBetweenNonReflex(angle1Opposite, angle2, angle3)); +} + +//! Check if angle1 lies between angles 2 and 3 +/*! +* @param angle1 The angle which lies between angle2 and angle3 or not +* @param angle2 One of the boundary angles +* @param angle3 The other boundary angle +*/ +static bool isAngleBetween(double angle1, double angle2, double angle3) { + if ((((int)(angle2 - angle3)) % 180) > 0) { + return ((angle3 < angle1) && (angle1 < angle2)); + } else { + return ((angle2 < angle1) && (angle1 < angle3)); + } +} + +//! Return the angle opposite to the given angle +/*! +* if (angle < 180) then +* return (angle + 180); +* else +* return (angle - 180); +* endif +* +* @param angle Angle +*/ +static double oppositeAngle(double angle) { + return (angle > 180) ? (angle - 180) + : (angle + 180); +} + +//! Compute the distance from a point "a" to a line specified by two points "B" and "C" +/*! +* Formula used: +* +* |(x_c - x_b) * (y_b - y_a) - (x_b - x_a) * (y_c - y_b)| +* d = ------------------------------------------------------- +* sqrt(((x_c - x_b)^2) + ((y_c - y_b)^2)) +* +* Reference: http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html +* (Last access: 15.09.2013) +* +* @param a Point from which the distance is measures +* @param linePointB One of the points determining the line +* @param linePointC One of the points determining the line +*/ +static double distanceFromPointToLine(const cv::Point2f &a, const cv::Point2f &linePointB, + const cv::Point2f &linePointC) { + double term1 = linePointC.x - linePointB.x; + double term2 = linePointB.y - a.y; + double term3 = linePointB.x - a.x; + double term4 = linePointC.y - linePointB.y; + + double nominator = std::abs((term1 * term2) - (term3 * term4)); + double denominator = std::sqrt((term1 * term1) + (term4 * term4)); + + return (denominator != 0) ? (nominator / denominator) + : 0; +} + +//! Compute the distance between two points +/*! Compute the Euclidean distance between two points +* +* @param a Point a +* @param b Point b +*/ +static double distanceBtwPoints(const cv::Point2f &a, const cv::Point2f &b) { + double xDiff = a.x - b.x; + double yDiff = a.y - b.y; + + return std::sqrt((xDiff * xDiff) + (yDiff * yDiff)); +} + +//! Compute the area of a triangle defined by three points +/*! +* The area is computed using the determinant method. +* An example is depicted at http://demonstrations.wolfram.com/TheAreaOfATriangleUsingADeterminant/ +* (Last access: 15.09.2013) +* +* @param a Point a +* @param b Point b +* @param c Point c +*/ +static double areaOfTriangle(const cv::Point2f &a, const cv::Point2f &b, const cv::Point2f &c) { + double posTerm = (a.x * b.y) + (a.y * c.x) + (b.x * c.y); + double negTerm = (b.y * c.x) + (a.x * c.y) + (a.y * b.x); + + double determinant = posTerm - negTerm; + + return std::abs(determinant) / 2; +} + +//! Get the point in the middle of the segment determined by points "a" and "b" +/*! +* @param a Point a +* @param b Point b +*/ +static cv::Point2f middlePoint(const cv::Point2f &a, const cv::Point2f &b) { + double middleX = static_cast((a.x + b.x) / 2); + double middleY = static_cast((a.y + b.y) / 2); + + return cv::Point2f(static_cast(middleX), static_cast(middleY)); +} + +//! Determine the intersection point of two lines, if this point exists +/*! Two lines intersect if they are not parallel (Parallel lines intersect at +* +/- infinity, but we do not consider this case here). +* +* The lines are specified in the following form: +* A1x + B1x = C1 +* A2x + B2x = C2 +* +* If det (= A1*B2 - A2*B1) == 0, then lines are parallel +* else they intersect +* +* If they intersect, then let us denote the intersection point with P(x, y) where: +* x = (C1*B2 - C2*B1) / (det) +* y = (C2*A1 - C1*A2) / (det) +* +* @param a1 A1 +* @param b1 B1 +* @param c1 C1 +* @param a2 A2 +* @param b2 B2 +* @param c2 C2 +* @param intersection The intersection point, if this point exists +*/ +static bool lineIntersection(double a1, double b1, double c1, double a2, double b2, double c2, + cv::Point2f &intersection) { + double det = (a1 * b2) - (a2 * b1); + + if (!(almostEqual(det, 0))) { + intersection.x = static_cast(((c1 * b2) - (c2 * b1)) / (det)); + intersection.y = static_cast(((c2 * a1) - (c1 * a2)) / (det)); + + return true; + } + + return false; +} + +//! Determine the intersection point of two lines, if this point exists +/*! Two lines intersect if they are not parallel (Parallel lines intersect at +* +/- infinity, but we do not consider this case here). +* +* The lines are specified by a pair of points each. If they intersect, then +* the function returns true, else it returns false. +* +* Lines can be specified in the following form: +* A1x + B1x = C1 +* A2x + B2x = C2 +* +* If det (= A1*B2 - A2*B1) == 0, then lines are parallel +* else they intersect +* +* If they intersect, then let us denote the intersection point with P(x, y) where: +* x = (C1*B2 - C2*B1) / (det) +* y = (C2*A1 - C1*A2) / (det) +* +* @param a1 First point for determining the first line +* @param b1 Second point for determining the first line +* @param a2 First point for determining the second line +* @param b2 Second point for determining the second line +* @param intersection The intersection point, if this point exists +*/ +static bool lineIntersection(const cv::Point2f &a1, const cv::Point2f &b1, const cv::Point2f &a2, + const cv::Point2f &b2, cv::Point2f &intersection) { + double A1 = b1.y - a1.y; + double B1 = a1.x - b1.x; + double C1 = (a1.x * A1) + (a1.y * B1); + + double A2 = b2.y - a2.y; + double B2 = a2.x - b2.x; + double C2 = (a2.x * A2) + (a2.y * B2); + + double det = (A1 * B2) - (A2 * B1); + + if (!almostEqual(det, 0)) { + intersection.x = static_cast(((C1 * B2) - (C2 * B1)) / (det)); + intersection.y = static_cast(((C2 * A1) - (C1 * A2)) / (det)); + + return true; + } + + return false; +} + +//! Get the values of "a", "b" and "c" of the line equation ax + by + c = 0 knowing that point "p" and "q" are on the line +/*! +* a = q.y - p.y +* b = p.x - q.x +* c = - (p.x * a) - (p.y * b) +* +* @param p Point p +* @param q Point q +* @param a Parameter "a" from the line equation +* @param b Parameter "b" from the line equation +* @param c Parameter "c" from the line equation +*/ +static void lineEquationDeterminedByPoints(const cv::Point2f &p, const cv::Point2f &q, + double &a, double &b, double &c) { + CV_Assert(areEqualPoints(p, q) == false); + + a = q.y - p.y; + b = p.x - q.x; + c = ((-p.y) * b) - (p.x * a); +} + +//! Check if p1 and p2 are on the same side of the line determined by points a and b +/*! +* @param p1 Point p1 +* @param p2 Point p2 +* @param a First point for determining line +* @param b Second point for determining line +*/ +static bool areOnTheSameSideOfLine(const cv::Point2f &p1, const cv::Point2f &p2, + const cv::Point2f &a, const cv::Point2f &b) { + double a1, b1, c1; + + lineEquationDeterminedByPoints(a, b, a1, b1, c1); + + double p1OnLine = (a1 * p1.x) + (b1 * p1.y) + c1; + double p2OnLine = (a1 * p2.x) + (b1 * p2.y) + c1; + + return (sign(p1OnLine) == sign(p2OnLine)); +} + +//! Check if one point lies between two other points +/*! +* @param point Point lying possibly outside the line segment +* @param lineSegmentStart First point determining the line segment +* @param lineSegmentEnd Second point determining the line segment +*/ +static bool isPointOnLineSegment(const cv::Point2f &point, const cv::Point2f &lineSegmentStart, + const cv::Point2f &lineSegmentEnd) { + double d1 = distanceBtwPoints(point, lineSegmentStart); + double d2 = distanceBtwPoints(point, lineSegmentEnd); + double lineSegmentLength = distanceBtwPoints(lineSegmentStart, lineSegmentEnd); + + return (almostEqual(d1 + d2, lineSegmentLength)); +} + +//! Check if two lines are identical +/*! +* Lines are be specified in the following form: +* A1x + B1x = C1 +* A2x + B2x = C2 +* +* If (A1/A2) == (B1/B2) == (C1/C2), then the lines are identical +* else they are not +* +* @param a1 A1 +* @param b1 B1 +* @param c1 C1 +* @param a2 A2 +* @param b2 B2 +* @param c2 C2 +*/ +static bool areIdenticalLines(double a1, double b1, double c1, double a2, double b2, double c2) { + double a1B2 = a1 * b2; + double a2B1 = a2 * b1; + double a1C2 = a1 * c2; + double a2C1 = a2 * c1; + double b1C2 = b1 * c2; + double b2C1 = b2 * c1; + + return ((almostEqual(a1B2, a2B1)) && (almostEqual(b1C2, b2C1)) && (almostEqual(a1C2, a2C1))); +} + +//! Check if points point1 and point2 are equal or not +/*! +* @param point1 One point +* @param point2 The other point +*/ +static bool areEqualPoints(const cv::Point2f &point1, const cv::Point2f &point2) { + return (almostEqual(point1.x, point2.x) && almostEqual(point1.y, point2.y)); +} + +//! Return the sign of the number +/*! +* The sign function returns: +* -1, if number < 0 +* +1, if number > 0 +* 0, otherwise +*/ +static int sign(double number) { + return (number > 0) ? 1 : ((number < 0) ? -1 : 0); +} + +//! Return the maximum of the provided numbers +static double maximum(double number1, double number2, double number3) { + return std::max(std::max(number1, number2), number3); +} + +//! Check if the two numbers are equal (almost) +/*! +* The expression for determining if two real numbers are equal is: +* if (Abs(x - y) <= EPSILON * Max(1.0f, Abs(x), Abs(y))). +* +* @param number1 First number +* @param number2 Second number +*/ +static bool almostEqual(double number1, double number2) { + return (std::abs(number1 - number2) <= (EPSILON * maximum(1.0, std::abs(number1), std::abs(number2)))); +} + +//! Check if the first number is greater than or equal to the second number +/*! +* @param number1 First number +* @param number2 Second number +*/ +static bool greaterOrEqual(double number1, double number2) { + return ((number1 > number2) || (almostEqual(number1, number2))); +} + +//! Check if the first number is less than or equal to the second number +/*! +* @param number1 First number +* @param number2 Second number +*/ +static bool lessOrEqual(double number1, double number2) { + return ((number1 < number2) || (almostEqual(number1, number2))); +} + +}; \ No newline at end of file diff --git a/modules/imgproc/test/test_convhull.cpp b/modules/imgproc/test/test_convhull.cpp index 49456c6cc9..9e4cd07c44 100644 --- a/modules/imgproc/test/test_convhull.cpp +++ b/modules/imgproc/test/test_convhull.cpp @@ -161,6 +161,22 @@ cvTsPointPolygonTest( CvPoint2D32f pt, const CvPoint2D32f* vv, int n, int* _idx= return result; } +static cv::Point2f +cvTsMiddlePoint(const cv::Point2f &a, const cv::Point2f &b) +{ + return cv::Point2f((a.x + b.x) / 2, (a.y + b.y) / 2); +} + +static bool +cvTsIsPointOnLineSegment(const cv::Point2f &x, const cv::Point2f &a, const cv::Point2f &b) +{ + double d1 = cvTsDist(CvPoint2D32f(x.x, x.y), CvPoint2D32f(a.x, a.y)); + double d2 = cvTsDist(CvPoint2D32f(x.x, x.y), CvPoint2D32f(b.x, b.y)); + double d3 = cvTsDist(CvPoint2D32f(a.x, a.y), CvPoint2D32f(b.x, b.y)); + + return (abs(d1 + d2 - d3) <= (1E-5)); +} + /****************************************************************************************\ * Base class for shape descriptor tests * @@ -769,6 +785,145 @@ _exit_: } +/****************************************************************************************\ +* MinEnclosingTriangle Test * +\****************************************************************************************/ + +class CV_MinTriangleTest : public CV_BaseShapeDescrTest +{ +public: + CV_MinTriangleTest(); + +protected: + void run_func(void); + int validate_test_results( int test_case_idx ); + std::vector getTriangleMiddlePoints(); + + std::vector convexPolygon; + std::vector triangle; +}; + + +CV_MinTriangleTest::CV_MinTriangleTest() +{ +} + +std::vector CV_MinTriangleTest::getTriangleMiddlePoints() +{ + std::vector triangleMiddlePoints; + + for (int i = 0; i < 3; i++) { + triangleMiddlePoints.push_back(cvTsMiddlePoint(triangle[i], triangle[(i + 1) % 3])); + } + + return triangleMiddlePoints; +} + + +void CV_MinTriangleTest::run_func() +{ + std::vector pointsAsVector; + + cv::cvarrToMat(points).convertTo(pointsAsVector, CV_32F); + + cv::minEnclosingTriangle(pointsAsVector, triangle); + cv::convexHull(pointsAsVector, convexPolygon, true, true); +} + + +int CV_MinTriangleTest::validate_test_results( int test_case_idx ) +{ + bool errorEnclosed = false, errorMiddlePoints = false, errorFlush = true; + double eps = 1e-4; + int code = CV_BaseShapeDescrTest::validate_test_results( test_case_idx ); + +#if 0 + { + int n = 3; + double a = 8, c = 8, b = 100, d = 150; + CvPoint bp[4], *bpp = bp; + cvNamedWindow( "test", 1 ); + IplImage* img = cvCreateImage( cvSize(500,500), 8, 3 ); + cvZero(img); + for( i = 0; i < point_count; i++ ) + cvCircle(img,cvPoint(cvRound(p[i].x*a+b),cvRound(p[i].y*c+d)), 3, CV_RGB(0,255,0), -1 ); + for( i = 0; i < n; i++ ) + bp[i] = cvPoint(cvRound(triangle[i].x*a+b),cvRound(triangle[i].y*c+d)); + cvPolyLine( img, &bpp, &n, 1, 1, CV_RGB(255,255,0), 1, CV_AA, 0 ); + cvShowImage( "test", img ); + cvWaitKey(); + cvReleaseImage(&img); + } +#endif + + int polygonVertices = (int) convexPolygon.size(); + + if (polygonVertices > 2) { + // Check if all points are enclosed by the triangle + for (int i = 0; (i < polygonVertices) && (!errorEnclosed); i++) + { + if (cv::pointPolygonTest(triangle, cv::Point2f(convexPolygon[i].x, convexPolygon[i].y), true) < (-eps)) + errorEnclosed = true; + } + + // Check if triangle edges middle points touch the polygon + std::vector middlePoints = getTriangleMiddlePoints(); + + for (int i = 0; (i < 3) && (!errorMiddlePoints); i++) + { + bool isTouching = false; + + for (int j = 0; (j < polygonVertices) && (!isTouching); j++) + { + if (cvTsIsPointOnLineSegment(middlePoints[i], convexPolygon[j], + convexPolygon[(j + 1) % polygonVertices])) + isTouching = true; + } + + errorMiddlePoints = (isTouching) ? false : true; + } + + // Check if at least one of the edges is flush + for (int i = 0; (i < 3) && (errorFlush); i++) + { + for (int j = 0; (j < polygonVertices) && (errorFlush); j++) + { + if ((cvTsIsPointOnLineSegment(convexPolygon[j], triangle[i], + triangle[(i + 1) % 3])) && + (cvTsIsPointOnLineSegment(convexPolygon[(j + 1) % polygonVertices], triangle[i], + triangle[(i + 1) % 3]))) + errorFlush = false; + } + } + + // Report any found errors + if (errorEnclosed) + { + ts->printf( cvtest::TS::LOG, + "All points should be enclosed by the triangle.\n" ); + code = cvtest::TS::FAIL_BAD_ACCURACY; + } + else if (errorMiddlePoints) + { + ts->printf( cvtest::TS::LOG, + "All triangle edges middle points should touch the convex hull of the points.\n" ); + code = cvtest::TS::FAIL_INVALID_OUTPUT; + } + else if (errorFlush) + { + ts->printf( cvtest::TS::LOG, + "At least one edge of the enclosing triangle should be flush with one edge of the polygon.\n" ); + code = cvtest::TS::FAIL_INVALID_OUTPUT; + } + } + + if ( code < 0 ) + ts->set_failed_test_info( code ); + + return code; +} + + /****************************************************************************************\ * MinEnclosingCircle Test * \****************************************************************************************/ @@ -1691,6 +1846,7 @@ void CV_PerimeterAreaSliceTest::run( int ) TEST(Imgproc_ConvexHull, accuracy) { CV_ConvHullTest test; test.safe_run(); } TEST(Imgproc_MinAreaRect, accuracy) { CV_MinAreaRectTest test; test.safe_run(); } +TEST(Imgproc_MinTriangle, accuracy) { CV_MinTriangleTest test; test.safe_run(); } TEST(Imgproc_MinCircle, accuracy) { CV_MinCircleTest test; test.safe_run(); } TEST(Imgproc_ContourPerimeter, accuracy) { CV_PerimeterTest test; test.safe_run(); } TEST(Imgproc_FitEllipse, accuracy) { CV_FitEllipseTest test; test.safe_run(); } diff --git a/samples/cpp/minarea.cpp b/samples/cpp/minarea.cpp index 13ac7c6d52..91ad5a37bf 100644 --- a/samples/cpp/minarea.cpp +++ b/samples/cpp/minarea.cpp @@ -8,12 +8,13 @@ using namespace std; static void help() { - cout << "This program demonstrates finding the minimum enclosing box or circle of a set\n" - "of points using functions: minAreaRect() minEnclosingCircle().\n" - "Random points are generated and then enclosed.\n" - "Call:\n" - "./minarea\n" - "Using OpenCV v" << CV_VERSION << "\n" << endl; + cout << "This program demonstrates finding the minimum enclosing box, triangle or circle of a set\n" + << "of points using functions: minAreaRect() minEnclosingTriangle() minEnclosingCircle().\n" + << "Random points are generated and then enclosed.\n\n" + << "Press ESC, 'q' or 'Q' to exit and any other key to regenerate the set of points.\n\n" + << "Call:\n" + << "./minarea\n" + << "Using OpenCV v" << CV_VERSION << "\n" << endl; } int main( int /*argc*/, char** /*argv*/ ) @@ -27,6 +28,8 @@ int main( int /*argc*/, char** /*argv*/ ) { int i, count = rng.uniform(1, 101); vector points; + + // Generate a random set of points for( i = 0; i < count; i++ ) { Point pt; @@ -36,23 +39,38 @@ int main( int /*argc*/, char** /*argv*/ ) points.push_back(pt); } + // Find the minimum area enclosing bounding box RotatedRect box = minAreaRect(Mat(points)); + // Find the minimum area enclosing triangle + vector triangle; + + minEnclosingTriangle(points, triangle); + + // Find the minimum area enclosing circle Point2f center, vtx[4]; float radius = 0; minEnclosingCircle(Mat(points), center, radius); box.points(vtx); img = Scalar::all(0); + + // Draw the points for( i = 0; i < count; i++ ) circle( img, points[i], 3, Scalar(0, 0, 255), FILLED, LINE_AA ); + // Draw the bounding box for( i = 0; i < 4; i++ ) line(img, vtx[i], vtx[(i+1)%4], Scalar(0, 255, 0), 1, LINE_AA); + // Draw the triangle + for( i = 0; i < 3; i++ ) + line(img, triangle[i], triangle[(i+1)%3], Scalar(255, 255, 0), 1, LINE_AA); + + // Draw the circle circle(img, center, cvRound(radius), Scalar(0, 255, 255), 1, LINE_AA); - imshow( "rect & circle", img ); + imshow( "Rectangle, triangle & circle", img ); char key = (char)waitKey(); if( key == 27 || key == 'q' || key == 'Q' ) // 'ESC'