using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Origin; using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Poly; using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Rectangle; using Hawkeye.VisionBuilder.Workflow.Operations.Attributes; using OpenCvSharp; using Point = OpenCvSharp.Point; namespace Hawkeye.VisionBuilder.Workflow.Operations; [Category("Simple/Blobs")] public class FindManyBlobsOperation:BaseOperation { private readonly WorkflowList _workflowList; public int MinArea { get; set; } = 100; public int MaxArea { get; set; } = 99999; public int MinRadius { get; set; } = 0; public int MaxRadius { get; set; } = 99999; public Guid ReferenceId { get; set; } = new Guid(); public RectangleElement SearchArea { get; set; } //processing result of current element public FindManyBlobsOperation(WorkflowList workflowList) { _workflowList = workflowList; SearchArea = new RectangleElement() { Editable = true, Location = Vector2.One * 100, Size = Vector2.One * 100, IsGood = true }; } public record ContourMatch(Point[] c, int area, int radius) { public override string ToString() { return $"{{area={area}, radius={radius}}}"; } } protected override void InterpretInternal(Context context) { Result = true; if (!CheckImageExists(context)) return; if (!CheckGrayscale(context)) return; if (SearchArea==null) { SearchArea = new RectangleElement() { Editable = true, Location = Vector2.Zero, Size = new Vector2(context.ActiveImage.ImageData.Width, context.ActiveImage.ImageData.Height), IsGood = true }; } SearchArea.MovePivot(_workflowList.GetOriginById(ReferenceId).Origin.Location); var img = context.ActiveImage.ImageData; var mat = new Mat(img.Size(), img.Type(), Scalar.Black); SearchArea.FillMat(mat); var contoursMat = img.BitwiseAnd(mat).ToMat(); var contours = contoursMat.Threshold(128, 255, ThresholdTypes.Binary) .FindContoursAsArray(RetrievalModes.List, ContourApproximationModes.ApproxSimple); context.GraphicsElements.Add(SearchArea); //case: no blobs found if (contours.Length == 0) { Status = "No blobs found"; SearchArea.IsGood = true; return; } int GetContourRadius(Point[] contour) { Cv2.MinEnclosingCircle(contour, out var center, out var radius); return (int)radius; } //case: blobs found but not matching criteria var contour = contours.Select(x => new ContourMatch(x, (int)Cv2.ContourArea(x), GetContourRadius(x))) .OrderBy(x => Math.Min(Math.Abs(x.area - MinArea), Math.Abs(x.area - MaxArea))).ToList(); var match = contour.Where(x => x.c.Length > 1) .Where(x => x.area >= MinArea && x.area <= MaxArea) .Where(x => x.radius >= MinRadius && x.radius <= MaxRadius).ToList(); int badCount = 0; foreach (ContourMatch contourMatch in match) { PolyElement poly = new PolyElement() { IsGood = false }; poly.Points = contourMatch.c.Select(x => new Vector2(x.X, x.Y)).ToArray(); context.GraphicsElements.Add(poly); badCount++; } if (badCount > 0) { Status = $"Found: {badCount} First: {match.First().ToString()}"; Result = false; SearchArea.IsGood = false; return; } SearchArea.IsGood = true; Status = $"Best: {contour.First().ToString()}"; Result = true; } public override void SetParameters(Dictionary parameters) { base.SetParameters(parameters); SearchArea.SetPivot(_workflowList.GetOriginById(ReferenceId).Origin.Location); } public override void Save(BinaryWriter bw) { bw.Write(Id.ToString()); bw.Write(Label); bw.Write(MinArea); bw.Write(MaxArea); bw.Write(MinRadius); bw.Write(MaxRadius); bw.Write(ReferenceId.ToString()); SearchArea.Save(bw); } public override void Load(BinaryReader br) { Id = new Guid(br.ReadString()); Label = br.ReadString(); MinArea = br.ReadInt32(); MaxArea = br.ReadInt32(); MinRadius = br.ReadInt32(); MaxRadius = br.ReadInt32(); ReferenceId = new Guid(br.ReadString()); SearchArea.Load(br); } }