// ============================================================================ // Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. // 文件名: PointToLineProcessor.cs // 描述: 点到直线距离测量算子 // 功能: // - 用户定义一条直线(两个端点)和一个测量点 // - 计算测量点到直线的垂直距离 // - 支持像素尺寸标定输出物理距离 // - 在图像上绘制直线、测量点、垂足和距离标注 // 算法: 点到直线距离公式 // 作者: 李伟 wei.lw.li@hexagon.com // ============================================================================ using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; using XP.ImageProcessing.Core; using Serilog; using System.Drawing; namespace XP.ImageProcessing.Processors; public class PointToLineProcessor : ImageProcessorBase { private static readonly ILogger _logger = Log.ForContext(); public PointToLineProcessor() { Name = LocalizationHelper.GetString("PointToLineProcessor_Name"); Description = LocalizationHelper.GetString("PointToLineProcessor_Description"); } protected override void InitializeParameters() { // 直线两端点 + 测量点(由交互控件注入) Parameters.Add("L1X", new ProcessorParameter("L1X", "L1X", typeof(int), 100, null, null, "") { IsVisible = false }); Parameters.Add("L1Y", new ProcessorParameter("L1Y", "L1Y", typeof(int), 200, null, null, "") { IsVisible = false }); Parameters.Add("L2X", new ProcessorParameter("L2X", "L2X", typeof(int), 400, null, null, "") { IsVisible = false }); Parameters.Add("L2Y", new ProcessorParameter("L2Y", "L2Y", typeof(int), 200, null, null, "") { IsVisible = false }); Parameters.Add("PX", new ProcessorParameter("PX", "PX", typeof(int), 250, null, null, "") { IsVisible = false }); Parameters.Add("PY", new ProcessorParameter("PY", "PY", typeof(int), 100, null, null, "") { IsVisible = false }); Parameters.Add("PixelSize", new ProcessorParameter( "PixelSize", LocalizationHelper.GetString("PointToLineProcessor_PixelSize"), typeof(double), 1.0, null, null, LocalizationHelper.GetString("PointToLineProcessor_PixelSize_Desc"))); Parameters.Add("Unit", new ProcessorParameter( "Unit", LocalizationHelper.GetString("PointToLineProcessor_Unit"), typeof(string), "px", null, null, LocalizationHelper.GetString("PointToLineProcessor_Unit_Desc"), new string[] { "px", "mm", "μm", "cm" })); Parameters.Add("Thickness", new ProcessorParameter( "Thickness", LocalizationHelper.GetString("PointToLineProcessor_Thickness"), typeof(int), 2, 1, 10, LocalizationHelper.GetString("PointToLineProcessor_Thickness_Desc"))); } public override Image Process(Image inputImage) { int l1x = GetParameter("L1X"), l1y = GetParameter("L1Y"); int l2x = GetParameter("L2X"), l2y = GetParameter("L2Y"); int px = GetParameter("PX"), py = GetParameter("PY"); double pixelSize = GetParameter("PixelSize"); string unit = GetParameter("Unit"); int thickness = GetParameter("Thickness"); OutputData.Clear(); // 点到直线距离公式: |AB × AP| / |AB| double abx = l2x - l1x, aby = l2y - l1y; double abLen = Math.Sqrt(abx * abx + aby * aby); double pixelDistance = 0; int footX = px, footY = py; if (abLen > 0.001) { // 叉积求距离 double cross = Math.Abs(abx * (l1y - py) - aby * (l1x - px)); pixelDistance = cross / abLen; // 垂足: 投影参数 t = AP·AB / |AB|² double apx = px - l1x, apy = py - l1y; double t = (apx * abx + apy * aby) / (abLen * abLen); footX = (int)(l1x + t * abx); footY = (int)(l1y + t * aby); OutputData["ProjectionT"] = t; } double actualDistance = pixelDistance * pixelSize; string distanceText = unit == "px" ? $"{pixelDistance:F2} px" : $"{actualDistance:F4} {unit} ({pixelDistance:F2} px)"; OutputData["PointToLineResult"] = true; OutputData["Line1"] = new Point(l1x, l1y); OutputData["Line2"] = new Point(l2x, l2y); OutputData["MeasurePoint"] = new Point(px, py); OutputData["FootPoint"] = new Point(footX, footY); OutputData["PixelDistance"] = pixelDistance; OutputData["ActualDistance"] = actualDistance; OutputData["Unit"] = unit; OutputData["Thickness"] = thickness; OutputData["DistanceText"] = distanceText; _logger.Information("PointToLine: Distance={Dist}, Foot=({FX},{FY})", distanceText, footX, footY); return inputImage.Clone(); } }