白底/黑底检测:轮廓与最远弦度量,UI 分色与标注优化
- 算子:输出轮廓顶点及顶点间最远弦(微米标定与原先一致) - 视图:实线轮廓;白底红/黑底绿;尺寸文字置于 ROI 外右侧垂直居中 - 事件与 MainViewModel 载荷改为 BackgroundDefectDetectionItem Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,10 +2,11 @@
|
||||
// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved.
|
||||
// 文件名: BackgroundDefectAnalyzer.cs
|
||||
// 描述: 白底/黑底对比下的缺陷斑点分析(仅 ROI 内计算,不接入流水线算子)
|
||||
// 算法: Otsu 二值化 → 形态学开运算 → 外轮廓 → 面积过滤 → 外接矩形等效圆
|
||||
// 算法: Otsu 二值化 → 形态学开运算 → 外轮廓 → 面积过滤 → 轮廓顶点最远弦(物理长度与历史等效直径同一标定:mm/px → μm)
|
||||
// 作者: 李伟 wei.lw.li@hexagon.com
|
||||
// ============================================================================
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using Emgu.CV;
|
||||
using Emgu.CV.CvEnum;
|
||||
@@ -27,9 +28,15 @@ public enum BackgroundDefectMode
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单个斑点,坐标相对于输入 ROI 左上角;<see cref="SizeMicrometers"/> 与主界面标注逻辑一致(等效直径,微米)。
|
||||
/// 单个斑点:轮廓顶点相对于 ROI 左上角;<see cref="MaxChordMicrometers"/> 为轮廓顶点间欧氏距离最大值(微米)。
|
||||
/// </summary>
|
||||
public readonly record struct BackgroundDefectBlob(Point CenterInRoi, int RadiusPixels, double SizeMicrometers);
|
||||
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"/>。
|
||||
@@ -42,7 +49,7 @@ public static class BackgroundDefectAnalyzer
|
||||
/// <param name="roiGray">ROI 灰度图(单通道 8 位)。</param>
|
||||
/// <param name="mode">白底或黑底模式。</param>
|
||||
/// <param name="minAreaPixels">轮廓最小面积(像素²),小于此值的轮廓丢弃。</param>
|
||||
/// <param name="mmPerPixel">像素物理尺寸(毫米/像素),用于等效直径换算。</param>
|
||||
/// <param name="mmPerPixel">像素物理尺寸(毫米/像素),用于轮廓最远弦换算为微米。</param>
|
||||
/// <param name="morphKernelSize">形态学开运算核尺寸(奇数,默认 3)。</param>
|
||||
public static List<BackgroundDefectBlob> DetectBlobs(
|
||||
Image<Gray, byte> roiGray,
|
||||
@@ -84,19 +91,56 @@ public static class BackgroundDefectAnalyzer
|
||||
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);
|
||||
int n = contours[i].Size;
|
||||
if (n < 2) continue;
|
||||
|
||||
var centerInRoi = new Point((int)centerF.X, (int)centerF.Y);
|
||||
int radiusPx = (int)radiusF;
|
||||
double sizeMicrometers = radiusF * 2.0 * mmPerPixel * 1000.0;
|
||||
var pts = new Point[n];
|
||||
for (int j = 0; j < n; j++)
|
||||
pts[j] = contours[i][j];
|
||||
|
||||
result.Add(new BackgroundDefectBlob(centerInRoi, radiusPx, sizeMicrometers));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user