From e689352230d3d5d8787840ff5950b1b43203a74a Mon Sep 17 00:00:00 2001 From: rainyl Date: Thu, 28 Nov 2024 21:10:36 +0800 Subject: [PATCH] add test for isContourConvex and intersectConvexConvex --- .gitignore | 1 + packages/dartcv/src | 2 +- .../dartcv/test/images_out/intersections.png | Bin 0 -> 19467 bytes .../test/imgproc/imgproc_async_test.dart | 115 +++++++++++++++- .../dartcv/test/imgproc/imgproc_test.dart | 128 +++++++++++++++++- 5 files changed, 241 insertions(+), 5 deletions(-) create mode 100644 packages/dartcv/test/images_out/intersections.png diff --git a/.gitignore b/.gitignore index 03de607c..ca5beea3 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,4 @@ doc/api/ .flutter-plugins .flutter-plugins-dependencies +Podfile.lock diff --git a/packages/dartcv/src b/packages/dartcv/src index 75b1be38..7bd81ecc 160000 --- a/packages/dartcv/src +++ b/packages/dartcv/src @@ -1 +1 @@ -Subproject commit 75b1be38ac50c58a5c7e18d80a4826a0124c513f +Subproject commit 7bd81ecc6273087926761a0e6e64791e3fecb1d7 diff --git a/packages/dartcv/test/images_out/intersections.png b/packages/dartcv/test/images_out/intersections.png new file mode 100644 index 0000000000000000000000000000000000000000..4ccd00b185d5382f9e359df951fdda45e58320dd GIT binary patch literal 19467 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 + [topLeft, cv.Point(bottomRiht.x, topLeft.y), bottomRiht, cv.Point(topLeft.x, bottomRiht.y)].asVec(); + + Future drawIntersection(cv.Mat image, cv.VecPoint p1, cv.VecPoint p2, + {bool handleNested = true}) async { + final (intersectArea, intersectionPolygon) = + await cv.intersectConvexConvexAsync(p1, p2, handleNested: handleNested); + if (intersectArea > 0) { + final fillColor = + !cv.isContourConvex(p1) || !cv.isContourConvex(p2) ? cv.Scalar(0, 0, 255) : cv.Scalar.all(200); + await cv.fillPolyAsync(image, cv.VecVecPoint.fromVecPoint(intersectionPolygon), fillColor); + } + await cv.polylinesAsync(image, cv.VecVecPoint.fromVecPoint(intersectionPolygon), true, cv.Scalar.black); + return intersectArea; + } + + Future drawDescription( + cv.Mat image, int intersectionArea, String description, cv.Point origin) async { + final caption = "Intersection area: $intersectionArea$description"; + await cv.putTextAsync(image, caption, origin, cv.FONT_HERSHEY_SIMPLEX, 0.6, cv.Scalar.black); + } + + // start testing + final image = cv.Mat.fromScalar(610, 550, cv.MatType.CV_8UC3, cv.Scalar.white); + double intersectionArea = await drawIntersection( + image, + makeRectangle(cv.Point(10, 10), cv.Point(50, 50)), + makeRectangle(cv.Point(20, 20), cv.Point(60, 60)), + ); + await drawDescription(image, intersectionArea.toInt(), "", cv.Point(70, 40)); + + intersectionArea = await drawIntersection( + image, + makeRectangle(cv.Point(10, 70), cv.Point(35, 95)), + makeRectangle(cv.Point(35, 95), cv.Point(60, 120)), + ); + await drawDescription(image, intersectionArea.toInt(), "", cv.Point(70, 100)); + + intersectionArea = await drawIntersection( + image, + makeRectangle(cv.Point(10, 130), cv.Point(60, 180)), + makeRectangle(cv.Point(20, 140), cv.Point(50, 170)), + handleNested: true, + ); + await drawDescription(image, intersectionArea.toInt(), " (handleNested true)", cv.Point(70, 160)); + + intersectionArea = await drawIntersection( + image, + makeRectangle(cv.Point(10, 250), cv.Point(60, 300)), + makeRectangle(cv.Point(20, 250), cv.Point(50, 290)), + handleNested: true, + ); + + await drawDescription(image, intersectionArea.toInt(), " (handleNested true)", cv.Point(70, 280)); + + // These rectangles share an edge so handleNested can be false and an intersection is still found + intersectionArea = await drawIntersection( + image, + makeRectangle(cv.Point(10, 310), cv.Point(60, 360)), + makeRectangle(cv.Point(20, 310), cv.Point(50, 350)), + handleNested: false, + ); + + await drawDescription(image, intersectionArea.toInt(), " (handleNested false)", cv.Point(70, 340)); + + intersectionArea = await drawIntersection( + image, + makeRectangle(cv.Point(10, 370), cv.Point(60, 420)), + makeRectangle(cv.Point(20, 371), cv.Point(50, 410)), + handleNested: false, + ); + + await drawDescription(image, intersectionArea.toInt(), " (handleNested false)", cv.Point(70, 400)); + + // A vertex of the triangle lies on an edge of the rectangle so handleNested can be false and an intersection is still found + intersectionArea = await drawIntersection( + image, + makeRectangle(cv.Point(10, 430), cv.Point(60, 480)), + [cv.Point(35, 430), cv.Point(20, 470), cv.Point(50, 470)].asVec(), + handleNested: false, + ); + + await drawDescription(image, intersectionArea.toInt(), " (handleNested false)", cv.Point(70, 460)); + + // Show intersection of overlapping rectangle and triangle + intersectionArea = await drawIntersection( + image, + makeRectangle(cv.Point(10, 490), cv.Point(40, 540)), + [cv.Point(25, 500), cv.Point(25, 530), cv.Point(60, 515)].asVec(), + handleNested: false, + ); + + await drawDescription(image, intersectionArea.toInt(), "", cv.Point(70, 520)); + + // This concave polygon is invalid input to intersectConvexConvex so it returns an invalid intersection + final cv.VecPoint notConvex = [ + cv.Point(25, 560), + cv.Point(25, 590), + cv.Point(45, 580), + cv.Point(60, 600), + cv.Point(60, 550), + cv.Point(45, 570), + ].asVec(); + intersectionArea = await drawIntersection( + image, + makeRectangle(cv.Point(10, 550), cv.Point(50, 600)), + notConvex, + handleNested: false, + ); + + await drawDescription(image, intersectionArea.toInt(), " (invalid input: not convex)", cv.Point(70, 580)); + + await cv.imwriteAsync("test/images_out/intersections.png", image); }); } diff --git a/packages/dartcv/test/imgproc/imgproc_test.dart b/packages/dartcv/test/imgproc/imgproc_test.dart index b6fe254c..bedcce1b 100644 --- a/packages/dartcv/test/imgproc/imgproc_test.dart +++ b/packages/dartcv/test/imgproc/imgproc_test.dart @@ -976,12 +976,134 @@ void main() async { }); test('cv.isContourConvex', () { - final contour = [cv.Point(0, 0), cv.Point(0, 100), cv.Point(100, 0), cv.Point(100, 100)].asVec(); - final res = cv.isContourConvex(contour); + final rectangle = [cv.Point(0, 0), cv.Point(100, 0), cv.Point(100, 100), cv.Point(0, 100)].asVec(); + final res = cv.isContourConvex(rectangle); expect(res, true); + + final notConvex = [ + cv.Point(25, 560), + cv.Point(25, 590), + cv.Point(45, 580), + cv.Point(60, 600), + cv.Point(60, 550), + cv.Point(45, 570), + ].asVec(); + expect(cv.isContourConvex(notConvex), false); }); + // https://docs.opencv.org/4.x/df/da5/samples_2cpp_2intersectExample_8cpp-example.html test('cv.intersectConvexConvex', () { - // TODO add test + // helper functions + cv.VecPoint makeRectangle(cv.Point topLeft, cv.Point bottomRiht) => + [topLeft, cv.Point(bottomRiht.x, topLeft.y), bottomRiht, cv.Point(topLeft.x, bottomRiht.y)].asVec(); + + double drawIntersection(cv.Mat image, cv.VecPoint p1, cv.VecPoint p2, {bool handleNested = true}) { + final (intersectArea, intersectionPolygon) = + cv.intersectConvexConvex(p1, p2, handleNested: handleNested); + if (intersectArea > 0) { + final fillColor = + !cv.isContourConvex(p1) || !cv.isContourConvex(p2) ? cv.Scalar(0, 0, 255) : cv.Scalar.all(200); + cv.fillPoly(image, cv.VecVecPoint.fromVecPoint(intersectionPolygon), fillColor); + } + cv.polylines(image, cv.VecVecPoint.fromVecPoint(intersectionPolygon), true, cv.Scalar.black); + return intersectArea; + } + + void drawDescription(cv.Mat image, int intersectionArea, String description, cv.Point origin) { + final caption = "Intersection area: $intersectionArea$description"; + cv.putText(image, caption, origin, cv.FONT_HERSHEY_SIMPLEX, 0.6, cv.Scalar.black); + } + + // start testing + final image = cv.Mat.fromScalar(610, 550, cv.MatType.CV_8UC3, cv.Scalar.white); + double intersectionArea = drawIntersection( + image, + makeRectangle(cv.Point(10, 10), cv.Point(50, 50)), + makeRectangle(cv.Point(20, 20), cv.Point(60, 60)), + ); + drawDescription(image, intersectionArea.toInt(), "", cv.Point(70, 40)); + + intersectionArea = drawIntersection( + image, + makeRectangle(cv.Point(10, 70), cv.Point(35, 95)), + makeRectangle(cv.Point(35, 95), cv.Point(60, 120)), + ); + drawDescription(image, intersectionArea.toInt(), "", cv.Point(70, 100)); + + intersectionArea = drawIntersection( + image, + makeRectangle(cv.Point(10, 130), cv.Point(60, 180)), + makeRectangle(cv.Point(20, 140), cv.Point(50, 170)), + handleNested: true, + ); + drawDescription(image, intersectionArea.toInt(), " (handleNested true)", cv.Point(70, 160)); + + intersectionArea = drawIntersection( + image, + makeRectangle(cv.Point(10, 250), cv.Point(60, 300)), + makeRectangle(cv.Point(20, 250), cv.Point(50, 290)), + handleNested: true, + ); + + drawDescription(image, intersectionArea.toInt(), " (handleNested true)", cv.Point(70, 280)); + + // These rectangles share an edge so handleNested can be false and an intersection is still found + intersectionArea = drawIntersection( + image, + makeRectangle(cv.Point(10, 310), cv.Point(60, 360)), + makeRectangle(cv.Point(20, 310), cv.Point(50, 350)), + handleNested: false, + ); + + drawDescription(image, intersectionArea.toInt(), " (handleNested false)", cv.Point(70, 340)); + + intersectionArea = drawIntersection( + image, + makeRectangle(cv.Point(10, 370), cv.Point(60, 420)), + makeRectangle(cv.Point(20, 371), cv.Point(50, 410)), + handleNested: false, + ); + + drawDescription(image, intersectionArea.toInt(), " (handleNested false)", cv.Point(70, 400)); + + // A vertex of the triangle lies on an edge of the rectangle so handleNested can be false and an intersection is still found + intersectionArea = drawIntersection( + image, + makeRectangle(cv.Point(10, 430), cv.Point(60, 480)), + [cv.Point(35, 430), cv.Point(20, 470), cv.Point(50, 470)].asVec(), + handleNested: false, + ); + + drawDescription(image, intersectionArea.toInt(), " (handleNested false)", cv.Point(70, 460)); + + // Show intersection of overlapping rectangle and triangle + intersectionArea = drawIntersection( + image, + makeRectangle(cv.Point(10, 490), cv.Point(40, 540)), + [cv.Point(25, 500), cv.Point(25, 530), cv.Point(60, 515)].asVec(), + handleNested: false, + ); + + drawDescription(image, intersectionArea.toInt(), "", cv.Point(70, 520)); + + // This concave polygon is invalid input to intersectConvexConvex so it returns an invalid intersection + final cv.VecPoint notConvex = [ + cv.Point(25, 560), + cv.Point(25, 590), + cv.Point(45, 580), + cv.Point(60, 600), + cv.Point(60, 550), + cv.Point(45, 570), + ].asVec(); + intersectionArea = drawIntersection( + image, + makeRectangle(cv.Point(10, 550), cv.Point(50, 600)), + notConvex, + handleNested: false, + ); + + drawDescription(image, intersectionArea.toInt(), " (invalid input: not convex)", cv.Point(70, 580)); + + cv.imwrite("test/images_out/intersections.png", image); }); }