using HalconDotNet; using OpenCvSharp; using OpenCvSharp.Extensions; using System.Drawing.Imaging; using Point = OpenCvSharp.Point; namespace LB_VisionProcesses.Alogrithms.OpenCvSharp { public class BlobTool : TAlgorithm { public BlobTool() { strProcessClass = "LB_VisionProcesses.Alogrithms.OpenCvSharp.BlobTool"; strProcessName = "OpenCvSharp_斑点工具"; Params.Inputs.Add("MinThreshold", 127); Params.Inputs.Add("MaxThreshold", 255); Params.Inputs.Add("MinArea", 0); Params.Inputs.Add("MaxArea", double.MaxValue); Params.Inputs.Add("MinRow", 0.0); Params.Inputs.Add("MaxRow", double.MaxValue); Params.Inputs.Add("MinColumn", 0.0); Params.Inputs.Add("MaxColumn", double.MaxValue); Params.Inputs.Add("MinCircularity", 0.0); Params.Inputs.Add("MaxCircularity", 1.0); ; Params.Inputs.Add("MinRectangularity", 0.0); Params.Inputs.Add("MaxRectangularity", 1.0); Params.Inputs.Add("MinCount", 0); Params.Inputs.Add("MaxCount", 9999); Params.Outputs.Add("CenterX", new List()); Params.Outputs.Add("CenterY", new List()); Params.Outputs.Add("Angle", new List()); Params.Outputs.Add("Width", new List()); Params.Outputs.Add("Height", new List()); Params.Outputs.Add("Area", new List()); Params.Outputs.Add("Count", 0); } public override bool Run() { DateTime StartTime = DateTime.Now; InitRunParams(); HOperatorSet.GenEmptyObj(out HObject EmptyObj); OutputImage = EmptyObj; // 创建并启动任务 TAlgorithmMain(); RunTime = (DateTime.Now - StartTime).TotalMilliseconds; return Result; } /// /// 算子逻辑 /// public override void TAlgorithmMain() { #region 初始化变量 HObject ho_Regions, ho_ConnectedRegions, ho_SelectedRegions; HOperatorSet.GenEmptyObj(out ho_Regions); HOperatorSet.GenEmptyObj(out ho_ConnectedRegions); #endregion try { if (InputImage == null) { Msg = "输入图片为空"; Result = false; return; } if (InputImage is Bitmap) { try { using (Mat hImage = ((Bitmap)InputImage).ToMat()) { ((Bitmap)InputImage).Dispose(); InputImage = null; InputImage = hImage.Clone(); } } catch (Exception ex) { } } if (!(InputImage is Mat)) { Msg = "输入图片格式不为Mat"; Result = false; return; } #region 裁剪区域 object DomainImage = null; if (!ReduceDomainImage(InputImage, ref DomainImage)) { Msg = "裁剪区域失败"; Result = false; return; } Mat hoDomainImage = DomainImage as Mat; //判断是否为灰度图 try { if (hoDomainImage.Channels() != 1) Cv2.CvtColor(hoDomainImage, hoDomainImage, ColorConversionCodes.RGB2GRAY); //转换后再次检查是否为灰度图 if (hoDomainImage.Channels() != 1) { Msg = "输入图片不为灰度图"; Result = false; return; } } catch { Msg = "输入图片不为灰度图且转换失败"; Result = false; return; } #endregion #region 算子逻辑 Record = new ObjectRecord(); double MinThreshold = Convert.ToDouble(Params.Inputs["MinThreshold"]); double MaxThreshold = Convert.ToDouble(Params.Inputs["MaxThreshold"]); double MinArea = Convert.ToDouble(Params.Inputs["MinArea"]); double MaxArea = Convert.ToDouble(Params.Inputs["MaxArea"]); double MinRow = Convert.ToDouble(Params.Inputs["MinRow"]); double MaxRow = Convert.ToDouble(Params.Inputs["MaxRow"]); double MinColumn = Convert.ToDouble(Params.Inputs["MinColumn"]); double MaxColumn = Convert.ToDouble(Params.Inputs["MaxColumn"]); double MinCircularity = Convert.ToDouble(Params.Inputs["MinCircularity"]); double MaxCircularity = Convert.ToDouble(Params.Inputs["MaxCircularity"]); double MinRectangularity = Convert.ToDouble(Params.Inputs["MinRectangularity"]); double MaxRectangularity = Convert.ToDouble(Params.Inputs["MaxRectangularity"]); List results = AnalyzeBlobs(hoDomainImage, MinArea, MaxArea, MinCircularity, MinRectangularity, MinThreshold, MaxThreshold); #endregion #region 结果处理 //计数用于后续判断 List CenterX = new List(); List CenterY = new List(); List Angle = new List(); List Width = new List(); List Height = new List(); List Area = new List(); for (int i = 0; i < results.Count; i++) { // 填充为矩形 CenterX.Add(Convert.ToDouble(results[i].RotatedBoundingBox.Center.X)); CenterY.Add(Convert.ToDouble(results[i].RotatedBoundingBox.Center.Y)); Angle.Add(Convert.ToDouble(results[i].RotatedBoundingBox.Angle)); Width.Add(Convert.ToDouble(results[i].RotatedBoundingBox.Size.Width)); Height.Add(Convert.ToDouble(results[i].RotatedBoundingBox.Size.Height)); Area.Add(results[i].ContourArea); //HOperatorSet.GenRectangle2(out HObject hRectangle, CenterY[i], CenterX[i], -1 * Angle[i] / 180 * Math.PI // , Width[i] / 2, Height[i] / 2); //((ObjectRecord)Record).AddRecord(hRectangle); var contourArray = results[i].Contour.ToArray(); int count = contourArray.Length; int[] Xpoints = new int[count + 1]; int[] Ypoints = new int[count + 1]; Parallel.For(0, count, j => { var point = contourArray[j]; Xpoints[j] = point.X; Ypoints[j] = point.Y; }); Xpoints[count] = Xpoints[0]; Ypoints[count] = Ypoints[0]; HOperatorSet.GenContourPolygonXld(out HObject contour, new HTuple(Ypoints), new HTuple(Xpoints)); ((ObjectRecord)Record).AddXld(contour); } Params.Outputs["CenterX"] = CenterX; Params.Outputs["CenterY"] = CenterY; Params.Outputs["Angle"] = Angle; Params.Outputs["Width"] = Width; Params.Outputs["Height"] = Height; Params.Outputs["Area"] = Area; Params.Outputs["Count"] = CenterX.Count; #endregion #region 生成OutputImage给后续处理 try { OutputImage = DomainImage; } catch (Exception ex) { Msg = "生成OutputImage失败,原因是:" + ex.ToString(); Result = false; return; } #endregion if (Msg == "运行超时") { Result = false; return; } int MinCount = ProcessParams.ConvertToInt32(Params.Inputs["MinCount"]); int MaxCount = ProcessParams.ConvertToInt32(Params.Inputs["MaxCount"]); if (CenterX.Count < MinCount || CenterX.Count > MaxCount) { Msg = string.Format("结果个数超出范围({0},{1})", MinCount, MaxCount); Record.ChangeAll2False(); Result = false; return; } Msg = "运行成功"; Result = true; return; } catch (Exception ex) { Msg = "运行失败,原因是:" + ex.ToString().TrimEnd(); OutputImage = null; Result = false; return; } finally { bCompleted = true; #region 内存释放 ho_Regions.Dispose(); ho_ConnectedRegions.Dispose(); #endregion } } public class BlobRotatedRect { public Point2f Center { get; set; } public Size2f Size { get; set; } public float Angle { get; set; } public Point2f[] Points { get; set; } public BlobRotatedRect() { } public BlobRotatedRect(RotatedRect rect) { Center = rect.Center; Size = rect.Size; Angle = rect.Angle; Points = Cv2.BoxPoints(rect); } } public class Blob { public int Id { get; set; } //public Rect BoundingBox { get; set; } public BlobRotatedRect RotatedBoundingBox { get; set; } //public Point2f Centroid { get; set; } //public double Area { get; set; } public double Perimeter { get; set; } public double Circularity { get; set; } public double Rectangularity { get; set; } public double AspectRatio { get; set; } public List Contour { get; set; } public double ContourArea { get; set; } //public double RotatedRectArea { get; set; } } public static List AnalyzeBlobs(Mat image, double minArea = 50, double maxArea = 10000, double minCircularity = 0.1, double minRectangularity = 0.5, double minThreshold = 0, double maxThreshold = 255, ThresholdTypes thresholdType = ThresholdTypes.Binary) { var blobs = new List(); // 转换为灰度图 Mat gray = new Mat(); if (image.Channels() == 3) Cv2.CvtColor(image, gray, ColorConversionCodes.BGR2GRAY); else image.CopyTo(gray); // 阈值分割 Mat binary = new Mat(); Cv2.Threshold(gray, binary, minThreshold, maxThreshold, thresholdType); // 查找轮廓 Point[][] contours; HierarchyIndex[] hierarchy; Cv2.FindContours(binary, out contours, out hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple); // 处理每个轮廓 for (int i = 0; i < contours.Length; i++) { var contour = contours[i]; if (contour.Length < 5) continue; // 至少需要5个点才能拟合旋转矩形 double area = Cv2.ContourArea(contour); // 面积过滤 if (area < minArea || area > maxArea) continue; // 计算周长 double perimeter = Cv2.ArcLength(contour, true); // 计算圆形度 double circularity = (4 * Math.PI * area) / (perimeter * perimeter); // 圆形度过滤 if (circularity < minCircularity) continue; // 计算外接矩形 Rect boundingBox = Cv2.BoundingRect(contour); // 计算最小外接矩形(带角度) RotatedRect rotatedRect = Cv2.MinAreaRect(contour); BlobRotatedRect rotatedBoundingBox = new BlobRotatedRect(rotatedRect); // 计算质心 Moments moments = Cv2.Moments(contour); Point2f centroid = new Point2f( (float)(moments.M10 / moments.M00), (float)(moments.M01 / moments.M00) ); // 计算矩形度 double rectangularity = CalculateRectangularity(contour, rotatedRect); // 矩形度过滤 if (rectangularity < minRectangularity) continue; // 计算宽高比 double aspectRatio = CalculateAspectRatio(rotatedRect); blobs.Add(new Blob { Id = i, //Area = area, Perimeter = perimeter, Circularity = circularity, Rectangularity = rectangularity, AspectRatio = aspectRatio, //BoundingBox = boundingBox, RotatedBoundingBox = rotatedBoundingBox, //Centroid = centroid, Contour = new List(contour), ContourArea = area //RotatedRectArea = rotatedRect.Size.Width * rotatedRect.Size.Height }); } gray.Dispose(); binary.Dispose(); return blobs; } /// /// 计算矩形度(轮廓面积与最小外接矩形面积的比值) /// private static double CalculateRectangularity(Point[] contour, RotatedRect rotatedRect) { double contourArea = Cv2.ContourArea(contour); double rotatedRectArea = rotatedRect.Size.Width * rotatedRect.Size.Height; if (rotatedRectArea == 0) return 0; return contourArea / rotatedRectArea; } /// /// 计算宽高比 /// private static double CalculateAspectRatio(RotatedRect rotatedRect) { double width = rotatedRect.Size.Width; double height = rotatedRect.Size.Height; if (height == 0) return double.MaxValue; // 确保宽高比始终 >= 1 return width >= height ? width / height : height / width; } /// /// 根据矩形度过滤Blob /// public static List FilterByRectangularity(List blobs, double minRectangularity, double maxRectangularity = 1.0) { return blobs.Where(b => b.Rectangularity >= minRectangularity && b.Rectangularity <= maxRectangularity).ToList(); } /// /// 根据宽高比过滤Blob /// public static List FilterByAspectRatio(List blobs, double minAspectRatio, double maxAspectRatio) { return blobs.Where(b => b.AspectRatio >= minAspectRatio && b.AspectRatio <= maxAspectRatio).ToList(); } /// /// 根据方向角度过滤Blob /// public static List FilterByAngle(List blobs, double minAngle, double maxAngle) { return blobs.Where(b => { double angle = Math.Abs(b.RotatedBoundingBox.Angle); // 处理角度周期性 if (angle > 90) angle = 180 - angle; return angle >= minAngle && angle <= maxAngle; }).ToList(); } } }