From e6b58c4e79c4e57ac2b25919dc66c510ef0ad35c Mon Sep 17 00:00:00 2001 From: Ovidiu Parvu Date: Wed, 11 Sep 2013 18:02:10 +0100 Subject: [PATCH 01/18] - Added the minEnclosingTriangle function declaration to the imgproc header - Added the source code for the function in the separate file min_enclosing_triangle.cpp --- modules/imgproc/include/opencv2/imgproc.hpp | 4 + .../imgproc/src/min_enclosing_triangle.cpp | 1209 +++++++++++++++++ 2 files changed, 1213 insertions(+) create mode 100644 modules/imgproc/src/min_enclosing_triangle.cpp diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 55816ccb7..7011bb35c 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -1451,6 +1451,10 @@ 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 convex polygon defined by at least three points +CV_EXPORTS_W void minEnclosingTriangle( const std::vector &convexPolygon, + CV_OUT std::vector &triangle, CV_OUT double& area ); + //! 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 000000000..cc6e96b47 --- /dev/null +++ b/modules/imgproc/src/min_enclosing_triangle.cpp @@ -0,0 +1,1209 @@ +/*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. +// 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 +#define INTERSECTS_LIMIT 4 + +// Error messages + +#define ERR_MIDPOINT_SIDE_B "The position of the middle point of side B could not be determined." +#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." +#define ERR_TRIANGLE_VERTICES "The position of the triangle vertices could not be determined, because the sides of the triangle 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 + +// Constant values + +#define PI 3.14159265358979323846264338327950288419716939937510 +#define EPSILON 1E-5 + + +/////////////////////////////////// Global variables ///////////////////////////////////// + + +static unsigned int validationFlag; + +static cv::Point2f vertexA; +static cv::Point2f vertexB; +static cv::Point2f vertexC; + +static cv::Point2f sideAStartVertex; +static cv::Point2f sideAEndVertex; + +static cv::Point2f sideBStartVertex; +static cv::Point2f sideBEndVertex; + +static cv::Point2f sideCStartVertex; +static cv::Point2f sideCEndVertex; + +static double triangleArea; + +static unsigned int a; +static unsigned int b; +static unsigned int c; + +static unsigned int nrOfPoints; + +static std::vector polygon; + + +////////////////////////////// Helper functions declarations ///////////////////////////// + + +static void advance(unsigned int &index); + +static void advanceBToRightChain(); + +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 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(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 findMinimumAreaEnclosingTriangle(std::vector &triangle, double &area); + +static cv::Point2f findVertexCOnSideB(); + +static bool gamma(unsigned int polygonPointIndex, cv::Point2f &gammaPoint); + +static bool greaterOrEqual(double number1, double number2); + +static double height(const cv::Point2f &polygonPoint); + +static double height(unsigned int polygonPointIndex); + +static void initialise(); + +static unsigned int intersects(double angleGammaAndPoint, unsigned int polygonPointIndex); + +static bool intersectsAbove(const cv::Point2f &gammaPoint, unsigned int polygonPointIndex); + +static unsigned int intersectsAboveOrBelow(unsigned int succPredIndex, unsigned int pointIndex); + +static bool intersectsBelow(const cv::Point2f &gammaPoint, unsigned int polygonPointIndex); + +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(); + +static bool isNotBTangency(); + +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(); + +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); + +static void moveAIfLowAndBIfHigh(); + +static double oppositeAngle(double angle); + +static unsigned int predecessor(unsigned int index); + +static void searchForBTangency(); + +static int sign(double number); + +static unsigned int successor(unsigned int index); + +static void updateMinimumAreaEnclosingTriangle(std::vector &triangle, double &area); + +static void updateSideB(); + +static void updateSidesBA(); + +static void updateSidesCA(); + + +///////////////////////////////////// Main functions ///////////////////////////////////// + + +//! Find the minimum enclosing triangle and its area for the given polygon +/*! +* The overall complexity of the algorithm is theta(n) where "n" represents the number +* of vertices in the convex polygon +* +* @param convexPolygon Convex polygon defined by at least three points +* @param triangle Minimum area triangle enclosing the given polygon +* @param area Area of the minimum area enclosing triangle +*/ +void cv::minEnclosingTriangle( const std::vector &convexPolygon, + CV_OUT std::vector &triangle, CV_OUT double& area ) { + // Check if the polygon is convex and is a k-gon with k > 3 + CV_Assert(isContourConvex(convexPolygon) && (convexPolygon.size() > 3)); + + polygon = convexPolygon; + area = std::numeric_limits::max(); + + // Clear all points previously stored in the vector + triangle.clear(); + + initialise(); + + findMinimumAreaEnclosingTriangle(triangle, area); +} + + +/////////////////////////////// Helper functions definition ////////////////////////////// + + +//! Initialisation function +static void initialise() { + nrOfPoints = static_cast(polygon.size()); + + a = 1; + b = 2; + c = 0; +} + +//! Find the minimum area enclosing triangle for the given polygon +/*! +* @param triangle Minimum area triangle enclosing the given polygon +* @param area Area of the minimum area enclosing triangle +*/ +static void findMinimumAreaEnclosingTriangle(std::vector &triangle, double &area) { + for (c = 0; c < nrOfPoints; c++) { + advanceBToRightChain(); + moveAIfLowAndBIfHigh(); + searchForBTangency(); + + updateSidesCA(); + + if (isNotBTangency()) { + updateSidesBA(); + } else { + updateSideB(); + } + + if (isLocalMinimalTriangle()) { + updateMinimumAreaEnclosingTriangle(triangle, area); + } + } +} + +//! Advance b to the right chain +/*! +* See paper [2] for more details +*/ +static void advanceBToRightChain() { + while (greaterOrEqual(height(successor(b)), height(b))) { + advance(b); + } +} + +//! Move "a" if it is low and "b" if it is high +/*! +* See paper [2] for more details +*/ +static void moveAIfLowAndBIfHigh() { + while(height(b) > height(a)) { + cv::Point2f gammaOfA; + + if ((gamma(a, gammaOfA)) && (intersectsBelow(gammaOfA, b))) { + advance(b); + } else { + advance(a); + } + } +} + +//! Search for the tangency of side B +/*! +* See paper [2] for more details +*/ +static void searchForBTangency() { + cv::Point2f gammaOfB; + + while (((gamma(b, gammaOfB)) && (intersectsBelow(gammaOfB, b))) && + (greaterOrEqual(height(b), height(predecessor(a))))) { + advance(b); + } +} + +//! Check if tangency for side B was not obtained +/*! +* See paper [2] for more details +*/ +static bool isNotBTangency() { + cv::Point2f gammaOfB; + + if (((gamma(b, gammaOfB)) && (intersectsAbove(gammaOfB, b))) || (height(b) < height(predecessor(a)))) { + 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" +*/ +static void updateSidesCA() { + sideCStartVertex = polygon[predecessor(c)]; + sideCEndVertex = polygon[c]; + + sideAStartVertex = polygon[predecessor(a)]; + sideAEndVertex = polygon[a]; +} + +//! Update sides B and possibly A if tangency for side B was not obtained +/*! +* See paper [2] for more details +*/ +static void updateSidesBA() { + // Side B is flush with edge [b, b-1] + sideBStartVertex = polygon[predecessor(b)]; + sideBEndVertex = polygon[b]; + + // Find middle point of side B + cv::Point2f sideBMiddlePoint; + + if ((middlePointOfSideB(sideBMiddlePoint)) && + (height(sideBMiddlePoint) < height(predecessor(a)))) { + sideAStartVertex = polygon[predecessor(a)]; + sideAEndVertex = findVertexCOnSideB(); + + 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 +*/ +static void updateSideB() { + if (!gamma(b, sideBStartVertex)) { + 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 +*/ +static bool isLocalMinimalTriangle() { + if ((!lineIntersection(sideAStartVertex, sideAEndVertex, sideBStartVertex, sideBEndVertex, vertexC)) || + (!lineIntersection(sideAStartVertex, sideAEndVertex, sideCStartVertex, sideCEndVertex, vertexB)) || + (!lineIntersection(sideBStartVertex, sideBEndVertex, sideCStartVertex, sideCEndVertex, vertexA))) { + return false; + } + + return isValidMinimalTriangle(); +} + +//! 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 +*/ +static bool isValidMinimalTriangle() { + 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)])) + : (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 minimumAreaEnclosingTriangle Minimum area triangle enclosing the given polygon +* @param minimumAreaEnclosingTriangleArea Area of the minimum area triangle enclosing the given polygon +*/ +static void updateMinimumAreaEnclosingTriangle(std::vector &triangle, double &area) { + 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 +static bool middlePointOfSideB(cv::Point2f& middlePointOfSideB) { + 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 +*/ +static bool intersectsBelow(const cv::Point2f &gammaPoint, unsigned int polygonPointIndex) { + double angleOfGammaAndPoint = angleOfLineWrtOxAxis(polygon[polygonPointIndex], gammaPoint); + + return (intersects(angleOfGammaAndPoint, polygonPointIndex) == 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 +*/ +static bool intersectsAbove(const cv::Point2f &gammaPoint, unsigned int polygonPointIndex) { + double angleOfGammaAndPoint = angleOfLineWrtOxAxis(gammaPoint, polygon[polygonPointIndex]); + + return (intersects(angleOfGammaAndPoint, polygonPointIndex) == INTERSECTS_ABOVE); +} + +//! Check if/where the line determined by gammaPoint and polygon[polygonPointIndex] intersects the polygon +/*! +* @param angleGammaAndPoint Angle between gammaPoint and polygon[polygonPointIndex] +* @param polygonPointIndex Index of the polygon point which is considered when determining the line +*/ +static unsigned int intersects(double angleGammaAndPoint, unsigned int polygonPointIndex) { + double anglePointPredecessor = angleOfLineWrtOxAxis(polygon[predecessor(polygonPointIndex)], + polygon[polygonPointIndex]); + double anglePointSuccessor = angleOfLineWrtOxAxis(polygon[successor(polygonPointIndex)], + polygon[polygonPointIndex]); + double angleFlushEdge = angleOfLineWrtOxAxis(polygon[predecessor(c)], + polygon[c]); + + if (isFlushAngleBtwPredAndSucc(angleFlushEdge, anglePointPredecessor, anglePointSuccessor)) { + if ((isGammaAngleBtw(angleGammaAndPoint, anglePointPredecessor, angleFlushEdge)) || + (almostEqual(angleGammaAndPoint, anglePointPredecessor))) { + return intersectsAboveOrBelow(predecessor(polygonPointIndex), polygonPointIndex); + } else if ((isGammaAngleBtw(angleGammaAndPoint, anglePointSuccessor, angleFlushEdge)) || + (almostEqual(angleGammaAndPoint, anglePointSuccessor))) { + return intersectsAboveOrBelow(successor(polygonPointIndex), polygonPointIndex); + } + } 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 +*/ +static unsigned int intersectsAboveOrBelow(unsigned int succPredIndex, unsigned int pointIndex) { + if (height(succPredIndex) > height(pointIndex)) { + 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 closest to point polygon[a]. +* +* 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]) +*/ +static bool gamma(unsigned int polygonPointIndex, cv::Point2f &gammaPoint) { + cv::Point2f intersectionPoint1, intersectionPoint2; + + // Get intersection points if they exist + if (!findGammaIntersectionPoints(polygonPointIndex, polygon[a], polygon[predecessor(a)], polygon[c], + polygon[predecessor(c)], 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)], + polygon[c], polygon[predecessor(c)])) { + gammaPoint = intersectionPoint1; + } else { + gammaPoint = intersectionPoint2; + } + + return true; +} + +//! Find the intersection points to compute gamma(point) +/*! +* @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 another pair of lines +*/ +static bool findGammaIntersectionPoints(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); + 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; +} + +//! 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 closest to point polygon[b]. +* +* See paper [2] and formula for distance from point to a line for more details +*/ +static cv::Point2f findVertexCOnSideB() { + cv::Point2f intersectionPoint1, intersectionPoint2; + + // Get intersection points if they exist + if (!findGammaIntersectionPoints(predecessor(a), 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)], + polygon[c], polygon[predecessor(c)])) { + return intersectionPoint1; + } else { + return intersectionPoint2; + } +} + +//! Compute the height of the point +/*! +* See paper [2] for more details +* +* @param polygonPoint Polygon point +*/ +static double height(const cv::Point2f &polygonPoint) { + cv::Point2f pointC = polygon[c]; + cv::Point2f pointCPredecessor = polygon[predecessor(c)]; + + 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 +*/ +static double height(unsigned int polygonPointIndex) { + cv::Point2f pointC = polygon[c]; + cv::Point2f pointCPredecessor = polygon[predecessor(c)]; + + cv::Point2f polygonPoint = polygon[polygonPointIndex]; + + return distanceFromPointToLine(polygonPoint, pointC, pointCPredecessor); +} + +//! Advance the given index with one position +/*! +* @param index Index of the point +*/ +static void advance(unsigned int &index) { + index = successor(index); +} + +//! 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 +*/ +static unsigned int successor(unsigned int index) { + 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 +*/ +static unsigned int predecessor(unsigned int index) { + 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 / 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 +* +* @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 (nominator / denominator); +} + +//! 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 presented at http://demonstrations.wolfram.com/TheAreaOfATriangleUsingADeterminant/ +* (Last access: 10.07.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 = (double)((a.x + b.x) / 2); + double middleY = (double)((a.y + b.y) / 2); + + return cv::Point2f((float) middleX, (float) 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 (= A1xB2 - A2xB1) == 0, then lines are parallel +* else they intersect +* +* If they intersect, then let us denote the intersection point with P(x, y) where: +* x = (C1xB2 - C2xB1) / (det) +* y = (C2xA1 - C1xA2) / (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 = (float)(((c1 * b2) - (c2 * b1)) / (det)); + intersection.y = (float)(((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 (= A1xB2 - A2xB1) == 0, then lines are parallel +* else they intersect +* +* If they intersect, then let us denote the intersection point with P(x, y) where: +* x = (C1xB2 - C2xB1) / (det) +* y = (C2xA1 - C1xA2) / (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 = (float)(((C1 * B2) - (C2 * B1)) / (det)); + intersection.y = (float)(((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))); +} + + +/* End of file. */ From 510ad8e77927a1d71be14921a5717e0791f60330 Mon Sep 17 00:00:00 2001 From: Ovidiu Parvu Date: Thu, 12 Sep 2013 10:11:38 +0100 Subject: [PATCH 02/18] - Changed the type of the minEnclosingTriangle function parameters by using proxy classes InputArray/OutputArray instead of std::vector - Adapted the source code to accommodate this change --- modules/imgproc/include/opencv2/imgproc.hpp | 4 +- .../imgproc/src/min_enclosing_triangle.cpp | 59 +++++++++++++++---- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 7011bb35c..57456b7e7 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -1452,8 +1452,8 @@ CV_EXPORTS_W void minEnclosingCircle( InputArray points, CV_OUT Point2f& center, CV_OUT float& radius ); //! computes the minimal enclosing triangle for a convex polygon defined by at least three points -CV_EXPORTS_W void minEnclosingTriangle( const std::vector &convexPolygon, - CV_OUT std::vector &triangle, CV_OUT double& area ); +CV_EXPORTS_W void minEnclosingTriangle( InputArray convexPolygon, + CV_OUT OutputArray triangle, CV_OUT double& area ); //! matches two contours using one of the available algorithms CV_EXPORTS_W double matchShapes( InputArray contour1, InputArray contour2, diff --git a/modules/imgproc/src/min_enclosing_triangle.cpp b/modules/imgproc/src/min_enclosing_triangle.cpp index cc6e96b47..7b64f6518 100644 --- a/modules/imgproc/src/min_enclosing_triangle.cpp +++ b/modules/imgproc/src/min_enclosing_triangle.cpp @@ -94,9 +94,8 @@ #define VALIDATION_SIDE_B_TANGENT 1 #define VALIDATION_SIDES_FLUSH 2 -// Constant values +// Threshold value for geometrical comparisons -#define PI 3.14159265358979323846264338327950288419716939937510 #define EPSILON 1E-5 @@ -157,6 +156,10 @@ static bool areOnTheSameSideOfLine(const cv::Point2f &p1, const cv::Point2f &p2, static double areaOfTriangle(const cv::Point2f &a, const cv::Point2f &b, const cv::Point2f &c); +static void copyConvexPolygon(cv::InputArray convexPolygon); + +static void copyResultingTriangle(const std::vector &resultingTriangle, cv::OutputArray triangle); + static double distanceBtwPoints(const cv::Point2f &a, const cv::Point2f &b); static double distanceFromPointToLine(const cv::Point2f &a, const cv::Point2f &linePointB, @@ -167,6 +170,8 @@ static bool findGammaIntersectionPoints(unsigned int polygonPointIndex, const cv const cv::Point2f &side2EndVertex, cv::Point2f &intersectionPoint1, cv::Point2f &intersectionPoint2); +static void findMinEnclosingTriangle(std::vector &triangle, double& area); + static void findMinimumAreaEnclosingTriangle(std::vector &triangle, double &area); static cv::Point2f findVertexCOnSideB(); @@ -262,12 +267,42 @@ static void updateSidesCA(); * @param triangle Minimum area triangle enclosing the given polygon * @param area Area of the minimum area enclosing triangle */ -void cv::minEnclosingTriangle( const std::vector &convexPolygon, - CV_OUT std::vector &triangle, CV_OUT double& area ) { - // Check if the polygon is convex and is a k-gon with k > 3 - CV_Assert(isContourConvex(convexPolygon) && (convexPolygon.size() > 3)); +void cv::minEnclosingTriangle(cv::InputArray convexPolygon, + CV_OUT cv::OutputArray triangle, CV_OUT double& area) { + std::vector resultingTriangle; + + copyConvexPolygon(convexPolygon); + findMinEnclosingTriangle(resultingTriangle, area); + copyResultingTriangle(resultingTriangle, triangle); +} + + +/////////////////////////////// Helper functions definition ////////////////////////////// + + +//! Copy the provided convex polygon to the global variable "polygon" +/*! +* @param convexPolygon The provided convex polygon +*/ +static void copyConvexPolygon(cv::InputArray convexPolygon) { + cv::Mat convexPolygonMat = convexPolygon.getMat(); + + convexPolygonMat.copyTo(polygon); +} + +//! Find the minimum enclosing triangle and its area for the given polygon +/*! +* The overall complexity of the algorithm is theta(n) where "n" represents the number +* of vertices in the convex polygon +* +* @param convexPolygon Convex polygon defined by at least three points +* @param triangle Minimum area triangle enclosing the given polygon +* @param area Area of the minimum area enclosing triangle +*/ +static void findMinEnclosingTriangle( std::vector &triangle, double& area) { + // Check if the polygon is convex and is a k-gon with k > 3 + CV_Assert(isContourConvex(polygon) && (polygon.size() > 3)); - polygon = convexPolygon; area = std::numeric_limits::max(); // Clear all points previously stored in the vector @@ -278,9 +313,11 @@ void cv::minEnclosingTriangle( const std::vector &convexPolygon, findMinimumAreaEnclosingTriangle(triangle, area); } - -/////////////////////////////// Helper functions definition ////////////////////////////// - +//! Copy resultingTriangle to the OutputArray triangle +static void copyResultingTriangle(const std::vector &resultingTriangle, + cv::OutputArray triangle) { + cv::Mat(resultingTriangle).convertTo(triangle, triangle.fixedType() ? triangle.type() : CV_32F); +} //! Initialisation function static void initialise() { @@ -847,7 +884,7 @@ 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 / PI); + double angle = (std::atan2(y, x) * 180 / CV_PI); return (angle < 0) ? (angle + 360) : angle; From 4fce8e6b0e6985150f9525b493a9dba7b6473618 Mon Sep 17 00:00:00 2001 From: Ovidiu Parvu Date: Thu, 12 Sep 2013 13:29:10 +0100 Subject: [PATCH 03/18] Changed the semantics of the minEnclosingTriangle function such that: 1. The function receives a set of points as input instead of a convex polygon with more than three vertices 2. The convex hull P is computed inside the function 3.1. If the number of vertices of P is greater than three then the algorithm which was implemented before executes 3.2. Otherwise the vertices of the triangle are picked from the vertices of the given polygon and the area computed --- modules/imgproc/include/opencv2/imgproc.hpp | 4 +- .../imgproc/src/min_enclosing_triangle.cpp | 70 ++++++++++++------- 2 files changed, 46 insertions(+), 28 deletions(-) diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 57456b7e7..c09afe9f8 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -1451,8 +1451,8 @@ 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 convex polygon defined by at least three points -CV_EXPORTS_W void minEnclosingTriangle( InputArray convexPolygon, +//! computes the minimal enclosing triangle for a set of points +CV_EXPORTS_W void minEnclosingTriangle( InputArray points, CV_OUT OutputArray triangle, CV_OUT double& area ); //! matches two contours using one of the available algorithms diff --git a/modules/imgproc/src/min_enclosing_triangle.cpp b/modules/imgproc/src/min_enclosing_triangle.cpp index 7b64f6518..ccbbf973b 100644 --- a/modules/imgproc/src/min_enclosing_triangle.cpp +++ b/modules/imgproc/src/min_enclosing_triangle.cpp @@ -156,10 +156,10 @@ static bool areOnTheSameSideOfLine(const cv::Point2f &p1, const cv::Point2f &p2, static double areaOfTriangle(const cv::Point2f &a, const cv::Point2f &b, const cv::Point2f &c); -static void copyConvexPolygon(cv::InputArray convexPolygon); - static void copyResultingTriangle(const std::vector &resultingTriangle, cv::OutputArray triangle); +static void createConvexHull(cv::InputArray points); + static double distanceBtwPoints(const cv::Point2f &a, const cv::Point2f &b); static double distanceFromPointToLine(const cv::Point2f &a, const cv::Point2f &linePointB, @@ -184,7 +184,7 @@ static double height(const cv::Point2f &polygonPoint); static double height(unsigned int polygonPointIndex); -static void initialise(); +static void initialise(std::vector &triangle, double &area); static unsigned int intersects(double angleGammaAndPoint, unsigned int polygonPointIndex); @@ -240,6 +240,8 @@ static double oppositeAngle(double angle); static unsigned int predecessor(unsigned int index); +static void returnMinimumAreaEnclosingTriangle(std::vector &triangle, double &area); + static void searchForBTangency(); static int sign(double number); @@ -258,20 +260,20 @@ static void updateSidesCA(); ///////////////////////////////////// Main functions ///////////////////////////////////// -//! Find the minimum enclosing triangle and its area for the given polygon +//! Find the minimum enclosing triangle and its area for the given set of points /*! * The overall complexity of the algorithm is theta(n) where "n" represents the number -* of vertices in the convex polygon +* of vertices in the convex hull of the points * -* @param convexPolygon Convex polygon defined by at least three points +* @param points Set of points * @param triangle Minimum area triangle enclosing the given polygon * @param area Area of the minimum area enclosing triangle */ -void cv::minEnclosingTriangle(cv::InputArray convexPolygon, +void cv::minEnclosingTriangle(cv::InputArray points, CV_OUT cv::OutputArray triangle, CV_OUT double& area) { std::vector resultingTriangle; - copyConvexPolygon(convexPolygon); + createConvexHull(points); findMinEnclosingTriangle(resultingTriangle, area); copyResultingTriangle(resultingTriangle, triangle); } @@ -280,37 +282,35 @@ void cv::minEnclosingTriangle(cv::InputArray convexPolygon, /////////////////////////////// Helper functions definition ////////////////////////////// -//! Copy the provided convex polygon to the global variable "polygon" +//! Create the convex hull of the given set of points /*! -* @param convexPolygon The provided convex polygon +* @param points The provided set of points */ -static void copyConvexPolygon(cv::InputArray convexPolygon) { - cv::Mat convexPolygonMat = convexPolygon.getMat(); +static void createConvexHull(cv::InputArray points) { + cv::Mat pointsMat = points.getMat(); - convexPolygonMat.copyTo(polygon); + CV_Assert((pointsMat.checkVector(2) > 0) && + ((pointsMat.depth() == CV_32F) || (pointsMat.depth() == CV_32S))); + + convexHull(points, polygon, true, true); } -//! Find the minimum enclosing triangle and its area for the given polygon +//! 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 convexPolygon Convex polygon defined by at least three points * @param triangle Minimum area triangle enclosing the given polygon * @param area Area of the minimum area enclosing triangle */ static void findMinEnclosingTriangle( std::vector &triangle, double& area) { - // Check if the polygon is convex and is a k-gon with k > 3 - CV_Assert(isContourConvex(polygon) && (polygon.size() > 3)); + initialise(triangle, area); - area = std::numeric_limits::max(); - - // Clear all points previously stored in the vector - triangle.clear(); - - initialise(); - - findMinimumAreaEnclosingTriangle(triangle, area); + if (polygon.size() > 3) { + findMinimumAreaEnclosingTriangle(triangle, area); + } else { + returnMinimumAreaEnclosingTriangle(triangle, area); + } } //! Copy resultingTriangle to the OutputArray triangle @@ -320,9 +320,14 @@ static void copyResultingTriangle(const std::vector &resultingTrian } //! Initialisation function -static void initialise() { +static void initialise(std::vector &triangle, double &area) { nrOfPoints = static_cast(polygon.size()); + area = std::numeric_limits::max(); + // Clear all points previously stored in the vector + triangle.clear(); + + // Initialise the values of the indices for the algorithm a = 1; b = 2; c = 0; @@ -353,6 +358,19 @@ static void findMinimumAreaEnclosingTriangle(std::vector &triangle, } } +//! Return the minimum area enclosing (pseudo-)triangle in case the convex polygon has at most three points +/*! +* @param triangle Minimum area triangle enclosing the given polygon +* @param area Area of the minimum area enclosing triangle +*/ +static void returnMinimumAreaEnclosingTriangle(std::vector &triangle, double &area) { + 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 From 9902affae6ee3f16be1958653684edb49cc2add4 Mon Sep 17 00:00:00 2001 From: Ovidiu Parvu Date: Thu, 12 Sep 2013 14:34:08 +0100 Subject: [PATCH 04/18] Added some assert statements to constrain the type of the input and output parameters. Convert the input set of points to vector before passing it to the findMinimumAreaEnclosingTriangle function. --- modules/imgproc/src/min_enclosing_triangle.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/imgproc/src/min_enclosing_triangle.cpp b/modules/imgproc/src/min_enclosing_triangle.cpp index ccbbf973b..640308515 100644 --- a/modules/imgproc/src/min_enclosing_triangle.cpp +++ b/modules/imgproc/src/min_enclosing_triangle.cpp @@ -273,6 +273,8 @@ void cv::minEnclosingTriangle(cv::InputArray points, CV_OUT cv::OutputArray triangle, CV_OUT double& area) { std::vector resultingTriangle; + CV_Assert(triangle.getMat().depth() == CV_32F); + createConvexHull(points); findMinEnclosingTriangle(resultingTriangle, area); copyResultingTriangle(resultingTriangle, triangle); @@ -288,11 +290,14 @@ void cv::minEnclosingTriangle(cv::InputArray points, */ static void createConvexHull(cv::InputArray points) { cv::Mat pointsMat = points.getMat(); + std::vector pointsVector; CV_Assert((pointsMat.checkVector(2) > 0) && ((pointsMat.depth() == CV_32F) || (pointsMat.depth() == CV_32S))); - convexHull(points, polygon, true, true); + pointsMat.convertTo(pointsVector, CV_32F); + + convexHull(pointsVector, polygon, true, true); } //! Find the minimum enclosing triangle and its area From 0ed2f6201d962e8c130c055aae8be232c3c650d6 Mon Sep 17 00:00:00 2001 From: Ovidiu Parvu Date: Thu, 12 Sep 2013 15:01:21 +0100 Subject: [PATCH 05/18] Optimised one assert statement in the min_enclosing_triangle.cpp file. Added the minEnclosingTriangle functionality to the existing minarea.cpp sample file. --- .../imgproc/src/min_enclosing_triangle.cpp | 2 +- samples/cpp/minarea.cpp | 33 +++++++++++++++---- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/modules/imgproc/src/min_enclosing_triangle.cpp b/modules/imgproc/src/min_enclosing_triangle.cpp index 640308515..3d89f473c 100644 --- a/modules/imgproc/src/min_enclosing_triangle.cpp +++ b/modules/imgproc/src/min_enclosing_triangle.cpp @@ -273,7 +273,7 @@ void cv::minEnclosingTriangle(cv::InputArray points, CV_OUT cv::OutputArray triangle, CV_OUT double& area) { std::vector resultingTriangle; - CV_Assert(triangle.getMat().depth() == CV_32F); + CV_Assert(triangle.depth() == CV_32F); createConvexHull(points); findMinEnclosingTriangle(resultingTriangle, area); diff --git a/samples/cpp/minarea.cpp b/samples/cpp/minarea.cpp index 13ac7c6d5..6837dc70d 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,39 @@ 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; + double area; + + minEnclosingTriangle(points, triangle, area); + + // 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' From 8bada4c751985d0664abfd9cbc34b34e0aa0abff Mon Sep 17 00:00:00 2001 From: Ovidiu Parvu Date: Thu, 12 Sep 2013 23:47:48 +0100 Subject: [PATCH 06/18] Overloaded the minEnclosingTriangle function such that there is an alternative function with the same name which does not require the output parameter ``area''. Changed the sample source file minarea.cpp to use the overloaded version of the function. Updated some comments in the min_enclosing_triangle.cpp source file. --- modules/imgproc/include/opencv2/imgproc.hpp | 4 ++ .../imgproc/src/min_enclosing_triangle.cpp | 47 +++++++++++++++---- samples/cpp/minarea.cpp | 3 +- 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index c09afe9f8..cb3713af8 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -1451,6 +1451,10 @@ 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 +CV_EXPORTS_W void minEnclosingTriangle( InputArray points, + CV_OUT OutputArray triangle ); + //! computes the minimal enclosing triangle for a set of points CV_EXPORTS_W void minEnclosingTriangle( InputArray points, CV_OUT OutputArray triangle, CV_OUT double& area ); diff --git a/modules/imgproc/src/min_enclosing_triangle.cpp b/modules/imgproc/src/min_enclosing_triangle.cpp index 3d89f473c..ddf175c42 100644 --- a/modules/imgproc/src/min_enclosing_triangle.cpp +++ b/modules/imgproc/src/min_enclosing_triangle.cpp @@ -170,6 +170,9 @@ static bool findGammaIntersectionPoints(unsigned int polygonPointIndex, const cv 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(std::vector &triangle, double& area); static void findMinimumAreaEnclosingTriangle(std::vector &triangle, double &area); @@ -262,15 +265,39 @@ static void updateSidesCA(); //! Find the minimum enclosing triangle and its area for the given set of points /*! -* The overall complexity of the algorithm is theta(n) where "n" represents the number -* of vertices in the convex hull of the points -* * @param points Set of points -* @param triangle Minimum area triangle enclosing the given polygon +* @param triangle Minimum area triangle enclosing the given set of points * @param area Area of the minimum area enclosing triangle */ void cv::minEnclosingTriangle(cv::InputArray points, CV_OUT cv::OutputArray triangle, CV_OUT double& area) { + findMinEnclosingTriangle(points, triangle, area); +} + +//! Find the minimum enclosing triangle and its area for the given set of points +/*! +* @param points Set of points +* @param triangle Minimum area triangle enclosing the given set of points +*/ +void cv::minEnclosingTriangle(cv::InputArray points, + CV_OUT cv::OutputArray triangle) { + double area; + + findMinEnclosingTriangle(points, triangle, area); +} + + +/////////////////////////////// Helper functions definition ////////////////////////////// + + +//! 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; CV_Assert(triangle.depth() == CV_32F); @@ -280,10 +307,6 @@ void cv::minEnclosingTriangle(cv::InputArray points, copyResultingTriangle(resultingTriangle, triangle); } - -/////////////////////////////// Helper functions definition ////////////////////////////// - - //! Create the convex hull of the given set of points /*! * @param points The provided set of points @@ -319,12 +342,20 @@ static void findMinEnclosingTriangle( std::vector &triangle, double } //! 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 return to the user +*/ static void copyResultingTriangle(const std::vector &resultingTriangle, cv::OutputArray triangle) { cv::Mat(resultingTriangle).convertTo(triangle, triangle.fixedType() ? triangle.type() : CV_32F); } //! 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) { nrOfPoints = static_cast(polygon.size()); area = std::numeric_limits::max(); diff --git a/samples/cpp/minarea.cpp b/samples/cpp/minarea.cpp index 6837dc70d..91ad5a37b 100644 --- a/samples/cpp/minarea.cpp +++ b/samples/cpp/minarea.cpp @@ -44,9 +44,8 @@ int main( int /*argc*/, char** /*argv*/ ) // Find the minimum area enclosing triangle vector triangle; - double area; - minEnclosingTriangle(points, triangle, area); + minEnclosingTriangle(points, triangle); // Find the minimum area enclosing circle Point2f center, vtx[4]; From caaa9e0f2dcd20e6ba1a45e2dcb73bc344116188 Mon Sep 17 00:00:00 2001 From: Ovidiu Parvu Date: Sun, 15 Sep 2013 15:07:17 +0100 Subject: [PATCH 07/18] - Wrote the documentation for the minEnclosingTriangle function (+1 extra picture depicting a sample output) --- .../imgproc/doc/pics/minenclosingtriangle.png | Bin 0 -> 16927 bytes ...uctural_analysis_and_shape_descriptors.rst | 39 ++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 modules/imgproc/doc/pics/minenclosingtriangle.png diff --git a/modules/imgproc/doc/pics/minenclosingtriangle.png b/modules/imgproc/doc/pics/minenclosingtriangle.png new file mode 100644 index 0000000000000000000000000000000000000000..ec89c6aa255106ed92edd39be7ebb7c76bbebf66 GIT binary patch literal 16927 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 DPuLoC literal 0 HcmV?d00001 diff --git a/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst b/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst index d34600669..c091ba3b0 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. + +.. ocv:function:: void minEnclosingTriangle( InputArray points, OutputArray triangle ) + +.. ocv:function:: void minEnclosingTriangle( InputArray points, OutputArray triangle, double &area ) + +.. ocv:pyfunction:: cv2.minEnclosingTriangle(points[, triangle]) -> triangle, area + + :param points: Input vector of 2D points with depth ``CV_32S`` or ``CV_32F``. + + :param triangle: Output vector of three 2D points defining the vertices of the triangle. The depth of the OutputArray must be ``CV_32F``. + + :param area: The area of the minimum enclosing triangle. + +The output for a given 2D point set is shown in the image below. The 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) From 0117d77cd128351a50a9330a356c2acbaf39e113 Mon Sep 17 00:00:00 2001 From: Ovidiu Parvu Date: Sun, 15 Sep 2013 22:25:58 +0100 Subject: [PATCH 08/18] Added the tests for the minEnclosingTriangle function in the existing test_convhull.cpp file. --- modules/imgproc/test/test_convhull.cpp | 157 +++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/modules/imgproc/test/test_convhull.cpp b/modules/imgproc/test/test_convhull.cpp index 49456c6cc..7e1f02bb1 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,146 @@ _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; + double area; +}; + + +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, area); + 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 +1847,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(); } From 7ac768651f29e35bfefa35f3534636485af79733 Mon Sep 17 00:00:00 2001 From: Ovidiu Parvu Date: Sun, 15 Sep 2013 22:40:57 +0100 Subject: [PATCH 09/18] Added the signature cvMinEnclosingTriangle (C version) to the imgproc_c.h header --- modules/imgproc/include/opencv2/imgproc/imgproc_c.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/imgproc/include/opencv2/imgproc/imgproc_c.h b/modules/imgproc/include/opencv2/imgproc/imgproc_c.h index 4e2dc7142..f691e3eeb 100644 --- a/modules/imgproc/include/opencv2/imgproc/imgproc_c.h +++ b/modules/imgproc/include/opencv2/imgproc/imgproc_c.h @@ -377,6 +377,10 @@ CVAPI(double) cvContourArea( const CvArr* contour, CVAPI(CvBox2D) cvMinAreaRect2( const CvArr* points, CvMemStorage* storage CV_DEFAULT(NULL)); +/* Finds minimum enclosing triangle for a set of points */ +CVAPI(int) cvMinEnclosingTriangle( const CvArr* points, + CvArr* triangle, double* area ); + /* Finds minimum enclosing circle for a set of points */ CVAPI(int) cvMinEnclosingCircle( const CvArr* points, CvPoint2D32f* center, float* radius ); From 130b4d8e26f5a8a189c4782110e6cb172479ebb4 Mon Sep 17 00:00:00 2001 From: Ovidiu Parvu Date: Sun, 15 Sep 2013 23:25:36 +0100 Subject: [PATCH 10/18] Changed the return type of cvMinEnclosingTriangle to CVAPI(void). Added the implementation of the function to the min_enclosing_triangle.cpp source file. --- .../include/opencv2/imgproc/imgproc_c.h | 2 +- .../imgproc/src/min_enclosing_triangle.cpp | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/modules/imgproc/include/opencv2/imgproc/imgproc_c.h b/modules/imgproc/include/opencv2/imgproc/imgproc_c.h index f691e3eeb..40e8b4e44 100644 --- a/modules/imgproc/include/opencv2/imgproc/imgproc_c.h +++ b/modules/imgproc/include/opencv2/imgproc/imgproc_c.h @@ -378,7 +378,7 @@ CVAPI(CvBox2D) cvMinAreaRect2( const CvArr* points, CvMemStorage* storage CV_DEFAULT(NULL)); /* Finds minimum enclosing triangle for a set of points */ -CVAPI(int) cvMinEnclosingTriangle( const CvArr* points, +CVAPI(void) cvMinEnclosingTriangle( const CvArr* points, CvArr* triangle, double* area ); /* Finds minimum enclosing circle for a set of points */ diff --git a/modules/imgproc/src/min_enclosing_triangle.cpp b/modules/imgproc/src/min_enclosing_triangle.cpp index ddf175c42..3bddf5772 100644 --- a/modules/imgproc/src/min_enclosing_triangle.cpp +++ b/modules/imgproc/src/min_enclosing_triangle.cpp @@ -1297,4 +1297,25 @@ static bool lessOrEqual(double number1, double number2) { } +////////////////////////////////////////////// C API /////////////////////////////////////////// + + +//! Find the minimum enclosing triangle and its area for the given set of points +/*! +* @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 +*/ +CV_IMPL void +cvMinEnclosingTriangle( const CvArr* _points, CvArr* _triangle, double* _area ) { + cv::Mat points = cv::cvarrToMat(_points); + cv::Mat triangle = cv::cvarrToMat(_triangle); + double area = 0; + + cv::minEnclosingTriangle(points, triangle, area); + + if (_area) + *_area = area; +} + /* End of file. */ From b570a4ac47842856c8fb91932aa5faa4b2c47801 Mon Sep 17 00:00:00 2001 From: Ovidiu Parvu Date: Sun, 15 Sep 2013 23:40:40 +0100 Subject: [PATCH 11/18] Added the C interface function signature and the overloaded minEnclosingTriangle Python function to the documentation. --- .../structural_analysis_and_shape_descriptors.rst | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst b/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst index c091ba3b0..734808486 100644 --- a/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst +++ b/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst @@ -568,13 +568,23 @@ Finds a triangle of minimum area enclosing a 2D point set. .. ocv:function:: void minEnclosingTriangle( InputArray points, OutputArray triangle, double &area ) +.. ocv:pyfunction:: cv2.minEnclosingTriangle(points[, triangle]) -> triangle + .. ocv:pyfunction:: cv2.minEnclosingTriangle(points[, triangle]) -> triangle, area - :param points: Input vector of 2D points with depth ``CV_32S`` or ``CV_32F``. +.. ocv:cfunction:: int cvMinEnclosingTriangle( const CvArr* points, CvArr* triangle, double* area ) + + :param points: Input vector of 2D points with depth ``CV_32S`` or ``CV_32F``, stored in: + + * ``std::vector<>`` or ``Mat`` (C++ interface) + + * ``CvSeq*`` or ``CvMat*`` (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``. - :param area: The area of the minimum enclosing triangle. + :param area: The area of the minimum enclosing triangle. The ``C`` interface of the function allows providing a ``NULL`` pointer for this parameter in order for the value of the area not to be returned. The output for a given 2D point set is shown in the image below. The 2D points are depicted in *red* and the enclosing triangle in *yellow*. From dc64dd731596ad5071892e551b507a3ac781ea1c Mon Sep 17 00:00:00 2001 From: Ovidiu Parvu Date: Wed, 18 Sep 2013 12:21:24 +0100 Subject: [PATCH 12/18] Made the following changes after code inspection (min_enclosing_triangle.cpp): * Corrected minor typos in comments/function signatures * Added new details to copyright statement * Removed unreferenced macros * Removed the assert statement which was checking the type of the OutputArray triangle * When returning results using Mat::copyTo instead of Mat::convertTo * Changed C-style casts to static_casts * Added division by zero check to distanceFromPointToLine() function * Updated reference webpages last access dates * Moved the declaration of the gammaOfA variable outside the while loop in moveAIfLowAndBIfHigh() function for efficiency reasons --- .../imgproc/src/min_enclosing_triangle.cpp | 79 +++++++++---------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/modules/imgproc/src/min_enclosing_triangle.cpp b/modules/imgproc/src/min_enclosing_triangle.cpp index 3bddf5772..33c4a28a3 100644 --- a/modules/imgproc/src/min_enclosing_triangle.cpp +++ b/modules/imgproc/src/min_enclosing_triangle.cpp @@ -21,9 +21,9 @@ // 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. +// 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. +// 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. @@ -35,6 +35,7 @@ // // 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, @@ -79,14 +80,11 @@ #define INTERSECTS_BELOW 1 #define INTERSECTS_ABOVE 2 #define INTERSECTS_CRITICAL 3 -#define INTERSECTS_LIMIT 4 // Error messages -#define ERR_MIDPOINT_SIDE_B "The position of the middle point of side B could not be determined." #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." -#define ERR_TRIANGLE_VERTICES "The position of the triangle vertices could not be determined, because the sides of the triangle do not intersect." // Possible values for validation flag @@ -94,7 +92,7 @@ #define VALIDATION_SIDE_B_TANGENT 1 #define VALIDATION_SIDES_FLUSH 2 -// Threshold value for geometrical comparisons +// Threshold value for comparisons #define EPSILON 1E-5 @@ -171,9 +169,9 @@ static bool findGammaIntersectionPoints(unsigned int polygonPointIndex, const cv cv::Point2f &intersectionPoint2); static void findMinEnclosingTriangle(cv::InputArray points, - CV_OUT cv::OutputArray triangle, CV_OUT double& area); + CV_OUT cv::OutputArray triangle, CV_OUT double &area); -static void findMinEnclosingTriangle(std::vector &triangle, double& area); +static void findMinEnclosingTriangle(std::vector &triangle, double &area); static void findMinimumAreaEnclosingTriangle(std::vector &triangle, double &area); @@ -270,7 +268,7 @@ static void updateSidesCA(); * @param area Area of the minimum area enclosing triangle */ void cv::minEnclosingTriangle(cv::InputArray points, - CV_OUT cv::OutputArray triangle, CV_OUT double& area) { + CV_OUT cv::OutputArray triangle, CV_OUT double &area) { findMinEnclosingTriangle(points, triangle, area); } @@ -297,11 +295,9 @@ void cv::minEnclosingTriangle(cv::InputArray 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) { + CV_OUT cv::OutputArray triangle, CV_OUT double &area) { std::vector resultingTriangle; - CV_Assert(triangle.depth() == CV_32F); - createConvexHull(points); findMinEnclosingTriangle(resultingTriangle, area); copyResultingTriangle(resultingTriangle, triangle); @@ -331,7 +327,7 @@ static void createConvexHull(cv::InputArray points) { * @param triangle Minimum area triangle enclosing the given polygon * @param area Area of the minimum area enclosing triangle */ -static void findMinEnclosingTriangle( std::vector &triangle, double& area) { +static void findMinEnclosingTriangle( std::vector &triangle, double &area) { initialise(triangle, area); if (polygon.size() > 3) { @@ -344,11 +340,11 @@ static void findMinEnclosingTriangle( std::vector &triangle, double //! 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 return to the user +* @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).convertTo(triangle, triangle.fixedType() ? triangle.type() : CV_32F); + cv::Mat(resultingTriangle).copyTo(triangle); } //! Initialisation function @@ -422,9 +418,9 @@ static void advanceBToRightChain() { * See paper [2] for more details */ static void moveAIfLowAndBIfHigh() { - while(height(b) > height(a)) { - cv::Point2f gammaOfA; + cv::Point2f gammaOfA; + while(height(b) > height(a)) { if ((gamma(a, gammaOfA)) && (intersectsBelow(gammaOfA, b))) { advance(b); } else { @@ -550,8 +546,8 @@ static bool isValidMinimalTriangle() { //! Update the current minimum area enclosing triangle if the newly obtained one has a smaller area /*! -* @param minimumAreaEnclosingTriangle Minimum area triangle enclosing the given polygon -* @param minimumAreaEnclosingTriangleArea Area of the minimum area triangle enclosing the given polygon +* @param triangle Minimum area triangle enclosing the given polygon +* @param area Area of the minimum area triangle enclosing the given polygon */ static void updateMinimumAreaEnclosingTriangle(std::vector &triangle, double &area) { triangleArea = areaOfTriangle(vertexA, vertexB, vertexC); @@ -611,7 +607,7 @@ static bool intersectsAbove(const cv::Point2f &gammaPoint, unsigned int polygonP //! Check if/where the line determined by gammaPoint and polygon[polygonPointIndex] intersects the polygon /*! -* @param angleGammaAndPoint Angle between gammaPoint and polygon[polygonPointIndex] +* @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 */ static unsigned int intersects(double angleGammaAndPoint, unsigned int polygonPointIndex) { @@ -671,7 +667,7 @@ static unsigned int intersectsAboveOrBelow(unsigned int succPredIndex, unsigned * 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 closest to point polygon[a]. +* 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 * @@ -706,7 +702,7 @@ static bool gamma(unsigned int polygonPointIndex, cv::Point2f &gammaPoint) { * @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 another pair of lines +* @param intersectionPoint2 Second intersection point between other pair of lines */ static bool findGammaIntersectionPoints(unsigned int polygonPointIndex, const cv::Point2f &side1StartVertex, const cv::Point2f &side1EndVertex, const cv::Point2f &side2StartVertex, @@ -806,7 +802,7 @@ static std::vector lineEquationParameters(const cv::Point2f& p, const cv * 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 closest to point polygon[b]. +* 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 */ @@ -1014,6 +1010,7 @@ static double oppositeAngle(double angle) { * 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 @@ -1029,7 +1026,8 @@ static double distanceFromPointToLine(const cv::Point2f &a, const cv::Point2f &l double nominator = std::abs((term1 * term2) - (term3 * term4)); double denominator = std::sqrt((term1 * term1) + (term4 * term4)); - return (nominator / denominator); + return (denominator != 0) ? (nominator / denominator) + : 0; } //! Compute the distance between two points @@ -1048,8 +1046,8 @@ static double distanceBtwPoints(const cv::Point2f &a, const cv::Point2f &b) { //! Compute the area of a triangle defined by three points /*! * The area is computed using the determinant method. -* An example is presented at http://demonstrations.wolfram.com/TheAreaOfATriangleUsingADeterminant/ -* (Last access: 10.07.2013) +* An example is depicted at http://demonstrations.wolfram.com/TheAreaOfATriangleUsingADeterminant/ +* (Last access: 15.09.2013) * * @param a Point a * @param b Point b @@ -1070,10 +1068,10 @@ static double areaOfTriangle(const cv::Point2f &a, const cv::Point2f &b, const c * @param b Point b */ static cv::Point2f middlePoint(const cv::Point2f &a, const cv::Point2f &b) { - double middleX = (double)((a.x + b.x) / 2); - double middleY = (double)((a.y + b.y) / 2); + double middleX = static_cast((a.x + b.x) / 2); + double middleY = static_cast((a.y + b.y) / 2); - return cv::Point2f((float) middleX, (float) middleY); + return cv::Point2f(static_cast(middleX), static_cast(middleY)); } //! Determine the intersection point of two lines, if this point exists @@ -1084,12 +1082,12 @@ static cv::Point2f middlePoint(const cv::Point2f &a, const cv::Point2f &b) { * A1x + B1x = C1 * A2x + B2x = C2 * -* If det (= A1xB2 - A2xB1) == 0, then lines are parallel +* 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 = (C1xB2 - C2xB1) / (det) -* y = (C2xA1 - C1xA2) / (det) +* x = (C1*B2 - C2*B1) / (det) +* y = (C2*A1 - C1*A2) / (det) * * @param a1 A1 * @param b1 B1 @@ -1104,8 +1102,8 @@ static bool lineIntersection(double a1, double b1, double c1, double a2, double double det = (a1 * b2) - (a2 * b1); if (!(almostEqual(det, 0))) { - intersection.x = (float)(((c1 * b2) - (c2 * b1)) / (det)); - intersection.y = (float)(((c2 * a1) - (c1 * a2)) / (det)); + intersection.x = static_cast(((c1 * b2) - (c2 * b1)) / (det)); + intersection.y = static_cast(((c2 * a1) - (c1 * a2)) / (det)); return true; } @@ -1124,12 +1122,12 @@ static bool lineIntersection(double a1, double b1, double c1, double a2, double * A1x + B1x = C1 * A2x + B2x = C2 * -* If det (= A1xB2 - A2xB1) == 0, then lines are parallel +* 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 = (C1xB2 - C2xB1) / (det) -* y = (C2xA1 - C1xA2) / (det) +* 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 @@ -1150,8 +1148,8 @@ static bool lineIntersection(const cv::Point2f &a1, const cv::Point2f &b1, const double det = (A1 * B2) - (A2 * B1); if (!almostEqual(det, 0)) { - intersection.x = (float)(((C1 * B2) - (C2 * B1)) / (det)); - intersection.y = (float)(((C2 * A1) - (C1 * A2)) / (det)); + intersection.x = static_cast(((C1 * B2) - (C2 * B1)) / (det)); + intersection.y = static_cast(((C2 * A1) - (C1 * A2)) / (det)); return true; } @@ -1314,8 +1312,9 @@ cvMinEnclosingTriangle( const CvArr* _points, CvArr* _triangle, double* _area ) cv::minEnclosingTriangle(points, triangle, area); - if (_area) + if (_area) { *_area = area; + } } /* End of file. */ From 737c5fe781b69ad08ec9bb0184beb4f957a8fb3e Mon Sep 17 00:00:00 2001 From: Ovidiu Parvu Date: Wed, 18 Sep 2013 13:09:55 +0100 Subject: [PATCH 13/18] Updated the return type of the cvMinEnclosingTriangle function in the documentation --- .../imgproc/doc/structural_analysis_and_shape_descriptors.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst b/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst index 734808486..e95a38cee 100644 --- a/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst +++ b/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst @@ -572,7 +572,7 @@ Finds a triangle of minimum area enclosing a 2D point set. .. ocv:pyfunction:: cv2.minEnclosingTriangle(points[, triangle]) -> triangle, area -.. ocv:cfunction:: int cvMinEnclosingTriangle( const CvArr* points, CvArr* triangle, double* area ) +.. ocv:cfunction:: void cvMinEnclosingTriangle( const CvArr* points, CvArr* triangle, double* area ) :param points: Input vector of 2D points with depth ``CV_32S`` or ``CV_32F``, stored in: From 73f476bd4b36d926c04f4760a4dfd32bd18f566b Mon Sep 17 00:00:00 2001 From: Ovidiu Parvu Date: Wed, 18 Sep 2013 15:51:10 +0100 Subject: [PATCH 14/18] Added a namespace and prefixed global variables with "G_" in order to prevent variable shadowing problems. --- .../imgproc/src/min_enclosing_triangle.cpp | 208 ++++++++++-------- 1 file changed, 113 insertions(+), 95 deletions(-) diff --git a/modules/imgproc/src/min_enclosing_triangle.cpp b/modules/imgproc/src/min_enclosing_triangle.cpp index 33c4a28a3..fdc0073a5 100644 --- a/modules/imgproc/src/min_enclosing_triangle.cpp +++ b/modules/imgproc/src/min_enclosing_triangle.cpp @@ -100,35 +100,41 @@ /////////////////////////////////// Global variables ///////////////////////////////////// -static unsigned int validationFlag; +namespace minEnclosingTriangle { -static cv::Point2f vertexA; -static cv::Point2f vertexB; -static cv::Point2f vertexC; +static unsigned int G_validationFlag; -static cv::Point2f sideAStartVertex; -static cv::Point2f sideAEndVertex; +static cv::Point2f G_vertexA; +static cv::Point2f G_vertexB; +static cv::Point2f G_vertexC; -static cv::Point2f sideBStartVertex; -static cv::Point2f sideBEndVertex; +static cv::Point2f G_sideAStartVertex; +static cv::Point2f G_sideAEndVertex; -static cv::Point2f sideCStartVertex; -static cv::Point2f sideCEndVertex; +static cv::Point2f G_sideBStartVertex; +static cv::Point2f G_sideBEndVertex; -static double triangleArea; +static cv::Point2f G_sideCStartVertex; +static cv::Point2f G_sideCEndVertex; -static unsigned int a; -static unsigned int b; -static unsigned int c; +static double G_triangleArea; -static unsigned int nrOfPoints; +static unsigned int G_a; +static unsigned int G_b; +static unsigned int G_c; -static std::vector polygon; +static unsigned int G_nrOfPoints; + +static std::vector G_polygon; + +}; ////////////////////////////// Helper functions declarations ///////////////////////////// +namespace minEnclosingTriangle { + static void advance(unsigned int &index); static void advanceBToRightChain(); @@ -257,6 +263,8 @@ static void updateSidesBA(); static void updateSidesCA(); +}; + ///////////////////////////////////// Main functions ///////////////////////////////////// @@ -269,7 +277,7 @@ static void updateSidesCA(); */ void cv::minEnclosingTriangle(cv::InputArray points, CV_OUT cv::OutputArray triangle, CV_OUT double &area) { - findMinEnclosingTriangle(points, triangle, area); + minEnclosingTriangle::findMinEnclosingTriangle(points, triangle, area); } //! Find the minimum enclosing triangle and its area for the given set of points @@ -281,13 +289,15 @@ void cv::minEnclosingTriangle(cv::InputArray points, CV_OUT cv::OutputArray triangle) { double area; - findMinEnclosingTriangle(points, triangle, area); + minEnclosingTriangle::findMinEnclosingTriangle(points, triangle, area); } /////////////////////////////// Helper functions definition ////////////////////////////// +namespace minEnclosingTriangle { + //! Find the minimum enclosing triangle and its area /*! * @param points Set of points @@ -316,7 +326,7 @@ static void createConvexHull(cv::InputArray points) { pointsMat.convertTo(pointsVector, CV_32F); - convexHull(pointsVector, polygon, true, true); + convexHull(pointsVector, G_polygon, true, true); } //! Find the minimum enclosing triangle and its area @@ -327,10 +337,10 @@ static void createConvexHull(cv::InputArray points) { * @param triangle Minimum area triangle enclosing the given polygon * @param area Area of the minimum area enclosing triangle */ -static void findMinEnclosingTriangle( std::vector &triangle, double &area) { +static void findMinEnclosingTriangle(std::vector &triangle, double &area) { initialise(triangle, area); - if (polygon.size() > 3) { + if (G_polygon.size() > 3) { findMinimumAreaEnclosingTriangle(triangle, area); } else { returnMinimumAreaEnclosingTriangle(triangle, area); @@ -353,16 +363,16 @@ static void copyResultingTriangle(const std::vector &resultingTrian * @param area Area of the minimum area enclosing triangle */ static void initialise(std::vector &triangle, double &area) { - nrOfPoints = static_cast(polygon.size()); + G_nrOfPoints = static_cast(G_polygon.size()); area = std::numeric_limits::max(); // Clear all points previously stored in the vector triangle.clear(); // Initialise the values of the indices for the algorithm - a = 1; - b = 2; - c = 0; + G_a = 1; + G_b = 2; + G_c = 0; } //! Find the minimum area enclosing triangle for the given polygon @@ -371,7 +381,7 @@ static void initialise(std::vector &triangle, double &area) { * @param area Area of the minimum area enclosing triangle */ static void findMinimumAreaEnclosingTriangle(std::vector &triangle, double &area) { - for (c = 0; c < nrOfPoints; c++) { + for (G_c = 0; G_c < G_nrOfPoints; G_c++) { advanceBToRightChain(); moveAIfLowAndBIfHigh(); searchForBTangency(); @@ -397,7 +407,7 @@ static void findMinimumAreaEnclosingTriangle(std::vector &triangle, */ static void returnMinimumAreaEnclosingTriangle(std::vector &triangle, double &area) { for (int i = 0; i < 3; i++) { - triangle.push_back(polygon[i % nrOfPoints]); + triangle.push_back(G_polygon[i % G_nrOfPoints]); } area = areaOfTriangle(triangle[0], triangle[1], triangle[2]); @@ -408,8 +418,8 @@ static void returnMinimumAreaEnclosingTriangle(std::vector &triangl * See paper [2] for more details */ static void advanceBToRightChain() { - while (greaterOrEqual(height(successor(b)), height(b))) { - advance(b); + while (greaterOrEqual(height(successor(G_b)), height(G_b))) { + advance(G_b); } } @@ -420,11 +430,11 @@ static void advanceBToRightChain() { static void moveAIfLowAndBIfHigh() { cv::Point2f gammaOfA; - while(height(b) > height(a)) { - if ((gamma(a, gammaOfA)) && (intersectsBelow(gammaOfA, b))) { - advance(b); + while(height(G_b) > height(G_a)) { + if ((gamma(G_a, gammaOfA)) && (intersectsBelow(gammaOfA, G_b))) { + advance(G_b); } else { - advance(a); + advance(G_a); } } } @@ -436,9 +446,9 @@ static void moveAIfLowAndBIfHigh() { static void searchForBTangency() { cv::Point2f gammaOfB; - while (((gamma(b, gammaOfB)) && (intersectsBelow(gammaOfB, b))) && - (greaterOrEqual(height(b), height(predecessor(a))))) { - advance(b); + while (((gamma(G_b, gammaOfB)) && (intersectsBelow(gammaOfB, G_b))) && + (greaterOrEqual(height(G_b), height(predecessor(G_a))))) { + advance(G_b); } } @@ -449,7 +459,8 @@ static void searchForBTangency() { static bool isNotBTangency() { cv::Point2f gammaOfB; - if (((gamma(b, gammaOfB)) && (intersectsAbove(gammaOfB, b))) || (height(b) < height(predecessor(a)))) { + if (((gamma(G_b, gammaOfB)) && (intersectsAbove(gammaOfB, G_b))) || + (height(G_b) < height(predecessor(G_a)))) { return true; } @@ -462,11 +473,11 @@ static bool isNotBTangency() { * Side A will have as start and end vertices the polygon points "a" and "a-1" */ static void updateSidesCA() { - sideCStartVertex = polygon[predecessor(c)]; - sideCEndVertex = polygon[c]; + G_sideCStartVertex = G_polygon[predecessor(G_c)]; + G_sideCEndVertex = G_polygon[G_c]; - sideAStartVertex = polygon[predecessor(a)]; - sideAEndVertex = polygon[a]; + G_sideAStartVertex = G_polygon[predecessor(G_a)]; + G_sideAEndVertex = G_polygon[G_a]; } //! Update sides B and possibly A if tangency for side B was not obtained @@ -475,20 +486,20 @@ static void updateSidesCA() { */ static void updateSidesBA() { // Side B is flush with edge [b, b-1] - sideBStartVertex = polygon[predecessor(b)]; - sideBEndVertex = polygon[b]; + G_sideBStartVertex = G_polygon[predecessor(G_b)]; + G_sideBEndVertex = G_polygon[G_b]; // Find middle point of side B cv::Point2f sideBMiddlePoint; if ((middlePointOfSideB(sideBMiddlePoint)) && - (height(sideBMiddlePoint) < height(predecessor(a)))) { - sideAStartVertex = polygon[predecessor(a)]; - sideAEndVertex = findVertexCOnSideB(); + (height(sideBMiddlePoint) < height(predecessor(G_a)))) { + G_sideAStartVertex = G_polygon[predecessor(G_a)]; + G_sideAEndVertex = findVertexCOnSideB(); - validationFlag = VALIDATION_SIDE_A_TANGENT; + G_validationFlag = VALIDATION_SIDE_A_TANGENT; } else { - validationFlag = VALIDATION_SIDES_FLUSH; + G_validationFlag = VALIDATION_SIDES_FLUSH; } } @@ -497,13 +508,13 @@ static void updateSidesBA() { * See paper [2] for more details */ static void updateSideB() { - if (!gamma(b, sideBStartVertex)) { + if (!gamma(G_b, G_sideBStartVertex)) { CV_Error(cv::Error::StsInternal, ERR_SIDE_B_GAMMA); } - sideBEndVertex = polygon[b]; + G_sideBEndVertex = G_polygon[G_b]; - validationFlag = VALIDATION_SIDE_B_TANGENT; + G_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 @@ -511,9 +522,12 @@ static void updateSideB() { * See paper [2] for more details */ static bool isLocalMinimalTriangle() { - if ((!lineIntersection(sideAStartVertex, sideAEndVertex, sideBStartVertex, sideBEndVertex, vertexC)) || - (!lineIntersection(sideAStartVertex, sideAEndVertex, sideCStartVertex, sideCEndVertex, vertexB)) || - (!lineIntersection(sideBStartVertex, sideBEndVertex, sideCStartVertex, sideCEndVertex, vertexA))) { + if ((!lineIntersection(G_sideAStartVertex, G_sideAEndVertex, + G_sideBStartVertex, G_sideBEndVertex, G_vertexC)) || + (!lineIntersection(G_sideAStartVertex, G_sideAEndVertex, + G_sideCStartVertex, G_sideCEndVertex, G_vertexB)) || + (!lineIntersection(G_sideBStartVertex, G_sideBEndVertex, + G_sideCStartVertex, G_sideCEndVertex, G_vertexA))) { return false; } @@ -527,19 +541,19 @@ static bool isLocalMinimalTriangle() { * See paper [2] for more details */ static bool isValidMinimalTriangle() { - cv::Point2f midpointSideA = middlePoint(vertexB, vertexC); - cv::Point2f midpointSideB = middlePoint(vertexA, vertexC); - cv::Point2f midpointSideC = middlePoint(vertexA, vertexB); + cv::Point2f midpointSideA = middlePoint(G_vertexB, G_vertexC); + cv::Point2f midpointSideB = middlePoint(G_vertexA, G_vertexC); + cv::Point2f midpointSideC = middlePoint(G_vertexA, G_vertexB); - bool sideAValid = (validationFlag == VALIDATION_SIDE_A_TANGENT) - ? (areEqualPoints(midpointSideA, polygon[predecessor(a)])) - : (isPointOnLineSegment(midpointSideA, sideAStartVertex, sideAEndVertex)); + bool sideAValid = (G_validationFlag == VALIDATION_SIDE_A_TANGENT) + ? (areEqualPoints(midpointSideA, G_polygon[predecessor(G_a)])) + : (isPointOnLineSegment(midpointSideA, G_sideAStartVertex, G_sideAEndVertex)); - bool sideBValid = (validationFlag == VALIDATION_SIDE_B_TANGENT) - ? (areEqualPoints(midpointSideB, polygon[b])) - : (isPointOnLineSegment(midpointSideB, sideBStartVertex, sideBEndVertex)); + bool sideBValid = (G_validationFlag == VALIDATION_SIDE_B_TANGENT) + ? (areEqualPoints(midpointSideB, G_polygon[G_b])) + : (isPointOnLineSegment(midpointSideB, G_sideBStartVertex, G_sideBEndVertex)); - bool sideCValid = isPointOnLineSegment(midpointSideC, sideCStartVertex, sideCEndVertex); + bool sideCValid = isPointOnLineSegment(midpointSideC, G_sideCStartVertex, G_sideCEndVertex); return (sideAValid && sideBValid && sideCValid); } @@ -550,16 +564,16 @@ static bool isValidMinimalTriangle() { * @param area Area of the minimum area triangle enclosing the given polygon */ static void updateMinimumAreaEnclosingTriangle(std::vector &triangle, double &area) { - triangleArea = areaOfTriangle(vertexA, vertexB, vertexC); + G_triangleArea = areaOfTriangle(G_vertexA, G_vertexB, G_vertexC); - if (triangleArea < area) { + if (G_triangleArea < area) { triangle.clear(); - triangle.push_back(vertexA); - triangle.push_back(vertexB); - triangle.push_back(vertexC); + triangle.push_back(G_vertexA); + triangle.push_back(G_vertexB); + triangle.push_back(G_vertexC); - area = triangleArea; + area = G_triangleArea; } } @@ -567,8 +581,8 @@ static void updateMinimumAreaEnclosingTriangle(std::vector &triangl static bool middlePointOfSideB(cv::Point2f& middlePointOfSideB) { cv::Point2f vertexA, vertexC; - if ((!lineIntersection(sideBStartVertex, sideBEndVertex, sideCStartVertex, sideCEndVertex, vertexA)) || - (!lineIntersection(sideBStartVertex, sideBEndVertex, sideAStartVertex, sideAEndVertex, vertexC))) { + if ((!lineIntersection(G_sideBStartVertex, G_sideBEndVertex, G_sideCStartVertex, G_sideCEndVertex, vertexA)) || + (!lineIntersection(G_sideBStartVertex, G_sideBEndVertex, G_sideAStartVertex, G_sideAEndVertex, vertexC))) { return false; } @@ -586,7 +600,7 @@ static bool middlePointOfSideB(cv::Point2f& middlePointOfSideB) { * @param polygonPointIndex Index of the polygon point which is considered when determining the line */ static bool intersectsBelow(const cv::Point2f &gammaPoint, unsigned int polygonPointIndex) { - double angleOfGammaAndPoint = angleOfLineWrtOxAxis(polygon[polygonPointIndex], gammaPoint); + double angleOfGammaAndPoint = angleOfLineWrtOxAxis(G_polygon[polygonPointIndex], gammaPoint); return (intersects(angleOfGammaAndPoint, polygonPointIndex) == INTERSECTS_BELOW); } @@ -600,7 +614,7 @@ static bool intersectsBelow(const cv::Point2f &gammaPoint, unsigned int polygonP * @param polygonPointIndex Index of the polygon point which is considered when determining the line */ static bool intersectsAbove(const cv::Point2f &gammaPoint, unsigned int polygonPointIndex) { - double angleOfGammaAndPoint = angleOfLineWrtOxAxis(gammaPoint, polygon[polygonPointIndex]); + double angleOfGammaAndPoint = angleOfLineWrtOxAxis(gammaPoint, G_polygon[polygonPointIndex]); return (intersects(angleOfGammaAndPoint, polygonPointIndex) == INTERSECTS_ABOVE); } @@ -611,12 +625,12 @@ static bool intersectsAbove(const cv::Point2f &gammaPoint, unsigned int polygonP * @param polygonPointIndex Index of the polygon point which is considered when determining the line */ static unsigned int intersects(double angleGammaAndPoint, unsigned int polygonPointIndex) { - double anglePointPredecessor = angleOfLineWrtOxAxis(polygon[predecessor(polygonPointIndex)], - polygon[polygonPointIndex]); - double anglePointSuccessor = angleOfLineWrtOxAxis(polygon[successor(polygonPointIndex)], - polygon[polygonPointIndex]); - double angleFlushEdge = angleOfLineWrtOxAxis(polygon[predecessor(c)], - polygon[c]); + double anglePointPredecessor = angleOfLineWrtOxAxis(G_polygon[predecessor(polygonPointIndex)], + G_polygon[polygonPointIndex]); + double anglePointSuccessor = angleOfLineWrtOxAxis(G_polygon[successor(polygonPointIndex)], + G_polygon[polygonPointIndex]); + double angleFlushEdge = angleOfLineWrtOxAxis(G_polygon[predecessor(G_c)], + G_polygon[G_c]); if (isFlushAngleBtwPredAndSucc(angleFlushEdge, anglePointPredecessor, anglePointSuccessor)) { if ((isGammaAngleBtw(angleGammaAndPoint, anglePointPredecessor, angleFlushEdge)) || @@ -678,14 +692,15 @@ static bool gamma(unsigned int polygonPointIndex, cv::Point2f &gammaPoint) { cv::Point2f intersectionPoint1, intersectionPoint2; // Get intersection points if they exist - if (!findGammaIntersectionPoints(polygonPointIndex, polygon[a], polygon[predecessor(a)], polygon[c], - polygon[predecessor(c)], intersectionPoint1, intersectionPoint2)) { + if (!findGammaIntersectionPoints(polygonPointIndex, G_polygon[G_a], G_polygon[predecessor(G_a)], + G_polygon[G_c], G_polygon[predecessor(G_c)], + 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)], - polygon[c], polygon[predecessor(c)])) { + if (areOnTheSameSideOfLine(intersectionPoint1, G_polygon[successor(G_c)], + G_polygon[G_c], G_polygon[predecessor(G_c)])) { gammaPoint = intersectionPoint1; } else { gammaPoint = intersectionPoint2; @@ -810,14 +825,15 @@ static cv::Point2f findVertexCOnSideB() { cv::Point2f intersectionPoint1, intersectionPoint2; // Get intersection points if they exist - if (!findGammaIntersectionPoints(predecessor(a), sideBStartVertex, sideBEndVertex, sideCStartVertex, - sideCEndVertex, intersectionPoint1, intersectionPoint2)) { + if (!findGammaIntersectionPoints(predecessor(G_a), G_sideBStartVertex, G_sideBEndVertex, + G_sideCStartVertex, G_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)], - polygon[c], polygon[predecessor(c)])) { + if (areOnTheSameSideOfLine(intersectionPoint1, G_polygon[successor(G_c)], + G_polygon[G_c], G_polygon[predecessor(G_c)])) { return intersectionPoint1; } else { return intersectionPoint2; @@ -831,8 +847,8 @@ static cv::Point2f findVertexCOnSideB() { * @param polygonPoint Polygon point */ static double height(const cv::Point2f &polygonPoint) { - cv::Point2f pointC = polygon[c]; - cv::Point2f pointCPredecessor = polygon[predecessor(c)]; + cv::Point2f pointC = G_polygon[G_c]; + cv::Point2f pointCPredecessor = G_polygon[predecessor(G_c)]; return distanceFromPointToLine(polygonPoint, pointC, pointCPredecessor); } @@ -844,10 +860,10 @@ static double height(const cv::Point2f &polygonPoint) { * @param polygonPointIndex Index of the polygon point */ static double height(unsigned int polygonPointIndex) { - cv::Point2f pointC = polygon[c]; - cv::Point2f pointCPredecessor = polygon[predecessor(c)]; + cv::Point2f pointC = G_polygon[G_c]; + cv::Point2f pointCPredecessor = G_polygon[predecessor(G_c)]; - cv::Point2f polygonPoint = polygon[polygonPointIndex]; + cv::Point2f polygonPoint = G_polygon[polygonPointIndex]; return distanceFromPointToLine(polygonPoint, pointC, pointCPredecessor); } @@ -868,7 +884,7 @@ static void advance(unsigned int &index) { * @param index Index of the point */ static unsigned int successor(unsigned int index) { - return ((index + 1) % nrOfPoints); + return ((index + 1) % G_nrOfPoints); } //! Return the predecessor of the provided point index @@ -879,7 +895,7 @@ static unsigned int successor(unsigned int index) { * @param index Index of the point */ static unsigned int predecessor(unsigned int index) { - return (index == 0) ? (nrOfPoints - 1) + return (index == 0) ? (G_nrOfPoints - 1) : (index - 1); } @@ -1294,6 +1310,8 @@ static bool lessOrEqual(double number1, double number2) { return ((number1 < number2) || (almostEqual(number1, number2))); } +}; + ////////////////////////////////////////////// C API /////////////////////////////////////////// From 21390d806ff0071e7ccdb752219035537b527003 Mon Sep 17 00:00:00 2001 From: Ovidiu Parvu Date: Thu, 19 Sep 2013 15:12:07 +0100 Subject: [PATCH 15/18] Removed non-ASCII characters from the comments --- modules/imgproc/src/min_enclosing_triangle.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/imgproc/src/min_enclosing_triangle.cpp b/modules/imgproc/src/min_enclosing_triangle.cpp index fdc0073a5..0c9d23bb3 100644 --- a/modules/imgproc/src/min_enclosing_triangle.cpp +++ b/modules/imgproc/src/min_enclosing_triangle.cpp @@ -20,10 +20,10 @@ // // 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. +// [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. From 52cdae6e2bf10f5781d5a86a5f3d1154ae794ffc Mon Sep 17 00:00:00 2001 From: Ovidiu Parvu Date: Thu, 19 Sep 2013 15:37:10 +0100 Subject: [PATCH 16/18] Removed the cvMinEnclosingTriangle function since the C API will be deprecated starting with OpenCV 3.0 --- ...uctural_analysis_and_shape_descriptors.rst | 10 +++---- .../include/opencv2/imgproc/imgproc_c.h | 4 --- .../imgproc/src/min_enclosing_triangle.cpp | 27 +------------------ 3 files changed, 4 insertions(+), 37 deletions(-) diff --git a/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst b/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst index e95a38cee..7dc3ef0b2 100644 --- a/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst +++ b/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst @@ -572,19 +572,15 @@ Finds a triangle of minimum area enclosing a 2D point set. .. ocv:pyfunction:: cv2.minEnclosingTriangle(points[, triangle]) -> triangle, area -.. ocv:cfunction:: void cvMinEnclosingTriangle( const CvArr* points, CvArr* triangle, double* area ) - :param points: Input vector of 2D points with depth ``CV_32S`` or ``CV_32F``, stored in: * ``std::vector<>`` or ``Mat`` (C++ interface) - * ``CvSeq*`` or ``CvMat*`` (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``. - :param area: The area of the minimum enclosing triangle. The ``C`` interface of the function allows providing a ``NULL`` pointer for this parameter in order for the value of the area not to be returned. + :param area: The area of the minimum enclosing triangle. The output for a given 2D point set is shown in the image below. The 2D points are depicted in *red* and the enclosing triangle in *yellow*. @@ -717,9 +713,9 @@ 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) +.. [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) +.. [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) diff --git a/modules/imgproc/include/opencv2/imgproc/imgproc_c.h b/modules/imgproc/include/opencv2/imgproc/imgproc_c.h index 40e8b4e44..4e2dc7142 100644 --- a/modules/imgproc/include/opencv2/imgproc/imgproc_c.h +++ b/modules/imgproc/include/opencv2/imgproc/imgproc_c.h @@ -377,10 +377,6 @@ CVAPI(double) cvContourArea( const CvArr* contour, CVAPI(CvBox2D) cvMinAreaRect2( const CvArr* points, CvMemStorage* storage CV_DEFAULT(NULL)); -/* Finds minimum enclosing triangle for a set of points */ -CVAPI(void) cvMinEnclosingTriangle( const CvArr* points, - CvArr* triangle, double* area ); - /* Finds minimum enclosing circle for a set of points */ CVAPI(int) cvMinEnclosingCircle( const CvArr* points, CvPoint2D32f* center, float* radius ); diff --git a/modules/imgproc/src/min_enclosing_triangle.cpp b/modules/imgproc/src/min_enclosing_triangle.cpp index 0c9d23bb3..a952fe46e 100644 --- a/modules/imgproc/src/min_enclosing_triangle.cpp +++ b/modules/imgproc/src/min_enclosing_triangle.cpp @@ -1310,29 +1310,4 @@ static bool lessOrEqual(double number1, double number2) { return ((number1 < number2) || (almostEqual(number1, number2))); } -}; - - -////////////////////////////////////////////// C API /////////////////////////////////////////// - - -//! Find the minimum enclosing triangle and its area for the given set of points -/*! -* @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 -*/ -CV_IMPL void -cvMinEnclosingTriangle( const CvArr* _points, CvArr* _triangle, double* _area ) { - cv::Mat points = cv::cvarrToMat(_points); - cv::Mat triangle = cv::cvarrToMat(_triangle); - double area = 0; - - cv::minEnclosingTriangle(points, triangle, area); - - if (_area) { - *_area = area; - } -} - -/* End of file. */ +}; \ No newline at end of file From e324446c70e119b6f31955342cfc7bf5f03b2db8 Mon Sep 17 00:00:00 2001 From: Ovidiu Parvu Date: Thu, 19 Sep 2013 16:22:30 +0100 Subject: [PATCH 17/18] Changed the signature of the minEnclosingTriangle function such that it returns the area automatically. Moreover, the overloaded function was no longer required so it was removed. Sample code, documentation and unit tests were updated correspondingly. --- ...ructural_analysis_and_shape_descriptors.rst | 14 ++++---------- modules/imgproc/include/opencv2/imgproc.hpp | 9 ++------- modules/imgproc/src/min_enclosing_triangle.cpp | 18 ++++-------------- modules/imgproc/test/test_convhull.cpp | 3 +-- 4 files changed, 11 insertions(+), 33 deletions(-) diff --git a/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst b/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst index 7dc3ef0b2..094424178 100644 --- a/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst +++ b/modules/imgproc/doc/structural_analysis_and_shape_descriptors.rst @@ -562,15 +562,11 @@ 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. +Finds a triangle of minimum area enclosing a 2D point set and returns its area. -.. ocv:function:: void minEnclosingTriangle( InputArray points, OutputArray triangle ) +.. ocv:function:: double minEnclosingTriangle( InputArray points, OutputArray triangle ) -.. ocv:function:: void minEnclosingTriangle( InputArray points, OutputArray triangle, double &area ) - -.. ocv:pyfunction:: cv2.minEnclosingTriangle(points[, triangle]) -> triangle - -.. ocv:pyfunction:: cv2.minEnclosingTriangle(points[, triangle]) -> triangle, area +.. ocv:pyfunction:: cv2.minEnclosingTriangle(points[, triangle]) -> retval, triangle :param points: Input vector of 2D points with depth ``CV_32S`` or ``CV_32F``, stored in: @@ -580,9 +576,7 @@ Finds a triangle of minimum area enclosing a 2D point set. :param triangle: Output vector of three 2D points defining the vertices of the triangle. The depth of the OutputArray must be ``CV_32F``. - :param area: The area of the minimum enclosing triangle. - -The output for a given 2D point set is shown in the image below. The 2D points are depicted in *red* and the enclosing triangle in *yellow*. +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 diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index cb3713af8..cc64174cf 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -1451,13 +1451,8 @@ 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 -CV_EXPORTS_W void minEnclosingTriangle( InputArray points, - CV_OUT OutputArray triangle ); - -//! computes the minimal enclosing triangle for a set of points -CV_EXPORTS_W void minEnclosingTriangle( InputArray points, - CV_OUT OutputArray triangle, CV_OUT double& area ); +//! 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, diff --git a/modules/imgproc/src/min_enclosing_triangle.cpp b/modules/imgproc/src/min_enclosing_triangle.cpp index a952fe46e..df2d75490 100644 --- a/modules/imgproc/src/min_enclosing_triangle.cpp +++ b/modules/imgproc/src/min_enclosing_triangle.cpp @@ -269,27 +269,17 @@ static void updateSidesCA(); ///////////////////////////////////// Main functions ///////////////////////////////////// -//! Find the minimum enclosing triangle and its area for the given set of points -/*! -* @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 -*/ -void cv::minEnclosingTriangle(cv::InputArray points, - CV_OUT cv::OutputArray triangle, CV_OUT double &area) { - minEnclosingTriangle::findMinEnclosingTriangle(points, triangle, area); -} - -//! Find the minimum enclosing triangle and its area for the given set of points +//! 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 */ -void cv::minEnclosingTriangle(cv::InputArray points, - CV_OUT cv::OutputArray triangle) { +double cv::minEnclosingTriangle(cv::InputArray points, CV_OUT cv::OutputArray triangle) { double area; minEnclosingTriangle::findMinEnclosingTriangle(points, triangle, area); + + return area; } diff --git a/modules/imgproc/test/test_convhull.cpp b/modules/imgproc/test/test_convhull.cpp index 7e1f02bb1..9e4cd07c4 100644 --- a/modules/imgproc/test/test_convhull.cpp +++ b/modules/imgproc/test/test_convhull.cpp @@ -801,7 +801,6 @@ protected: std::vector convexPolygon; std::vector triangle; - double area; }; @@ -827,7 +826,7 @@ void CV_MinTriangleTest::run_func() cv::cvarrToMat(points).convertTo(pointsAsVector, CV_32F); - cv::minEnclosingTriangle(pointsAsVector, triangle, area); + cv::minEnclosingTriangle(pointsAsVector, triangle); cv::convexHull(pointsAsVector, convexPolygon, true, true); } From 2bf36c312f89b9696feea5b8f63eb8785802cfd2 Mon Sep 17 00:00:00 2001 From: Ovidiu Parvu Date: Thu, 19 Sep 2013 18:32:03 +0100 Subject: [PATCH 18/18] Removed static variables which were not read-only and used more function parameters instead. --- .../imgproc/src/min_enclosing_triangle.cpp | 742 ++++++++++++------ 1 file changed, 501 insertions(+), 241 deletions(-) diff --git a/modules/imgproc/src/min_enclosing_triangle.cpp b/modules/imgproc/src/min_enclosing_triangle.cpp index df2d75490..98bfd46e5 100644 --- a/modules/imgproc/src/min_enclosing_triangle.cpp +++ b/modules/imgproc/src/min_enclosing_triangle.cpp @@ -97,47 +97,16 @@ #define EPSILON 1E-5 -/////////////////////////////////// Global variables ///////////////////////////////////// - - -namespace minEnclosingTriangle { - -static unsigned int G_validationFlag; - -static cv::Point2f G_vertexA; -static cv::Point2f G_vertexB; -static cv::Point2f G_vertexC; - -static cv::Point2f G_sideAStartVertex; -static cv::Point2f G_sideAEndVertex; - -static cv::Point2f G_sideBStartVertex; -static cv::Point2f G_sideBEndVertex; - -static cv::Point2f G_sideCStartVertex; -static cv::Point2f G_sideCEndVertex; - -static double G_triangleArea; - -static unsigned int G_a; -static unsigned int G_b; -static unsigned int G_c; - -static unsigned int G_nrOfPoints; - -static std::vector G_polygon; - -}; - - ////////////////////////////// Helper functions declarations ///////////////////////////// namespace minEnclosingTriangle { -static void advance(unsigned int &index); +static void advance(unsigned int &index, unsigned int nrOfPoints); -static void advanceBToRightChain(); +static void advanceBToRightChain(const std::vector &polygon, + unsigned int nrOfPoints, unsigned int &b, + unsigned int c); static bool almostEqual(double number1, double number2); @@ -162,44 +131,64 @@ static double areaOfTriangle(const cv::Point2f &a, const cv::Point2f &b, const c static void copyResultingTriangle(const std::vector &resultingTriangle, cv::OutputArray triangle); -static void createConvexHull(cv::InputArray points); +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(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 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(std::vector &triangle, double &area); +static void findMinEnclosingTriangle(const std::vector &polygon, + std::vector &triangle, double &area); -static void findMinimumAreaEnclosingTriangle(std::vector &triangle, double &area); +static void findMinimumAreaEnclosingTriangle(const std::vector &polygon, + std::vector &triangle, double &area); -static cv::Point2f findVertexCOnSideB(); +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); +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); +static double height(const cv::Point2f &polygonPoint, const std::vector &polygon, + unsigned int nrOfPoints, unsigned int c); -static double height(unsigned int polygonPointIndex); +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); +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); +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); +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); +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); @@ -211,16 +200,30 @@ static bool isGammaAngleBtw(double &gammaAngle, double angle1, double angle2); static bool isGammaAngleEqualTo(double &gammaAngle, double angle); -static bool isLocalMinimalTriangle(); +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(); +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(); +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); @@ -239,29 +242,50 @@ 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); +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(); +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); +static unsigned int predecessor(unsigned int index, unsigned int nrOfPoints); -static void returnMinimumAreaEnclosingTriangle(std::vector &triangle, double &area); +static void returnMinimumAreaEnclosingTriangle(const std::vector &polygon, + std::vector &triangle, double &area); -static void searchForBTangency(); +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); +static unsigned int successor(unsigned int index, unsigned int nrOfPoints); -static void updateMinimumAreaEnclosingTriangle(std::vector &triangle, double &area); +static void updateMinimumAreaEnclosingTriangle(std::vector &triangle, double &area, + const cv::Point2f &vertexA, const cv::Point2f &vertexB, + const cv::Point2f &vertexC); -static void updateSideB(); +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(); +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(); +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); }; @@ -296,18 +320,19 @@ namespace minEnclosingTriangle { */ static void findMinEnclosingTriangle(cv::InputArray points, CV_OUT cv::OutputArray triangle, CV_OUT double &area) { - std::vector resultingTriangle; + std::vector resultingTriangle, polygon; - createConvexHull(points); - findMinEnclosingTriangle(resultingTriangle, area); + 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 points The provided set of points +* @param polygon The polygon representing the convex hull of the points */ -static void createConvexHull(cv::InputArray points) { +static void createConvexHull(cv::InputArray points, std::vector &polygon) { cv::Mat pointsMat = points.getMat(); std::vector pointsVector; @@ -316,7 +341,7 @@ static void createConvexHull(cv::InputArray points) { pointsMat.convertTo(pointsVector, CV_32F); - convexHull(pointsVector, G_polygon, true, true); + convexHull(pointsVector, polygon, true, true); } //! Find the minimum enclosing triangle and its area @@ -324,16 +349,18 @@ static void createConvexHull(cv::InputArray points) { * The overall complexity of the algorithm is theta(n) where "n" represents the number * of vertices in the convex polygon * -* @param triangle Minimum area triangle enclosing the given polygon -* @param area Area of the minimum area enclosing triangle +* @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(std::vector &triangle, double &area) { +static void findMinEnclosingTriangle(const std::vector &polygon, + std::vector &triangle, double &area) { initialise(triangle, area); - if (G_polygon.size() > 3) { - findMinimumAreaEnclosingTriangle(triangle, area); + if (polygon.size() > 3) { + findMinimumAreaEnclosingTriangle(polygon, triangle, area); } else { - returnMinimumAreaEnclosingTriangle(triangle, area); + returnMinimumAreaEnclosingTriangle(polygon, triangle, area); } } @@ -353,51 +380,81 @@ static void copyResultingTriangle(const std::vector &resultingTrian * @param area Area of the minimum area enclosing triangle */ static void initialise(std::vector &triangle, double &area) { - G_nrOfPoints = static_cast(G_polygon.size()); area = std::numeric_limits::max(); // Clear all points previously stored in the vector triangle.clear(); - - // Initialise the values of the indices for the algorithm - G_a = 1; - G_b = 2; - G_c = 0; } //! 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(std::vector &triangle, double &area) { - for (G_c = 0; G_c < G_nrOfPoints; G_c++) { - advanceBToRightChain(); - moveAIfLowAndBIfHigh(); - searchForBTangency(); +static void findMinimumAreaEnclosingTriangle(const std::vector &polygon, + std::vector &triangle, double &area) { + // Algorithm specific variables - updateSidesCA(); + unsigned int validationFlag; - if (isNotBTangency()) { - updateSidesBA(); + 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(); + updateSideB(polygon, nrOfPoints, a, b, c, validationFlag, + sideBStartVertex, sideBEndVertex); } - if (isLocalMinimalTriangle()) { - updateMinimumAreaEnclosingTriangle(triangle, area); + 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(std::vector &triangle, double &area) { +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(G_polygon[i % G_nrOfPoints]); + triangle.push_back(polygon[i % nrOfPoints]); } area = areaOfTriangle(triangle[0], triangle[1], triangle[2]); @@ -406,25 +463,41 @@ static void returnMinimumAreaEnclosingTriangle(std::vector &triangl //! 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() { - while (greaterOrEqual(height(successor(G_b)), height(G_b))) { - advance(G_b); +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() { +static void moveAIfLowAndBIfHigh(const std::vector &polygon, + unsigned int nrOfPoints, unsigned int &a, unsigned int &b, + unsigned int c) { cv::Point2f gammaOfA; - while(height(G_b) > height(G_a)) { - if ((gamma(G_a, gammaOfA)) && (intersectsBelow(gammaOfA, G_b))) { - advance(G_b); + 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(G_a); + advance(a, nrOfPoints); } } } @@ -432,25 +505,45 @@ static void moveAIfLowAndBIfHigh() { //! 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() { +static void searchForBTangency(const std::vector &polygon, + unsigned int nrOfPoints, unsigned int a, unsigned int &b, + unsigned int c) { cv::Point2f gammaOfB; - while (((gamma(G_b, gammaOfB)) && (intersectsBelow(gammaOfB, G_b))) && - (greaterOrEqual(height(G_b), height(predecessor(G_a))))) { - advance(G_b); + 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() { +static bool isNotBTangency(const std::vector &polygon, + unsigned int nrOfPoints, unsigned int a, unsigned int b, + unsigned int c) { cv::Point2f gammaOfB; - if (((gamma(G_b, gammaOfB)) && (intersectsAbove(gammaOfB, G_b))) || - (height(G_b) < height(predecessor(G_a)))) { + 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; } @@ -461,67 +554,137 @@ static bool isNotBTangency() { /*! * 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() { - G_sideCStartVertex = G_polygon[predecessor(G_c)]; - G_sideCEndVertex = G_polygon[G_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]; - G_sideAStartVertex = G_polygon[predecessor(G_a)]; - G_sideAEndVertex = G_polygon[G_a]; + 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() { +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] - G_sideBStartVertex = G_polygon[predecessor(G_b)]; - G_sideBEndVertex = G_polygon[G_b]; + sideBStartVertex = polygon[predecessor(b, nrOfPoints)]; + sideBEndVertex = polygon[b]; // Find middle point of side B cv::Point2f sideBMiddlePoint; - if ((middlePointOfSideB(sideBMiddlePoint)) && - (height(sideBMiddlePoint) < height(predecessor(G_a)))) { - G_sideAStartVertex = G_polygon[predecessor(G_a)]; - G_sideAEndVertex = findVertexCOnSideB(); + 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); - G_validationFlag = VALIDATION_SIDE_A_TANGENT; + validationFlag = VALIDATION_SIDE_A_TANGENT; } else { - G_validationFlag = VALIDATION_SIDES_FLUSH; + 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() { - if (!gamma(G_b, G_sideBStartVertex)) { +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); } - G_sideBEndVertex = G_polygon[G_b]; + sideBEndVertex = polygon[b]; - G_validationFlag = VALIDATION_SIDE_B_TANGENT; + 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() { - if ((!lineIntersection(G_sideAStartVertex, G_sideAEndVertex, - G_sideBStartVertex, G_sideBEndVertex, G_vertexC)) || - (!lineIntersection(G_sideAStartVertex, G_sideAEndVertex, - G_sideCStartVertex, G_sideCEndVertex, G_vertexB)) || - (!lineIntersection(G_sideBStartVertex, G_sideBEndVertex, - G_sideCStartVertex, G_sideCEndVertex, G_vertexA))) { +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(); + return isValidMinimalTriangle(vertexA, vertexB, vertexC, polygon, nrOfPoints, a, b, + validationFlag, sideAStartVertex, sideAEndVertex, + sideBStartVertex, sideBEndVertex, sideCStartVertex, + sideCEndVertex); } //! Check if the found minimal triangle is valid @@ -529,21 +692,42 @@ static bool isLocalMinimalTriangle() { * 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() { - cv::Point2f midpointSideA = middlePoint(G_vertexB, G_vertexC); - cv::Point2f midpointSideB = middlePoint(G_vertexA, G_vertexC); - cv::Point2f midpointSideC = middlePoint(G_vertexA, G_vertexB); +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 = (G_validationFlag == VALIDATION_SIDE_A_TANGENT) - ? (areEqualPoints(midpointSideA, G_polygon[predecessor(G_a)])) - : (isPointOnLineSegment(midpointSideA, G_sideAStartVertex, G_sideAEndVertex)); + bool sideAValid = (validationFlag == VALIDATION_SIDE_A_TANGENT) + ? (areEqualPoints(midpointSideA, polygon[predecessor(a, nrOfPoints)])) + : (isPointOnLineSegment(midpointSideA, sideAStartVertex, sideAEndVertex)); - bool sideBValid = (G_validationFlag == VALIDATION_SIDE_B_TANGENT) - ? (areEqualPoints(midpointSideB, G_polygon[G_b])) - : (isPointOnLineSegment(midpointSideB, G_sideBStartVertex, G_sideBEndVertex)); + bool sideBValid = (validationFlag == VALIDATION_SIDE_B_TANGENT) + ? (areEqualPoints(midpointSideB, polygon[b])) + : (isPointOnLineSegment(midpointSideB, sideBStartVertex, sideBEndVertex)); - bool sideCValid = isPointOnLineSegment(midpointSideC, G_sideCStartVertex, G_sideCEndVertex); + bool sideCValid = isPointOnLineSegment(midpointSideC, sideCStartVertex, sideCEndVertex); return (sideAValid && sideBValid && sideCValid); } @@ -552,27 +736,44 @@ static bool isValidMinimalTriangle() { /*! * @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) { - G_triangleArea = areaOfTriangle(G_vertexA, G_vertexB, G_vertexC); +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 (G_triangleArea < area) { + if (triangleArea < area) { triangle.clear(); - triangle.push_back(G_vertexA); - triangle.push_back(G_vertexB); - triangle.push_back(G_vertexC); + triangle.push_back(vertexA); + triangle.push_back(vertexB); + triangle.push_back(vertexC); - area = G_triangleArea; + area = triangleArea; } } //! Return the middle point of side B -static bool middlePointOfSideB(cv::Point2f& middlePointOfSideB) { +/*! +* @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(G_sideBStartVertex, G_sideBEndVertex, G_sideCStartVertex, G_sideCEndVertex, vertexA)) || - (!lineIntersection(G_sideBStartVertex, G_sideBEndVertex, G_sideAStartVertex, G_sideAEndVertex, vertexC))) { + if ((!lineIntersection(sideBStartVertex, sideBEndVertex, sideCStartVertex, sideCEndVertex, vertexA)) || + (!lineIntersection(sideBStartVertex, sideBEndVertex, sideAStartVertex, sideAEndVertex, vertexC))) { return false; } @@ -586,13 +787,18 @@ static bool middlePointOfSideB(cv::Point2f& middlePointOfSideB) { * 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 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) { - double angleOfGammaAndPoint = angleOfLineWrtOxAxis(G_polygon[polygonPointIndex], gammaPoint); +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) == INTERSECTS_BELOW); + return (intersects(angleOfGammaAndPoint, polygonPointIndex, polygon, nrOfPoints, c) == INTERSECTS_BELOW); } //! Check if the line intersects above @@ -600,35 +806,47 @@ static bool intersectsBelow(const cv::Point2f &gammaPoint, unsigned int polygonP * 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 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) { - double angleOfGammaAndPoint = angleOfLineWrtOxAxis(gammaPoint, G_polygon[polygonPointIndex]); +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) == INTERSECTS_ABOVE); + 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) { - double anglePointPredecessor = angleOfLineWrtOxAxis(G_polygon[predecessor(polygonPointIndex)], - G_polygon[polygonPointIndex]); - double anglePointSuccessor = angleOfLineWrtOxAxis(G_polygon[successor(polygonPointIndex)], - G_polygon[polygonPointIndex]); - double angleFlushEdge = angleOfLineWrtOxAxis(G_polygon[predecessor(G_c)], - G_polygon[G_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), polygonPointIndex); + return intersectsAboveOrBelow(predecessor(polygonPointIndex, nrOfPoints), + polygonPointIndex, polygon, nrOfPoints, c); } else if ((isGammaAngleBtw(angleGammaAndPoint, anglePointSuccessor, angleFlushEdge)) || (almostEqual(angleGammaAndPoint, anglePointSuccessor))) { - return intersectsAboveOrBelow(successor(polygonPointIndex), polygonPointIndex); + return intersectsAboveOrBelow(successor(polygonPointIndex, nrOfPoints), + polygonPointIndex, polygon, nrOfPoints, c); } } else { if ( @@ -653,9 +871,14 @@ static unsigned int intersects(double angleGammaAndPoint, unsigned int polygonPo /*! * @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) { - if (height(succPredIndex) > height(pointIndex)) { +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; @@ -677,20 +900,27 @@ static unsigned int intersectsAboveOrBelow(unsigned int succPredIndex, unsigned * * @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) { +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(polygonPointIndex, G_polygon[G_a], G_polygon[predecessor(G_a)], - G_polygon[G_c], G_polygon[predecessor(G_c)], + 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, G_polygon[successor(G_c)], - G_polygon[G_c], G_polygon[predecessor(G_c)])) { + if (areOnTheSameSideOfLine(intersectionPoint1, polygon[successor(c, nrOfPoints)], + polygon[c], polygon[predecessor(c, nrOfPoints)])) { gammaPoint = intersectionPoint1; } else { gammaPoint = intersectionPoint2; @@ -699,25 +929,73 @@ static bool gamma(unsigned int polygonPointIndex, cv::Point2f &gammaPoint) { 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 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 +* @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(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 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); + double polygonPointHeight = height(polygonPointIndex, polygon, nrOfPoints, c); double distFormulaDenom = sqrt((side2Params[0] * side2Params[0]) + (side2Params[1] * side2Params[1])); double sideCExtraParam = 2 * polygonPointHeight * distFormulaDenom; @@ -801,44 +1079,19 @@ static std::vector lineEquationParameters(const cv::Point2f& p, const cv return lineEquationParameters; } -//! 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 -*/ -static cv::Point2f findVertexCOnSideB() { - cv::Point2f intersectionPoint1, intersectionPoint2; - - // Get intersection points if they exist - if (!findGammaIntersectionPoints(predecessor(G_a), G_sideBStartVertex, G_sideBEndVertex, - G_sideCStartVertex, G_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, G_polygon[successor(G_c)], - G_polygon[G_c], G_polygon[predecessor(G_c)])) { - return intersectionPoint1; - } else { - return intersectionPoint2; - } -} - //! Compute the height of the point /*! * See paper [2] for more details * -* @param polygonPoint Polygon point +* @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) { - cv::Point2f pointC = G_polygon[G_c]; - cv::Point2f pointCPredecessor = G_polygon[predecessor(G_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); } @@ -847,23 +1100,28 @@ static double height(const cv::Point2f &polygonPoint) { /*! * See paper [2] for more details * -* @param polygonPointIndex Index of the polygon point +* @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) { - cv::Point2f pointC = G_polygon[G_c]; - cv::Point2f pointCPredecessor = G_polygon[predecessor(G_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 = G_polygon[polygonPointIndex]; + cv::Point2f polygonPoint = polygon[polygonPointIndex]; return distanceFromPointToLine(polygonPoint, pointC, pointCPredecessor); } //! Advance the given index with one position /*! -* @param index Index of the point +* @param index Index of the point +* @param nrOfPoints Number of points defining the convex polygon */ -static void advance(unsigned int &index) { - index = successor(index); +static void advance(unsigned int &index, unsigned int nrOfPoints) { + index = successor(index, nrOfPoints); } //! Return the succesor of the provided point index @@ -871,10 +1129,11 @@ static void advance(unsigned int &index) { * The succesor of the last polygon point is the first polygon point * (circular referencing) * -* @param index Index of the point +* @param index Index of the point +* @param nrOfPoints Number of points defining the convex polygon */ -static unsigned int successor(unsigned int index) { - return ((index + 1) % G_nrOfPoints); +static unsigned int successor(unsigned int index, unsigned int nrOfPoints) { + return ((index + 1) % nrOfPoints); } //! Return the predecessor of the provided point index @@ -882,10 +1141,11 @@ static unsigned int successor(unsigned int index) { * The predecessor of the first polygon point is the last polygon point * (circular referencing) * -* @param index Index of the point +* @param index Index of the point +* @param nrOfPoints Number of points defining the convex polygon */ -static unsigned int predecessor(unsigned int index) { - return (index == 0) ? (G_nrOfPoints - 1) +static unsigned int predecessor(unsigned int index, unsigned int nrOfPoints) { + return (index == 0) ? (nrOfPoints - 1) : (index - 1); }