refactor: 白底/黑底检测算法迁至 BackgroundDefectAnalyzer
- 在 XP.ImageProcessing.Processors 新增静态分析类与 BackgroundDefectMode/BackgroundDefectBlob。 - MainViewModel 仅负责灰度 ROI 提取、坐标平移与 Prism 事件发布。
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
// ============================================================================
|
||||
// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved.
|
||||
// 文件名: BackgroundDefectAnalyzer.cs
|
||||
// 描述: 白底/黑底对比下的缺陷斑点分析(仅 ROI 内计算,不接入流水线算子)
|
||||
// 算法: Otsu 二值化 → 形态学开运算 → 外轮廓 → 面积过滤 → 外接矩形等效圆
|
||||
// 作者: 李伟 wei.lw.li@hexagon.com
|
||||
// ============================================================================
|
||||
|
||||
using System.Drawing;
|
||||
using Emgu.CV;
|
||||
using Emgu.CV.CvEnum;
|
||||
using Emgu.CV.Structure;
|
||||
using Emgu.CV.Util;
|
||||
|
||||
namespace XP.ImageProcessing.Processors;
|
||||
|
||||
/// <summary>
|
||||
/// 底色类型:决定 Otsu 后保留的前景是暗区还是亮区。
|
||||
/// </summary>
|
||||
public enum BackgroundDefectMode
|
||||
{
|
||||
/// <summary>白底图像上检测偏暗区域(BinaryInv + Otsu)。</summary>
|
||||
WhiteBackground,
|
||||
|
||||
/// <summary>黑底图像上检测偏亮区域(Binary + Otsu)。</summary>
|
||||
BlackBackground
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单个斑点,坐标相对于输入 ROI 左上角;<see cref="SizeMicrometers"/> 与主界面标注逻辑一致(等效直径,微米)。
|
||||
/// </summary>
|
||||
public readonly record struct BackgroundDefectBlob(Point CenterInRoi, int RadiusPixels, double SizeMicrometers);
|
||||
|
||||
/// <summary>
|
||||
/// 在灰度 ROI 上执行底色缺陷斑点检测。调用方负责构造与释放 <paramref name="roiGray"/>。
|
||||
/// </summary>
|
||||
public static class BackgroundDefectAnalyzer
|
||||
{
|
||||
/// <summary>
|
||||
/// 在 ROI 灰度图上检测斑点。
|
||||
/// </summary>
|
||||
/// <param name="roiGray">ROI 灰度图(单通道 8 位)。</param>
|
||||
/// <param name="mode">白底或黑底模式。</param>
|
||||
/// <param name="minAreaPixels">轮廓最小面积(像素²),小于此值的轮廓丢弃。</param>
|
||||
/// <param name="mmPerPixel">像素物理尺寸(毫米/像素),用于等效直径换算。</param>
|
||||
/// <param name="morphKernelSize">形态学开运算核尺寸(奇数,默认 3)。</param>
|
||||
public static List<BackgroundDefectBlob> DetectBlobs(
|
||||
Image<Gray, byte> roiGray,
|
||||
BackgroundDefectMode mode,
|
||||
int minAreaPixels = 50,
|
||||
double mmPerPixel = 0.139,
|
||||
int morphKernelSize = 3)
|
||||
{
|
||||
if (roiGray == null) throw new ArgumentNullException(nameof(roiGray));
|
||||
if (minAreaPixels < 1) minAreaPixels = 1;
|
||||
if (mmPerPixel <= 0) mmPerPixel = 0.139;
|
||||
if (morphKernelSize < 1) morphKernelSize = 1;
|
||||
if ((morphKernelSize & 1) == 0) morphKernelSize++;
|
||||
|
||||
int rw = roiGray.Width;
|
||||
int rh = roiGray.Height;
|
||||
if (rw < 1 || rh < 1) return new List<BackgroundDefectBlob>();
|
||||
|
||||
var thresholdType = mode == BackgroundDefectMode.WhiteBackground
|
||||
? ThresholdType.BinaryInv | ThresholdType.Otsu
|
||||
: ThresholdType.Binary | ThresholdType.Otsu;
|
||||
|
||||
using var binary = new Image<Gray, byte>(rw, rh);
|
||||
CvInvoke.Threshold(roiGray, binary, 0, 255, thresholdType);
|
||||
|
||||
using var kernel = CvInvoke.GetStructuringElement(
|
||||
ElementShape.Ellipse, new Size(morphKernelSize, morphKernelSize), new Point(-1, -1));
|
||||
CvInvoke.MorphologyEx(binary, binary, MorphOp.Open, kernel, new Point(-1, -1), 1,
|
||||
BorderType.Default, new MCvScalar(0));
|
||||
|
||||
using var contours = new VectorOfVectorOfPoint();
|
||||
using var hierarchy = new Mat();
|
||||
CvInvoke.FindContours(binary, contours, hierarchy, RetrType.External, ChainApproxMethod.ChainApproxSimple);
|
||||
|
||||
var result = new List<BackgroundDefectBlob>();
|
||||
|
||||
for (int i = 0; i < contours.Size; i++)
|
||||
{
|
||||
double area = CvInvoke.ContourArea(contours[i]);
|
||||
if (area < minAreaPixels) continue;
|
||||
|
||||
var boundRect = CvInvoke.BoundingRectangle(contours[i]);
|
||||
double radiusF = Math.Max(boundRect.Width, boundRect.Height) / 2.0;
|
||||
var centerF = new PointF(
|
||||
boundRect.X + boundRect.Width / 2.0f,
|
||||
boundRect.Y + boundRect.Height / 2.0f);
|
||||
|
||||
var centerInRoi = new Point((int)centerF.X, (int)centerF.Y);
|
||||
int radiusPx = (int)radiusF;
|
||||
double sizeMicrometers = radiusF * 2.0 * mmPerPixel * 1000.0;
|
||||
|
||||
result.Add(new BackgroundDefectBlob(centerInRoi, radiusPx, sizeMicrometers));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user