合并图像处理库,删除图像lib库
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
// ============================================================================
|
||||
// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved.
|
||||
// 文件名: ContourProcessor.cs
|
||||
// 描述: 轮廓查找算子,用于检测和分析图像中的轮廓
|
||||
// 功能:
|
||||
// - 检测图像中的外部轮廓
|
||||
// - 根据面积范围过滤轮廓
|
||||
// - 计算轮廓的几何特征(面积、周长、中心、外接矩形等)
|
||||
// - 输出轮廓信息供后续处理使用
|
||||
// 算法: 基于OpenCV的轮廓检测算法
|
||||
// 作者: 李伟 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 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