Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 39 additions & 25 deletions src/OpenCvSharp/Cv2/Cv2_imgproc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2227,36 +2227,50 @@ public static int ConnectedComponentsWithStats(
/// <param name="image">the image to be labeled</param>
/// <param name="connectivity">8 or 4 for 8-way or 4-way connectivity respectively</param>
/// <param name="ccltype"></param>
/// <returns></returns>
/// <param name="ltype">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.</param>
/// <returns>A <see cref="global::OpenCvSharp.ConnectedComponents"/> instance. Dispose it to release the underlying native label image.</returns>
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<int>();
using var statsMat = new Mat<int>();
using var centroidsMat = new Mat<double>();
var nLabels = ConnectedComponentsWithStatsWithAlgorithm(
image, labelsMat, statsMat, centroidsMat, connectivity, MatType.CV_32S, ccltype);
var labels = labelsMat.ToRectangularArray();
var stats = statsMat.ToRectangularArray();
var centroids = centroidsMat.ToRectangularArray();

var blobs = new ConnectedComponents.Blob[nLabels];
for (var i = 0; i < nLabels; i++)
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));

var labelsMat = new Mat();
try
{
blobs[i] = new ConnectedComponents.Blob
using var statsMat = new Mat<int>();
using var centroidsMat = new Mat<double>();
var nLabels = ConnectedComponentsWithStatsWithAlgorithm(
image, labelsMat, statsMat, centroidsMat, connectivity, labelType, ccltype);
var stats = statsMat.ToRectangularArray();
var centroids = centroidsMat.ToRectangularArray();

var blobs = new ConnectedComponents.Blob[nLabels];
for (var i = 0; i < nLabels; i++)
{
Label = i,
Left = stats[i, 0],
Top = stats[i, 1],
Width = stats[i, 2],
Height = stats[i, 3],
Area = stats[i, 4],
Centroid = new Point2d(centroids[i, 0], centroids[i, 1]),
};
blobs[i] = new ConnectedComponents.Blob
{
Label = i,
Left = stats[i, 0],
Top = stats[i, 1],
Width = stats[i, 2],
Height = stats[i, 3],
Area = stats[i, 4],
Centroid = new Point2d(centroids[i, 0], centroids[i, 1]),
};
}
return new ConnectedComponents(blobs, labelsMat, nLabels);
}
catch
{
labelsMat.Dispose();
throw;
}
return new ConnectedComponents(blobs, labels, nLabels);
}

/// <summary>
Expand Down
120 changes: 81 additions & 39 deletions src/OpenCvSharp/Modules/imgproc/ConnectedComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,48 +5,65 @@ namespace OpenCvSharp;
/// <summary>
/// connected components that is returned from Cv2.ConnectedComponentsEx
/// </summary>
public class ConnectedComponents
public sealed class ConnectedComponents : IDisposable
{
private readonly Mat labelsMat;
private ReadOnlyArray2D<int>? labelsCache;
private bool disposed;

/// <summary>
/// All blobs
/// All blobs, including the background blob at index 0 (<see cref="Blob.Label"/> == 0).
/// Most callers should skip index 0 unless the background itself is of interest
/// (see <see cref="GetLargestBlob"/>, which starts scanning from index 1).
/// </summary>
public IReadOnlyList<Blob> Blobs { get; }

/// <summary>
/// destination labeled value
/// destination labeled value. Materialized from the native label image on first access.
/// </summary>
public ReadOnlyArray2D<int> Labels { get; }
public ReadOnlyArray2D<int> Labels => labelsCache ??= new ReadOnlyArray2D<int>(ToInt32Array(labelsMat));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

/// <summary>
/// The number of labels -1
/// The total number of labels, including the background label (0).
/// </summary>
public int LabelCount { get; internal set; }
public int LabelCount { get; }

/// <summary>
/// Constructor
/// </summary>
/// <param name="blobs"></param>
/// <param name="labels"></param>
/// <param name="labelsMat">The native label image (CV_32S or CV_16U). Ownership is transferred to this instance.</param>
/// <param name="labelCount"></param>
internal ConnectedComponents(IReadOnlyList<Blob> blobs, int[,] labels, int labelCount)
internal ConnectedComponents(IReadOnlyList<Blob> blobs, Mat labelsMat, int labelCount)
{
Blobs = blobs;
Labels = new ReadOnlyArray2D<int>(labels);
this.labelsMat = labelsMat;
LabelCount = labelCount;
}

/// <summary>
/// Filter a image with the specified label value.
/// Releases the underlying native label image.
/// </summary>
public void Dispose()
{
if (disposed)
return;
labelsMat.Dispose();
disposed = true;
}

/// <summary>
/// Filter a image with the specified label value.
/// </summary>
/// <param name="src">Source image.</param>
/// <param name="dst">Destination image.</param>
/// <param name="labelValue">Label value.</param>
/// <returns>Filtered image.</returns>
public void FilterByLabel(Mat src, Mat dst, int labelValue)
public void FilterByLabel(Mat src, Mat dst, int labelValue)
=> FilterByLabels(src, dst, [labelValue]);

/// <summary>
/// Filter a image with the specified label values.
/// Filter a image with the specified label values.
/// </summary>
/// <param name="src">Source image.</param>
/// <param name="dst">Destination image.</param>
Expand Down Expand Up @@ -76,13 +93,13 @@ public void FilterByLabels(Mat src, Mat dst, IEnumerable<int> labelValues)
for (var i = 1; i < labelArray.Length; i++)
{
using var maskI = GetLabelMask(labelArray[i]);
Cv2.BitwiseOr(mask, maskI, mask);
Cv2.BitwiseOr(mask, maskI, mask);
}
src.CopyTo(dst, mask);
}

