82465e6510
- 算子:输出轮廓顶点及顶点间最远弦(微米标定与原先一致) - 视图:实线轮廓;白底红/黑底绿;尺寸文字置于 ROI 外右侧垂直居中 - 事件与 MainViewModel 载荷改为 BackgroundDefectDetectionItem Co-authored-by: Cursor <cursoragent@cursor.com>
147 lines
5.5 KiB
C#
147 lines
5.5 KiB
C#
// ============================================================================
|
|
// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved.
|
|
// 文件名: BackgroundDefectAnalyzer.cs
|
|
// 描述: 白底/黑底对比下的缺陷斑点分析(仅 ROI 内计算,不接入流水线算子)
|
|
// 算法: Otsu 二值化 → 形态学开运算 → 外轮廓 → 面积过滤 → 轮廓顶点最远弦(物理长度与历史等效直径同一标定:mm/px → μm)
|
|
// 作者: 李伟 wei.lw.li@hexagon.com
|
|
// ============================================================================
|
|
|
|
using System.Collections.Generic;
|
|
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="MaxChordMicrometers"/> 为轮廓顶点间欧氏距离最大值(微米)。
|
|
/// </summary>
|
|
public sealed class BackgroundDefectBlob
|
|
{
|
|
public Point[] ContourInRoi { get; init; } = Array.Empty<Point>();
|
|
public double MaxChordMicrometers { get; init; }
|
|
public Point MaxChordEndAInRoi { get; init; }
|
|
public Point MaxChordEndBInRoi { get; init; }
|
|
}
|
|
|
|
/// <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;
|
|
|
|
int n = contours[i].Size;
|
|
if (n < 2) continue;
|
|
|
|
var pts = new Point[n];
|
|
for (int j = 0; j < n; j++)
|
|
pts[j] = contours[i][j];
|
|
|
|
MaxChordInPixelSpace(pts, out double maxChordPx, out Point pa, out Point pb);
|
|
double maxChordMicrometers = maxChordPx * mmPerPixel * 1000.0;
|
|
|
|
result.Add(new BackgroundDefectBlob
|
|
{
|
|
ContourInRoi = pts,
|
|
MaxChordMicrometers = maxChordMicrometers,
|
|
MaxChordEndAInRoi = pa,
|
|
MaxChordEndBInRoi = pb
|
|
});
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>轮廓顶点集合上的最远点对(欧氏距离,像素)。</summary>
|
|
private static void MaxChordInPixelSpace(Point[] pts, out double maxChordPx, out Point a, out Point b)
|
|
{
|
|
maxChordPx = 0;
|
|
a = pts[0];
|
|
b = pts.Length > 1 ? pts[1] : pts[0];
|
|
long bestSq = 0;
|
|
int bestI = 0, bestJ = 1;
|
|
int n = pts.Length;
|
|
for (int i = 0; i < n; i++)
|
|
{
|
|
int iX = pts[i].X, iY = pts[i].Y;
|
|
for (int j = i + 1; j < n; j++)
|
|
{
|
|
long dx = iX - pts[j].X;
|
|
long dy = iY - pts[j].Y;
|
|
long sq = dx * dx + dy * dy;
|
|
if (sq > bestSq)
|
|
{
|
|
bestSq = sq;
|
|
bestI = i;
|
|
bestJ = j;
|
|
}
|
|
}
|
|
}
|
|
|
|
a = pts[bestI];
|
|
b = pts[bestJ];
|
|
maxChordPx = Math.Sqrt(bestSq);
|
|
}
|
|
}
|