diff --git a/src/Sdcb.PaddleOCR/PaddleOcrAll.cs b/src/Sdcb.PaddleOCR/PaddleOcrAll.cs index 8cc2a53..ffcf88e 100644 --- a/src/Sdcb.PaddleOCR/PaddleOcrAll.cs +++ b/src/Sdcb.PaddleOCR/PaddleOcrAll.cs @@ -166,8 +166,6 @@ public PaddleOcrResult Run(Mat src, int recognizeBatchSize = 0) /// The cropped and rotated image. public static Mat GetRotateCropImage(Mat src, RotatedRect rect) { - bool wider = rect.Size.Width > rect.Size.Height; - float angle = rect.Angle; Size srcSize = src.Size(); Rect boundingRect = rect.BoundingRect(); @@ -188,32 +186,62 @@ public static Mat GetRotateCropImage(Mat src, RotatedRect rect) .Select(v => new Point2f(v.X - rectToExp.X, v.Y - rectToExp.Y)) .ToArray(); - Point2f[] srcPoints = (wider, angle) switch + // Convention-independent corner ordering. RotatedRect.Angle / Points() ordering changed + // across OpenCV versions (4.13 differs from 4.11), which broke the previous hard-coded + // angle-based corner selection. OrderPointsClockwise orders the four corners geometrically + // and is robust even near ±45° (a min/max of x±y can select duplicate points there and + // produce a degenerate perspective transform). + Point2f[] srcPoints = OrderPointsClockwise(rp); // tl, tr, br, bl + + int cw = Math.Max(1, (int)Math.Round(Math.Max( + GetDistance(srcPoints[0], srcPoints[1]), + GetDistance(srcPoints[2], srcPoints[3])))); + int ch = Math.Max(1, (int)Math.Round(Math.Max( + GetDistance(srcPoints[0], srcPoints[3]), + GetDistance(srcPoints[1], srcPoints[2])))); + + Point2f[] dstPoints = { - (true, >= 0 and < 45) => new[] { rp[1], rp[2], rp[3], rp[0] }, - _ => new[] { rp[0], rp[3], rp[2], rp[1] } + new Point2f(0, 0), + new Point2f(cw, 0), + new Point2f(cw, ch), + new Point2f(0, ch), }; - var ptsDst0 = new Point2f(0, 0); - var ptsDst1 = new Point2f(rect.Size.Width, 0); - var ptsDst2 = new Point2f(rect.Size.Width, rect.Size.Height); - var ptsDst3 = new Point2f(0, rect.Size.Height); + using Mat matrix = Cv2.GetPerspectiveTransform(srcPoints, dstPoints); + Mat dest = expanded.WarpPerspective(matrix, new Size(cw, ch), InterpolationFlags.Nearest, BorderTypes.Replicate); - using Mat matrix = Cv2.GetPerspectiveTransform(srcPoints, new[] { ptsDst0, ptsDst1, ptsDst2, ptsDst3 }); - - Mat dest = expanded.WarpPerspective(matrix, new Size(rect.Size.Width, rect.Size.Height), InterpolationFlags.Nearest, BorderTypes.Replicate); - - if (!wider) + // Vertical text line → rotate 90° (CCW) so the recognizer sees a horizontal strip. + if (ch >= cw * 1.5) { Cv2.Transpose(dest, dest); - } - else if (angle > 45) - { Cv2.Flip(dest, dest, FlipMode.X); } return dest; } + /// + /// Orders four polygon corners as top-left, top-right, bottom-right, bottom-left, independently + /// of any OpenCV RotatedRect angle/point convention. Sorts by X, splits the two left-most and + /// two right-most points (always disjoint), then disambiguates by Y on the left and by distance + /// to the top-left corner on the right. Robust near ±45° where x±y heuristics pick duplicates. + /// + private static Point2f[] OrderPointsClockwise(Point2f[] points) + { + Point2f[] xSorted = points.OrderBy(p => p.X).ToArray(); + Point2f[] leftMost = xSorted.Take(2).OrderBy(p => p.Y).ToArray(); + Point2f[] rightMost = xSorted.Skip(2).ToArray(); + + Point2f tl = leftMost[0]; + Point2f bl = leftMost[1]; + Point2f br = rightMost.OrderByDescending(p => GetDistance(tl, p)).First(); + Point2f tr = rightMost.Single(p => p != br); + return new[] { tl, tr, br, bl }; + } + + private static float GetDistance(Point2f a, Point2f b) + => (float)Math.Sqrt((a.X - b.X) * (a.X - b.X) + (a.Y - b.Y) * (a.Y - b.Y)); + /// /// Releases the resources used by this OCR engine. ///