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()
{