规范类名及命名空间名称
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
// ============================================================================
|
||||
// Copyright 穢 2026 Hexagon Technology Center GmbH. All Rights Reserved.
|
||||
// ��辣�? ContourProcessor.cs
|
||||
// �讛膩: 頧桀��交𪄳蝞堒�嚗𣬚鍂鈭擧�瘚见�����曉�銝剔�頧桀�
|
||||
// �蠘�:
|
||||
// - 璉�瘚见㦛�譍葉����刻蔭撱?
|
||||
// - �寞旿�Y妖��凒餈�誘頧桀�
|
||||
// - 霈∠�頧桀����雿閧鸌敺���Y妖��𪂹�踴��葉敹�����亦畆敶Y�嚗?
|
||||
// - 颲枏枂頧桀�靽⊥�靘𥕦�蝏剖���蝙�?
|
||||
// 蝞埈�: �箔�OpenCV��蔭撱𤘪�瘚讠�瘜?
|
||||
// 雿𡏭�? �𦒘� wei.lw.li@hexagon.com
|
||||
// ============================================================================
|
||||
|
||||
using Emgu.CV;
|
||||
using Emgu.CV.CvEnum;
|
||||
using Emgu.CV.Structure;
|
||||
using Emgu.CV.Util;
|
||||
using Serilog;
|
||||
using System.Drawing;
|
||||
using XP.ImageProcessing.Core;
|
||||
|
||||
namespace XP.ImageProcessing.Processors;
|
||||
|
||||
/// <summary>
|
||||
/// 頧桀��交𪄳蝞堒�
|
||||
/// </summary>
|
||||
public class ContourProcessor : ImageProcessorBase
|
||||
{
|
||||
private static readonly ILogger _logger = Log.ForContext<ContourProcessor>();
|
||||
|
||||
public ContourProcessor()
|
||||
{
|
||||
Name = LocalizationHelper.GetString("ContourProcessor_Name");
|
||||
Description = LocalizationHelper.GetString("ContourProcessor_Description");
|
||||
}
|
||||
|
||||
protected override void InitializeParameters()
|
||||
{
|
||||
Parameters.Add("TargetColor", new ProcessorParameter(
|
||||
"TargetColor",
|
||||
LocalizationHelper.GetString("ContourProcessor_TargetColor"),
|
||||
typeof(string),
|
||||
"White",
|
||||
null,
|
||||
null,
|
||||
LocalizationHelper.GetString("ContourProcessor_TargetColor_Desc"),
|
||||
new string[] { "White", "Black" }));
|
||||
|
||||
Parameters.Add("UseThreshold", new ProcessorParameter(
|
||||
"UseThreshold",
|
||||
LocalizationHelper.GetString("ContourProcessor_UseThreshold"),
|
||||
typeof(bool),
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
LocalizationHelper.GetString("ContourProcessor_UseThreshold_Desc")));
|
||||
|
||||
Parameters.Add("ThresholdValue", new ProcessorParameter(
|
||||
"ThresholdValue",
|
||||
LocalizationHelper.GetString("ContourProcessor_ThresholdValue"),
|
||||
typeof(int),
|
||||
120,
|
||||
0,
|
||||
255,
|
||||
LocalizationHelper.GetString("ContourProcessor_ThresholdValue_Desc")));
|
||||
|
||||
Parameters.Add("UseOtsu", new ProcessorParameter(
|
||||
"UseOtsu",
|
||||
LocalizationHelper.GetString("ContourProcessor_UseOtsu"),
|
||||
typeof(bool),
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
LocalizationHelper.GetString("ContourProcessor_UseOtsu_Desc")));
|
||||
|
||||
Parameters.Add("MinArea", new ProcessorParameter(
|
||||
"MinArea",
|
||||
LocalizationHelper.GetString("ContourProcessor_MinArea"),
|
||||
typeof(double),
|
||||
10.0,
|
||||
0.0,
|
||||
10000.0,
|
||||
LocalizationHelper.GetString("ContourProcessor_MinArea_Desc")));
|
||||
|
||||
Parameters.Add("MaxArea", new ProcessorParameter(
|
||||
"MaxArea",
|
||||
LocalizationHelper.GetString("ContourProcessor_MaxArea"),
|
||||
typeof(double),
|
||||
100000.0,
|
||||
0.0,
|
||||
1000000.0,
|
||||
LocalizationHelper.GetString("ContourProcessor_MaxArea_Desc")));
|
||||
|
||||
Parameters.Add("Thickness", new ProcessorParameter(
|
||||
"Thickness",
|
||||
LocalizationHelper.GetString("ContourProcessor_Thickness"),
|
||||
typeof(int),
|
||||
2,
|
||||
1,
|
||||
10,
|
||||
LocalizationHelper.GetString("ContourProcessor_Thickness_Desc")));
|
||||
_logger.Debug("InitializeParameters");
|
||||
}
|
||||
|
||||
public override Image<Gray, byte> Process(Image<Gray, byte> inputImage)
|
||||
{
|
||||
string targetColor = GetParameter<string>("TargetColor");
|
||||
bool useThreshold = GetParameter<bool>("UseThreshold");
|
||||
int thresholdValue = GetParameter<int>("ThresholdValue");
|
||||
bool useOtsu = GetParameter<bool>("UseOtsu");
|
||||
double minArea = GetParameter<double>("MinArea");
|
||||
double maxArea = GetParameter<double>("MaxArea");
|
||||
int thickness = GetParameter<int>("Thickness");
|
||||
|
||||
_logger.Debug("Process started: TargetColor = '{TargetColor}', UseThreshold = {UseThreshold}, ThresholdValue = {ThresholdValue}, UseOtsu = {UseOtsu}",
|
||||
targetColor, useThreshold, thresholdValue, useOtsu);
|
||||
|
||||
OutputData.Clear();
|
||||
|
||||
// �𥕦遣颲枏��曉�����祉鍂鈭𤾸��?
|
||||
Image<Gray, byte> processImage = inputImage.Clone();
|
||||
|
||||
// 甇仿炊1嚗𡁜��𨅯鍳�券��澆��莎����銵䔶��澆�
|
||||
if (useThreshold)
|
||||
{
|
||||
_logger.Debug("Applying threshold processing");
|
||||
Image<Gray, byte> thresholdImage = new Image<Gray, byte>(processImage.Size);
|
||||
|
||||
if (useOtsu)
|
||||
{
|
||||
// 雿輻鍂Otsu�芸𢆡���?
|
||||
CvInvoke.Threshold(processImage, thresholdImage, 0, 255, ThresholdType.Otsu);
|
||||
_logger.Debug("Applied Otsu threshold");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 雿輻鍂�箏����?
|
||||
CvInvoke.Threshold(processImage, thresholdImage, thresholdValue, 255, ThresholdType.Binary);
|
||||
_logger.Debug("Applied binary threshold with value {ThresholdValue}", thresholdValue);
|
||||
}
|
||||
|
||||
// 靽嘥����澆������㦛�讐鍂鈭舘�霂?
|
||||
try
|
||||
{
|
||||
string debugPath = Path.Combine("logs", $"contour_threshold_{DateTime.Now:yyyyMMdd_HHmmss}.png");
|
||||
Directory.CreateDirectory("logs");
|
||||
thresholdImage.Save(debugPath);
|
||||
_logger.Information("Saved threshold image to: {DebugPath}", debugPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning(ex, "Failed to save threshold image for debugging");
|
||||
}
|
||||
|
||||
processImage.Dispose();
|
||||
processImage = thresholdImage;
|
||||
}
|
||||
|
||||
// 甇仿炊2嚗𡁜��𦦵𤌍��糓暺𤏸𠧧�箏�嚗屸�閬��頧砍㦛�?
|
||||
bool isBlackTarget = targetColor != null &&
|
||||
(targetColor.Equals("Black", StringComparison.OrdinalIgnoreCase) ||
|
||||
targetColor.Equals("暺𤏸𠧧", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (isBlackTarget)
|
||||
{
|
||||
_logger.Debug("Inverting image for black region detection");
|
||||
CvInvoke.BitwiseNot(processImage, processImage);
|
||||
|
||||
// 靽嘥�蝧餉蓮�𡒊��曉��其�靚��
|
||||
try
|
||||
{
|
||||
string debugPath = Path.Combine("logs", $"contour_inverted_{DateTime.Now:yyyyMMdd_HHmmss}.png");
|
||||
Directory.CreateDirectory("logs");
|
||||
processImage.Save(debugPath);
|
||||
_logger.Information("Saved inverted image to: {DebugPath}", debugPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning(ex, "Failed to save inverted image for debugging");
|
||||
}
|
||||
}
|
||||
|
||||
// 甇仿炊3嚗𡁏䰻�曇蔭撱?
|
||||
using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint())
|
||||
{
|
||||
Mat hierarchy = new Mat();
|
||||
CvInvoke.FindContours(processImage, contours, hierarchy, RetrType.External, ChainApproxMethod.ChainApproxSimple);
|
||||
|
||||
_logger.Debug("Found {TotalContours} total contours before filtering", contours.Size);
|
||||
|
||||
List<ContourInfo> contourInfos = new();
|
||||
|
||||
for (int i = 0; i < contours.Size; i++)
|
||||
{
|
||||
double area = CvInvoke.ContourArea(contours[i]);
|
||||
if (area >= minArea && area <= maxArea)
|
||||
{
|
||||
var moments = CvInvoke.Moments(contours[i]);
|
||||
var boundingRect = CvInvoke.BoundingRectangle(contours[i]);
|
||||
double perimeter = CvInvoke.ArcLength(contours[i], true);
|
||||
var circle = CvInvoke.MinEnclosingCircle(contours[i]);
|
||||
|
||||
contourInfos.Add(new ContourInfo
|
||||
{
|
||||
Index = i,
|
||||
Area = area,
|
||||
Perimeter = perimeter,
|
||||
CenterX = moments.M10 / moments.M00,
|
||||
CenterY = moments.M01 / moments.M00,
|
||||
BoundingBox = boundingRect,
|
||||
Points = contours[i].ToArray(),
|
||||
CircleCenter = circle.Center,
|
||||
CircleRadius = circle.Radius
|
||||
});
|
||||
|
||||
_logger.Debug("Contour {Index}: Area = {Area}, Center = ({CenterX:F2}, {CenterY:F2})",
|
||||
i, area, moments.M10 / moments.M00, moments.M01 / moments.M00);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug("Contour {Index} filtered out: Area = {Area} (not in range {MinArea} - {MaxArea})",
|
||||
i, area, minArea, maxArea);
|
||||
}
|
||||
}
|
||||
|
||||
OutputData["ContourCount"] = contourInfos.Count;
|
||||
OutputData["Contours"] = contourInfos;
|
||||
OutputData["Thickness"] = thickness;
|
||||
|
||||
hierarchy.Dispose();
|
||||
processImage.Dispose();
|
||||
|
||||
_logger.Information("Process completed: TargetColor = '{TargetColor}', Found {ContourCount} contours (filtered from {TotalContours})",
|
||||
targetColor, contourInfos.Count, contours.Size);
|
||||
return inputImage.Clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 頧桀�靽⊥�
|
||||
/// </summary>
|
||||
public class ContourInfo
|
||||
{
|
||||
public int Index { get; set; }
|
||||
public double Area { get; set; }
|
||||
public double Perimeter { get; set; }
|
||||
public double CenterX { get; set; }
|
||||
public double CenterY { get; set; }
|
||||
public Rectangle BoundingBox { get; set; }
|
||||
public Point[] Points { get; set; } = Array.Empty<Point>();
|
||||
public PointF CircleCenter { get; set; }
|
||||
public float CircleRadius { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user