using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Origin; using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Poly; using Hawkeye.VisionBuilder.Workflow.Links; using Hawkeye.VisionBuilder.Workflow.Operations.Attributes; using OpenCvSharp; using Point = OpenCvSharp.Point; using RectangleElement = Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Rectangle.RectangleElement; namespace Hawkeye.VisionBuilder.Workflow.Operations; [Category("Simple/Blobs")] public class FindBlobOperation : BaseOperation, IHaveOrigin, IHaveSearchArea { 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 bool BadIfFound { get; set; } public Guid ReferenceId { get; set; } = new Guid(); public RectangleElement SearchArea { get; set; } //processing result of current element [NotForTool] public OriginElement Origin { get; private set; } = OriginElement.Default; public FindBlobOperation(WorkflowList workflowList) { _workflowList = workflowList; SearchArea = new RectangleElement() { Editable = true, Location = Vector2.One * 100, Size = Vector2.One * 100, IsGood = true }; } protected override void InterpretInternal(Context context) { if (!CheckImageExists(context)) return; if (!CheckGrayscale(context)) return; 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); //case: no blobs found if (contours.Length == 0) { Status = "No blobs found"; SearchArea.IsGood = false; context.GraphicsElements.Add(SearchArea); Result = BadIfFound; 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 { c = x, area = (int)Cv2.ContourArea(x),radius= GetContourRadius(x) }) .OrderBy(x => Math.Min(Math.Abs(x.area - MinArea), Math.Abs(x.area - MaxArea))); var match = contour.Where(x=>x.c.Length>1) .Where(x => x.area >= MinArea && x.area <= MaxArea) .FirstOrDefault(x => x.radius >= MinRadius && x.radius <= MaxRadius); if (match == null) { var best = contour.First(); Status = $"Area: {best.area} Radius: {best.radius}"; PolyElement poly = new PolyElement() { IsGood = BadIfFound }; poly.Points = best.c.Select(x => new Vector2(x.X, x.Y)).ToArray(); context.GraphicsElements.Add(poly); context.GraphicsElements.Add(SearchArea); Result = BadIfFound; return; } { PolyElement poly = new PolyElement() { IsGood = !BadIfFound }; poly.Points = match.c.Select(x => new Vector2(x.X, x.Y)).ToArray(); context.GraphicsElements.Add(poly); SearchArea.IsGood = true; context.GraphicsElements.Add(SearchArea); var center = poly.Points.Aggregate((v1, v2) => v1 + v2) / poly.Points.Length; Origin = new OriginElement() { Location = center }; Status = $"Area: {match.area} Radius: {match.radius}"; Result = !BadIfFound; } } 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(BadIfFound); 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(); BadIfFound = br.ReadBoolean(); ReferenceId = new Guid(br.ReadString()); SearchArea.Load(br); } }