/// <summary>
/// Filter a image with the specified blob object.
/// Filter a image with the specified blob object.
/// </summary>
/// <param name="src">Source image.</param>
/// <param name="dst">Destination image.</param>
Expand All @@ -96,7 +113,7 @@ public void FilterByBlob(Mat src, Mat dst, Blob blob)
}

/// <summary>
/// Filter a image with the specified blob objects.
/// Filter a image with the specified blob objects.
/// </summary>
/// <param name="src">Source image.</param>
/// <param name="dst">Destination image.</param>
Expand All @@ -115,35 +132,40 @@ 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)
throw new OpenCvSharpException("Labels is empty");

var height = Labels.GetLength(0);
var width = Labels.GetLength(1);
var height = labelsMat.Rows;
var width = labelsMat.Cols;
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<Vec3b>(img);
var indexer = imgt.GetIndexer();
for (var y = 0; y < height; y++)
var imgRows = img.AsRows<Vec3b>();
if (labelsMat.Type() == MatType.CV_16U)
{
var labelRows = labelsMat.AsRows<ushort>();
for (var y = 0; y < height; y++)
{
var labelRow = labelRows[y];
var imgRow = imgRows[y];
for (var x = 0; x < width; x++)
imgRow[x] = colors[labelRow[x]];
}
}
else
{
for (var x = 0; x < width; x++)
var labelRows = labelsMat.AsRows<int>();
for (var y = 0; y < height; y++)
{
var labelValue = Labels[y, x];
indexer[y, x] = colors[labelValue].ToVec3b();
var labelRow = labelRows[y];
var imgRow = imgRows[y];
for (var x = 0; x < width; x++)
imgRow[x] = colors[labelRow[x]];
}
}
}
Expand Down Expand Up @@ -175,23 +197,43 @@ public Blob GetLargestBlob()
/// <returns></returns>
private Mat GetLabelMask(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(labelsMat, new Scalar(label), result, CmpTypes.EQ);
return result;
}

/// <summary>
/// Copies the native label image (CV_32S or CV_16U) into a managed <c>int[,]</c>.
/// </summary>
private static int[,] ToInt32Array(Mat mat)
{
if (mat.Type() == MatType.CV_16U)
{
var rows = mat.Rows;
var cols = mat.Cols;
var result = new int[rows, cols];
var srcRows = mat.AsRows<ushort>();
for (var y = 0; y < rows; y++)
{
var srcRow = srcRows[y];
for (var x = 0; x < cols; x++)
result[y, x] = srcRow[x];
}
return result;
}

using var int32Mat = new Mat<int>(mat);
return int32Mat.ToRectangularArray();
}

#pragma warning disable CA1034
/// <summary>
/// One blob
/// </summary>
public class Blob
{
/// <summary>
/// Label value
/// Label value. 0 is the background label (see <see cref="Blobs"/>).
/// </summary>
public int Label { get; internal set; }

Expand Down
25 changes: 22 additions & 3 deletions test/OpenCvSharp.Tests/imgproc/ConnectedComponentsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public void Run(PixelConnectivity connectivity, ConnectedComponentsAlgorithmsTyp
Cv2.Threshold(src, binary, 128, 255, ThresholdTypes.BinaryInv);
ShowImagesWhenDebugMode(src, binary);

var cc = Cv2.ConnectedComponentsEx(binary, connectivity, algorithmType);
using var cc = Cv2.ConnectedComponentsEx(binary, connectivity, algorithmType);

Assert.Equal(27, cc.LabelCount);
Assert.NotEmpty(cc.Labels.GetBuffer());
Expand All @@ -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);

using 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()
{
Expand All @@ -36,7 +55,7 @@ public void GetLargestBlob()
Cv2.Rectangle(src, new Rect(50, 60, 20, 30), Scalar.White, -1); // greater
ShowImagesWhenDebugMode(src);

var cc = Cv2.ConnectedComponentsEx(src, PixelConnectivity.Connectivity8, ConnectedComponentsAlgorithmsTypes.Default);
using var cc = Cv2.ConnectedComponentsEx(src, PixelConnectivity.Connectivity8, ConnectedComponentsAlgorithmsTypes.Default);

var largestBlob = cc.GetLargestBlob();
Assert.Equal(50, largestBlob.Left);
Expand All @@ -55,7 +74,7 @@ public void FilterByLabel()
Cv2.Rectangle(src, new Rect(10, 20, 10, 20), Scalar.White, -1);
Cv2.Rectangle(src, new Rect(50, 60, 20, 30), Scalar.White, -1); // greater

var cc = Cv2.ConnectedComponentsEx(src, PixelConnectivity.Connectivity8, ConnectedComponentsAlgorithmsTypes.Default);
using var cc = Cv2.ConnectedComponentsEx(src, PixelConnectivity.Connectivity8, ConnectedComponentsAlgorithmsTypes.Default);

using var dst = new Mat();
cc.FilterByLabel(src, dst, 1);
Expand Down
Loading