diff --git a/src/OpenCvSharp/Cv2/Cv2_imgproc.cs b/src/OpenCvSharp/Cv2/Cv2_imgproc.cs index c95f2b6c8..c781ece8a 100644 --- a/src/OpenCvSharp/Cv2/Cv2_imgproc.cs +++ b/src/OpenCvSharp/Cv2/Cv2_imgproc.cs @@ -2227,18 +2227,25 @@ public static int ConnectedComponentsWithStats( /// the image to be labeled /// 8 or 4 for 8-way or 4-way connectivity respectively /// + /// output label image type. Currently CV_32S (default) and CV_16U are supported. + /// CV_16U halves the native label image's memory footprint, at the cost of a 65535 label limit. /// public static ConnectedComponents ConnectedComponentsEx( - InputArray image, - PixelConnectivity connectivity = PixelConnectivity.Connectivity8, - ConnectedComponentsAlgorithmsTypes ccltype = ConnectedComponentsAlgorithmsTypes.Default) + InputArray image, + PixelConnectivity connectivity = PixelConnectivity.Connectivity8, + ConnectedComponentsAlgorithmsTypes ccltype = ConnectedComponentsAlgorithmsTypes.Default, + MatType? ltype = null) { - using var labelsMat = new Mat(); + var labelType = ltype ?? MatType.CV_32S; + if (labelType != MatType.CV_32S && labelType != MatType.CV_16U) + throw new ArgumentException("ltype must be CV_32S or CV_16U", nameof(ltype)); + + using var labelsMat = new Mat(); using var statsMat = new Mat(); using var centroidsMat = new Mat(); var nLabels = ConnectedComponentsWithStatsWithAlgorithm( - image, labelsMat, statsMat, centroidsMat, connectivity, MatType.CV_32S, ccltype); - var labels = labelsMat.ToRectangularArray(); + image, labelsMat, statsMat, centroidsMat, connectivity, labelType, ccltype); + var labels = ToInt32RectangularArray(labelsMat, labelType); var stats = statsMat.ToRectangularArray(); var centroids = centroidsMat.ToRectangularArray(); @@ -2258,6 +2265,26 @@ public static ConnectedComponents ConnectedComponentsEx( } return new ConnectedComponents(blobs, labels, nLabels); } + + /// + /// Copies a CV_32S or CV_16U label Mat into a managed int[,], widening CV_16U elements. + /// + private static int[,] ToInt32RectangularArray(Mat labelsMat, MatType labelType) + { + if (labelType == MatType.CV_16U) + { + using var labelsU16 = new Mat(labelsMat); + var raw = labelsU16.ToRectangularArray(); + var widened = new int[raw.GetLength(0), raw.GetLength(1)]; + for (var y = 0; y < raw.GetLength(0); y++) + for (var x = 0; x < raw.GetLength(1); x++) + widened[y, x] = raw[y, x]; + return widened; + } + + using var labelsI32 = new Mat(labelsMat); + return labelsI32.ToRectangularArray(); + } /// /// Finds contours in a binary image. diff --git a/src/OpenCvSharp/Modules/imgproc/ConnectedComponent.cs b/src/OpenCvSharp/Modules/imgproc/ConnectedComponent.cs index 1bed710f1..cf67b525a 100644 --- a/src/OpenCvSharp/Modules/imgproc/ConnectedComponent.cs +++ b/src/OpenCvSharp/Modules/imgproc/ConnectedComponent.cs @@ -8,17 +8,19 @@ namespace OpenCvSharp; public class ConnectedComponents { /// - /// All blobs + /// All blobs, including the background blob at index 0 ( == 0). + /// Most callers should skip index 0 unless the background itself is of interest + /// (see , which starts scanning from index 1). /// public IReadOnlyList Blobs { get; } /// /// destination labeled value /// - public ReadOnlyArray2D Labels { get; } + public ReadOnlyArray2D Labels { get; } /// - /// The number of labels -1 + /// The total number of labels, including the background label (0). /// public int LabelCount { get; internal set; } @@ -36,17 +38,17 @@ internal ConnectedComponents(IReadOnlyList blobs, int[,] labels, int label } /// - /// Filter a image with the specified label value. + /// Filter a image with the specified label value. /// /// Source image. /// Destination image. /// Label value. /// Filtered image. - public void FilterByLabel(Mat src, Mat dst, int labelValue) + public void FilterByLabel(Mat src, Mat dst, int labelValue) => FilterByLabels(src, dst, [labelValue]); /// - /// Filter a image with the specified label values. + /// Filter a image with the specified label values. /// /// Source image. /// Destination image. @@ -71,18 +73,19 @@ public void FilterByLabels(Mat src, Mat dst, IEnumerable labelValues) } // マスク用Matを用意し、Andで切り抜く - using var mask = GetLabelMask(labelArray[0]); + using var labelsView = Mat.FromPixelData(Labels.GetLength(0), Labels.GetLength(1), MatType.CV_32SC1, Labels.GetBuffer()); + using var mask = GetLabelMask(labelsView, labelArray[0]); for (var i = 1; i < labelArray.Length; i++) { - using var maskI = GetLabelMask(labelArray[i]); - Cv2.BitwiseOr(mask, maskI, mask); + using var maskI = GetLabelMask(labelsView, labelArray[i]); + Cv2.BitwiseOr(mask, maskI, mask); } src.CopyTo(dst, mask); } /// - /// Filter a image with the specified blob object. + /// Filter a image with the specified blob object. /// /// Source image. /// Destination image. @@ -96,7 +99,7 @@ public void FilterByBlob(Mat src, Mat dst, Blob blob) } /// - /// Filter a image with the specified blob objects. + /// Filter a image with the specified blob objects. /// /// Source image. /// Destination image. @@ -115,11 +118,6 @@ public void RenderBlobs(Mat img) { if (img is null) throw new ArgumentNullException(nameof(img)); - /* - if (img.Empty()) - throw new ArgumentException("img is empty"); - if (img.Type() != MatType.CV_8UC3) - throw new ArgumentException("img must be CV_8UC3");*/ if (Blobs is null || Blobs.Count == 0) throw new OpenCvSharpException("Blobs is empty"); if (Labels is null) @@ -129,22 +127,21 @@ public void RenderBlobs(Mat img) var width = Labels.GetLength(1); img.Create(new Size(width, height), MatType.CV_8UC3); - var colors = new Scalar[Blobs.Count]; - colors[0] = Scalar.All(0); + var colors = new Vec3b[Blobs.Count]; for (var i = 1; i < Blobs.Count; i++) { - colors[i] = Scalar.RandomColor(); + colors[i] = Scalar.RandomColor().ToVec3b(); } - using var imgt = new Mat(img); - var indexer = imgt.GetIndexer(); + // Reads straight from the plain int[,] buffer (fast array indexing) and writes via + // Mat.AsRows (no P/Invoke per pixel), instead of the previous Mat generic indexer. + var labelsBuffer = Labels.GetBuffer(); + var imgRows = img.AsRows(); for (var y = 0; y < height; y++) { + var imgRow = imgRows[y]; for (var x = 0; x < width; x++) - { - var labelValue = Labels[y, x]; - indexer[y, x] = colors[labelValue].ToVec3b(); - } + imgRow[x] = colors[labelsBuffer[y, x]]; } } @@ -171,16 +168,13 @@ public Blob GetLargestBlob() /// /// 指定したラベル値のところのみを非0で残したマスク画像を返す /// + /// Zero-copy Mat view over the buffer. /// /// - private Mat GetLabelMask(int label) + private static Mat GetLabelMask(Mat labelsView, int label) { - var rows = Labels.GetLength(0); - var cols = Labels.GetLength(1); - using var labels = Mat.FromPixelData(rows, cols, MatType.CV_32SC1, Labels.GetBuffer()); - using var cmp = new Mat(rows, cols, MatType.CV_32SC1, Scalar.All(label)); var result = new Mat(); - Cv2.Compare(labels, cmp, result, CmpTypes.EQ); + Cv2.Compare(labelsView, new Scalar(label), result, CmpTypes.EQ); return result; } @@ -191,7 +185,7 @@ private Mat GetLabelMask(int label) public class Blob { /// - /// Label value + /// Label value. 0 is the background label (see ). /// public int Label { get; internal set; } diff --git a/test/OpenCvSharp.Tests/imgproc/ConnectedComponentsTest.cs b/test/OpenCvSharp.Tests/imgproc/ConnectedComponentsTest.cs index 83dc78fb4..ef72a7fab 100644 --- a/test/OpenCvSharp.Tests/imgproc/ConnectedComponentsTest.cs +++ b/test/OpenCvSharp.Tests/imgproc/ConnectedComponentsTest.cs @@ -28,6 +28,25 @@ public void Run(PixelConnectivity connectivity, ConnectedComponentsAlgorithmsTyp ShowImagesWhenDebugMode(render); } + [Fact] + public void RunWithCv16ULabelType() + { + using var src = LoadImage("alphabet.png", ImreadModes.Grayscale); + using var binary = new Mat(); + Cv2.Threshold(src, binary, 128, 255, ThresholdTypes.BinaryInv); + + var cc = Cv2.ConnectedComponentsEx( + binary, PixelConnectivity.Connectivity8, ConnectedComponentsAlgorithmsTypes.Default, MatType.CV_16U); + + Assert.Equal(27, cc.LabelCount); + Assert.Equal(src.Rows, cc.Labels.GetLength(0)); + Assert.Equal(src.Cols, cc.Labels.GetLength(1)); + + using var render = new Mat(); + cc.RenderBlobs(render); + ShowImagesWhenDebugMode(render); + } + [Fact] public void GetLargestBlob() {