合并图像处理库,删除图像lib库
This commit is contained in:
@@ -0,0 +1,303 @@
|
||||
// ============================================================================
|
||||
// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved.
|
||||
// 文件名: EllipseDetectionProcessor.cs
|
||||
// 描述: 椭圆检测算子,基于轮廓分析和椭圆拟合检测图像中的椭圆
|
||||
// 功能:
|
||||
// - 阈值分割 + 轮廓提取
|
||||
// - 椭圆拟合(FitEllipse)
|
||||
// - 面积/轴长/离心率/拟合误差多维过滤
|
||||
// - 支持双阈值分割和 Otsu 自动阈值
|
||||
// 算法: 阈值分割 + OpenCV FitEllipse
|
||||
// 作者: 李伟 wei.lw.li@hexagon.com
|
||||
// ============================================================================
|
||||
|
||||
using Emgu.CV;
|
||||
using Emgu.CV.CvEnum;
|
||||
using Emgu.CV.Structure;
|
||||
using Emgu.CV.Util;
|
||||
using ImageProcessing.Core;
|
||||
using Serilog;
|
||||
using System.Drawing;
|
||||
|
||||
namespace ImageProcessing.Processors;
|
||||
|
||||
/// <summary>
|
||||
/// 椭圆检测结果
|
||||
/// </summary>
|
||||
public class EllipseInfo
|
||||
{
|
||||
/// <summary>序号</summary>
|
||||
public int Index { get; set; }
|
||||
/// <summary>中心点X</summary>
|
||||
public float CenterX { get; set; }
|
||||
/// <summary>中心点Y</summary>
|
||||
public float CenterY { get; set; }
|
||||
/// <summary>长轴长度</summary>
|
||||
public float MajorAxis { get; set; }
|
||||
/// <summary>短轴长度</summary>
|
||||
public float MinorAxis { get; set; }
|
||||
/// <summary>旋转角度(度)</summary>
|
||||
public float Angle { get; set; }
|
||||
/// <summary>面积</summary>
|
||||
public double Area { get; set; }
|
||||
/// <summary>周长</summary>
|
||||
public double Perimeter { get; set; }
|
||||
/// <summary>离心率 (0=圆, 接近1=扁椭圆)</summary>
|
||||
public double Eccentricity { get; set; }
|
||||
/// <summary>拟合误差(像素)</summary>
|
||||
public double FitError { get; set; }
|
||||
/// <summary>轮廓点集</summary>
|
||||
public Point[] ContourPoints { get; set; } = Array.Empty<Point>();
|
||||
/// <summary>外接矩形</summary>
|
||||
public Rectangle BoundingBox { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 椭圆检测器
|
||||
/// </summary>
|
||||
public class EllipseDetector
|
||||
{
|
||||
private static readonly ILogger _logger = Log.ForContext<EllipseDetector>();
|
||||
|
||||
public int MinThreshold { get; set; } = 64;
|
||||
public int MaxThreshold { get; set; } = 192;
|
||||
public bool UseOtsu { get; set; } = false;
|
||||
public int MinContourPoints { get; set; } = 30;
|
||||
public double MinArea { get; set; } = 100;
|
||||
public double MaxArea { get; set; } = 1000000;
|
||||
public float MinMajorAxis { get; set; } = 10;
|
||||
public double MaxEccentricity { get; set; } = 0.95;
|
||||
public double MaxFitError { get; set; } = 5.0;
|
||||
public int Thickness { get; set; } = 2;
|
||||
|
||||
/// <summary>执行椭圆检测</summary>
|
||||
public List<EllipseInfo> Detect(Image<Gray, byte> inputImage, Image<Gray, byte>? roiMask = null)
|
||||
{
|
||||
_logger.Debug("Ellipse detection started: UseOtsu={UseOtsu}, MinThreshold={Min}, MaxThreshold={Max}",
|
||||
UseOtsu, MinThreshold, MaxThreshold);
|
||||
var results = new List<EllipseInfo>();
|
||||
|
||||
using var binary = new Image<Gray, byte>(inputImage.Size);
|
||||
|
||||
if (UseOtsu)
|
||||
{
|
||||
CvInvoke.Threshold(inputImage, binary, MinThreshold, 255, ThresholdType.Otsu);
|
||||
_logger.Debug("Using Otsu auto threshold");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 双阈值分割:介于MinThreshold和MaxThreshold之间的为前景(255),其他为背景(0)
|
||||
byte[,,] inputData = inputImage.Data;
|
||||
byte[,,] outputData = binary.Data;
|
||||
int height = inputImage.Height;
|
||||
int width = inputImage.Width;
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
byte pixelValue = inputData[y, x, 0];
|
||||
outputData[y, x, 0] = (pixelValue >= MinThreshold && pixelValue <= MaxThreshold)
|
||||
? (byte)255
|
||||
: (byte)0;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("Dual threshold segmentation: MinThreshold={Min}, MaxThreshold={Max}", MinThreshold, MaxThreshold);
|
||||
}
|
||||
|
||||
// 应用ROI掩码
|
||||
if (roiMask != null)
|
||||
{
|
||||
CvInvoke.BitwiseAnd(binary, roiMask, binary);
|
||||
}
|
||||
|
||||
using var contours = new VectorOfVectorOfPoint();
|
||||
using var hierarchy = new Mat();
|
||||
CvInvoke.FindContours(binary, contours, hierarchy, RetrType.List, ChainApproxMethod.ChainApproxNone);
|
||||
_logger.Debug("Found {Count} contours", contours.Size);
|
||||
|
||||
int index = 0;
|
||||
for (int i = 0; i < contours.Size; i++)
|
||||
{
|
||||
var contour = contours[i];
|
||||
if (contour.Size < Math.Max(5, MinContourPoints)) continue;
|
||||
|
||||
double area = CvInvoke.ContourArea(contour);
|
||||
if (area < MinArea || area > MaxArea) continue;
|
||||
|
||||
RotatedRect ellipseRect = CvInvoke.FitEllipse(contour);
|
||||
float majorAxis = Math.Max(ellipseRect.Size.Width, ellipseRect.Size.Height);
|
||||
float minorAxis = Math.Min(ellipseRect.Size.Width, ellipseRect.Size.Height);
|
||||
if (majorAxis < MinMajorAxis) continue;
|
||||
|
||||
double eccentricity = 0;
|
||||
if (majorAxis > 0)
|
||||
{
|
||||
double ratio = minorAxis / majorAxis;
|
||||
eccentricity = Math.Sqrt(1.0 - ratio * ratio);
|
||||
}
|
||||
if (eccentricity > MaxEccentricity) continue;
|
||||
|
||||
double fitError = ComputeFitError(contour.ToArray(), ellipseRect);
|
||||
if (fitError > MaxFitError) continue;
|
||||
|
||||
results.Add(new EllipseInfo
|
||||
{
|
||||
Index = index++,
|
||||
CenterX = ellipseRect.Center.X,
|
||||
CenterY = ellipseRect.Center.Y,
|
||||
MajorAxis = majorAxis,
|
||||
MinorAxis = minorAxis,
|
||||
Angle = ellipseRect.Angle,
|
||||
Area = area,
|
||||
Perimeter = CvInvoke.ArcLength(contour, true),
|
||||
Eccentricity = eccentricity,
|
||||
FitError = fitError,
|
||||
ContourPoints = contour.ToArray(),
|
||||
BoundingBox = CvInvoke.BoundingRectangle(contour)
|
||||
});
|
||||
}
|
||||
|
||||
_logger.Information("Ellipse detection completed: detected {Count} ellipses", results.Count);
|
||||
return results;
|
||||
}
|
||||
|
||||
private static double ComputeFitError(Point[] contourPoints, RotatedRect ellipse)
|
||||
{
|
||||
double cx = ellipse.Center.X, cy = ellipse.Center.Y;
|
||||
double a = Math.Max(ellipse.Size.Width, ellipse.Size.Height) / 2.0;
|
||||
double b = Math.Min(ellipse.Size.Width, ellipse.Size.Height) / 2.0;
|
||||
double angleRad = ellipse.Angle * Math.PI / 180.0;
|
||||
double cosA = Math.Cos(angleRad), sinA = Math.Sin(angleRad);
|
||||
if (a < 1e-6) return double.MaxValue;
|
||||
|
||||
double totalError = 0;
|
||||
foreach (var pt in contourPoints)
|
||||
{
|
||||
double dx = pt.X - cx, dy = pt.Y - cy;
|
||||
double localX = dx * cosA + dy * sinA;
|
||||
double localY = -dx * sinA + dy * cosA;
|
||||
double ellipseVal = (localX * localX) / (a * a) + (localY * localY) / (b * b);
|
||||
totalError += Math.Abs(Math.Sqrt(ellipseVal) - 1.0) * Math.Sqrt(a * b);
|
||||
}
|
||||
return totalError / contourPoints.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 椭圆检测算子
|
||||
/// </summary>
|
||||
public class EllipseDetectionProcessor : ImageProcessorBase
|
||||
{
|
||||
private static readonly ILogger _logger = Log.ForContext<EllipseDetectionProcessor>();
|
||||
|
||||
public EllipseDetectionProcessor()
|
||||
{
|
||||
Name = LocalizationHelper.GetString("EllipseDetectionProcessor_Name");
|
||||
Description = LocalizationHelper.GetString("EllipseDetectionProcessor_Description");
|
||||
}
|
||||
|
||||
protected override void InitializeParameters()
|
||||
{
|
||||
// ── 多边形ROI(由UI注入,最多32个点) ──
|
||||
Parameters.Add("PolyCount", new ProcessorParameter("PolyCount", "PolyCount", typeof(int), 0, null, null, "") { IsVisible = false });
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
Parameters.Add($"PolyX{i}", new ProcessorParameter($"PolyX{i}", $"PolyX{i}", typeof(int), 0, null, null, "") { IsVisible = false });
|
||||
Parameters.Add($"PolyY{i}", new ProcessorParameter($"PolyY{i}", $"PolyY{i}", typeof(int), 0, null, null, "") { IsVisible = false });
|
||||
}
|
||||
|
||||
Parameters.Add("MinThreshold", new ProcessorParameter(
|
||||
"MinThreshold", LocalizationHelper.GetString("EllipseDetectionProcessor_MinThreshold"),
|
||||
typeof(int), 64, 0, 255,
|
||||
LocalizationHelper.GetString("EllipseDetectionProcessor_MinThreshold_Desc")));
|
||||
|
||||
Parameters.Add("MaxThreshold", new ProcessorParameter(
|
||||
"MaxThreshold", LocalizationHelper.GetString("EllipseDetectionProcessor_MaxThreshold"),
|
||||
typeof(int), 192, 0, 255,
|
||||
LocalizationHelper.GetString("EllipseDetectionProcessor_MaxThreshold_Desc")));
|
||||
|
||||
Parameters.Add("UseOtsu", new ProcessorParameter(
|
||||
"UseOtsu", LocalizationHelper.GetString("EllipseDetectionProcessor_UseOtsu"),
|
||||
typeof(bool), false, null, null,
|
||||
LocalizationHelper.GetString("EllipseDetectionProcessor_UseOtsu_Desc")));
|
||||
|
||||
Parameters.Add("MinContourPoints", new ProcessorParameter(
|
||||
"MinContourPoints", LocalizationHelper.GetString("EllipseDetectionProcessor_MinContourPoints"),
|
||||
typeof(int), 30, 5, 1000,
|
||||
LocalizationHelper.GetString("EllipseDetectionProcessor_MinContourPoints_Desc")));
|
||||
|
||||
Parameters.Add("MinArea", new ProcessorParameter(
|
||||
"MinArea", LocalizationHelper.GetString("EllipseDetectionProcessor_MinArea"),
|
||||
typeof(double), 100.0, 0.0, 1000000.0,
|
||||
LocalizationHelper.GetString("EllipseDetectionProcessor_MinArea_Desc")));
|
||||
|
||||
Parameters.Add("MaxArea", new ProcessorParameter(
|
||||
"MaxArea", LocalizationHelper.GetString("EllipseDetectionProcessor_MaxArea"),
|
||||
typeof(double), 1000000.0, 0.0, 10000000.0,
|
||||
LocalizationHelper.GetString("EllipseDetectionProcessor_MaxArea_Desc")));
|
||||
|
||||
Parameters.Add("MaxEccentricity", new ProcessorParameter(
|
||||
"MaxEccentricity", LocalizationHelper.GetString("EllipseDetectionProcessor_MaxEccentricity"),
|
||||
typeof(double), 0.95, 0.0, 1.0,
|
||||
LocalizationHelper.GetString("EllipseDetectionProcessor_MaxEccentricity_Desc")));
|
||||
|
||||
Parameters.Add("MaxFitError", new ProcessorParameter(
|
||||
"MaxFitError", LocalizationHelper.GetString("EllipseDetectionProcessor_MaxFitError"),
|
||||
typeof(double), 5.0, 0.0, 50.0,
|
||||
LocalizationHelper.GetString("EllipseDetectionProcessor_MaxFitError_Desc")));
|
||||
|
||||
Parameters.Add("Thickness", new ProcessorParameter(
|
||||
"Thickness", LocalizationHelper.GetString("EllipseDetectionProcessor_Thickness"),
|
||||
typeof(int), 2, 1, 10,
|
||||
LocalizationHelper.GetString("EllipseDetectionProcessor_Thickness_Desc")));
|
||||
|
||||
_logger.Debug("InitializeParameters");
|
||||
}
|
||||
|
||||
public override Image<Gray, byte> Process(Image<Gray, byte> inputImage)
|
||||
{
|
||||
int thickness = GetParameter<int>("Thickness");
|
||||
|
||||
_logger.Debug("Ellipse detection started");
|
||||
OutputData.Clear();
|
||||
|
||||
// 构建多边形ROI掩码
|
||||
int polyCount = GetParameter<int>("PolyCount");
|
||||
Image<Gray, byte>? roiMask = null;
|
||||
if (polyCount >= 3)
|
||||
{
|
||||
var pts = new Point[polyCount];
|
||||
for (int i = 0; i < polyCount; i++)
|
||||
pts[i] = new Point(GetParameter<int>($"PolyX{i}"), GetParameter<int>($"PolyY{i}"));
|
||||
roiMask = new Image<Gray, byte>(inputImage.Width, inputImage.Height);
|
||||
using var vop = new VectorOfPoint(pts);
|
||||
using var vvop = new VectorOfVectorOfPoint(vop);
|
||||
CvInvoke.DrawContours(roiMask, vvop, 0, new MCvScalar(255), -1);
|
||||
}
|
||||
|
||||
var detector = new EllipseDetector
|
||||
{
|
||||
MinThreshold = GetParameter<int>("MinThreshold"),
|
||||
MaxThreshold = GetParameter<int>("MaxThreshold"),
|
||||
UseOtsu = GetParameter<bool>("UseOtsu"),
|
||||
MinContourPoints = GetParameter<int>("MinContourPoints"),
|
||||
MinArea = GetParameter<double>("MinArea"),
|
||||
MaxArea = GetParameter<double>("MaxArea"),
|
||||
MaxEccentricity = GetParameter<double>("MaxEccentricity"),
|
||||
MaxFitError = GetParameter<double>("MaxFitError"),
|
||||
Thickness = thickness
|
||||
};
|
||||
|
||||
var ellipses = detector.Detect(inputImage, roiMask);
|
||||
|
||||
OutputData["Ellipses"] = ellipses;
|
||||
OutputData["EllipseCount"] = ellipses.Count;
|
||||
OutputData["Thickness"] = thickness;
|
||||
|
||||
roiMask?.Dispose();
|
||||
_logger.Information("Ellipse detection completed: detected {Count} ellipses", ellipses.Count);
|
||||
return inputImage.Clone();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user