diff --git a/XP.ImageProcessing.Processors/检测分析/BgaVoidRateProcessor.cs b/XP.ImageProcessing.Processors/检测分析/BgaVoidRateProcessor.cs index c5aaf769..192ce5df 100644 --- a/XP.ImageProcessing.Processors/检测分析/BgaVoidRateProcessor.cs +++ b/XP.ImageProcessing.Processors/检测分析/BgaVoidRateProcessor.cs @@ -82,10 +82,30 @@ public class BgaVoidRateProcessor : ImageProcessorBase Parameters.Add("BgaCircularity", new ProcessorParameter( "BgaCircularity", LocalizationHelper.GetString("BgaVoidRateProcessor_BgaCircularity"), - typeof(double), 0.5, 0.0, 1.0, + typeof(double), 0.6, 0.0, 1.0, LocalizationHelper.GetString("BgaVoidRateProcessor_BgaCircularity_Desc"))); + Parameters.Add("BgaMinCenterDistance", new ProcessorParameter( + "BgaMinCenterDistance", + "焊球最小中心距", typeof(int), 0, 0, 5000, + "焊球中心之间的最小距离(像素),0=不启用。用于去除粘连和背景误检")); + + Parameters.Add("BgaAreaConsistency", new ProcessorParameter( + "BgaAreaConsistency", + "焊球面积一致性", typeof(double), 0.5, 0.0, 1.0, + "焊球面积一致性系数(0-1),去除与均值偏差过大的离群焊球。0=不启用")); + // ── 第二步:气泡检测参数(阈值 0-65535)── + Parameters.Add("UseAdaptiveThreshold", new ProcessorParameter( + "UseAdaptiveThreshold", + "自适应阈值", typeof(bool), false, null, null, + "启用自适应阈值后,每个焊球的气泡阈值根据其内部灰度分布自动计算")); + + Parameters.Add("VoidThresholdPercentile", new ProcessorParameter( + "VoidThresholdPercentile", + "气泡阈值百分位", typeof(double), 80.0, 0.0, 100.0, + "自适应阈值百分位(0-100),例如80表示取焊球内部灰度第80百分位作为气泡阈值")); + Parameters.Add("MinThreshold", new ProcessorParameter( "MinThreshold", LocalizationHelper.GetString("BgaVoidRateProcessor_MinThreshold"), @@ -131,6 +151,10 @@ public class BgaVoidRateProcessor : ImageProcessorBase double bgaCircularity = GetParameter("BgaCircularity"); int bgaThreshLow = GetParameter("BgaThresholdLow"); int bgaThreshHigh = GetParameter("BgaThresholdHigh"); + int bgaMinCenterDist = GetParameter("BgaMinCenterDistance"); + double bgaAreaConsistency = GetParameter("BgaAreaConsistency"); + bool useAdaptiveThreshold = GetParameter("UseAdaptiveThreshold"); + double voidThresholdPercentile = GetParameter("VoidThresholdPercentile"); int minThresh = GetParameter("MinThreshold"); int maxThresh = GetParameter("MaxThreshold"); int minVoidArea = GetParameter("MinVoidArea"); @@ -166,7 +190,7 @@ public class BgaVoidRateProcessor : ImageProcessorBase // 第一步:BGA焊球定位(在 16 位图上做双阈值分割) // ================================================================ var bgaResults = DetectBgaBalls(inputImage, bgaBlurSize, bgaMinArea, bgaMaxArea, - bgaCircularity, bgaThreshLow, bgaThreshHigh, roiMask); + bgaCircularity, bgaThreshLow, bgaThreshHigh, bgaMinCenterDist, bgaAreaConsistency, roiMask); _logger.Information("第一步完成: 检测到 {Count} 个BGA焊球", bgaResults.Count); @@ -196,7 +220,8 @@ public class BgaVoidRateProcessor : ImageProcessorBase foreach (var bga in bgaResults) { - DetectVoidsInBga(inputImage, bga, minThresh, maxThresh, minVoidArea); + DetectVoidsInBga(inputImage, bga, minThresh, maxThresh, minVoidArea, + useAdaptiveThreshold, voidThresholdPercentile); totalBgaArea += bga.BgaArea; totalVoidArea += bga.VoidPixels; totalVoidCount += bga.Voids.Count; @@ -229,12 +254,14 @@ public class BgaVoidRateProcessor : ImageProcessorBase } /// - /// 第一步:BGA焊球定位(16 位双阈值分割 → 8 位二值图 → FindContours) + /// 第一步:BGA焊球定位(16 位双阈值分割 → 8 位二值图 → 形态学 → FindContours → 过滤) + /// 改进:增加开运算去噪、闭运算1次防粘连、距离过滤、面积一致性过滤 /// private List DetectBgaBalls( Image input, int blurSize, int minArea, int maxArea, double minCircularity, - int threshLow, int threshHigh, Image? roiMask) + int threshLow, int threshHigh, int minCenterDist, double areaConsistency, + Image? roiMask) { var results = new List(); int w = input.Width, h = input.Height; @@ -258,9 +285,13 @@ public class BgaVoidRateProcessor : ImageProcessorBase if (roiMask != null) CvInvoke.BitwiseAnd(binary, roiMask, binary); - // 形态学闭运算 using var kernel = CvInvoke.GetStructuringElement(ElementShape.Ellipse, new Size(5, 5), new Point(-1, -1)); - CvInvoke.MorphologyEx(binary, binary, MorphOp.Close, kernel, new Point(-1, -1), 2, BorderType.Default, new MCvScalar(0)); + + // 开运算去除小噪点(先腐蚀后膨胀) + CvInvoke.MorphologyEx(binary, binary, MorphOp.Open, kernel, new Point(-1, -1), 1, BorderType.Default, new MCvScalar(0)); + + // 闭运算填补焊球内部小孔(迭代1次,减少粘连) + CvInvoke.MorphologyEx(binary, binary, MorphOp.Close, kernel, new Point(-1, -1), 1, BorderType.Default, new MCvScalar(0)); // 轮廓检测 using var contours = new VectorOfVectorOfPoint(); @@ -297,6 +328,39 @@ public class BgaVoidRateProcessor : ImageProcessorBase }); } + // ── 距离过滤:去除中心距过近的粘连轮廓 ── + if (minCenterDist > 0 && results.Count > 1) + { + var filtered = new List(); + foreach (var bga in results) + { + bool tooClose = false; + foreach (var other in filtered) + { + double dx = bga.CenterX - other.CenterX; + double dy = bga.CenterY - other.CenterY; + double dist = Math.Sqrt(dx * dx + dy * dy); + if (dist < minCenterDist) + { + tooClose = true; + break; + } + } + if (!tooClose) + filtered.Add(bga); + } + results = filtered; + } + + // ── 面积一致性过滤:去除与均值偏差过大的离群焊球 ── + if (areaConsistency > 0 && results.Count > 2) + { + double avgArea = results.Average(r => r.BgaArea); + double minAllowed = avgArea * (1.0 - areaConsistency); + double maxAllowed = avgArea * (1.0 + areaConsistency); + results = results.Where(r => r.BgaArea >= minAllowed && r.BgaArea <= maxAllowed).ToList(); + } + results.Sort((a, b) => b.BgaArea.CompareTo(a.BgaArea)); for (int i = 0; i < results.Count; i++) results[i].Index = i + 1; @@ -308,8 +372,9 @@ public class BgaVoidRateProcessor : ImageProcessorBase /// /// 第二步:在焊球区域内检测气泡(16 位双阈值分割 → 8 位二值图 → FindContours) + /// 改进:支持自适应阈值,基于焊球内部灰度分布自动计算气泡阈值 /// - private void DetectVoidsInBga(Image input, BgaBallInfo bga, int minThresh, int maxThresh, int minVoidArea) + private void DetectVoidsInBga(Image input, BgaBallInfo bga, int minThresh, int maxThresh, int minVoidArea, bool useAdaptiveThreshold, double thresholdPercentile) { int w = input.Width, h = input.Height; @@ -324,19 +389,45 @@ public class BgaVoidRateProcessor : ImageProcessorBase int bgaPixels = CvInvoke.CountNonZero(mask); bga.BgaArea = bgaPixels; + // 确定气泡阈值 + int effectiveMinThresh = minThresh; + int effectiveMaxThresh = maxThresh; + + if (useAdaptiveThreshold && bgaPixels > 0) + { + // 收集焊球内部所有像素灰度值 + var grayValues = new List(); + var srcData = input.Data; + var maskData = mask.Data; + + for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) + if (maskData[y, x, 0] > 0) + grayValues.Add(srcData[y, x, 0]); + + if (grayValues.Count > 0) + { + grayValues.Sort(); + int percentileIndex = (int)(grayValues.Count * thresholdPercentile / 100.0); + percentileIndex = Math.Min(percentileIndex, grayValues.Count - 1); + effectiveMinThresh = grayValues[percentileIndex]; + effectiveMaxThresh = 65535; + } + } + // 在 16 位图上做双阈值分割,输出 8 位二值图 var voidImg = new Image(w, h); - var srcData = input.Data; + var srcData2 = input.Data; var dstData = voidImg.Data; - var maskData = mask.Data; + var maskData2 = mask.Data; for (int y = 0; y < h; y++) for (int x = 0; x < w; x++) { - if (maskData[y, x, 0] > 0) + if (maskData2[y, x, 0] > 0) { - ushort val = srcData[y, x, 0]; - dstData[y, x, 0] = (val >= minThresh && val <= maxThresh) ? (byte)255 : (byte)0; + ushort val = srcData2[y, x, 0]; + dstData[y, x, 0] = (val >= effectiveMinThresh && val <= effectiveMaxThresh) ? (byte)255 : (byte)0; } } diff --git a/XplorePlane/ViewModels/ImageProcessing/BgaDetectionViewModel.cs b/XplorePlane/ViewModels/ImageProcessing/BgaDetectionViewModel.cs index 2daeb214..c48deeaf 100644 --- a/XplorePlane/ViewModels/ImageProcessing/BgaDetectionViewModel.cs +++ b/XplorePlane/ViewModels/ImageProcessing/BgaDetectionViewModel.cs @@ -79,9 +79,15 @@ namespace XplorePlane.ViewModels.ImageProcessing private int _blurSize = 5; public int BlurSize { get => _blurSize; set => SetProperty(ref _blurSize, value); } - private double _circularity = 0.5; + private double _circularity = 0.6; public double Circularity { get => _circularity; set => SetProperty(ref _circularity, value); } + private int _bgaMinCenterDistance = 0; + public int BgaMinCenterDistance { get => _bgaMinCenterDistance; set => SetProperty(ref _bgaMinCenterDistance, value); } + + private double _bgaAreaConsistency = 0.5; + public double BgaAreaConsistency { get => _bgaAreaConsistency; set => SetProperty(ref _bgaAreaConsistency, value); } + // BGA定位阈值参数 private int _bgaThresholdLow = 0; public int BgaThresholdLow { get => _bgaThresholdLow; set => SetProperty(ref _bgaThresholdLow, value); } @@ -96,6 +102,12 @@ namespace XplorePlane.ViewModels.ImageProcessing private int _maxThreshold = 65535; public int MaxThreshold { get => _maxThreshold; set => SetProperty(ref _maxThreshold, value); } + private bool _useAdaptiveThreshold = false; + public bool UseAdaptiveThreshold { get => _useAdaptiveThreshold; set => SetProperty(ref _useAdaptiveThreshold, value); } + + private double _voidThresholdPercentile = 80.0; + public double VoidThresholdPercentile { get => _voidThresholdPercentile; set => SetProperty(ref _voidThresholdPercentile, value); } + private int _minVoidArea = 10; public int MinVoidArea { get => _minVoidArea; set => SetProperty(ref _minVoidArea, value); } @@ -315,6 +327,10 @@ namespace XplorePlane.ViewModels.ImageProcessing processor.SetParameter("BgaCircularity", Circularity); processor.SetParameter("BgaThresholdLow", BgaThresholdLow); processor.SetParameter("BgaThresholdHigh", BgaThresholdHigh); + processor.SetParameter("BgaMinCenterDistance", BgaMinCenterDistance); + processor.SetParameter("BgaAreaConsistency", BgaAreaConsistency); + processor.SetParameter("UseAdaptiveThreshold", UseAdaptiveThreshold); + processor.SetParameter("VoidThresholdPercentile", VoidThresholdPercentile); processor.SetParameter("MinThreshold", MinThreshold); processor.SetParameter("MaxThreshold", MaxThreshold); processor.SetParameter("MinVoidArea", MinVoidArea); @@ -471,6 +487,10 @@ namespace XplorePlane.ViewModels.ImageProcessing parameters["BgaCircularity"] = Circularity; parameters["BgaThresholdLow"] = BgaThresholdLow; parameters["BgaThresholdHigh"] = BgaThresholdHigh; + parameters["BgaMinCenterDistance"] = BgaMinCenterDistance; + parameters["BgaAreaConsistency"] = BgaAreaConsistency; + parameters["UseAdaptiveThreshold"] = UseAdaptiveThreshold; + parameters["VoidThresholdPercentile"] = VoidThresholdPercentile; parameters["MinThreshold"] = MinThreshold; parameters["MaxThreshold"] = MaxThreshold; parameters["MinVoidArea"] = MinVoidArea; diff --git a/XplorePlane/Views/ImageProcessing/BgaDetectionPanel.xaml b/XplorePlane/Views/ImageProcessing/BgaDetectionPanel.xaml index 12cdfe09..58b4212b 100644 --- a/XplorePlane/Views/ImageProcessing/BgaDetectionPanel.xaml +++ b/XplorePlane/Views/ImageProcessing/BgaDetectionPanel.xaml @@ -114,10 +114,20 @@ - + + + + + + + + + + + @@ -125,6 +135,12 @@ + + + + + +