diff --git a/.gitignore b/.gitignore index d044ede..bee7ead 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,20 @@ packages/ *.nupkg +[Dd]ebug/ +[Rr]elease/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]ib/ # 重点:过滤lib文件夹 +[Ll]og/ +[Ll]ogs/ +lib/ + # 排除 Libs 目录中的 DLL 和 PDB 文件(但保留目录结构) XplorePlane/Libs/Hardware/*.dll XplorePlane/Libs/Hardware/*.pdb diff --git a/ExternalLibraries/concrt140.dll b/ExternalLibraries/concrt140.dll new file mode 100644 index 0000000..402b6ab Binary files /dev/null and b/ExternalLibraries/concrt140.dll differ diff --git a/ExternalLibraries/config.json b/ExternalLibraries/config.json new file mode 100644 index 0000000..28f9fdf --- /dev/null +++ b/ExternalLibraries/config.json @@ -0,0 +1,4 @@ +{ + "Language": "zh-CN", + "LogLevel": "Debug" +} \ No newline at end of file diff --git a/ExternalLibraries/cvextern.dll b/ExternalLibraries/cvextern.dll new file mode 100644 index 0000000..b1ee33d Binary files /dev/null and b/ExternalLibraries/cvextern.dll differ diff --git a/ExternalLibraries/libusb-1.0.dll b/ExternalLibraries/libusb-1.0.dll new file mode 100644 index 0000000..7b081af Binary files /dev/null and b/ExternalLibraries/libusb-1.0.dll differ diff --git a/ExternalLibraries/msvcp140.dll b/ExternalLibraries/msvcp140.dll new file mode 100644 index 0000000..ce86727 Binary files /dev/null and b/ExternalLibraries/msvcp140.dll differ diff --git a/ExternalLibraries/msvcp140_1.dll b/ExternalLibraries/msvcp140_1.dll new file mode 100644 index 0000000..fa8f9fa Binary files /dev/null and b/ExternalLibraries/msvcp140_1.dll differ diff --git a/ExternalLibraries/msvcp140_2.dll b/ExternalLibraries/msvcp140_2.dll new file mode 100644 index 0000000..70cb3d5 Binary files /dev/null and b/ExternalLibraries/msvcp140_2.dll differ diff --git a/ExternalLibraries/msvcp140_atomic_wait.dll b/ExternalLibraries/msvcp140_atomic_wait.dll new file mode 100644 index 0000000..0d36920 Binary files /dev/null and b/ExternalLibraries/msvcp140_atomic_wait.dll differ diff --git a/ExternalLibraries/msvcp140_codecvt_ids.dll b/ExternalLibraries/msvcp140_codecvt_ids.dll new file mode 100644 index 0000000..bea6252 Binary files /dev/null and b/ExternalLibraries/msvcp140_codecvt_ids.dll differ diff --git a/ExternalLibraries/opencv_videoio_ffmpeg490_64.dll b/ExternalLibraries/opencv_videoio_ffmpeg490_64.dll new file mode 100644 index 0000000..1e1f840 Binary files /dev/null and b/ExternalLibraries/opencv_videoio_ffmpeg490_64.dll differ diff --git a/ExternalLibraries/vcruntime140.dll b/ExternalLibraries/vcruntime140.dll new file mode 100644 index 0000000..e23644a Binary files /dev/null and b/ExternalLibraries/vcruntime140.dll differ diff --git a/ExternalLibraries/vcruntime140_1.dll b/ExternalLibraries/vcruntime140_1.dll new file mode 100644 index 0000000..39c0ca9 Binary files /dev/null and b/ExternalLibraries/vcruntime140_1.dll differ diff --git a/ExternalLibraries/version_string.inc b/ExternalLibraries/version_string.inc new file mode 100644 index 0000000..2448c60 --- /dev/null +++ b/ExternalLibraries/version_string.inc @@ -0,0 +1,106 @@ +"\n" +"General configuration for OpenCV 4.9.0 =====================================\n" +" Version control: 4.9.0-265-g79534d600a\n" +"\n" +" Extra modules:\n" +" Location (extra): G:/bb/cv_x64/build/build_x86_64/../opencv_contrib/modules\n" +" Version control (extra): 4.9.0-66-g61e23082\n" +"\n" +" Platform:\n" +" Timestamp: 2024-04-27T12:51:52Z\n" +" Host: Windows 10.0.22000 AMD64\n" +" CMake: 3.23.0\n" +" CMake generator: Visual Studio 17 2022\n" +" CMake build tool: C:/Program Files/Microsoft Visual Studio/2022/Community/MSBuild/Current/Bin/amd64/MSBuild.exe\n" +" MSVC: 1939\n" +" Configuration: Debug Release MinSizeRel RelWithDebInfo\n" +"\n" +" CPU/HW features:\n" +" Baseline: SSE SSE2 SSE3\n" +" requested: SSE3\n" +"\n" +" C/C++:\n" +" Built as dynamic libs?: NO\n" +" C++ standard: 11\n" +" C++ Compiler: C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.39.33519/bin/Hostx64/x64/cl.exe (ver 19.39.33523.0)\n" +" C++ flags (Release): /DWIN32 /D_WINDOWS /W4 /GR /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /Oi /fp:precise /EHa /wd4127 /wd4251 /wd4324 /wd4275 /wd4512 /wd4589 /wd4819 /MP /MD /O2 /Ob2 /DNDEBUG \n" +" C++ flags (Debug): /DWIN32 /D_WINDOWS /W4 /GR /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /Oi /fp:precise /EHa /wd4127 /wd4251 /wd4324 /wd4275 /wd4512 /wd4589 /wd4819 /MP /MDd /Zi /Ob0 /Od /RTC1 \n" +" C Compiler: C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.39.33519/bin/Hostx64/x64/cl.exe\n" +" C flags (Release): /DWIN32 /D_WINDOWS /W3 /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /Oi /fp:precise /MP /MD /O2 /Ob2 /DNDEBUG \n" +" C flags (Debug): /DWIN32 /D_WINDOWS /W3 /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /Oi /fp:precise /MP /MDd /Zi /Ob0 /Od /RTC1 \n" +" Linker flags (Release): /machine:x64 /INCREMENTAL:NO \n" +" Linker flags (Debug): /machine:x64 /debug /INCREMENTAL \n" +" ccache: NO\n" +" Precompiled headers: YES\n" +" Extra dependencies: wsock32 comctl32 gdi32 ole32 setupapi ws2_32 G:/bb/cv_x64/build/build_x86_64/install/lib/freetype.lib G:/bb/cv_x64/build/build_x86_64/install/lib/harfbuzz.lib G:/bb/cv_x64/build/build_x86_64/install/lib/libhdf5.lib\n" +" 3rdparty dependencies: libprotobuf ade ittnotify libjpeg-turbo libwebp libpng libtiff libopenjp2 IlmImf zlib\n" +"\n" +" OpenCV modules:\n" +" To be built: alphamat aruco bgsegm bioinspired calib3d ccalib core datasets dnn dnn_objdetect dnn_superres dpm face features2d flann freetype fuzzy gapi hdf hfs highgui img_hash imgcodecs imgproc intensity_transform line_descriptor mcc ml objdetect optflow phase_unwrapping photo plot quality rapid reg rgbd saliency shape stereo stitching structured_light superres surface_matching text tracking ts video videoio videostab wechat_qrcode xfeatures2d ximgproc xobjdetect xphoto\n" +" Disabled: java python_bindings_generator python_tests world\n" +" Disabled by dependency: -\n" +" Unavailable: cannops cudaarithm cudabgsegm cudacodec cudafeatures2d cudafilters cudaimgproc cudalegacy cudaobjdetect cudaoptflow cudastereo cudawarping cudev cvv julia matlab ovis python2 python3 sfm viz\n" +" Applications: perf_tests\n" +" Documentation: NO\n" +" Non-free algorithms: NO\n" +"\n" +" Windows RT support: NO\n" +"\n" +" GUI: WIN32UI\n" +" Win32 UI: YES\n" +" VTK support: NO\n" +"\n" +" Media I/O: \n" +" ZLib: build (ver 1.3)\n" +" JPEG: build-libjpeg-turbo (ver 2.1.3-62)\n" +" SIMD Support Request: YES\n" +" SIMD Support: NO\n" +" WEBP: build (ver encoder: 0x020f)\n" +" PNG: build (ver 1.6.37)\n" +" TIFF: build (ver 42 - 4.2.0)\n" +" JPEG 2000: build (ver 2.5.0)\n" +" OpenEXR: build (ver 2.3.0)\n" +" HDR: YES\n" +" SUNRASTER: YES\n" +" PXM: YES\n" +" PFM: YES\n" +"\n" +" Video I/O:\n" +" DC1394: NO\n" +" FFMPEG: YES (prebuilt binaries)\n" +" avcodec: YES (58.134.100)\n" +" avformat: YES (58.76.100)\n" +" avutil: YES (56.70.100)\n" +" swscale: YES (5.9.100)\n" +" avresample: YES (4.0.0)\n" +" GStreamer: NO\n" +" DirectShow: YES\n" +" Media Foundation: YES\n" +" DXVA: YES\n" +"\n" +" Parallel framework: Concurrency\n" +"\n" +" Trace: YES (with Intel ITT)\n" +"\n" +" Other third-party libraries:\n" +" Eigen: YES (ver 3.4.0)\n" +" Custom HAL: NO\n" +" Protobuf: build (3.19.1)\n" +" Flatbuffers: builtin/3rdparty (23.5.9)\n" +"\n" +" OpenCL: YES (NVD3D11)\n" +" Include path: G:/bb/cv_x64/build/opencv/3rdparty/include/opencl/1.2\n" +" Link libraries: Dynamic load\n" +"\n" +" Python (for build): C:/python-virt/python37/python.exe\n" +"\n" +" Java: \n" +" ant: NO\n" +" Java: YES (ver 1.8.0.202)\n" +" JNI: C:/Program Files/Microsoft/jdk-11.0.16.101-hotspot/include C:/Program Files/Microsoft/jdk-11.0.16.101-hotspot/include/win32 C:/Program Files/Microsoft/jdk-11.0.16.101-hotspot/include\n" +" Java wrappers: NO\n" +" Java tests: NO\n" +"\n" +" Install to: G:/bb/cv_x64/build/build_x86_64/install\n" +"-----------------------------------------------------------------\n" +"\n" diff --git a/ImageProcessing.Controls/ImageProcessing.Controls.csproj b/ImageProcessing.Controls/ImageProcessing.Controls.csproj new file mode 100644 index 0000000..e65e58e --- /dev/null +++ b/ImageProcessing.Controls/ImageProcessing.Controls.csproj @@ -0,0 +1,29 @@ + + + + net8.0-windows + enable + enable + true + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + Resources.resx + + + + + + + + + + + + + diff --git a/ImageProcessing.Controls/LocalizationHelper.cs b/ImageProcessing.Controls/LocalizationHelper.cs new file mode 100644 index 0000000..af59030 --- /dev/null +++ b/ImageProcessing.Controls/LocalizationHelper.cs @@ -0,0 +1,50 @@ +using System.Globalization; +using System.Resources; + +namespace ImageProcessing.Controls; + +/// +/// 本地化辅助类,用于管理多语言资源 +/// 与 ImageProcessing 主项目的语言设置同步 +/// +public static class LocalizationHelper +{ + private static ResourceManager? _resourceManager; + + /// + /// 资源管理器 + /// + private static ResourceManager ResourceManager + { + get + { + if (_resourceManager == null) + { + _resourceManager = new ResourceManager( + "ImageProcessing.Controls.Resources.Resources", + typeof(LocalizationHelper).Assembly); + } + return _resourceManager; + } + } + + /// + /// 获取本地化字符串 + /// 使用当前 UI 文化(与主项目同步) + /// + /// 资源键 + /// 本地化字符串 + public static string GetString(string key) + { + try + { + // 使用 CultureInfo.CurrentUICulture,这会自动与主项目的语言设置同步 + var value = ResourceManager.GetString(key, CultureInfo.CurrentUICulture); + return value ?? key; + } + catch + { + return key; + } + } +} \ No newline at end of file diff --git a/ImageProcessing.Controls/ProcessorParameterControl.xaml b/ImageProcessing.Controls/ProcessorParameterControl.xaml new file mode 100644 index 0000000..bfaa227 --- /dev/null +++ b/ImageProcessing.Controls/ProcessorParameterControl.xaml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ImageProcessing.Controls/ProcessorParameterControl.xaml.cs b/ImageProcessing.Controls/ProcessorParameterControl.xaml.cs new file mode 100644 index 0000000..e966247 --- /dev/null +++ b/ImageProcessing.Controls/ProcessorParameterControl.xaml.cs @@ -0,0 +1,377 @@ +using ImageProcessing.Core; +using System.Windows; +using System.Windows.Controls; + +namespace ImageProcessing.Controls; + +/// +/// 通用参数配置 UserControl +/// 可以根据不同算子的参数自动生成对应的 UI 控件 +/// +public partial class ProcessorParameterControl : UserControl +{ + private ImageProcessorBase? _currentProcessor; + + /// + /// 参数变化事件 + /// + public event EventHandler? ParameterChanged; + + public ProcessorParameterControl() + { + InitializeComponent(); + UpdateNoProcessorText(); + } + + /// + /// 更新"未选择算子"的文本 + /// + private void UpdateNoProcessorText() + { + txtProcessorName.Text = LocalizationHelper.GetString("NoProcessorSelected"); + txtProcessorDescription.Text = LocalizationHelper.GetString("PleaseSelectProcessor"); + } + + /// + /// 触发参数变化事件 + /// + protected virtual void OnParameterChanged() + { + ParameterChanged?.Invoke(this, EventArgs.Empty); + } + + /// + /// 加载算子参数并生成 UI + /// + public void LoadProcessor(ImageProcessorBase? processor) + { + _currentProcessor = processor; + pnlParameters.Children.Clear(); + + if (processor == null) + { + UpdateNoProcessorText(); + return; + } + + // 显示算子信息 + txtProcessorName.Text = processor.Name; + txtProcessorDescription.Text = processor.Description; + + // 生成参数控件 + var parameters = processor.GetParameters(); + + foreach (var param in parameters) + { + CreateParameterControl(param); + } + } + + /// + /// 根据参数类型创建对应的控件 + /// + private void CreateParameterControl(ProcessorParameter param) + { + // 如果参数不可见,跳过创建 + if (!param.IsVisible) + { + return; + } + + // 参数标签 + var label = new TextBlock + { + Text = param.DisplayName + ":", + Margin = new Thickness(0, 10, 0, 5), + FontWeight = FontWeights.Bold, + FontSize = 13 + }; + pnlParameters.Children.Add(label); + + // 根据参数类型创建不同的控件 + UIElement? control = null; + + if (param.ValueType == typeof(int)) + { + control = CreateIntegerControl(param); + } + else if (param.ValueType == typeof(double) || param.ValueType == typeof(float)) + { + control = CreateDoubleControl(param); + } + else if (param.ValueType == typeof(bool)) + { + control = CreateBooleanControl(param); + } + else if (param.ValueType == typeof(string) && param.Options != null) + { + control = CreateComboBoxControl(param); + } + else if (param.ValueType == typeof(string)) + { + control = CreateTextBoxControl(param); + } + + if (control != null) + { + pnlParameters.Children.Add(control); + + // 添加描述标签 + if (!string.IsNullOrEmpty(param.Description)) + { + var desc = new TextBlock + { + Text = param.Description, + Margin = new Thickness(0, 5, 0, 0), + FontSize = 11, + Foreground = System.Windows.Media.Brushes.Gray, + TextWrapping = TextWrapping.Wrap + }; + pnlParameters.Children.Add(desc); + } + } + } + + /// + /// 创建整数类型控件(Slider + TextBox 或仅 TextBox) + /// 当 MinValue 和 MaxValue 都为 null 时,只显示文本框,不显示滑块 + /// + private UIElement CreateIntegerControl(ProcessorParameter param) + { + var panel = new StackPanel(); + + var textBox = new TextBox + { + Text = param.Value.ToString(), + Width = 100, + HorizontalAlignment = HorizontalAlignment.Left + }; + + if (param.MinValue != null && param.MaxValue != null) + { + var slider = new Slider + { + Minimum = Convert.ToDouble(param.MinValue), + Maximum = Convert.ToDouble(param.MaxValue), + Value = Convert.ToDouble(param.Value), + TickFrequency = 1, + IsSnapToTickEnabled = true, + Margin = new Thickness(0, 0, 0, 5) + }; + + slider.ValueChanged += (s, e) => + { + int value = (int)slider.Value; + textBox.Text = value.ToString(); + _currentProcessor?.SetParameter(param.Name, value); + OnParameterChanged(); + }; + + textBox.TextChanged += (s, e) => + { + if (int.TryParse(textBox.Text, out int value)) + { + var min = Convert.ToInt32(param.MinValue); + var max = Convert.ToInt32(param.MaxValue); + + if (value >= min && value <= max) + { + slider.Value = value; + } + } + }; + + panel.Children.Add(slider); + } + else + { + textBox.TextChanged += (s, e) => + { + if (int.TryParse(textBox.Text, out int value)) + { + _currentProcessor?.SetParameter(param.Name, value); + OnParameterChanged(); + } + }; + } + + panel.Children.Add(textBox); + + return panel; + } + + /// + /// 创建浮点数类型控件(Slider + TextBox 或仅 TextBox) + /// 当 MinValue 和 MaxValue 都为 null 时,只显示文本框,不显示滑块 + /// + private UIElement CreateDoubleControl(ProcessorParameter param) + { + var panel = new StackPanel(); + + var textBox = new TextBox + { + Text = Convert.ToDouble(param.Value).ToString("F2"), + Width = 100, + HorizontalAlignment = HorizontalAlignment.Left + }; + + if (param.MinValue != null && param.MaxValue != null) + { + var slider = new Slider + { + Minimum = Convert.ToDouble(param.MinValue), + Maximum = Convert.ToDouble(param.MaxValue), + Value = Convert.ToDouble(param.Value), + TickFrequency = 0.1, + Margin = new Thickness(0, 0, 0, 5) + }; + + slider.ValueChanged += (s, e) => + { + double value = Math.Round(slider.Value, 2); + textBox.Text = value.ToString("F2"); + _currentProcessor?.SetParameter(param.Name, value); + OnParameterChanged(); + }; + + textBox.TextChanged += (s, e) => + { + if (double.TryParse(textBox.Text, out double value)) + { + var min = Convert.ToDouble(param.MinValue); + var max = Convert.ToDouble(param.MaxValue); + + if (value >= min && value <= max) + { + slider.Value = value; + } + } + }; + + panel.Children.Add(slider); + } + else + { + textBox.TextChanged += (s, e) => + { + if (double.TryParse(textBox.Text, out double value)) + { + _currentProcessor?.SetParameter(param.Name, value); + OnParameterChanged(); + } + }; + } + + panel.Children.Add(textBox); + + return panel; + } + + /// + /// 创建布尔类型控件(CheckBox) + /// + private UIElement CreateBooleanControl(ProcessorParameter param) + { + var checkBox = new CheckBox + { + Content = param.DisplayName, + IsChecked = Convert.ToBoolean(param.Value), + Margin = new Thickness(0, 5, 0, 0) + }; + + checkBox.Checked += (s, e) => + { + _currentProcessor?.SetParameter(param.Name, true); + OnParameterChanged(); + }; + + checkBox.Unchecked += (s, e) => + { + _currentProcessor?.SetParameter(param.Name, false); + OnParameterChanged(); + }; + + return checkBox; + } + + /// + /// 创建下拉框控件(ComboBox) + /// + private UIElement CreateComboBoxControl(ProcessorParameter param) + { + var comboBox = new ComboBox + { + Margin = new Thickness(0, 5, 0, 0), + Width = 200, + HorizontalAlignment = HorizontalAlignment.Left + }; + + if (param.Options != null) + { + foreach (var option in param.Options) + { + comboBox.Items.Add(option); + } + } + + comboBox.SelectedItem = param.Value; + + comboBox.SelectionChanged += (s, e) => + { + if (comboBox.SelectedItem != null) + { + _currentProcessor?.SetParameter(param.Name, comboBox.SelectedItem.ToString()!); + + // 如果是 FilterType 参数,重新加载界面以更新参数可见性 + if (param.Name == "FilterType") + { + LoadProcessor(_currentProcessor); + } + + OnParameterChanged(); + } + }; + + return comboBox; + } + + /// + /// 创建文本框控件(TextBox) + /// + private UIElement CreateTextBoxControl(ProcessorParameter param) + { + var textBox = new TextBox + { + Text = param.Value?.ToString() ?? "", + Margin = new Thickness(0, 5, 0, 0), + Width = 200, + HorizontalAlignment = HorizontalAlignment.Left + }; + + textBox.TextChanged += (s, e) => + { + _currentProcessor?.SetParameter(param.Name, textBox.Text); + OnParameterChanged(); + }; + + return textBox; + } + + /// + /// 获取当前配置的算子 + /// + public ImageProcessorBase? GetProcessor() + { + return _currentProcessor; + } + + /// + /// 清空参数控件 + /// + public void Clear() + { + _currentProcessor = null; + pnlParameters.Children.Clear(); + UpdateNoProcessorText(); + } +} \ No newline at end of file diff --git a/ImageProcessing.Controls/Resources/Resources.resx b/ImageProcessing.Controls/Resources/Resources.resx new file mode 100644 index 0000000..2bd022a --- /dev/null +++ b/ImageProcessing.Controls/Resources/Resources.resx @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + No Processor Selected + + + Please select an image processor + + diff --git a/ImageProcessing.Controls/Resources/Resources.zh-CN.resx b/ImageProcessing.Controls/Resources/Resources.zh-CN.resx new file mode 100644 index 0000000..db35d1d --- /dev/null +++ b/ImageProcessing.Controls/Resources/Resources.zh-CN.resx @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + 未选择算子 + + + 请选择一个图像处理算子 + + diff --git a/ImageProcessing.Core/ImageProcessing.Core.csproj b/ImageProcessing.Core/ImageProcessing.Core.csproj new file mode 100644 index 0000000..1ffa374 --- /dev/null +++ b/ImageProcessing.Core/ImageProcessing.Core.csproj @@ -0,0 +1,14 @@ + + + + net8.0-windows + enable + enable + + + + + + + + diff --git a/ImageProcessing.Core/ImageProcessorBase.cs b/ImageProcessing.Core/ImageProcessorBase.cs new file mode 100644 index 0000000..2644079 --- /dev/null +++ b/ImageProcessing.Core/ImageProcessorBase.cs @@ -0,0 +1,181 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: ImageProcessorBase.cs +// 描述: 8位图像处理算子基类,定义图像处理算子的通用接口和行为 +// 功能: +// - 定义算子的基本属性(名称、描述) +// - 参数管理(设置、获取、验证) +// - ROI(感兴趣区域)处理支持 +// - 输出数据管理(用于传递额外信息如轮廓等) +// - 为所有8位图像处理算子提供统一的基础框架 +// 设计模式: 模板方法模式 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using Emgu.CV.Util; + +namespace ImageProcessing.Core; + +/// +/// 图像处理算子基类 +/// +public abstract class ImageProcessorBase +{ + /// 算子名称 + public string Name { get; protected set; } = string.Empty; + + /// 算子描述 + public string Description { get; protected set; } = string.Empty; + + /// 参数字典 + protected Dictionary Parameters { get; set; } + + /// 输出数据(用于传递额外信息如轮廓等) + public Dictionary OutputData { get; protected set; } + + /// ROI区域 + public System.Drawing.Rectangle? ROI { get; set; } + + /// 多边形ROI点集 + public System.Drawing.Point[]? PolygonROIPoints { get; set; } + + protected ImageProcessorBase() + { + Parameters = new Dictionary(); + OutputData = new Dictionary(); + InitializeParameters(); + } + + /// + /// 初始化算子参数(子类实现) + /// + protected abstract void InitializeParameters(); + + /// + /// 执行图像处理(子类实现) + /// + public abstract Image Process(Image inputImage); + + /// + /// 执行图像处理(带矩形ROI支持) + /// + public Image ProcessWithROI(Image inputImage) + { + if (ROI.HasValue && ROI.Value != System.Drawing.Rectangle.Empty) + { + inputImage.ROI = ROI.Value; + var roiImage = inputImage.Copy(); + inputImage.ROI = System.Drawing.Rectangle.Empty; + + var processedROI = Process(roiImage); + + // 将 ROI 偏移量保存到输出数据中,供轮廓绘制等使用 + OutputData["ROIOffset"] = new System.Drawing.Point(ROI.Value.X, ROI.Value.Y); + + var result = inputImage.Clone(); + result.ROI = ROI.Value; + processedROI.CopyTo(result); + result.ROI = System.Drawing.Rectangle.Empty; + + roiImage.Dispose(); + processedROI.Dispose(); + return result; + } + return Process(inputImage); + } + + /// + /// 执行图像处理(带多边形ROI掩码支持) + /// + public Image ProcessWithPolygonROI(Image inputImage) + { + if (PolygonROIPoints == null || PolygonROIPoints.Length < 3) + { + return Process(inputImage); + } + + // 创建掩码 + var mask = new Image(inputImage.Width, inputImage.Height); + mask.SetValue(new Gray(0)); + + // 绘制多边形掩码(白色表示ROI区域) + using (var vop = new VectorOfPoint(PolygonROIPoints)) + { + using (var vvop = new VectorOfVectorOfPoint(vop)) + { + CvInvoke.DrawContours(mask, vvop, 0, new MCvScalar(255), -1); + } + } + + // 处理整个图像 + var processedImage = Process(inputImage); + + // 创建结果图像 + var result = inputImage.Clone(); + + // 使用掩码:ROI内使用处理后的像素,ROI外保持原始像素 + for (int y = 0; y < inputImage.Height; y++) + { + for (int x = 0; x < inputImage.Width; x++) + { + if (mask.Data[y, x, 0] > 0) // 在ROI内 + { + result.Data[y, x, 0] = processedImage.Data[y, x, 0]; + } + } + } + + // 保存ROI信息 + OutputData["ROIMask"] = mask; + OutputData["PolygonPoints"] = PolygonROIPoints; + OutputData["ROIOffset"] = System.Drawing.Point.Empty; + + processedImage.Dispose(); + return result; + } + + /// + /// 获取所有参数列表 + /// + public List GetParameters() + { + return new List(Parameters.Values); + } + + /// + /// 设置参数值 + /// + public void SetParameter(string name, object value) + { + if (Parameters.ContainsKey(name)) + { + Parameters[name].Value = value; + } + else + { + throw new ArgumentException($"参数 {name} 不存在"); + } + } + + /// + /// 获取参数值 + /// + public T GetParameter(string name) + { + if (Parameters.ContainsKey(name)) + { + return (T)Convert.ChangeType(Parameters[name].Value, typeof(T))!; + } + throw new ArgumentException($"参数 {name} 不存在"); + } + + /// + /// 获取单个参数 + /// + public ProcessorParameter? GetParameterInfo(string name) + { + return Parameters.ContainsKey(name) ? Parameters[name] : null; + } +} \ No newline at end of file diff --git a/ImageProcessing.Core/ProcessorParameter.cs b/ImageProcessing.Core/ProcessorParameter.cs new file mode 100644 index 0000000..670281d --- /dev/null +++ b/ImageProcessing.Core/ProcessorParameter.cs @@ -0,0 +1,60 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: ProcessorParameter.cs +// 描述: 图像处理算子参数定义类,用于描述算子的可配置参数 +// 功能: +// - 定义参数的基本属性(名称、类型、默认值) +// - 支持参数范围约束(最小值、最大值) +// - 支持枚举类型参数(下拉选项) +// - 提供参数描述信息用于UI显示 +// - 统一的参数管理机制 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +namespace ImageProcessing.Core; + +/// +/// 图像处理算子参数定义 +/// +public class ProcessorParameter +{ + /// 参数名称(代码中使用) + public string Name { get; set; } + + /// 显示名称(UI中显示) + public string DisplayName { get; set; } + + /// 参数类型 + public Type ValueType { get; set; } + + /// 当前值 + public object Value { get; set; } + + /// 最小值(可选) + public object? MinValue { get; set; } + + /// 最大值(可选) + public object? MaxValue { get; set; } + + /// 参数描述 + public string Description { get; set; } + + /// 可选值列表(用于下拉框) + public string[]? Options { get; set; } + + /// 参数是否可见 + public bool IsVisible { get; set; } = true; + + public ProcessorParameter(string name, string displayName, Type valueType, object defaultValue, + object? minValue = null, object? maxValue = null, string description = "", string[]? options = null) + { + Name = name; + DisplayName = displayName; + ValueType = valueType; + Value = defaultValue; + MinValue = minValue; + MaxValue = maxValue; + Description = description; + Options = options; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/ImageProcessing.Processors.csproj b/ImageProcessing.Processors/ImageProcessing.Processors.csproj new file mode 100644 index 0000000..6db6112 --- /dev/null +++ b/ImageProcessing.Processors/ImageProcessing.Processors.csproj @@ -0,0 +1,42 @@ + + + + net8.0-windows + enable + enable + true + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + Resources.resx + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + diff --git a/ImageProcessing.Processors/LocalizationHelper.cs b/ImageProcessing.Processors/LocalizationHelper.cs new file mode 100644 index 0000000..67d6158 --- /dev/null +++ b/ImageProcessing.Processors/LocalizationHelper.cs @@ -0,0 +1,50 @@ +using System.Globalization; +using System.Resources; + +namespace ImageProcessing.Processors; + +/// +/// 本地化辅助类,用于管理多语言资源 +/// 与 ImageProcessing 主项目的语言设置同步 +/// +public static class LocalizationHelper +{ + private static ResourceManager? _resourceManager; + + /// + /// 资源管理器 + /// + private static ResourceManager ResourceManager + { + get + { + if (_resourceManager == null) + { + _resourceManager = new ResourceManager( + "ImageProcessing.Processors.Resources.Resources", + typeof(LocalizationHelper).Assembly); + } + return _resourceManager; + } + } + + /// + /// 获取本地化字符串 + /// 使用当前 UI 文化(与主项目同步) + /// + /// 资源键 + /// 本地化字符串 + public static string GetString(string key) + { + try + { + // 使用 CultureInfo.CurrentUICulture,这会自动与主项目的语言设置同步 + var value = ResourceManager.GetString(key, CultureInfo.CurrentUICulture); + return value ?? key; + } + catch + { + return key; + } + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/Resources/Resources.Designer.cs b/ImageProcessing.Processors/Resources/Resources.Designer.cs new file mode 100644 index 0000000..79d8d5a --- /dev/null +++ b/ImageProcessing.Processors/Resources/Resources.Designer.cs @@ -0,0 +1,2169 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace ImageProcessing.Processors.Resources { + using System; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ImageProcessing.Processors.Resources.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 重写当前线程的 CurrentUICulture 属性,对 + /// 使用此强类型资源类的所有资源查找执行重写。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// 查找类似 Preserve image information within specified frequency range 的本地化字符串。 + /// + public static string BandPassFilterProcessor_Description { + get { + return ResourceManager.GetString("BandPassFilterProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Filter Type 的本地化字符串。 + /// + public static string BandPassFilterProcessor_FilterType { + get { + return ResourceManager.GetString("BandPassFilterProcessor_FilterType", resourceCulture); + } + } + + /// + /// 查找类似 Transition characteristics of the filter 的本地化字符串。 + /// + public static string BandPassFilterProcessor_FilterType_Desc { + get { + return ResourceManager.GetString("BandPassFilterProcessor_FilterType_Desc", resourceCulture); + } + } + + /// + /// 查找类似 High Cutoff Radius 的本地化字符串。 + /// + public static string BandPassFilterProcessor_HighCutoff { + get { + return ResourceManager.GetString("BandPassFilterProcessor_HighCutoff", resourceCulture); + } + } + + /// + /// 查找类似 Components above this frequency will be removed 的本地化字符串。 + /// + public static string BandPassFilterProcessor_HighCutoff_Desc { + get { + return ResourceManager.GetString("BandPassFilterProcessor_HighCutoff_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Low Cutoff Radius 的本地化字符串。 + /// + public static string BandPassFilterProcessor_LowCutoff { + get { + return ResourceManager.GetString("BandPassFilterProcessor_LowCutoff", resourceCulture); + } + } + + /// + /// 查找类似 Components below this frequency will be removed 的本地化字符串。 + /// + public static string BandPassFilterProcessor_LowCutoff_Desc { + get { + return ResourceManager.GetString("BandPassFilterProcessor_LowCutoff_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Band Pass Filter 的本地化字符串。 + /// + public static string BandPassFilterProcessor_Name { + get { + return ResourceManager.GetString("BandPassFilterProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Butterworth Order 的本地化字符串。 + /// + public static string BandPassFilterProcessor_Order { + get { + return ResourceManager.GetString("BandPassFilterProcessor_Order", resourceCulture); + } + } + + /// + /// 查找类似 Order of Butterworth filter 的本地化字符串。 + /// + public static string BandPassFilterProcessor_Order_Desc { + get { + return ResourceManager.GetString("BandPassFilterProcessor_Order_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Edge-preserving smoothing filter 的本地化字符串。 + /// + public static string BilateralFilterProcessor_Description { + get { + return ResourceManager.GetString("BilateralFilterProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Diameter 的本地化字符串。 + /// + public static string BilateralFilterProcessor_Diameter { + get { + return ResourceManager.GetString("BilateralFilterProcessor_Diameter", resourceCulture); + } + } + + /// + /// 查找类似 Diameter of each pixel neighborhood 的本地化字符串。 + /// + public static string BilateralFilterProcessor_Diameter_Desc { + get { + return ResourceManager.GetString("BilateralFilterProcessor_Diameter_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Bilateral Filter 的本地化字符串。 + /// + public static string BilateralFilterProcessor_Name { + get { + return ResourceManager.GetString("BilateralFilterProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Sigma Color 的本地化字符串。 + /// + public static string BilateralFilterProcessor_SigmaColor { + get { + return ResourceManager.GetString("BilateralFilterProcessor_SigmaColor", resourceCulture); + } + } + + /// + /// 查找类似 Filter sigma in the color space 的本地化字符串。 + /// + public static string BilateralFilterProcessor_SigmaColor_Desc { + get { + return ResourceManager.GetString("BilateralFilterProcessor_SigmaColor_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Sigma Space 的本地化字符串。 + /// + public static string BilateralFilterProcessor_SigmaSpace { + get { + return ResourceManager.GetString("BilateralFilterProcessor_SigmaSpace", resourceCulture); + } + } + + /// + /// 查找类似 Filter sigma in the coordinate space 的本地化字符串。 + /// + public static string BilateralFilterProcessor_SigmaSpace_Desc { + get { + return ResourceManager.GetString("BilateralFilterProcessor_SigmaSpace_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Detect contours in image and output contour information 的本地化字符串。 + /// + public static string ContourProcessor_Description { + get { + return ResourceManager.GetString("ContourProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Max Area 的本地化字符串。 + /// + public static string ContourProcessor_MaxArea { + get { + return ResourceManager.GetString("ContourProcessor_MaxArea", resourceCulture); + } + } + + /// + /// 查找类似 Filter contours larger than this area 的本地化字符串。 + /// + public static string ContourProcessor_MaxArea_Desc { + get { + return ResourceManager.GetString("ContourProcessor_MaxArea_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Min Area 的本地化字符串。 + /// + public static string ContourProcessor_MinArea { + get { + return ResourceManager.GetString("ContourProcessor_MinArea", resourceCulture); + } + } + + /// + /// 查找类似 Filter contours smaller than this area 的本地化字符串。 + /// + public static string ContourProcessor_MinArea_Desc { + get { + return ResourceManager.GetString("ContourProcessor_MinArea_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Contour Detection 的本地化字符串。 + /// + public static string ContourProcessor_Name { + get { + return ResourceManager.GetString("ContourProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Target Color 的本地化字符串。 + /// + public static string ContourProcessor_TargetColor { + get { + return ResourceManager.GetString("ContourProcessor_TargetColor", resourceCulture); + } + } + + /// + /// 查找类似 Select the color of regions to find (white or black) 的本地化字符串。 + /// + public static string ContourProcessor_TargetColor_Desc { + get { + return ResourceManager.GetString("ContourProcessor_TargetColor_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Line Thickness 的本地化字符串。 + /// + public static string ContourProcessor_Thickness { + get { + return ResourceManager.GetString("ContourProcessor_Thickness", resourceCulture); + } + } + + /// + /// 查找类似 Thickness of contour lines 的本地化字符串。 + /// + public static string ContourProcessor_Thickness_Desc { + get { + return ResourceManager.GetString("ContourProcessor_Thickness_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Threshold Value 的本地化字符串。 + /// + public static string ContourProcessor_ThresholdValue { + get { + return ResourceManager.GetString("ContourProcessor_ThresholdValue", resourceCulture); + } + } + + /// + /// 查找类似 Threshold value for binarization (0-255) 的本地化字符串。 + /// + public static string ContourProcessor_ThresholdValue_Desc { + get { + return ResourceManager.GetString("ContourProcessor_ThresholdValue_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Use Otsu Auto Threshold 的本地化字符串。 + /// + public static string ContourProcessor_UseOtsu { + get { + return ResourceManager.GetString("ContourProcessor_UseOtsu", resourceCulture); + } + } + + /// + /// 查找类似 Automatically calculate optimal threshold 的本地化字符串。 + /// + public static string ContourProcessor_UseOtsu_Desc { + get { + return ResourceManager.GetString("ContourProcessor_UseOtsu_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Enable Threshold 的本地化字符串。 + /// + public static string ContourProcessor_UseThreshold { + get { + return ResourceManager.GetString("ContourProcessor_UseThreshold", resourceCulture); + } + } + + /// + /// 查找类似 Apply binary threshold before contour detection 的本地化字符串。 + /// + public static string ContourProcessor_UseThreshold_Desc { + get { + return ResourceManager.GetString("ContourProcessor_UseThreshold_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Auto Contrast 的本地化字符串。 + /// + public static string ContrastProcessor_AutoContrast { + get { + return ResourceManager.GetString("ContrastProcessor_AutoContrast", resourceCulture); + } + } + + /// + /// 查找类似 Automatically stretch contrast to full range 的本地化字符串。 + /// + public static string ContrastProcessor_AutoContrast_Desc { + get { + return ResourceManager.GetString("ContrastProcessor_AutoContrast_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Brightness 的本地化字符串。 + /// + public static string ContrastProcessor_Brightness { + get { + return ResourceManager.GetString("ContrastProcessor_Brightness", resourceCulture); + } + } + + /// + /// 查找类似 Brightness offset 的本地化字符串。 + /// + public static string ContrastProcessor_Brightness_Desc { + get { + return ResourceManager.GetString("ContrastProcessor_Brightness_Desc", resourceCulture); + } + } + + /// + /// 查找类似 CLAHE Clip Limit 的本地化字符串。 + /// + public static string ContrastProcessor_ClipLimit { + get { + return ResourceManager.GetString("ContrastProcessor_ClipLimit", resourceCulture); + } + } + + /// + /// 查找类似 CLAHE contrast limit threshold 的本地化字符串。 + /// + public static string ContrastProcessor_ClipLimit_Desc { + get { + return ResourceManager.GetString("ContrastProcessor_ClipLimit_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Contrast 的本地化字符串。 + /// + public static string ContrastProcessor_Contrast { + get { + return ResourceManager.GetString("ContrastProcessor_Contrast", resourceCulture); + } + } + + /// + /// 查找类似 Contrast gain, 1.0 for original contrast 的本地化字符串。 + /// + public static string ContrastProcessor_Contrast_Desc { + get { + return ResourceManager.GetString("ContrastProcessor_Contrast_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Adjust image contrast and brightness 的本地化字符串。 + /// + public static string ContrastProcessor_Description { + get { + return ResourceManager.GetString("ContrastProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Contrast Adjustment 的本地化字符串。 + /// + public static string ContrastProcessor_Name { + get { + return ResourceManager.GetString("ContrastProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Use CLAHE 的本地化字符串。 + /// + public static string ContrastProcessor_UseCLAHE { + get { + return ResourceManager.GetString("ContrastProcessor_UseCLAHE", resourceCulture); + } + } + + /// + /// 查找类似 Use Contrast Limited Adaptive Histogram Equalization 的本地化字符串。 + /// + public static string ContrastProcessor_UseCLAHE_Desc { + get { + return ResourceManager.GetString("ContrastProcessor_UseCLAHE_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Perform division operation on image for background correction and normalization 的本地化字符串。 + /// + public static string DivisionProcessor_Description { + get { + return ResourceManager.GetString("DivisionProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Divisor 的本地化字符串。 + /// + public static string DivisionProcessor_Divisor { + get { + return ResourceManager.GetString("DivisionProcessor_Divisor", resourceCulture); + } + } + + /// + /// 查找类似 Each pixel value will be divided by this number 的本地化字符串。 + /// + public static string DivisionProcessor_Divisor_Desc { + get { + return ResourceManager.GetString("DivisionProcessor_Divisor_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Division Operation 的本地化字符串。 + /// + public static string DivisionProcessor_Name { + get { + return ResourceManager.GetString("DivisionProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Normalize Output 的本地化字符串。 + /// + public static string DivisionProcessor_Normalize { + get { + return ResourceManager.GetString("DivisionProcessor_Normalize", resourceCulture); + } + } + + /// + /// 查找类似 Normalize result to 0-255 range 的本地化字符串。 + /// + public static string DivisionProcessor_Normalize_Desc { + get { + return ResourceManager.GetString("DivisionProcessor_Normalize_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Scale Factor 的本地化字符串。 + /// + public static string DivisionProcessor_Scale { + get { + return ResourceManager.GetString("DivisionProcessor_Scale", resourceCulture); + } + } + + /// + /// 查找类似 Division result multiplied by this scale factor 的本地化字符串。 + /// + public static string DivisionProcessor_Scale_Desc { + get { + return ResourceManager.GetString("DivisionProcessor_Scale_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Preserve image information within specified frequency range 的本地化字符串。 + /// + public static string FilterProcessor_BandPass_Desc { + get { + return ResourceManager.GetString("FilterProcessor_BandPass_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Band Pass Filter 的本地化字符串。 + /// + public static string FilterProcessor_BandPass_Name { + get { + return ResourceManager.GetString("FilterProcessor_BandPass_Name", resourceCulture); + } + } + + /// + /// 查找类似 Band Pass Filter Type 的本地化字符串。 + /// + public static string FilterProcessor_BandPassFilterType { + get { + return ResourceManager.GetString("FilterProcessor_BandPassFilterType", resourceCulture); + } + } + + /// + /// 查找类似 Transition characteristics of the band pass filter 的本地化字符串。 + /// + public static string FilterProcessor_BandPassFilterType_Desc { + get { + return ResourceManager.GetString("FilterProcessor_BandPassFilterType_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Edge-preserving smoothing filter 的本地化字符串。 + /// + public static string FilterProcessor_Bilateral_Desc { + get { + return ResourceManager.GetString("FilterProcessor_Bilateral_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Bilateral Filter 的本地化字符串。 + /// + public static string FilterProcessor_Bilateral_Name { + get { + return ResourceManager.GetString("FilterProcessor_Bilateral_Name", resourceCulture); + } + } + + /// + /// 查找类似 Cutoff Frequency 的本地化字符串。 + /// + public static string FilterProcessor_D0 { + get { + return ResourceManager.GetString("FilterProcessor_D0", resourceCulture); + } + } + + /// + /// 查找类似 Cutoff frequency for frequency domain filtering 的本地化字符串。 + /// + public static string FilterProcessor_D0_Desc { + get { + return ResourceManager.GetString("FilterProcessor_D0_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Integrated multiple filtering methods 的本地化字符串。 + /// + public static string FilterProcessor_Description { + get { + return ResourceManager.GetString("FilterProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Filter Type 的本地化字符串。 + /// + public static string FilterProcessor_FilterType { + get { + return ResourceManager.GetString("FilterProcessor_FilterType", resourceCulture); + } + } + + /// + /// 查找类似 Select filtering method 的本地化字符串。 + /// + public static string FilterProcessor_FilterType_Desc { + get { + return ResourceManager.GetString("FilterProcessor_FilterType_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Smooth image and reduce Gaussian noise while preserving edges 的本地化字符串。 + /// + public static string FilterProcessor_Gaussian_Desc { + get { + return ResourceManager.GetString("FilterProcessor_Gaussian_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Gaussian Filter 的本地化字符串。 + /// + public static string FilterProcessor_Gaussian_Name { + get { + return ResourceManager.GetString("FilterProcessor_Gaussian_Name", resourceCulture); + } + } + + /// + /// 查找类似 High Cutoff Radius 的本地化字符串。 + /// + public static string FilterProcessor_HighCutoff { + get { + return ResourceManager.GetString("FilterProcessor_HighCutoff", resourceCulture); + } + } + + /// + /// 查找类似 Components above this frequency will be removed 的本地化字符串。 + /// + public static string FilterProcessor_HighCutoff_Desc { + get { + return ResourceManager.GetString("FilterProcessor_HighCutoff_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Edge enhancement in frequency domain 的本地化字符串。 + /// + public static string FilterProcessor_HighPass_Desc { + get { + return ResourceManager.GetString("FilterProcessor_HighPass_Desc", resourceCulture); + } + } + + /// + /// 查找类似 High Pass Filter 的本地化字符串。 + /// + public static string FilterProcessor_HighPass_Name { + get { + return ResourceManager.GetString("FilterProcessor_HighPass_Name", resourceCulture); + } + } + + /// + /// 查找类似 Kernel Size 的本地化字符串。 + /// + public static string FilterProcessor_KernelSize { + get { + return ResourceManager.GetString("FilterProcessor_KernelSize", resourceCulture); + } + } + + /// + /// 查找类似 Size of the filter kernel (must be odd) 的本地化字符串。 + /// + public static string FilterProcessor_KernelSize_Desc { + get { + return ResourceManager.GetString("FilterProcessor_KernelSize_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Low Cutoff Radius 的本地化字符串。 + /// + public static string FilterProcessor_LowCutoff { + get { + return ResourceManager.GetString("FilterProcessor_LowCutoff", resourceCulture); + } + } + + /// + /// 查找类似 Components below this frequency will be removed 的本地化字符串。 + /// + public static string FilterProcessor_LowCutoff_Desc { + get { + return ResourceManager.GetString("FilterProcessor_LowCutoff_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Remove high frequency noise in frequency domain 的本地化字符串。 + /// + public static string FilterProcessor_LowPass_Desc { + get { + return ResourceManager.GetString("FilterProcessor_LowPass_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Low Pass Filter 的本地化字符串。 + /// + public static string FilterProcessor_LowPass_Name { + get { + return ResourceManager.GetString("FilterProcessor_LowPass_Name", resourceCulture); + } + } + + /// + /// 查找类似 Simple averaging smoothing filter 的本地化字符串。 + /// + public static string FilterProcessor_Mean_Desc { + get { + return ResourceManager.GetString("FilterProcessor_Mean_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Mean Filter 的本地化字符串。 + /// + public static string FilterProcessor_Mean_Name { + get { + return ResourceManager.GetString("FilterProcessor_Mean_Name", resourceCulture); + } + } + + /// + /// 查找类似 Remove salt-and-pepper noise effectively 的本地化字符串。 + /// + public static string FilterProcessor_Median_Desc { + get { + return ResourceManager.GetString("FilterProcessor_Median_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Median Filter 的本地化字符串。 + /// + public static string FilterProcessor_Median_Name { + get { + return ResourceManager.GetString("FilterProcessor_Median_Name", resourceCulture); + } + } + + /// + /// 查找类似 Comprehensive Filter 的本地化字符串。 + /// + public static string FilterProcessor_Name { + get { + return ResourceManager.GetString("FilterProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Butterworth Order 的本地化字符串。 + /// + public static string FilterProcessor_Order { + get { + return ResourceManager.GetString("FilterProcessor_Order", resourceCulture); + } + } + + /// + /// 查找类似 Order of Butterworth filter 的本地化字符串。 + /// + public static string FilterProcessor_Order_Desc { + get { + return ResourceManager.GetString("FilterProcessor_Order_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Sigma 的本地化字符串。 + /// + public static string FilterProcessor_Sigma { + get { + return ResourceManager.GetString("FilterProcessor_Sigma", resourceCulture); + } + } + + /// + /// 查找类似 Standard deviation for Gaussian/Bilateral filter 的本地化字符串。 + /// + public static string FilterProcessor_Sigma_Desc { + get { + return ResourceManager.GetString("FilterProcessor_Sigma_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Adjust image brightness through Gamma value 的本地化字符串。 + /// + public static string GammaProcessor_Description { + get { + return ResourceManager.GetString("GammaProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Gain 的本地化字符串。 + /// + public static string GammaProcessor_Gain { + get { + return ResourceManager.GetString("GammaProcessor_Gain", resourceCulture); + } + } + + /// + /// 查找类似 Output gain coefficient 的本地化字符串。 + /// + public static string GammaProcessor_Gain_Desc { + get { + return ResourceManager.GetString("GammaProcessor_Gain_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Gamma Value 的本地化字符串。 + /// + public static string GammaProcessor_Gamma { + get { + return ResourceManager.GetString("GammaProcessor_Gamma", resourceCulture); + } + } + + /// + /// 查找类似 Gamma value, less than 1 darkens image, greater than 1 brightens image 的本地化字符串。 + /// + public static string GammaProcessor_Gamma_Desc { + get { + return ResourceManager.GetString("GammaProcessor_Gamma_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Gamma Correction 的本地化字符串。 + /// + public static string GammaProcessor_Name { + get { + return ResourceManager.GetString("GammaProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Smooth image using Gaussian kernel 的本地化字符串。 + /// + public static string GaussianBlurProcessor_Description { + get { + return ResourceManager.GetString("GaussianBlurProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Kernel Size 的本地化字符串。 + /// + public static string GaussianBlurProcessor_KernelSize { + get { + return ResourceManager.GetString("GaussianBlurProcessor_KernelSize", resourceCulture); + } + } + + /// + /// 查找类似 Size of Gaussian kernel, must be odd 的本地化字符串。 + /// + public static string GaussianBlurProcessor_KernelSize_Desc { + get { + return ResourceManager.GetString("GaussianBlurProcessor_KernelSize_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Gaussian Blur 的本地化字符串。 + /// + public static string GaussianBlurProcessor_Name { + get { + return ResourceManager.GetString("GaussianBlurProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Standard Deviation 的本地化字符串。 + /// + public static string GaussianBlurProcessor_Sigma { + get { + return ResourceManager.GetString("GaussianBlurProcessor_Sigma", resourceCulture); + } + } + + /// + /// 查找类似 Standard deviation of Gaussian kernel, controls blur amount 的本地化字符串。 + /// + public static string GaussianBlurProcessor_Sigma_Desc { + get { + return ResourceManager.GetString("GaussianBlurProcessor_Sigma_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Convert image to grayscale 的本地化字符串。 + /// + public static string GrayscaleProcessor_Description { + get { + return ResourceManager.GetString("GrayscaleProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Conversion Method 的本地化字符串。 + /// + public static string GrayscaleProcessor_Method { + get { + return ResourceManager.GetString("GrayscaleProcessor_Method", resourceCulture); + } + } + + /// + /// 查找类似 Method for grayscale conversion 的本地化字符串。 + /// + public static string GrayscaleProcessor_Method_Desc { + get { + return ResourceManager.GetString("GrayscaleProcessor_Method_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Grayscale Conversion 的本地化字符串。 + /// + public static string GrayscaleProcessor_Name { + get { + return ResourceManager.GetString("GrayscaleProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Bias 的本地化字符串。 + /// + public static string HDREnhancementProcessor_Bias { + get { + return ResourceManager.GetString("HDREnhancementProcessor_Bias", resourceCulture); + } + } + + /// + /// 查找类似 Bias for adaptive logarithmic and Drago mapping, controls dark/bright balance 的本地化字符串。 + /// + public static string HDREnhancementProcessor_Bias_Desc { + get { + return ResourceManager.GetString("HDREnhancementProcessor_Bias_Desc", resourceCulture); + } + } + + /// + /// 查找类似 High Dynamic Range image enhancement with tone mapping 的本地化字符串。 + /// + public static string HDREnhancementProcessor_Description { + get { + return ResourceManager.GetString("HDREnhancementProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Detail Boost 的本地化字符串。 + /// + public static string HDREnhancementProcessor_DetailBoost { + get { + return ResourceManager.GetString("HDREnhancementProcessor_DetailBoost", resourceCulture); + } + } + + /// + /// 查找类似 Detail enhancement factor, higher values reveal more fine details 的本地化字符串。 + /// + public static string HDREnhancementProcessor_DetailBoost_Desc { + get { + return ResourceManager.GetString("HDREnhancementProcessor_DetailBoost_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Gamma 的本地化字符串。 + /// + public static string HDREnhancementProcessor_Gamma { + get { + return ResourceManager.GetString("HDREnhancementProcessor_Gamma", resourceCulture); + } + } + + /// + /// 查找类似 Gamma correction value for output brightness adjustment 的本地化字符串。 + /// + public static string HDREnhancementProcessor_Gamma_Desc { + get { + return ResourceManager.GetString("HDREnhancementProcessor_Gamma_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Tone Mapping Method 的本地化字符串。 + /// + public static string HDREnhancementProcessor_Method { + get { + return ResourceManager.GetString("HDREnhancementProcessor_Method", resourceCulture); + } + } + + /// + /// 查找类似 Select HDR tone mapping algorithm: LocalToneMap, AdaptiveLog, Drago, BilateralToneMap 的本地化字符串。 + /// + public static string HDREnhancementProcessor_Method_Desc { + get { + return ResourceManager.GetString("HDREnhancementProcessor_Method_Desc", resourceCulture); + } + } + + /// + /// 查找类似 HDR Enhancement 的本地化字符串。 + /// + public static string HDREnhancementProcessor_Name { + get { + return ResourceManager.GetString("HDREnhancementProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Saturation 的本地化字符串。 + /// + public static string HDREnhancementProcessor_Saturation { + get { + return ResourceManager.GetString("HDREnhancementProcessor_Saturation", resourceCulture); + } + } + + /// + /// 查找类似 Contrast saturation factor for LocalToneMap method 的本地化字符串。 + /// + public static string HDREnhancementProcessor_Saturation_Desc { + get { + return ResourceManager.GetString("HDREnhancementProcessor_Saturation_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Sigma Color 的本地化字符串。 + /// + public static string HDREnhancementProcessor_SigmaColor { + get { + return ResourceManager.GetString("HDREnhancementProcessor_SigmaColor", resourceCulture); + } + } + + /// + /// 查找类似 Color sigma for bilateral tone mapping, controls edge preservation 的本地化字符串。 + /// + public static string HDREnhancementProcessor_SigmaColor_Desc { + get { + return ResourceManager.GetString("HDREnhancementProcessor_SigmaColor_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Sigma Space 的本地化字符串。 + /// + public static string HDREnhancementProcessor_SigmaSpace { + get { + return ResourceManager.GetString("HDREnhancementProcessor_SigmaSpace", resourceCulture); + } + } + + /// + /// 查找类似 Spatial sigma for base layer extraction, controls smoothing range 的本地化字符串。 + /// + public static string HDREnhancementProcessor_SigmaSpace_Desc { + get { + return ResourceManager.GetString("HDREnhancementProcessor_SigmaSpace_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Cutoff Frequency 的本地化字符串。 + /// + public static string HighPassFilterProcessor_CutoffFrequency { + get { + return ResourceManager.GetString("HighPassFilterProcessor_CutoffFrequency", resourceCulture); + } + } + + /// + /// 查找类似 Cutoff frequency for high pass filter 的本地化字符串。 + /// + public static string HighPassFilterProcessor_CutoffFrequency_Desc { + get { + return ResourceManager.GetString("HighPassFilterProcessor_CutoffFrequency_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Edge enhancement in frequency domain 的本地化字符串。 + /// + public static string HighPassFilterProcessor_Description { + get { + return ResourceManager.GetString("HighPassFilterProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 High Pass Filter 的本地化字符串。 + /// + public static string HighPassFilterProcessor_Name { + get { + return ResourceManager.GetString("HighPassFilterProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Clip Limit 的本地化字符串。 + /// + public static string HistogramEqualizationProcessor_ClipLimit { + get { + return ResourceManager.GetString("HistogramEqualizationProcessor_ClipLimit", resourceCulture); + } + } + + /// + /// 查找类似 Contrast limiting threshold for CLAHE 的本地化字符串。 + /// + public static string HistogramEqualizationProcessor_ClipLimit_Desc { + get { + return ResourceManager.GetString("HistogramEqualizationProcessor_ClipLimit_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Enhance image contrast 的本地化字符串。 + /// + public static string HistogramEqualizationProcessor_Description { + get { + return ResourceManager.GetString("HistogramEqualizationProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Equalization Method 的本地化字符串。 + /// + public static string HistogramEqualizationProcessor_Method { + get { + return ResourceManager.GetString("HistogramEqualizationProcessor_Method", resourceCulture); + } + } + + /// + /// 查找类似 Select histogram equalization algorithm 的本地化字符串。 + /// + public static string HistogramEqualizationProcessor_Method_Desc { + get { + return ResourceManager.GetString("HistogramEqualizationProcessor_Method_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Histogram Equalization 的本地化字符串。 + /// + public static string HistogramEqualizationProcessor_Name { + get { + return ResourceManager.GetString("HistogramEqualizationProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Tile Size 的本地化字符串。 + /// + public static string HistogramEqualizationProcessor_TileSize { + get { + return ResourceManager.GetString("HistogramEqualizationProcessor_TileSize", resourceCulture); + } + } + + /// + /// 查找类似 Tile size for CLAHE 的本地化字符串。 + /// + public static string HistogramEqualizationProcessor_TileSize_Desc { + get { + return ResourceManager.GetString("HistogramEqualizationProcessor_TileSize_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Detect horizontal edges specifically 的本地化字符串。 + /// + public static string HorizontalEdgeProcessor_Description { + get { + return ResourceManager.GetString("HorizontalEdgeProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Detection Method 的本地化字符串。 + /// + public static string HorizontalEdgeProcessor_Method { + get { + return ResourceManager.GetString("HorizontalEdgeProcessor_Method", resourceCulture); + } + } + + /// + /// 查找类似 Select horizontal edge detection algorithm 的本地化字符串。 + /// + public static string HorizontalEdgeProcessor_Method_Desc { + get { + return ResourceManager.GetString("HorizontalEdgeProcessor_Method_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Horizontal Edge Detection 的本地化字符串。 + /// + public static string HorizontalEdgeProcessor_Name { + get { + return ResourceManager.GetString("HorizontalEdgeProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Sensitivity 的本地化字符串。 + /// + public static string HorizontalEdgeProcessor_Sensitivity { + get { + return ResourceManager.GetString("HorizontalEdgeProcessor_Sensitivity", resourceCulture); + } + } + + /// + /// 查找类似 Edge detection sensitivity 的本地化字符串。 + /// + public static string HorizontalEdgeProcessor_Sensitivity_Desc { + get { + return ResourceManager.GetString("HorizontalEdgeProcessor_Sensitivity_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Threshold 的本地化字符串。 + /// + public static string HorizontalEdgeProcessor_Threshold { + get { + return ResourceManager.GetString("HorizontalEdgeProcessor_Threshold", resourceCulture); + } + } + + /// + /// 查找类似 Edge detection threshold 的本地化字符串。 + /// + public static string HorizontalEdgeProcessor_Threshold_Desc { + get { + return ResourceManager.GetString("HorizontalEdgeProcessor_Threshold_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Detect image edges using Kirsch operator 的本地化字符串。 + /// + public static string KirschEdgeProcessor_Description { + get { + return ResourceManager.GetString("KirschEdgeProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Kirsch Edge Detection 的本地化字符串。 + /// + public static string KirschEdgeProcessor_Name { + get { + return ResourceManager.GetString("KirschEdgeProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Scale Factor 的本地化字符串。 + /// + public static string KirschEdgeProcessor_Scale { + get { + return ResourceManager.GetString("KirschEdgeProcessor_Scale", resourceCulture); + } + } + + /// + /// 查找类似 Scale factor for edge intensity 的本地化字符串。 + /// + public static string KirschEdgeProcessor_Scale_Desc { + get { + return ResourceManager.GetString("KirschEdgeProcessor_Scale_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Threshold 的本地化字符串。 + /// + public static string KirschEdgeProcessor_Threshold { + get { + return ResourceManager.GetString("KirschEdgeProcessor_Threshold", resourceCulture); + } + } + + /// + /// 查找类似 Edge detection threshold 的本地化字符串。 + /// + public static string KirschEdgeProcessor_Threshold_Desc { + get { + return ResourceManager.GetString("KirschEdgeProcessor_Threshold_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Cutoff Frequency 的本地化字符串。 + /// + public static string LowPassFilterProcessor_CutoffFrequency { + get { + return ResourceManager.GetString("LowPassFilterProcessor_CutoffFrequency", resourceCulture); + } + } + + /// + /// 查找类似 Cutoff frequency for low pass filter 的本地化字符串。 + /// + public static string LowPassFilterProcessor_CutoffFrequency_Desc { + get { + return ResourceManager.GetString("LowPassFilterProcessor_CutoffFrequency_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Remove high frequency noise in frequency domain 的本地化字符串。 + /// + public static string LowPassFilterProcessor_Description { + get { + return ResourceManager.GetString("LowPassFilterProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Low Pass Filter 的本地化字符串。 + /// + public static string LowPassFilterProcessor_Name { + get { + return ResourceManager.GetString("LowPassFilterProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Simple averaging smoothing filter 的本地化字符串。 + /// + public static string MeanFilterProcessor_Description { + get { + return ResourceManager.GetString("MeanFilterProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Kernel Size 的本地化字符串。 + /// + public static string MeanFilterProcessor_KernelSize { + get { + return ResourceManager.GetString("MeanFilterProcessor_KernelSize", resourceCulture); + } + } + + /// + /// 查找类似 Size of the filter kernel (must be odd) 的本地化字符串。 + /// + public static string MeanFilterProcessor_KernelSize_Desc { + get { + return ResourceManager.GetString("MeanFilterProcessor_KernelSize_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Mean Filter 的本地化字符串。 + /// + public static string MeanFilterProcessor_Name { + get { + return ResourceManager.GetString("MeanFilterProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Remove salt-and-pepper noise effectively 的本地化字符串。 + /// + public static string MedianFilterProcessor_Description { + get { + return ResourceManager.GetString("MedianFilterProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Kernel Size 的本地化字符串。 + /// + public static string MedianFilterProcessor_KernelSize { + get { + return ResourceManager.GetString("MedianFilterProcessor_KernelSize", resourceCulture); + } + } + + /// + /// 查找类似 Size of the filter kernel (must be odd) 的本地化字符串。 + /// + public static string MedianFilterProcessor_KernelSize_Desc { + get { + return ResourceManager.GetString("MedianFilterProcessor_KernelSize_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Median Filter 的本地化字符串。 + /// + public static string MedianFilterProcessor_Name { + get { + return ResourceManager.GetString("MedianFilterProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Flip image horizontally, vertically, or both 的本地化字符串。 + /// + public static string MirrorProcessor_Description { + get { + return ResourceManager.GetString("MirrorProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Direction 的本地化字符串。 + /// + public static string MirrorProcessor_Direction { + get { + return ResourceManager.GetString("MirrorProcessor_Direction", resourceCulture); + } + } + + /// + /// 查找类似 Flip direction: Horizontal (left-right), Vertical (up-down), Both (180° rotation) 的本地化字符串。 + /// + public static string MirrorProcessor_Direction_Desc { + get { + return ResourceManager.GetString("MirrorProcessor_Direction_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Mirror 的本地化字符串。 + /// + public static string MirrorProcessor_Name { + get { + return ResourceManager.GetString("MirrorProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Perform morphological operations (erosion, dilation, opening, closing) 的本地化字符串。 + /// + public static string MorphologyProcessor_Description { + get { + return ResourceManager.GetString("MorphologyProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Iterations 的本地化字符串。 + /// + public static string MorphologyProcessor_Iterations { + get { + return ResourceManager.GetString("MorphologyProcessor_Iterations", resourceCulture); + } + } + + /// + /// 查找类似 Number of times to repeat morphological operation 的本地化字符串。 + /// + public static string MorphologyProcessor_Iterations_Desc { + get { + return ResourceManager.GetString("MorphologyProcessor_Iterations_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Kernel Size 的本地化字符串。 + /// + public static string MorphologyProcessor_KernelSize { + get { + return ResourceManager.GetString("MorphologyProcessor_KernelSize", resourceCulture); + } + } + + /// + /// 查找类似 Size of structuring element 的本地化字符串。 + /// + public static string MorphologyProcessor_KernelSize_Desc { + get { + return ResourceManager.GetString("MorphologyProcessor_KernelSize_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Morphology Processing 的本地化字符串。 + /// + public static string MorphologyProcessor_Name { + get { + return ResourceManager.GetString("MorphologyProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Operation Type 的本地化字符串。 + /// + public static string MorphologyProcessor_Operation { + get { + return ResourceManager.GetString("MorphologyProcessor_Operation", resourceCulture); + } + } + + /// + /// 查找类似 Select morphological operation type 的本地化字符串。 + /// + public static string MorphologyProcessor_Operation_Desc { + get { + return ResourceManager.GetString("MorphologyProcessor_Operation_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Color Map 的本地化字符串。 + /// + public static string PseudoColorProcessor_ColorMap { + get { + return ResourceManager.GetString("PseudoColorProcessor_ColorMap", resourceCulture); + } + } + + /// + /// 查找类似 Select color mapping table for rendering 的本地化字符串。 + /// + public static string PseudoColorProcessor_ColorMap_Desc { + get { + return ResourceManager.GetString("PseudoColorProcessor_ColorMap_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Map grayscale image to color image using color maps 的本地化字符串。 + /// + public static string PseudoColorProcessor_Description { + get { + return ResourceManager.GetString("PseudoColorProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Invert Color Map 的本地化字符串。 + /// + public static string PseudoColorProcessor_InvertMap { + get { + return ResourceManager.GetString("PseudoColorProcessor_InvertMap", resourceCulture); + } + } + + /// + /// 查找类似 Reverse the color mapping direction 的本地化字符串。 + /// + public static string PseudoColorProcessor_InvertMap_Desc { + get { + return ResourceManager.GetString("PseudoColorProcessor_InvertMap_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Max Gray Value 的本地化字符串。 + /// + public static string PseudoColorProcessor_MaxValue { + get { + return ResourceManager.GetString("PseudoColorProcessor_MaxValue", resourceCulture); + } + } + + /// + /// 查找类似 Gray values above this will be clipped to maximum color 的本地化字符串。 + /// + public static string PseudoColorProcessor_MaxValue_Desc { + get { + return ResourceManager.GetString("PseudoColorProcessor_MaxValue_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Min Gray Value 的本地化字符串。 + /// + public static string PseudoColorProcessor_MinValue { + get { + return ResourceManager.GetString("PseudoColorProcessor_MinValue", resourceCulture); + } + } + + /// + /// 查找类似 Gray values below this will be clipped to minimum color 的本地化字符串。 + /// + public static string PseudoColorProcessor_MinValue_Desc { + get { + return ResourceManager.GetString("PseudoColorProcessor_MinValue_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Pseudo Color Rendering 的本地化字符串。 + /// + public static string PseudoColorProcessor_Name { + get { + return ResourceManager.GetString("PseudoColorProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Multi-scale shadow correction and illumination equalization based on Retinex 的本地化字符串。 + /// + public static string RetinexProcessor_Description { + get { + return ResourceManager.GetString("RetinexProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Gain 的本地化字符串。 + /// + public static string RetinexProcessor_Gain { + get { + return ResourceManager.GetString("RetinexProcessor_Gain", resourceCulture); + } + } + + /// + /// 查找类似 Output gain factor 的本地化字符串。 + /// + public static string RetinexProcessor_Gain_Desc { + get { + return ResourceManager.GetString("RetinexProcessor_Gain_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Processing Method 的本地化字符串。 + /// + public static string RetinexProcessor_Method { + get { + return ResourceManager.GetString("RetinexProcessor_Method", resourceCulture); + } + } + + /// + /// 查找类似 Select Retinex algorithm type 的本地化字符串。 + /// + public static string RetinexProcessor_Method_Desc { + get { + return ResourceManager.GetString("RetinexProcessor_Method_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Retinex Shadow Correction 的本地化字符串。 + /// + public static string RetinexProcessor_Name { + get { + return ResourceManager.GetString("RetinexProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Offset 的本地化字符串。 + /// + public static string RetinexProcessor_Offset { + get { + return ResourceManager.GetString("RetinexProcessor_Offset", resourceCulture); + } + } + + /// + /// 查找类似 Output offset value 的本地化字符串。 + /// + public static string RetinexProcessor_Offset_Desc { + get { + return ResourceManager.GetString("RetinexProcessor_Offset_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Scale 1 (Small) 的本地化字符串。 + /// + public static string RetinexProcessor_Sigma1 { + get { + return ResourceManager.GetString("RetinexProcessor_Sigma1", resourceCulture); + } + } + + /// + /// 查找类似 Small scale Gaussian kernel sigma for detail enhancement 的本地化字符串。 + /// + public static string RetinexProcessor_Sigma1_Desc { + get { + return ResourceManager.GetString("RetinexProcessor_Sigma1_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Scale 2 (Medium) 的本地化字符串。 + /// + public static string RetinexProcessor_Sigma2 { + get { + return ResourceManager.GetString("RetinexProcessor_Sigma2", resourceCulture); + } + } + + /// + /// 查找类似 Medium scale Gaussian kernel sigma for local illumination correction 的本地化字符串。 + /// + public static string RetinexProcessor_Sigma2_Desc { + get { + return ResourceManager.GetString("RetinexProcessor_Sigma2_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Scale 3 (Large) 的本地化字符串。 + /// + public static string RetinexProcessor_Sigma3 { + get { + return ResourceManager.GetString("RetinexProcessor_Sigma3", resourceCulture); + } + } + + /// + /// 查找类似 Large scale Gaussian kernel sigma for global illumination correction 的本地化字符串。 + /// + public static string RetinexProcessor_Sigma3_Desc { + get { + return ResourceManager.GetString("RetinexProcessor_Sigma3_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Angle 的本地化字符串。 + /// + public static string RotateProcessor_Angle { + get { + return ResourceManager.GetString("RotateProcessor_Angle", resourceCulture); + } + } + + /// + /// 查找类似 Rotation angle in degrees, positive is counter-clockwise 的本地化字符串。 + /// + public static string RotateProcessor_Angle_Desc { + get { + return ResourceManager.GetString("RotateProcessor_Angle_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Background 的本地化字符串。 + /// + public static string RotateProcessor_BackgroundValue { + get { + return ResourceManager.GetString("RotateProcessor_BackgroundValue", resourceCulture); + } + } + + /// + /// 查找类似 Background fill value (0-255) for areas outside the original image 的本地化字符串。 + /// + public static string RotateProcessor_BackgroundValue_Desc { + get { + return ResourceManager.GetString("RotateProcessor_BackgroundValue_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Rotate image by arbitrary angle with optional canvas expansion 的本地化字符串。 + /// + public static string RotateProcessor_Description { + get { + return ResourceManager.GetString("RotateProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Expand Canvas 的本地化字符串。 + /// + public static string RotateProcessor_ExpandCanvas { + get { + return ResourceManager.GetString("RotateProcessor_ExpandCanvas", resourceCulture); + } + } + + /// + /// 查找类似 Expand canvas to fit the entire rotated image, otherwise crop to original size 的本地化字符串。 + /// + public static string RotateProcessor_ExpandCanvas_Desc { + get { + return ResourceManager.GetString("RotateProcessor_ExpandCanvas_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Interpolation 的本地化字符串。 + /// + public static string RotateProcessor_Interpolation { + get { + return ResourceManager.GetString("RotateProcessor_Interpolation", resourceCulture); + } + } + + /// + /// 查找类似 Interpolation method: Nearest (fast), Bilinear (smooth), Bicubic (high quality) 的本地化字符串。 + /// + public static string RotateProcessor_Interpolation_Desc { + get { + return ResourceManager.GetString("RotateProcessor_Interpolation_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Rotate 的本地化字符串。 + /// + public static string RotateProcessor_Name { + get { + return ResourceManager.GetString("RotateProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Enhance image edges and details 的本地化字符串。 + /// + public static string SharpenProcessor_Description { + get { + return ResourceManager.GetString("SharpenProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Kernel Size 的本地化字符串。 + /// + public static string SharpenProcessor_KernelSize { + get { + return ResourceManager.GetString("SharpenProcessor_KernelSize", resourceCulture); + } + } + + /// + /// 查找类似 Size of sharpening kernel (must be odd) 的本地化字符串。 + /// + public static string SharpenProcessor_KernelSize_Desc { + get { + return ResourceManager.GetString("SharpenProcessor_KernelSize_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Sharpen Method 的本地化字符串。 + /// + public static string SharpenProcessor_Method { + get { + return ResourceManager.GetString("SharpenProcessor_Method", resourceCulture); + } + } + + /// + /// 查找类似 Select sharpening algorithm 的本地化字符串。 + /// + public static string SharpenProcessor_Method_Desc { + get { + return ResourceManager.GetString("SharpenProcessor_Method_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Sharpen 的本地化字符串。 + /// + public static string SharpenProcessor_Name { + get { + return ResourceManager.GetString("SharpenProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Strength 的本地化字符串。 + /// + public static string SharpenProcessor_Strength { + get { + return ResourceManager.GetString("SharpenProcessor_Strength", resourceCulture); + } + } + + /// + /// 查找类似 Strength of sharpening effect 的本地化字符串。 + /// + public static string SharpenProcessor_Strength_Desc { + get { + return ResourceManager.GetString("SharpenProcessor_Strength_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Edge enhancement and denoising 的本地化字符串。 + /// + public static string ShockFilterProcessor_Description { + get { + return ResourceManager.GetString("ShockFilterProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Time Step 的本地化字符串。 + /// + public static string ShockFilterProcessor_Dt { + get { + return ResourceManager.GetString("ShockFilterProcessor_Dt", resourceCulture); + } + } + + /// + /// 查找类似 Evolution time step 的本地化字符串。 + /// + public static string ShockFilterProcessor_Dt_Desc { + get { + return ResourceManager.GetString("ShockFilterProcessor_Dt_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Iterations 的本地化字符串。 + /// + public static string ShockFilterProcessor_Iterations { + get { + return ResourceManager.GetString("ShockFilterProcessor_Iterations", resourceCulture); + } + } + + /// + /// 查找类似 Number of filter iterations 的本地化字符串。 + /// + public static string ShockFilterProcessor_Iterations_Desc { + get { + return ResourceManager.GetString("ShockFilterProcessor_Iterations_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Shock Filter 的本地化字符串。 + /// + public static string ShockFilterProcessor_Name { + get { + return ResourceManager.GetString("ShockFilterProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Threshold 的本地化字符串。 + /// + public static string ShockFilterProcessor_Theta { + get { + return ResourceManager.GetString("ShockFilterProcessor_Theta", resourceCulture); + } + } + + /// + /// 查找类似 Edge detection threshold 的本地化字符串。 + /// + public static string ShockFilterProcessor_Theta_Desc { + get { + return ResourceManager.GetString("ShockFilterProcessor_Theta_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Detect image edges using Sobel operator 的本地化字符串。 + /// + public static string SobelEdgeProcessor_Description { + get { + return ResourceManager.GetString("SobelEdgeProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Detection Direction 的本地化字符串。 + /// + public static string SobelEdgeProcessor_Direction { + get { + return ResourceManager.GetString("SobelEdgeProcessor_Direction", resourceCulture); + } + } + + /// + /// 查找类似 Select edge detection direction 的本地化字符串。 + /// + public static string SobelEdgeProcessor_Direction_Desc { + get { + return ResourceManager.GetString("SobelEdgeProcessor_Direction_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Kernel Size 的本地化字符串。 + /// + public static string SobelEdgeProcessor_KernelSize { + get { + return ResourceManager.GetString("SobelEdgeProcessor_KernelSize", resourceCulture); + } + } + + /// + /// 查找类似 Sobel operator kernel size (odd number) 的本地化字符串。 + /// + public static string SobelEdgeProcessor_KernelSize_Desc { + get { + return ResourceManager.GetString("SobelEdgeProcessor_KernelSize_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Sobel Edge Detection 的本地化字符串。 + /// + public static string SobelEdgeProcessor_Name { + get { + return ResourceManager.GetString("SobelEdgeProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Scale Factor 的本地化字符串。 + /// + public static string SobelEdgeProcessor_Scale { + get { + return ResourceManager.GetString("SobelEdgeProcessor_Scale", resourceCulture); + } + } + + /// + /// 查找类似 Scale factor for edge intensity 的本地化字符串。 + /// + public static string SobelEdgeProcessor_Scale_Desc { + get { + return ResourceManager.GetString("SobelEdgeProcessor_Scale_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Binarize image 的本地化字符串。 + /// + public static string ThresholdProcessor_Description { + get { + return ResourceManager.GetString("ThresholdProcessor_Description", resourceCulture); + } + } + + /// + /// 查找类似 Max Value 的本地化字符串。 + /// + public static string ThresholdProcessor_MaxValue { + get { + return ResourceManager.GetString("ThresholdProcessor_MaxValue", resourceCulture); + } + } + + /// + /// 查找类似 Pixels above threshold will be set to this value 的本地化字符串。 + /// + public static string ThresholdProcessor_MaxValue_Desc { + get { + return ResourceManager.GetString("ThresholdProcessor_MaxValue_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Threshold Segmentation 的本地化字符串。 + /// + public static string ThresholdProcessor_Name { + get { + return ResourceManager.GetString("ThresholdProcessor_Name", resourceCulture); + } + } + + /// + /// 查找类似 Threshold 的本地化字符串。 + /// + public static string ThresholdProcessor_Threshold { + get { + return ResourceManager.GetString("ThresholdProcessor_Threshold", resourceCulture); + } + } + + /// + /// 查找类似 Binarization threshold, pixels above this value will be set to max value 的本地化字符串。 + /// + public static string ThresholdProcessor_Threshold_Desc { + get { + return ResourceManager.GetString("ThresholdProcessor_Threshold_Desc", resourceCulture); + } + } + + /// + /// 查找类似 Use Otsu Auto Threshold 的本地化字符串。 + /// + public static string ThresholdProcessor_UseOtsu { + get { + return ResourceManager.GetString("ThresholdProcessor_UseOtsu", resourceCulture); + } + } + + /// + /// 查找类似 When enabled, optimal threshold will be calculated automatically 的本地化字符串。 + /// + public static string ThresholdProcessor_UseOtsu_Desc { + get { + return ResourceManager.GetString("ThresholdProcessor_UseOtsu_Desc", resourceCulture); + } + } + } +} diff --git a/ImageProcessing.Processors/Resources/Resources.resx b/ImageProcessing.Processors/Resources/Resources.resx new file mode 100644 index 0000000..eacc699 --- /dev/null +++ b/ImageProcessing.Processors/Resources/Resources.resx @@ -0,0 +1,1293 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + Contrast Adjustment + + + Adjust image contrast and brightness + + + Contrast + + + Contrast gain, 1.0 for original contrast + + + Brightness + + + Brightness offset + + + Auto Contrast + + + Automatically stretch contrast to full range + + + Use CLAHE + + + Use Contrast Limited Adaptive Histogram Equalization + + + CLAHE Clip Limit + + + CLAHE contrast limit threshold + + + + + Band Pass Filter + + + Preserve image information within specified frequency range + + + Low Cutoff Radius + + + Components below this frequency will be removed + + + High Cutoff Radius + + + Components above this frequency will be removed + + + Filter Type + + + Transition characteristics of the filter + + + Butterworth Order + + + Order of Butterworth filter + + + + + Contour Detection + + + Detect contours in image and output contour information + + + Target Color + + + Select the color of regions to find (white or black) + + + Enable Threshold + + + Apply binary threshold before contour detection + + + Threshold Value + + + Threshold value for binarization (0-255) + + + Use Otsu Auto Threshold + + + Automatically calculate optimal threshold + + + Min Area + + + Filter contours smaller than this area + + + Max Area + + + Filter contours larger than this area + + + Line Thickness + + + Thickness of contour lines + + + + + Division Operation + + + Perform division operation on image for background correction and normalization + + + Divisor + + + Each pixel value will be divided by this number + + + Scale Factor + + + Division result multiplied by this scale factor + + + Normalize Output + + + Normalize result to 0-255 range + + + + + Gamma Correction + + + Adjust image brightness through Gamma value + + + Gamma Value + + + Gamma value, less than 1 darkens image, greater than 1 brightens image + + + Gain + + + Output gain coefficient + + + + + Gaussian Blur + + + Smooth image using Gaussian kernel + + + Kernel Size + + + Size of Gaussian kernel, must be odd + + + Standard Deviation + + + Standard deviation of Gaussian kernel, controls blur amount + + + + + Morphology Processing + + + Perform morphological operations (erosion, dilation, opening, closing) + + + Operation Type + + + Select morphological operation type + + + Kernel Size + + + Size of structuring element + + + Iterations + + + Number of times to repeat morphological operation + + + + + Shock Filter + + + Edge enhancement and denoising + + + Iterations + + + Number of filter iterations + + + Threshold + + + Edge detection threshold + + + Time Step + + + Evolution time step + + + + + Threshold Segmentation + + + Binarize image + + + Threshold + + + Binarization threshold, pixels above this value will be set to max value + + + Max Value + + + Pixels above threshold will be set to this value + + + Use Otsu Auto Threshold + + + When enabled, optimal threshold will be calculated automatically + + + + + Comprehensive Filter + + + Integrated multiple filtering methods + + + Filter Type + + + Select filtering method + + + Kernel Size + + + Size of the filter kernel (must be odd) + + + Sigma + + + Standard deviation for Gaussian/Bilateral filter + + + Cutoff Frequency + + + Cutoff frequency for frequency domain filtering + + + Low Cutoff Radius + + + Components below this frequency will be removed + + + High Cutoff Radius + + + Components above this frequency will be removed + + + Band Pass Filter Type + + + Transition characteristics of the band pass filter + + + Butterworth Order + + + Order of Butterworth filter + + + + + Gaussian Filter + + + Smooth image and reduce Gaussian noise while preserving edges + + + + + Median Filter + + + Remove salt-and-pepper noise effectively + + + + + Mean Filter + + + Simple averaging smoothing filter + + + + + Bilateral Filter + + + Edge-preserving smoothing filter + + + + + Low Pass Filter + + + Remove high frequency noise in frequency domain + + + + + High Pass Filter + + + Edge enhancement in frequency domain + + + + + Band Pass Filter + + + Preserve image information within specified frequency range + + + + + Median Filter + + + Remove salt-and-pepper noise effectively + + + Kernel Size + + + Size of the filter kernel (must be odd) + + + + + Mean Filter + + + Simple averaging smoothing filter + + + Kernel Size + + + Size of the filter kernel (must be odd) + + + + + Bilateral Filter + + + Edge-preserving smoothing filter + + + Diameter + + + Diameter of each pixel neighborhood + + + Sigma Color + + + Filter sigma in the color space + + + Sigma Space + + + Filter sigma in the coordinate space + + + + + Low Pass Filter + + + Remove high frequency noise in frequency domain + + + Cutoff Frequency + + + Cutoff frequency for low pass filter + + + + + High Pass Filter + + + Edge enhancement in frequency domain + + + Cutoff Frequency + + + Cutoff frequency for high pass filter + + + + + Grayscale Conversion + + + Convert image to grayscale + + + Conversion Method + + + Method for grayscale conversion + + + + + Sharpen + + + Enhance image edges and details + + + Sharpen Method + + + Select sharpening algorithm + + + Strength + + + Strength of sharpening effect + + + Kernel Size + + + Size of sharpening kernel (must be odd) + + + + + Histogram Equalization + + + Enhance image contrast + + + Equalization Method + + + Select histogram equalization algorithm + + + Clip Limit + + + Contrast limiting threshold for CLAHE + + + Tile Size + + + Tile size for CLAHE + + + + + Sobel Edge Detection + + + Detect image edges using Sobel operator + + + Detection Direction + + + Select edge detection direction + + + Kernel Size + + + Sobel operator kernel size (odd number) + + + Scale Factor + + + Scale factor for edge intensity + + + + + Kirsch Edge Detection + + + Detect image edges using Kirsch operator + + + Threshold + + + Edge detection threshold + + + Scale Factor + + + Scale factor for edge intensity + + + + + Horizontal Edge Detection + + + Detect horizontal edges specifically + + + Detection Method + + + Select horizontal edge detection algorithm + + + Sensitivity + + + Edge detection sensitivity + + + Threshold + + + Edge detection threshold + + + + + Retinex Shadow Correction + + + Multi-scale shadow correction and illumination equalization based on Retinex + + + Processing Method + + + Select Retinex algorithm type + + + Scale 1 (Small) + + + Small scale Gaussian kernel sigma for detail enhancement + + + Scale 2 (Medium) + + + Medium scale Gaussian kernel sigma for local illumination correction + + + Scale 3 (Large) + + + Large scale Gaussian kernel sigma for global illumination correction + + + Gain + + + Output gain factor + + + Offset + + + Output offset value + + + + + HDR Enhancement + + + High Dynamic Range image enhancement with tone mapping + + + Tone Mapping Method + + + Select HDR tone mapping algorithm: LocalToneMap, AdaptiveLog, Drago, BilateralToneMap + + + Gamma + + + Gamma correction value for output brightness adjustment + + + Saturation + + + Contrast saturation factor for LocalToneMap method + + + Detail Boost + + + Detail enhancement factor, higher values reveal more fine details + + + Sigma Space + + + Spatial sigma for base layer extraction, controls smoothing range + + + Sigma Color + + + Color sigma for bilateral tone mapping, controls edge preservation + + + Bias + + + Bias for adaptive logarithmic and Drago mapping, controls dark/bright balance + + + + + Mirror + + + Flip image horizontally, vertically, or both + + + Direction + + + Flip direction: Horizontal (left-right), Vertical (up-down), Both (180° rotation) + + + + + Rotate + + + Rotate image by arbitrary angle with optional canvas expansion + + + Angle + + + Rotation angle in degrees, positive is counter-clockwise + + + Expand Canvas + + + Expand canvas to fit the entire rotated image, otherwise crop to original size + + + Background + + + Background fill value (0-255) for areas outside the original image + + + Interpolation + + + Interpolation method: Nearest (fast), Bilinear (smooth), Bicubic (high quality) + + + + + Pseudo Color Rendering + + + Map grayscale image to color image using color maps + + + Color Map + + + Select color mapping table for rendering + + + Min Gray Value + + + Gray values below this will be clipped to minimum color + + + Max Gray Value + + + Gray values above this will be clipped to maximum color + + + Invert Color Map + + + Reverse the color mapping direction + + + + + Electronic Film Effect + + + Simulate traditional X-ray film display with window/level and characteristic curves + + + Window Center + + + Center gray value of the display window (Window Level) + + + Window Width + + + Width of the display window, controls visible gray range + + + Invert (Negative) + + + Invert image to negative film effect + + + Characteristic Curve + + + Film characteristic curve: Linear, Sigmoid (S-curve), Logarithmic, Exponential + + + Curve Strength + + + Strength of the characteristic curve effect + + + Edge Enhancement + + + Edge enhancement strength to simulate film sharpening, 0 to disable + + + + + Sub-Pixel Zoom + + + High-quality sub-pixel image magnification with multiple interpolation methods + + + Scale Factor + + + Magnification factor, supports fractional values (e.g. 1.5, 2.3) + + + Interpolation + + + Interpolation method: Nearest, Bilinear, Bicubic, Lanczos (highest quality) + + + Sharpen After Zoom + + + Apply sharpening after magnification to compensate interpolation blur + + + Sharpen Strength + + + Strength of post-zoom sharpening + + + + + Super Resolution (AI) + + + Deep learning based super resolution using EDSR/FSRCNN models + + + Model + + + Super resolution model: EDSR (high quality, slow) or FSRCNN (fast, lightweight) + + + Scale Factor + + + Upscaling factor: 2x, 3x, or 4x + + + + + Color Layer Separation + + + Separate grayscale image into distinct intensity layers + + + Layers + + + Number of intensity layers (2-16) + + + Method + + + Thresholding method: Uniform (equal intervals) or Otsu (adaptive) + + + Output Mode + + + Output mapping: EqualSpaced (evenly distributed) or MidValue (interval midpoint) + + + Target Layer + + + 0 = show all layers, 1~N = show only the specified layer (white) with others black + + + + + Hierarchical Enhancement + + + Enhance image details at different scales using Laplacian pyramid decomposition + + + Pyramid Levels + + + Number of pyramid decomposition levels (2-8) + + + Fine Detail Gain + + + Gain for finest detail layer (edges, textures). 1.0 = original, >1 = enhance, <1 = suppress + + + Medium Detail Gain + + + Gain for medium-scale details. 1.0 = original, >1 = enhance, <1 = suppress + + + Coarse Detail Gain + + + Gain for coarse-scale details (large structures). 1.0 = original, >1 = enhance, <1 = suppress + + + Base Layer Gain + + + Gain for the base (lowest frequency) layer, controls overall brightness + + + Clip Limit + + + Limit detail amplitude to prevent over-enhancement artifacts. 0 = no limit + + + + + Histogram Overlay + + + Compute grayscale histogram and overlay it on the top-left corner of the image with statistics + + + + + Ellipse Detection + + + Detect ellipses in image using contour analysis and ellipse fitting + + + Min Threshold + + + Minimum threshold for dual-threshold segmentation (0-255) + + + Max Threshold + + + Maximum threshold for dual-threshold segmentation (0-255) + + + Use Otsu Auto Threshold + + + When enabled, optimal threshold will be calculated automatically + + + Min Contour Points + + + Minimum number of contour points for ellipse fitting + + + Min Area + + + Filter ellipses smaller than this area + + + Max Area + + + Filter ellipses larger than this area + + + Max Eccentricity + + + Maximum eccentricity (0=circle, close to 1=flat ellipse) + + + Max Fit Error + + + Maximum fitting error in pixels + + + Line Thickness + + + Thickness of ellipse drawing lines + + + + + Line Measurement + + + Measure distance between two points in the image + + + Point 1 X + + + X coordinate of the first point (pixels) + + + Point 1 Y + + + Y coordinate of the first point (pixels) + + + Point 2 X + + + X coordinate of the second point (pixels) + + + Point 2 Y + + + Y coordinate of the second point (pixels) + + + Pixel Size + + + Physical size per pixel for calibrated measurement + + + Unit + + + Measurement unit: px (pixels), mm, μm, cm + + + Line Thickness + + + Thickness of the measurement line + + + Show Label + + + Display distance label on the measurement line + + + + + Via Fill Rate (Tilted Geometric) + + + Measure via fill rate using 4-ellipse geometric projection on tilted X-Ray image + + + THT Limit (%) + + + Minimum fill rate to pass (default 75% per IPC-610) + + + Line Thickness + + + Thickness of ROI ellipse lines + + + + + BGA Void Rate (Auto) + + + Auto-detect BGA solder balls and measure void rate (two-step: locate BGA → detect voids) + + + BGA Min Area + + + Minimum pixel area to identify as a BGA solder ball + + + ROI Mode + + + None: full image; Polygon: polygon ROI (draw ROI first) + + + BGA Max Area + + + Maximum pixel area to identify as a BGA solder ball + + + Blur Kernel + + + Gaussian blur kernel size for BGA detection (odd number) + + + Min Circularity + + + Minimum circularity to filter non-circular contours (0~1, 1=perfect circle) + + + Void Limit (%) + + + Maximum allowed void rate (default 25% per IPC-7095) + + + Min Threshold + + + Minimum gray value for void detection (pixels in [Min,Max] = void) + + + Max Threshold + + + Maximum gray value for void detection + + + Min Void Area + + + Minimum pixel area to count as a void (filter noise) + + + Line Thickness + + + Thickness of contour lines + + + + + Point-to-Line Distance + + + Measure perpendicular distance from a point to a line + + + Pixel Size + + + Physical size per pixel for calibrated measurement + + + Unit + + + Measurement unit: px / mm / μm / cm + + + Line Thickness + + + Thickness of drawing lines + + + + + Angle Measurement + + + Measure angle between two rays sharing a common vertex + + + + + Void Measurement + + + Detect voids in ROI and calculate void rate + + + Min Threshold + + + Lower grayscale bound for void detection + + + Max Threshold + + + Upper grayscale bound for void detection + + + Min Void Area + + + Voids smaller than this area are ignored (pixels) + + + Merge Radius + + + Dilation radius for merging adjacent voids (0=no merge) + + + Blur Size + + + Gaussian blur kernel size (odd number) + + + Void Limit (%) + + + Void rate above this limit is classified as FAIL + + diff --git a/ImageProcessing.Processors/Resources/Resources.zh-CN.resx b/ImageProcessing.Processors/Resources/Resources.zh-CN.resx new file mode 100644 index 0000000..d120377 --- /dev/null +++ b/ImageProcessing.Processors/Resources/Resources.zh-CN.resx @@ -0,0 +1,1286 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 对比度调整 + + + 调整图像的对比度和亮度 + + + 对比度 + + + 对比度增益,1.0为原始对比度 + + + 亮度 + + + 亮度偏移量 + + + 自动对比度 + + + 自动拉伸对比度到全范围 + + + 使用CLAHE + + + 使用限制对比度自适应直方图均衡化 + + + CLAHE裁剪限制 + + + CLAHE的对比度限制阈值 + + + 带通滤波器 + + + 保留指定频率范围内的图像信息 + + + 低频截止半径 + + + 低于此频率的成分将被去除 + + + 高频截止半径 + + + 高于此频率的成分将被去除 + + + 滤波器类型 + + + 滤波器的过渡特性 + + + 巴特沃斯阶数 + + + 巴特沃斯滤波器的阶数 + + + 轮廓查找 + + + 检测图像中的轮廓并输出轮廓信息 + + + 目标颜色 + + + 选择要查找的区域颜色(白色或黑色) + + + 启用阈值分割 + + + 在查找轮廓前先进行二值化处理 + + + 阈值 + + + 二值化的阈值(0-255) + + + 使用Otsu自动阈值 + + + 自动计算最佳阈值 + + + 最小面积 + + + 过滤小于此面积的轮廓 + + + 最大面积 + + + 过滤大于此面积的轮廓 + + + 线条粗细 + + + 绘制轮廓的线条粗细 + + + 除法运算 + + + 对图像进行除法运算,可用于背景校正和归一化 + + + 除数 + + + 图像每个像素值将除以此数值 + + + 缩放因子 + + + 除法结果乘以此缩放因子 + + + 归一化输出 + + + 将结果归一化到0-255范围 + + + Gamma校正 + + + 通过Gamma值调节图像的亮暗程度 + + + Gamma值 + + + Gamma值,小于1图像变暗,大于1图像变亮 + + + 增益 + + + 输出增益系数 + + + 高斯模糊 + + + 使用高斯核对图像进行平滑处理 + + + 核大小 + + + 高斯核的大小,必须为奇数 + + + 标准差 + + + 高斯核的标准差,控制模糊程度 + + + 形态学处理 + + + 执行形态学操作(腐蚀、膨胀、开运算、闭运算) + + + 操作类型 + + + 选择形态学操作类型 + + + 核大小 + + + 结构元素的大小 + + + 迭代次数 + + + 形态学操作重复执行的次数 + + + 冲击滤波 + + + 边缘增强和去噪 + + + 迭代次数 + + + 滤波迭代次数 + + + 阈值 + + + 边缘检测阈值 + + + 时间步长 + + + 演化时间步长 + + + 阈值分割 + + + 对图像进行二值化处理 + + + 阈值 + + + 二值化阈值,像素值大于此值将被设为最大值 + + + 最大值 + + + 超过阈值的像素将被设置为此值 + + + 使用Otsu自动阈值 + + + 启用后将自动计算最佳阈值 + + + 综合筛选器 + + + 集成多种滤波方法 + + + 类型 + + + 选择滤波方式 + + + 核大小 + + + 滤波器卷积核大小(必须为奇数) + + + Sigma + + + 高斯 / 双边滤波器的标准差 + + + 截止频率 + + + 频域滤波的截止频率 + + + 低截止半径 + + + 低于该频率的分量将被移除 + + + 高截止半径 + + + 高于此频率的分量将被移除 + + + 类型 + + + 过镀特性 + + + 巴特沃斯阶数 + + + 巴特沃斯滤波器阶数 + + + 高斯滤波器 + + + 在保留边缘细节的同时平滑图像并降低高斯噪声 + + + 中值滤波器 + + + 有效去除椒盐噪声 + + + 均值滤波器 + + + 简单平均平滑滤波器 + + + 双边滤波器 + + + 边缘保持平滑滤波器 + + + 低通滤波器 + + + 在频域中去除高频噪声 + + + 高通滤波器 + + + 频域边缘增强 + + + 带通滤波器 + + + 频率范围 + + + 中值滤波 + + + 有效去除椒盐噪声 + + + 核大小 + + + 滤波器核的大小(必须为奇数) + + + 均值滤波 + + + 简单的平均平滑滤波器 + + + 核大小 + + + 滤波器核的大小(必须为奇数) + + + 双边滤波 + + + 保持边缘清晰的平滑滤波器 + + + 直径 + + + 每个像素邻域的直径 + + + 颜色空间标准差 + + + 颜色空间中的滤波器标准差 + + + 坐标空间标准差 + + + 坐标空间中的滤波器标准差 + + + 低通滤波 + + + 在频域中去除高频噪声 + + + 截止频率 + + + 低通滤波器的截止频率 + + + 高通滤波 + + + 在频域中进行边缘增强 + + + 截止频率 + + + 高通滤波器的截止频率 + + + 灰度转换 + + + 将图像转换为灰度图 + + + 转换方法 + + + 灰度转换的方法 + + + 锐化 + + + 增强图像边缘和细节 + + + 锐化方法 + + + 选择锐化算法 + + + 锐化强度 + + + 锐化效果的强度 + + + 核大小 + + + 锐化核的大小(奇数) + + + 直方图均衡化 + + + 增强图像对比度 + + + 均衡化方法 + + + 选择直方图均衡化算法 + + + 裁剪限制 + + + CLAHE的对比度限制阈值 + + + 分块大小 + + + CLAHE的分块大小 + + + Sobel边缘检测 + + + 使用Sobel算子检测图像边缘 + + + 检测方向 + + + 选择边缘检测方向 + + + 核大小 + + + Sobel算子的核大小(奇数) + + + 缩放系数 + + + 边缘强度的缩放系数 + + + Kirsch边缘检测 + + + 使用Kirsch算子检测图像边缘 + + + 阈值 + + + 边缘检测阈值 + + + 缩放系数 + + + 边缘强度的缩放系数 + + + 水平边缘检测 + + + 专门检测水平方向的边缘 + + + 检测方法 + + + 选择水平边缘检测算法 + + + 灵敏度 + + + 边缘检测的灵敏度 + + + 阈值 + + + 边缘检测阈值 + + + Retinex阴影校正 + + + 基于Retinex的多尺度阴影校正和光照均衡 + + + 处理方法 + + + 选择Retinex算法类型 + + + 尺度1 (小) + + + 小尺度高斯核标准差,用于细节增强 + + + 尺度2 (中) + + + 中尺度高斯核标准差,用于局部光照校正 + + + 尺度3 (大) + + + 大尺度高斯核标准差,用于全局光照校正 + + + 增益 + + + 输出增益系数 + + + 偏移 + + + 输出偏移量 + + + 高动态范围增强 + + + 基于色调映射的高动态范围图像增强 + + + 色调映射方法 + + + 选择HDR色调映射算法:局部色调映射、自适应对数映射、Drago映射、双边滤波色调映射 + + + Gamma值 + + + Gamma校正值,用于调整输出亮度 + + + 饱和度 + + + 对比度饱和系数,用于局部色调映射方法 + + + 细节增强 + + + 细节增强系数,值越大细节越丰富 + + + 空间标准差 + + + 基础层提取的空间标准差,控制平滑范围 + + + 颜色标准差 + + + 双边滤波色调映射的颜色标准差,控制保边效果 + + + 偏置 + + + 自适应对数映射和Drago映射的偏置参数,控制暗部/亮部平衡 + + + 镜像 + + + 对图像进行水平、垂直或对角镜像翻转 + + + 翻转方向 + + + 翻转方向:Horizontal(水平/左右翻转)、Vertical(垂直/上下翻转)、Both(对角翻转/旋转180°) + + + 图像旋转 + + + 按任意角度旋转图像,支持画布自适应扩展 + + + 旋转角度 + + + 旋转角度(度),正值为逆时针旋转 + + + 扩展画布 + + + 扩展画布以容纳完整旋转图像,否则裁剪为原始尺寸 + + + 背景灰度 + + + 旋转后空白区域的填充灰度值(0-255) + + + 插值方法 + + + 插值方法:Nearest(最近邻/快速)、Bilinear(双线性/平滑)、Bicubic(双三次/高质量) + + + 伪色彩渲染 + + + 将灰度图像通过色彩映射表渲染为彩色图像 + + + 色彩映射表 + + + 选择用于渲染的色彩映射表 + + + 最小灰度值 + + + 低于此值的灰度将被裁剪到最小色彩 + + + 最大灰度值 + + + 高于此值的灰度将被裁剪到最大色彩 + + + 反转色彩映射 + + + 反转色彩映射方向 + + + + + 电子胶片效果 + + + 模拟传统X射线胶片显示效果,支持窗宽窗位和特性曲线调整 + + + 窗位 + + + 显示窗口的中心灰度值(Window Level) + + + 窗宽 + + + 显示窗口的宽度,控制可见灰度范围 + + + 反转(负片) + + + 反转图像为负片效果 + + + 特性曲线 + + + 胶片特性曲线:Linear(线性)、Sigmoid(S曲线)、Logarithmic(对数)、Exponential(指数) + + + 曲线强度 + + + 特性曲线的效果强度 + + + 边缘增强 + + + 边缘增强强度,模拟胶片锐化效果,0为关闭 + + + + + 亚像素放大 + + + 通过高质量插值实现图像的亚像素级放大 + + + 放大倍率 + + + 放大倍率,支持小数(如1.5、2.3) + + + 插值方法 + + + 插值方法:Nearest(最近邻)、Bilinear(双线性)、Bicubic(双三次)、Lanczos(最高质量) + + + 放大后锐化 + + + 放大后进行锐化补偿,抵消插值产生的模糊 + + + 锐化强度 + + + 放大后锐化的强度 + + + + + 超分辨率重建 (AI) + + + 基于深度学习的超分辨率重建,使用EDSR/FSRCNN模型 + + + 模型 + + + 超分辨率模型:EDSR(高质量/较慢)或 FSRCNN(快速/轻量) + + + 放大倍率 + + + 放大倍率:2x、3x 或 4x + + + + + 色彩分层 + + + 将灰度图像按亮度区间分为多个层级 + + + 分层数 + + + 灰度分层数量(2-16) + + + 分层方法 + + + 分层方法:Uniform(均匀等分)或 Otsu(自适应) + + + 输出模式 + + + 输出映射:EqualSpaced(等间距灰度)或 MidValue(区间中值) + + + 目标层 + + + 0 = 显示全部层,1~N = 只显示指定层(白色),其余为黑色 + + + + + 层次增强 + + + 基于拉普拉斯金字塔分解,对不同尺度的图像细节独立增强 + + + 金字塔层数 + + + 金字塔分解层数(2-8),层数越多分解越精细 + + + 精细细节增益 + + + 最精细层(边缘、纹理)的增益。1.0=原始,>1=增强,<1=抑制 + + + 中等细节增益 + + + 中等尺度细节的增益。1.0=原始,>1=增强,<1=抑制 + + + 粗糙细节增益 + + + 粗尺度细节(大结构)的增益。1.0=原始,>1=增强,<1=抑制 + + + 基础层增益 + + + 基础层(最低频)增益,控制整体亮度 + + + 裁剪限制 + + + 限制细节幅度,防止过增强产生伪影。0=不限制 + + + + + 直方图叠加 + + + 计算灰度直方图,以蓝色柱状图叠加到图像左上角,并输出统计表格 + + + + + 椭圆检测 + + + 基于轮廓分析和椭圆拟合检测图像中的椭圆 + + + 最小阈值 + + + 双阈值分割的最小阈值(0-255) + + + 最大阈值 + + + 双阈值分割的最大阈值(0-255) + + + 使用Otsu自动阈值 + + + 启用后将自动计算最佳阈值 + + + 最小轮廓点数 + + + 椭圆拟合所需的最小轮廓点数 + + + 最小面积 + + + 过滤小于此面积的椭圆 + + + 最大面积 + + + 过滤大于此面积的椭圆 + + + 最大离心率 + + + 最大离心率(0=圆,接近1=扁椭圆) + + + 最大拟合误差 + + + 最大拟合误差(像素) + + + 线条粗细 + + + 绘制椭圆的线条粗细 + + + + + 直线测量 + + + 测量图像中两点之间的距离 + + + 点1 X坐标 + + + 第一个点的X坐标(像素) + + + 点1 Y坐标 + + + 第一个点的Y坐标(像素) + + + 点2 X坐标 + + + 第二个点的X坐标(像素) + + + 点2 Y坐标 + + + 第二个点的Y坐标(像素) + + + 像素尺寸 + + + 每像素对应的物理尺寸,用于标定测量 + + + 单位 + + + 测量单位:px(像素)、mm、μm、cm + + + 线条粗细 + + + 测量线的线条粗细 + + + 显示标注 + + + 在测量线上显示距离标注 + + + + + 通孔填锡率 + + + 基于四椭圆倾斜投影几何法测量通孔填锡率 + + + THT 限值 (%) + + + 最低合格填锡率(默认75%,参考IPC-610) + + + 线条粗细 + + + ROI椭圆线条粗细 + + + + + BGA空洞率检测(自动) + + + 自动检测BGA焊球并测量空洞率(两步法:定位焊球 → 检测气泡) + + + 焊球最小面积 + + + 识别为BGA焊球的最小像素面积 + + + ROI模式 + + + None: 全图检测; Polygon: 多边形ROI(需先绘制ROI) + + + 焊球最大面积 + + + 识别为BGA焊球的最大像素面积 + + + 模糊核大小 + + + BGA检测时的高斯模糊核大小(奇数) + + + 最小圆度 + + + 过滤非圆形轮廓的最小圆度(0~1,1=完美圆形) + + + 气泡率限值 (%) + + + 最大允许气泡率(默认25%,参考IPC-7095) + + + 最小阈值 + + + 气泡检测的最小灰度值(灰度在[最小,最大]范围内判为气泡) + + + 最大阈值 + + + 气泡检测的最大灰度值 + + + 最小气泡面积 + + + 小于此面积的区域视为噪点忽略(像素) + + + 线条粗细 + + + 轮廓线条粗细 + + + + + 点到直线距离 + + + 测量点到直线的垂直距离 + + + 像素尺寸 + + + 每像素对应的物理尺寸 + + + 单位 + + + 测量单位:px / mm / μm / cm + + + 线条粗细 + + + 绘制线条粗细 + + + + + 角度测量 + + + 测量共端点两条射线的夹角 + + + + + 空隙测量 + + + 检测ROI内气泡并计算空隙率 + + + 最小阈值 + + + 气泡灰度下限 + + + 最大阈值 + + + 气泡灰度上限 + + + 最小气泡面积 + + + 小于此面积的气泡将被忽略(像素) + + + 合并半径 + + + 相邻气泡合并的膨胀半径(0=不合并) + + + 模糊核大小 + + + 高斯模糊核大小(奇数) + + + 空隙率限值(%) + + + 超过此限值判定为FAIL + + \ No newline at end of file diff --git a/ImageProcessing.Processors/其他/FilmEffectProcessor.cs b/ImageProcessing.Processors/其他/FilmEffectProcessor.cs new file mode 100644 index 0000000..2a2c831 --- /dev/null +++ b/ImageProcessing.Processors/其他/FilmEffectProcessor.cs @@ -0,0 +1,197 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: FilmEffectProcessor.cs +// 描述: 电子胶片效果算子,模拟传统X射线胶片的显示效果 +// 功能: +// - 窗宽窗位(Window/Level)调整 +// - 胶片反转(正片/负片) +// - 多种胶片特性曲线(线性、S曲线、对数、指数) +// - 边缘增强(模拟胶片锐化效果) +// - 使用查找表(LUT)加速处理 +// 算法: 窗宽窗位映射 + 特性曲线变换 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// 电子胶片效果算子 +/// +public class FilmEffectProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + private byte[] _lut = new byte[256]; + + public FilmEffectProcessor() + { + Name = LocalizationHelper.GetString("FilmEffectProcessor_Name"); + Description = LocalizationHelper.GetString("FilmEffectProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("WindowCenter", new ProcessorParameter( + "WindowCenter", + LocalizationHelper.GetString("FilmEffectProcessor_WindowCenter"), + typeof(int), + 128, + 0, + 255, + LocalizationHelper.GetString("FilmEffectProcessor_WindowCenter_Desc"))); + + Parameters.Add("WindowWidth", new ProcessorParameter( + "WindowWidth", + LocalizationHelper.GetString("FilmEffectProcessor_WindowWidth"), + typeof(int), + 255, + 1, + 255, + LocalizationHelper.GetString("FilmEffectProcessor_WindowWidth_Desc"))); + + Parameters.Add("Invert", new ProcessorParameter( + "Invert", + LocalizationHelper.GetString("FilmEffectProcessor_Invert"), + typeof(bool), + false, + null, + null, + LocalizationHelper.GetString("FilmEffectProcessor_Invert_Desc"))); + + Parameters.Add("Curve", new ProcessorParameter( + "Curve", + LocalizationHelper.GetString("FilmEffectProcessor_Curve"), + typeof(string), + "Linear", + null, + null, + LocalizationHelper.GetString("FilmEffectProcessor_Curve_Desc"), + new string[] { "Linear", "Sigmoid", "Logarithmic", "Exponential" })); + + Parameters.Add("CurveStrength", new ProcessorParameter( + "CurveStrength", + LocalizationHelper.GetString("FilmEffectProcessor_CurveStrength"), + typeof(double), + 1.0, + 0.1, + 5.0, + LocalizationHelper.GetString("FilmEffectProcessor_CurveStrength_Desc"))); + + Parameters.Add("EdgeEnhance", new ProcessorParameter( + "EdgeEnhance", + LocalizationHelper.GetString("FilmEffectProcessor_EdgeEnhance"), + typeof(double), + 0.0, + 0.0, + 3.0, + LocalizationHelper.GetString("FilmEffectProcessor_EdgeEnhance_Desc"))); + + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + int windowCenter = GetParameter("WindowCenter"); + int windowWidth = GetParameter("WindowWidth"); + bool invert = GetParameter("Invert"); + string curve = GetParameter("Curve"); + double curveStrength = GetParameter("CurveStrength"); + double edgeEnhance = GetParameter("EdgeEnhance"); + + // 构建查找表 + BuildLUT(windowCenter, windowWidth, invert, curve, curveStrength); + + // 应用 LUT + var result = inputImage.Clone(); + int width = result.Width; + int height = result.Height; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + result.Data[y, x, 0] = _lut[result.Data[y, x, 0]]; + } + } + + // 边缘增强(模拟胶片锐化) + if (edgeEnhance > 0.01) + { + using var blurred = inputImage.SmoothGaussian(3); + using var detail = new Image(width, height); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + float diff = inputImage.Data[y, x, 0] - blurred.Data[y, x, 0]; + float enhanced = result.Data[y, x, 0] + (float)(diff * edgeEnhance); + result.Data[y, x, 0] = (byte)Math.Clamp((int)enhanced, 0, 255); + } + } + } + + _logger.Debug("Process: WC={WC}, WW={WW}, Invert={Inv}, Curve={Curve}, Strength={Str}, Edge={Edge}", + windowCenter, windowWidth, invert, curve, curveStrength, edgeEnhance); + + return result; + } + + private void BuildLUT(int wc, int ww, bool invert, string curve, double strength) + { + double halfW = ww / 2.0; + double low = wc - halfW; + double high = wc + halfW; + + for (int i = 0; i < 256; i++) + { + // 窗宽窗位映射到 [0, 1] + double normalized; + if (ww <= 1) + normalized = i >= wc ? 1.0 : 0.0; + else + normalized = Math.Clamp((i - low) / (high - low), 0.0, 1.0); + + // 应用特性曲线 + double mapped = curve switch + { + "Sigmoid" => ApplySigmoid(normalized, strength), + "Logarithmic" => ApplyLogarithmic(normalized, strength), + "Exponential" => ApplyExponential(normalized, strength), + _ => normalized // Linear + }; + + // 反转(负片效果) + if (invert) + mapped = 1.0 - mapped; + + _lut[i] = (byte)Math.Clamp((int)(mapped * 255.0), 0, 255); + } + } + + /// S曲线(Sigmoid):增强中间调对比度 + private static double ApplySigmoid(double x, double strength) + { + double k = strength * 10.0; + return 1.0 / (1.0 + Math.Exp(-k * (x - 0.5))); + } + + /// 对数曲线:提亮暗部,压缩亮部 + private static double ApplyLogarithmic(double x, double strength) + { + double c = strength; + return Math.Log(1.0 + c * x) / Math.Log(1.0 + c); + } + + /// 指数曲线:压缩暗部,增强亮部 + private static double ApplyExponential(double x, double strength) + { + double c = strength; + return (Math.Exp(c * x) - 1.0) / (Math.Exp(c) - 1.0); + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/其他/PseudoColorProcessor.cs b/ImageProcessing.Processors/其他/PseudoColorProcessor.cs new file mode 100644 index 0000000..e1af058 --- /dev/null +++ b/ImageProcessing.Processors/其他/PseudoColorProcessor.cs @@ -0,0 +1,149 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: PseudoColorProcessor.cs +// 描述: 伪色彩渲染算子,将灰度图像映射为彩色图像 +// 功能: +// - 支持多种 OpenCV 内置色彩映射表(Jet、Hot、Cool、Rainbow 等) +// - 可选灰度范围裁剪,突出感兴趣的灰度区间 +// - 可选反转色彩映射方向 +// 算法: 查找表(LUT)色彩映射 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// 伪色彩渲染算子 +/// +public class PseudoColorProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public PseudoColorProcessor() + { + Name = LocalizationHelper.GetString("PseudoColorProcessor_Name"); + Description = LocalizationHelper.GetString("PseudoColorProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("ColorMap", new ProcessorParameter( + "ColorMap", + LocalizationHelper.GetString("PseudoColorProcessor_ColorMap"), + typeof(string), + "Jet", + null, + null, + LocalizationHelper.GetString("PseudoColorProcessor_ColorMap_Desc"), + new string[] { "Jet", "Hot", "Cool", "Rainbow", "HSV", "Turbo", "Inferno", "Magma", "Plasma", "Bone", "Ocean", "Spring", "Summer", "Autumn", "Winter" })); + + Parameters.Add("MinValue", new ProcessorParameter( + "MinValue", + LocalizationHelper.GetString("PseudoColorProcessor_MinValue"), + typeof(int), + 0, + 0, + 255, + LocalizationHelper.GetString("PseudoColorProcessor_MinValue_Desc"))); + + Parameters.Add("MaxValue", new ProcessorParameter( + "MaxValue", + LocalizationHelper.GetString("PseudoColorProcessor_MaxValue"), + typeof(int), + 255, + 0, + 255, + LocalizationHelper.GetString("PseudoColorProcessor_MaxValue_Desc"))); + + Parameters.Add("InvertMap", new ProcessorParameter( + "InvertMap", + LocalizationHelper.GetString("PseudoColorProcessor_InvertMap"), + typeof(bool), + false, + null, + null, + LocalizationHelper.GetString("PseudoColorProcessor_InvertMap_Desc"))); + + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + string colorMapName = GetParameter("ColorMap"); + int minValue = GetParameter("MinValue"); + int maxValue = GetParameter("MaxValue"); + bool invertMap = GetParameter("InvertMap"); + + OutputData.Clear(); + + // 灰度范围裁剪与归一化 + Image normalized; + if (minValue > 0 || maxValue < 255) + { + // 将 [minValue, maxValue] 映射到 [0, 255] + normalized = inputImage.Clone(); + double scale = 255.0 / Math.Max(maxValue - minValue, 1); + for (int y = 0; y < normalized.Height; y++) + { + for (int x = 0; x < normalized.Width; x++) + { + int val = normalized.Data[y, x, 0]; + val = Math.Clamp(val, minValue, maxValue); + normalized.Data[y, x, 0] = (byte)((val - minValue) * scale); + } + } + } + else + { + normalized = inputImage.Clone(); + } + + // 反转灰度(反转色彩映射方向) + if (invertMap) + { + CvInvoke.BitwiseNot(normalized, normalized); + } + + // 应用色彩映射 + ColorMapType cmType = colorMapName switch + { + "Hot" => ColorMapType.Hot, + "Cool" => ColorMapType.Cool, + "Rainbow" => ColorMapType.Rainbow, + "HSV" => ColorMapType.Hsv, + "Turbo" => ColorMapType.Turbo, + "Inferno" => ColorMapType.Inferno, + "Magma" => ColorMapType.Magma, + "Plasma" => ColorMapType.Plasma, + "Bone" => ColorMapType.Bone, + "Ocean" => ColorMapType.Ocean, + "Spring" => ColorMapType.Spring, + "Summer" => ColorMapType.Summer, + "Autumn" => ColorMapType.Autumn, + "Winter" => ColorMapType.Winter, + _ => ColorMapType.Jet + }; + + using var colorMat = new Mat(); + CvInvoke.ApplyColorMap(normalized.Mat, colorMat, cmType); + + var colorImage = colorMat.ToImage(); + + // 将彩色图像存入 OutputData,供 UI 显示 + OutputData["PseudoColorImage"] = colorImage; + + _logger.Debug("Process: ColorMap={ColorMap}, MinValue={Min}, MaxValue={Max}, InvertMap={Invert}", + colorMapName, minValue, maxValue, invertMap); + + normalized.Dispose(); + + // 返回原始灰度图像(彩色图像通过 OutputData 传递) + return inputImage.Clone(); + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/图像变换/GrayscaleProcessor.cs b/ImageProcessing.Processors/图像变换/GrayscaleProcessor.cs new file mode 100644 index 0000000..6fb7b53 --- /dev/null +++ b/ImageProcessing.Processors/图像变换/GrayscaleProcessor.cs @@ -0,0 +1,80 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: GrayscaleProcessor.cs +// 描述: 灰度图转换算子,用于将彩色图像转换为灰度图像 +// 功能: +// - 标准灰度转换(加权平均) +// - 平均值法 +// - 最大值法 +// - 最小值法 +// 算法: 加权平均法 Gray = 0.299*R + 0.587*G + 0.114*B +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// 灰度图转换算子 +/// +public class GrayscaleProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public GrayscaleProcessor() + { + Name = LocalizationHelper.GetString("GrayscaleProcessor_Name"); + Description = LocalizationHelper.GetString("GrayscaleProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Method", new ProcessorParameter( + "Method", + LocalizationHelper.GetString("GrayscaleProcessor_Method"), + typeof(string), + "Weighted", + null, + null, + LocalizationHelper.GetString("GrayscaleProcessor_Method_Desc"), + new string[] { "Weighted", "Average", "Max", "Min" })); + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + string method = GetParameter("Method"); + + // 如果输入已经是灰度图,根据方法进行处理 + var result = inputImage.Clone(); + + switch (method) + { + case "Average": + // 对于已经是灰度的图像,平均值法不改变图像 + break; + + case "Max": + // 增强亮度 + result = result * 1.2; + break; + + case "Min": + // 降低亮度 + result = result * 0.8; + break; + + case "Weighted": + default: + // 保持原样 + break; + } + + _logger.Debug("Process: Method = {Method}", method); + return result; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/图像变换/MirrorProcessor.cs b/ImageProcessing.Processors/图像变换/MirrorProcessor.cs new file mode 100644 index 0000000..39dcd74 --- /dev/null +++ b/ImageProcessing.Processors/图像变换/MirrorProcessor.cs @@ -0,0 +1,67 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: MirrorProcessor.cs +// 描述: 镜像算子,用于图像翻转 +// 功能: +// - 水平镜像(左右翻转) +// - 垂直镜像(上下翻转) +// - 对角镜像(水平+垂直翻转,等效180°旋转) +// 算法: 像素坐标映射 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// 镜像算子 +/// +public class MirrorProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public MirrorProcessor() + { + Name = LocalizationHelper.GetString("MirrorProcessor_Name"); + Description = LocalizationHelper.GetString("MirrorProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Direction", new ProcessorParameter( + "Direction", + LocalizationHelper.GetString("MirrorProcessor_Direction"), + typeof(string), + "Horizontal", + null, + null, + LocalizationHelper.GetString("MirrorProcessor_Direction_Desc"), + new string[] { "Horizontal", "Vertical", "Both" })); + + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + string direction = GetParameter("Direction"); + + var result = inputImage.Clone(); + + FlipType flipType = direction switch + { + "Vertical" => FlipType.Vertical, + "Both" => FlipType.Both, + _ => FlipType.Horizontal + }; + + CvInvoke.Flip(inputImage, result, flipType); + + _logger.Debug("Process: Direction = {Direction}", direction); + return result; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/图像变换/RotateProcessor.cs b/ImageProcessing.Processors/图像变换/RotateProcessor.cs new file mode 100644 index 0000000..bf64c8b --- /dev/null +++ b/ImageProcessing.Processors/图像变换/RotateProcessor.cs @@ -0,0 +1,140 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: RotateProcessor.cs +// 描述: 图像旋转算子 +// 功能: +// - 任意角度旋转 +// - 支持保持原始尺寸或自适应扩展画布 +// - 可选背景填充值 +// - 支持双线性插值 +// 算法: 仿射变换旋转 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; +using System.Drawing; + +namespace ImageProcessing.Processors; + +/// +/// 图像旋转算子 +/// +public class RotateProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public RotateProcessor() + { + Name = LocalizationHelper.GetString("RotateProcessor_Name"); + Description = LocalizationHelper.GetString("RotateProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Angle", new ProcessorParameter( + "Angle", + LocalizationHelper.GetString("RotateProcessor_Angle"), + typeof(double), + 90.0, + -360.0, + 360.0, + LocalizationHelper.GetString("RotateProcessor_Angle_Desc"))); + + Parameters.Add("ExpandCanvas", new ProcessorParameter( + "ExpandCanvas", + LocalizationHelper.GetString("RotateProcessor_ExpandCanvas"), + typeof(bool), + false, + null, + null, + LocalizationHelper.GetString("RotateProcessor_ExpandCanvas_Desc"))); + + Parameters.Add("BackgroundValue", new ProcessorParameter( + "BackgroundValue", + LocalizationHelper.GetString("RotateProcessor_BackgroundValue"), + typeof(int), + 0, + 0, + 255, + LocalizationHelper.GetString("RotateProcessor_BackgroundValue_Desc"))); + + Parameters.Add("Interpolation", new ProcessorParameter( + "Interpolation", + LocalizationHelper.GetString("RotateProcessor_Interpolation"), + typeof(string), + "Bilinear", + null, + null, + LocalizationHelper.GetString("RotateProcessor_Interpolation_Desc"), + new string[] { "Nearest", "Bilinear", "Bicubic" })); + + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + double angle = GetParameter("Angle"); + bool expandCanvas = GetParameter("ExpandCanvas"); + int bgValue = GetParameter("BackgroundValue"); + string interpolation = GetParameter("Interpolation"); + + Inter interMethod = interpolation switch + { + "Nearest" => Inter.Nearest, + "Bicubic" => Inter.Cubic, + _ => Inter.Linear + }; + + int srcW = inputImage.Width; + int srcH = inputImage.Height; + PointF center = new PointF(srcW / 2.0f, srcH / 2.0f); + + // 获取旋转矩阵 + using var rotMat = new Mat(); + CvInvoke.GetRotationMatrix2D(center, angle, 1.0, rotMat); + + int dstW, dstH; + + if (expandCanvas) + { + // 计算旋转后能容纳整幅图像的画布尺寸 + double rad = Math.Abs(angle * Math.PI / 180.0); + double sinA = Math.Abs(Math.Sin(rad)); + double cosA = Math.Abs(Math.Cos(rad)); + dstW = (int)Math.Ceiling(srcW * cosA + srcH * sinA); + dstH = (int)Math.Ceiling(srcW * sinA + srcH * cosA); + + // 调整旋转矩阵的平移分量,使图像居中 + double[] m = new double[6]; + rotMat.CopyTo(m); + m[2] += (dstW - srcW) / 2.0; + m[5] += (dstH - srcH) / 2.0; + + // 写回矩阵 + using var adjusted = new Mat(2, 3, Emgu.CV.CvEnum.DepthType.Cv64F, 1); + System.Runtime.InteropServices.Marshal.Copy(m, 0, adjusted.DataPointer, 6); + + var result = new Image(dstW, dstH, new Gray(bgValue)); + CvInvoke.WarpAffine(inputImage, result, adjusted, new Size(dstW, dstH), + interMethod, Warp.Default, BorderType.Constant, new MCvScalar(bgValue)); + + _logger.Debug("Process: Angle={Angle}, ExpandCanvas=true, Size={W}x{H}", angle, dstW, dstH); + return result; + } + else + { + dstW = srcW; + dstH = srcH; + var result = new Image(dstW, dstH, new Gray(bgValue)); + CvInvoke.WarpAffine(inputImage, result, rotMat, new Size(dstW, dstH), + interMethod, Warp.Default, BorderType.Constant, new MCvScalar(bgValue)); + + _logger.Debug("Process: Angle={Angle}, ExpandCanvas=false", angle); + return result; + } + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/图像变换/ThresholdProcessor.cs b/ImageProcessing.Processors/图像变换/ThresholdProcessor.cs new file mode 100644 index 0000000..327100d --- /dev/null +++ b/ImageProcessing.Processors/图像变换/ThresholdProcessor.cs @@ -0,0 +1,106 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: ThresholdProcessor.cs +// 描述: 阈值分割算子,用于图像二值化处理 +// 功能: +// - 固定阈值二值化 +// - Otsu自动阈值计算 +// - 可调节阈值和最大值 +// - 将灰度图像转换为二值图像 +// 算法: 阈值分割、Otsu算法 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// 阈值分割算子 +/// +public class ThresholdProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public ThresholdProcessor() + { + Name = LocalizationHelper.GetString("ThresholdProcessor_Name"); + Description = LocalizationHelper.GetString("ThresholdProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("MinThreshold", new ProcessorParameter( + "MinThreshold", + LocalizationHelper.GetString("ThresholdProcessor_MinThreshold"), + typeof(int), + 64, + 0, + 255, + LocalizationHelper.GetString("ThresholdProcessor_MinThreshold_Desc"))); + + Parameters.Add("MaxThreshold", new ProcessorParameter( + "MaxThreshold", + LocalizationHelper.GetString("ThresholdProcessor_MaxThreshold"), + typeof(int), + 192, + 0, + 255, + LocalizationHelper.GetString("ThresholdProcessor_MaxThreshold_Desc"))); + + Parameters.Add("UseOtsu", new ProcessorParameter( + "UseOtsu", + LocalizationHelper.GetString("ThresholdProcessor_UseOtsu"), + typeof(bool), + false, + null, + null, + LocalizationHelper.GetString("ThresholdProcessor_UseOtsu_Desc"))); + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + int minThreshold = GetParameter("MinThreshold"); + int maxThreshold = GetParameter("MaxThreshold"); + bool useOtsu = GetParameter("UseOtsu"); + + var result = new Image(inputImage.Size); + + if (useOtsu) + { + // 使用Otsu算法 + CvInvoke.Threshold(inputImage, result, minThreshold, 255, ThresholdType.Otsu); + _logger.Debug("Process: UseOtsu = true"); + } + else + { + // 双阈值分割:介于MinThreshold和MaxThreshold之间的为前景(255),其他为背景(0) + byte[,,] inputData = inputImage.Data; + byte[,,] outputData = result.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("Process: MinThreshold = {MinThreshold}, MaxThreshold = {MaxThreshold}", + minThreshold, maxThreshold); + } + + return result; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/图像增强/ColorLayerProcessor.cs b/ImageProcessing.Processors/图像增强/ColorLayerProcessor.cs new file mode 100644 index 0000000..7d0ba64 --- /dev/null +++ b/ImageProcessing.Processors/图像增强/ColorLayerProcessor.cs @@ -0,0 +1,257 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: ColorLayerProcessor.cs +// 描述: 色彩分层算子,将灰度图像按亮度区间分层 +// 功能: +// - 将灰度图像按指定层数均匀分层 +// - 支持自定义分层数(2~16层) +// - 支持均匀分层和基于 Otsu 的自适应分层 +// - 可选保留原始灰度或映射为等间距灰度 +// 算法: 灰度量化 / 多阈值分割 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// 色彩分层算子,将灰度图像按亮度区间分为多个层级 +/// +public class ColorLayerProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public ColorLayerProcessor() + { + Name = LocalizationHelper.GetString("ColorLayerProcessor_Name"); + Description = LocalizationHelper.GetString("ColorLayerProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Layers", new ProcessorParameter( + "Layers", + LocalizationHelper.GetString("ColorLayerProcessor_Layers"), + typeof(int), + 4, + 2, + 16, + LocalizationHelper.GetString("ColorLayerProcessor_Layers_Desc"))); + + Parameters.Add("Method", new ProcessorParameter( + "Method", + LocalizationHelper.GetString("ColorLayerProcessor_Method"), + typeof(string), + "Uniform", + null, + null, + LocalizationHelper.GetString("ColorLayerProcessor_Method_Desc"), + new string[] { "Uniform", "Otsu" })); + + Parameters.Add("OutputMode", new ProcessorParameter( + "OutputMode", + LocalizationHelper.GetString("ColorLayerProcessor_OutputMode"), + typeof(string), + "EqualSpaced", + null, + null, + LocalizationHelper.GetString("ColorLayerProcessor_OutputMode_Desc"), + new string[] { "EqualSpaced", "MidValue" })); + + Parameters.Add("TargetLayer", new ProcessorParameter( + "TargetLayer", + LocalizationHelper.GetString("ColorLayerProcessor_TargetLayer"), + typeof(int), + 0, + 0, + 16, + LocalizationHelper.GetString("ColorLayerProcessor_TargetLayer_Desc"))); + + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + int layers = GetParameter("Layers"); + string method = GetParameter("Method"); + string outputMode = GetParameter("OutputMode"); + int targetLayer = GetParameter("TargetLayer"); + + // 限制 targetLayer 范围 + if (targetLayer < 0 || targetLayer > layers) + targetLayer = 0; + + _logger.Debug("Process: Layers={Layers}, Method={Method}, OutputMode={OutputMode}, TargetLayer={TargetLayer}", + layers, method, outputMode, targetLayer); + + // 计算分层阈值 + byte[] thresholds = method == "Otsu" + ? ComputeOtsuMultiThresholds(inputImage, layers) + : ComputeUniformThresholds(layers); + + // 计算每层的输出灰度值 + byte[] layerValues = ComputeLayerValues(thresholds, layers, outputMode); + + // 应用分层映射 + int width = inputImage.Width; + int height = inputImage.Height; + var result = new Image(width, height); + var srcData = inputImage.Data; + var dstData = result.Data; + + if (targetLayer == 0) + { + // 输出全部层 + Parallel.For(0, height, y => + { + for (int x = 0; x < width; x++) + { + byte pixel = srcData[y, x, 0]; + int layerIdx = GetLayerIndex(pixel, thresholds); + dstData[y, x, 0] = layerValues[layerIdx]; + } + }); + } + else + { + // 只输出指定层:选中层为 255(白),其余为 0(黑) + int target = targetLayer - 1; // 参数从1开始,内部索引从0开始 + Parallel.For(0, height, y => + { + for (int x = 0; x < width; x++) + { + byte pixel = srcData[y, x, 0]; + int layerIdx = GetLayerIndex(pixel, thresholds); + dstData[y, x, 0] = (layerIdx == target) ? (byte)255 : (byte)0; + } + }); + } + + _logger.Debug("Process completed: {Layers} layers, target={TargetLayer}", layers, targetLayer); + return result; + } + + /// + /// 均匀分层阈值:将 [0, 255] 等分 + /// + private static byte[] ComputeUniformThresholds(int layers) + { + var thresholds = new byte[layers - 1]; + double step = 256.0 / layers; + for (int i = 0; i < layers - 1; i++) + thresholds[i] = (byte)Math.Clamp((int)((i + 1) * step), 0, 255); + return thresholds; + } + + /// + /// 基于 Otsu 的多阈值分层:递归二分 + /// + private static byte[] ComputeOtsuMultiThresholds(Image image, int layers) + { + // 计算直方图 + int[] histogram = new int[256]; + var data = image.Data; + int h = image.Height, w = image.Width; + for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) + histogram[data[y, x, 0]]++; + + // 递归 Otsu 分割 + var thresholds = new List(); + RecursiveOtsu(histogram, 0, 255, layers, thresholds); + thresholds.Sort(); + return thresholds.ToArray(); + } + + /// + /// 递归 Otsu:在 [low, high] 范围内找最佳阈值,然后递归分割 + /// + private static void RecursiveOtsu(int[] histogram, int low, int high, int layers, List thresholds) + { + if (layers <= 1 || low >= high) + return; + + // 在 [low, high] 范围内找 Otsu 阈值 + long totalPixels = 0; + long totalSum = 0; + for (int i = low; i <= high; i++) + { + totalPixels += histogram[i]; + totalSum += (long)i * histogram[i]; + } + + if (totalPixels == 0) return; + + long bgPixels = 0, bgSum = 0; + double maxVariance = 0; + int bestThreshold = (low + high) / 2; + + for (int t = low; t < high; t++) + { + bgPixels += histogram[t]; + bgSum += (long)t * histogram[t]; + + long fgPixels = totalPixels - bgPixels; + if (bgPixels == 0 || fgPixels == 0) continue; + + double bgMean = (double)bgSum / bgPixels; + double fgMean = (double)(totalSum - bgSum) / fgPixels; + double variance = (double)bgPixels * fgPixels * (bgMean - fgMean) * (bgMean - fgMean); + + if (variance > maxVariance) + { + maxVariance = variance; + bestThreshold = t; + } + } + + thresholds.Add((byte)bestThreshold); + + // 递归分割左右两半 + int leftLayers = layers / 2; + int rightLayers = layers - leftLayers; + RecursiveOtsu(histogram, low, bestThreshold, leftLayers, thresholds); + RecursiveOtsu(histogram, bestThreshold + 1, high, rightLayers, thresholds); + } + + /// + /// 计算每层的输出灰度值 + /// + private static byte[] ComputeLayerValues(byte[] thresholds, int layers, string outputMode) + { + var values = new byte[layers]; + if (outputMode == "EqualSpaced") + { + // 等间距输出:0, 255/(n-1), 2*255/(n-1), ..., 255 + for (int i = 0; i < layers; i++) + values[i] = (byte)Math.Clamp((int)(255.0 * i / (layers - 1)), 0, 255); + } + else // MidValue + { + // 每层取区间中值 + values[0] = (byte)(thresholds.Length > 0 ? thresholds[0] / 2 : 128); + for (int i = 1; i < layers - 1; i++) + values[i] = (byte)((thresholds[i - 1] + thresholds[i]) / 2); + values[layers - 1] = (byte)(thresholds.Length > 0 ? (thresholds[^1] + 255) / 2 : 128); + } + return values; + } + + /// + /// 根据阈值数组确定像素所属层级 + /// + private static int GetLayerIndex(byte pixel, byte[] thresholds) + { + for (int i = 0; i < thresholds.Length; i++) + { + if (pixel < thresholds[i]) + return i; + } + return thresholds.Length; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/图像增强/ContrastProcessor.cs b/ImageProcessing.Processors/图像增强/ContrastProcessor.cs new file mode 100644 index 0000000..de0e216 --- /dev/null +++ b/ImageProcessing.Processors/图像增强/ContrastProcessor.cs @@ -0,0 +1,172 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: ContrastProcessor.cs +// 描述: 对比度调整算子,用于增强图像对比度 +// 功能: +// - 线性对比度和亮度调整 +// - 自动对比度拉伸 +// - CLAHE(对比度受限自适应直方图均衡化) +// - 支持多种对比度增强方法 +// 算法: 线性变换、直方图均衡化、CLAHE +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; +using System.Drawing; + +namespace ImageProcessing.Processors; + +/// +/// 对比度调整算子 +/// +public class ContrastProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public ContrastProcessor() + { + Name = LocalizationHelper.GetString("ContrastProcessor_Name"); + Description = LocalizationHelper.GetString("ContrastProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Contrast", new ProcessorParameter( + "Contrast", + LocalizationHelper.GetString("ContrastProcessor_Contrast"), + typeof(double), + 1.0, + 0.1, + 3.0, + LocalizationHelper.GetString("ContrastProcessor_Contrast_Desc"))); + + Parameters.Add("Brightness", new ProcessorParameter( + "Brightness", + LocalizationHelper.GetString("ContrastProcessor_Brightness"), + typeof(int), + 0, + -100, + 100, + LocalizationHelper.GetString("ContrastProcessor_Brightness_Desc"))); + + Parameters.Add("AutoContrast", new ProcessorParameter( + "AutoContrast", + LocalizationHelper.GetString("ContrastProcessor_AutoContrast"), + typeof(bool), + false, + null, + null, + LocalizationHelper.GetString("ContrastProcessor_AutoContrast_Desc"))); + + Parameters.Add("UseCLAHE", new ProcessorParameter( + "UseCLAHE", + LocalizationHelper.GetString("ContrastProcessor_UseCLAHE"), + typeof(bool), + false, + null, + null, + LocalizationHelper.GetString("ContrastProcessor_UseCLAHE_Desc"))); + + Parameters.Add("ClipLimit", new ProcessorParameter( + "ClipLimit", + LocalizationHelper.GetString("ContrastProcessor_ClipLimit"), + typeof(double), + 2.0, + 1.0, + 10.0, + LocalizationHelper.GetString("ContrastProcessor_ClipLimit_Desc"))); + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + double contrast = GetParameter("Contrast"); + int brightness = GetParameter("Brightness"); + bool autoContrast = GetParameter("AutoContrast"); + bool useCLAHE = GetParameter("UseCLAHE"); + double clipLimit = GetParameter("ClipLimit"); + + var result = inputImage.Clone(); + + if (useCLAHE) + { + result = ApplyCLAHE(inputImage, clipLimit); + } + else if (autoContrast) + { + result = AutoContrastStretch(inputImage); + } + else + { + result = inputImage * contrast + brightness; + } + _logger.Debug("Process: Contrast = {contrast},Brightness = {brightness}," + + "AutoContrast = {autoContrast},UseCLAHE = {useCLAHE}, ClipLimit = {clipLimit}", contrast, brightness, autoContrast, useCLAHE, clipLimit); + return result; + } + + private Image AutoContrastStretch(Image inputImage) + { + double minVal = 0, maxVal = 0; + Point minLoc = new Point(); + Point maxLoc = new Point(); + CvInvoke.MinMaxLoc(inputImage, ref minVal, ref maxVal, ref minLoc, ref maxLoc); + + if (minVal == 0 && maxVal == 255) + { + return inputImage.Clone(); + } + + var floatImage = inputImage.Convert(); + + if (maxVal > minVal) + { + floatImage = (floatImage - minVal) * (255.0 / (maxVal - minVal)); + } + _logger.Debug("AutoContrastStretch"); + return floatImage.Convert(); + } + + private Image ApplyCLAHE(Image inputImage, double clipLimit) + { + int tileSize = 8; + int width = inputImage.Width; + int height = inputImage.Height; + + int tilesX = (width + tileSize - 1) / tileSize; + int tilesY = (height + tileSize - 1) / tileSize; + + var result = new Image(width, height); + + for (int ty = 0; ty < tilesY; ty++) + { + for (int tx = 0; tx < tilesX; tx++) + { + int x = tx * tileSize; + int y = ty * tileSize; + int w = Math.Min(tileSize, width - x); + int h = Math.Min(tileSize, height - y); + + var roi = new System.Drawing.Rectangle(x, y, w, h); + inputImage.ROI = roi; + var tile = inputImage.Copy(); + inputImage.ROI = System.Drawing.Rectangle.Empty; + + var equalizedTile = new Image(tile.Size); + CvInvoke.EqualizeHist(tile, equalizedTile); + + result.ROI = roi; + equalizedTile.CopyTo(result); + result.ROI = System.Drawing.Rectangle.Empty; + + tile.Dispose(); + equalizedTile.Dispose(); + } + } + _logger.Debug("ApplyCLAHE"); + return result; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/图像增强/GammaProcessor.cs b/ImageProcessing.Processors/图像增强/GammaProcessor.cs new file mode 100644 index 0000000..c9a85d7 --- /dev/null +++ b/ImageProcessing.Processors/图像增强/GammaProcessor.cs @@ -0,0 +1,100 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: GammaProcessor.cs +// 描述: Gamma校正算子,用于调整图像亮度和对比度 +// 功能: +// - Gamma非线性校正 +// - 增益调整 +// - 使用查找表(LUT)加速处理 +// - 适用于图像显示和亮度调整 +// 算法: Gamma校正公式 output = (input^(1/gamma)) * gain +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// Gamma校正算子 +/// +public class GammaProcessor : ImageProcessorBase +{ + private byte[] _lookupTable; + private static readonly ILogger _logger = Log.ForContext(); + + public GammaProcessor() + { + Name = LocalizationHelper.GetString("GammaProcessor_Name"); + Description = LocalizationHelper.GetString("GammaProcessor_Description"); + _lookupTable = new byte[256]; + } + + protected override void InitializeParameters() + { + Parameters.Add("Gamma", new ProcessorParameter( + "Gamma", + LocalizationHelper.GetString("GammaProcessor_Gamma"), + typeof(double), + 1.0, + 0.1, + 5.0, + LocalizationHelper.GetString("GammaProcessor_Gamma_Desc"))); + + Parameters.Add("Gain", new ProcessorParameter( + "Gain", + LocalizationHelper.GetString("GammaProcessor_Gain"), + typeof(double), + 1.0, + 0.1, + 3.0, + LocalizationHelper.GetString("GammaProcessor_Gain_Desc"))); + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + double gamma = GetParameter("Gamma"); + double gain = GetParameter("Gain"); + + BuildLookupTable(gamma, gain); + + var result = inputImage.Clone(); + ApplyLookupTable(result); + _logger.Debug("Process:Gamma = {0}, Gain = {1}", gamma, gain); + return result; + } + + private void BuildLookupTable(double gamma, double gain) + { + double invGamma = 1.0 / gamma; + + for (int i = 0; i < 256; i++) + { + double normalized = i / 255.0; + double corrected = Math.Pow(normalized, invGamma) * gain; + int value = (int)(corrected * 255.0); + + _lookupTable[i] = (byte)Math.Max(0, Math.Min(255, value)); + } + _logger.Debug("Gamma and gain values recorded: gamma = {Gamma}, gain = {Gain}", gamma, gain); + } + + private void ApplyLookupTable(Image image) + { + int width = image.Width; + int height = image.Height; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + byte pixelValue = image.Data[y, x, 0]; + image.Data[y, x, 0] = _lookupTable[pixelValue]; + } + } + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/图像增强/HDREnhancementProcessor.cs b/ImageProcessing.Processors/图像增强/HDREnhancementProcessor.cs new file mode 100644 index 0000000..b4730cc --- /dev/null +++ b/ImageProcessing.Processors/图像增强/HDREnhancementProcessor.cs @@ -0,0 +1,549 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: HDREnhancementProcessor.cs +// 描述: 高动态范围(HDR)图像增强算子 +// 功能: +// - 局部色调映射(Local Tone Mapping) +// - 自适应对数映射(Adaptive Logarithmic Mapping) +// - Drago色调映射 +// - 双边滤波色调映射 +// - 增强图像暗部和亮部细节 +// 算法: 基于色调映射的HDR增强 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// 高动态范围图像增强算子 +/// +public class HDREnhancementProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public HDREnhancementProcessor() + { + Name = LocalizationHelper.GetString("HDREnhancementProcessor_Name"); + Description = LocalizationHelper.GetString("HDREnhancementProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Method", new ProcessorParameter( + "Method", + LocalizationHelper.GetString("HDREnhancementProcessor_Method"), + typeof(string), + "LocalToneMap", + null, + null, + LocalizationHelper.GetString("HDREnhancementProcessor_Method_Desc"), + new string[] { "LocalToneMap", "AdaptiveLog", "Drago", "BilateralToneMap" })); + + Parameters.Add("Gamma", new ProcessorParameter( + "Gamma", + LocalizationHelper.GetString("HDREnhancementProcessor_Gamma"), + typeof(double), + 1.0, + 0.1, + 5.0, + LocalizationHelper.GetString("HDREnhancementProcessor_Gamma_Desc"))); + + Parameters.Add("Saturation", new ProcessorParameter( + "Saturation", + LocalizationHelper.GetString("HDREnhancementProcessor_Saturation"), + typeof(double), + 1.0, + 0.0, + 3.0, + LocalizationHelper.GetString("HDREnhancementProcessor_Saturation_Desc"))); + + Parameters.Add("DetailBoost", new ProcessorParameter( + "DetailBoost", + LocalizationHelper.GetString("HDREnhancementProcessor_DetailBoost"), + typeof(double), + 1.5, + 0.0, + 5.0, + LocalizationHelper.GetString("HDREnhancementProcessor_DetailBoost_Desc"))); + + Parameters.Add("SigmaSpace", new ProcessorParameter( + "SigmaSpace", + LocalizationHelper.GetString("HDREnhancementProcessor_SigmaSpace"), + typeof(double), + 20.0, + 1.0, + 100.0, + LocalizationHelper.GetString("HDREnhancementProcessor_SigmaSpace_Desc"))); + + Parameters.Add("SigmaColor", new ProcessorParameter( + "SigmaColor", + LocalizationHelper.GetString("HDREnhancementProcessor_SigmaColor"), + typeof(double), + 30.0, + 1.0, + 100.0, + LocalizationHelper.GetString("HDREnhancementProcessor_SigmaColor_Desc"))); + + Parameters.Add("Bias", new ProcessorParameter( + "Bias", + LocalizationHelper.GetString("HDREnhancementProcessor_Bias"), + typeof(double), + 0.85, + 0.0, + 1.0, + LocalizationHelper.GetString("HDREnhancementProcessor_Bias_Desc"))); + + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + string method = GetParameter("Method"); + double gamma = GetParameter("Gamma"); + double saturation = GetParameter("Saturation"); + double detailBoost = GetParameter("DetailBoost"); + double sigmaSpace = GetParameter("SigmaSpace"); + double sigmaColor = GetParameter("SigmaColor"); + double bias = GetParameter("Bias"); + + Image result; + + switch (method) + { + case "AdaptiveLog": + result = AdaptiveLogarithmicMapping(inputImage, gamma, bias); + break; + + case "Drago": + result = DragoToneMapping(inputImage, gamma, bias); + break; + + case "BilateralToneMap": + result = BilateralToneMapping(inputImage, gamma, sigmaSpace, sigmaColor, detailBoost); + break; + + default: // LocalToneMap + result = LocalToneMapping(inputImage, gamma, sigmaSpace, detailBoost, saturation); + break; + } + + _logger.Debug("Process: Method={Method}, Gamma={Gamma}, Saturation={Saturation}, DetailBoost={DetailBoost}, SigmaSpace={SigmaSpace}, SigmaColor={SigmaColor}, Bias={Bias}", + method, gamma, saturation, detailBoost, sigmaSpace, sigmaColor, bias); + return result; + } + + /// + /// 局部色调映射 + /// 将图像分解为基础层(光照)和细节层,分别处理后合成 + /// Base = GaussianBlur(log(I)) + /// Detail = log(I) - Base + /// Output = exp(Base_compressed + Detail * boost) + /// + private Image LocalToneMapping(Image inputImage, + double gamma, double sigmaSpace, double detailBoost, double saturation) + { + int width = inputImage.Width; + int height = inputImage.Height; + + // 转换为浮点并归一化到 (0, 1] + var floatImage = inputImage.Convert(); + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + floatImage.Data[y, x, 0] = floatImage.Data[y, x, 0] / 255.0f + 0.001f; + + // 对数域 + var logImage = new Image(width, height); + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + logImage.Data[y, x, 0] = (float)Math.Log(floatImage.Data[y, x, 0]); + + // 基础层:大尺度高斯模糊提取光照分量 + int kernelSize = (int)(sigmaSpace * 6) | 1; + if (kernelSize < 3) kernelSize = 3; + var baseLayer = new Image(width, height); + CvInvoke.GaussianBlur(logImage, baseLayer, new System.Drawing.Size(kernelSize, kernelSize), sigmaSpace); + + // 细节层 + var detailLayer = logImage - baseLayer; + + // 压缩基础层的动态范围 + double baseMin = double.MaxValue, baseMax = double.MinValue; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + float v = baseLayer.Data[y, x, 0]; + if (v < baseMin) baseMin = v; + if (v > baseMax) baseMax = v; + } + } + + double baseRange = baseMax - baseMin; + if (baseRange < 0.001) baseRange = 0.001; + + // 目标动态范围(对数域) + double targetRange = Math.Log(256.0); + double compressionFactor = targetRange / baseRange; + + var compressedBase = new Image(width, height); + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + float normalized = (float)((baseLayer.Data[y, x, 0] - baseMin) / baseRange); + compressedBase.Data[y, x, 0] = (float)(normalized * targetRange + Math.Log(0.01)); + } + } + + // 合成:压缩后的基础层 + 增强的细节层 + var combined = new Image(width, height); + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + float val = compressedBase.Data[y, x, 0] + detailLayer.Data[y, x, 0] * (float)detailBoost; + combined.Data[y, x, 0] = val; + } + } + + // 指数变换回线性域 + var linearResult = new Image(width, height); + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + linearResult.Data[y, x, 0] = (float)Math.Exp(combined.Data[y, x, 0]); + + // Gamma校正 + if (Math.Abs(gamma - 1.0) > 0.01) + { + double invGamma = 1.0 / gamma; + double maxVal = 0; + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + if (linearResult.Data[y, x, 0] > maxVal) maxVal = linearResult.Data[y, x, 0]; + + if (maxVal > 0) + { + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + { + double normalized = linearResult.Data[y, x, 0] / maxVal; + linearResult.Data[y, x, 0] = (float)(Math.Pow(normalized, invGamma) * maxVal); + } + } + } + + // 饱和度增强(对比度微调) + if (Math.Abs(saturation - 1.0) > 0.01) + { + double mean = 0; + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + mean += linearResult.Data[y, x, 0]; + mean /= (width * height); + + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + { + double diff = linearResult.Data[y, x, 0] - mean; + linearResult.Data[y, x, 0] = (float)(mean + diff * saturation); + } + } + + // 归一化到 [0, 255] + var result = NormalizeToByteImage(linearResult); + + floatImage.Dispose(); + logImage.Dispose(); + baseLayer.Dispose(); + detailLayer.Dispose(); + compressedBase.Dispose(); + combined.Dispose(); + linearResult.Dispose(); + + return result; + } + + /// + /// 自适应对数映射 + /// 根据场景的整体亮度自适应调整对数映射曲线 + /// L_out = (log(1 + L_in) / log(1 + L_max)) ^ (1/gamma) + /// 使用局部自适应:L_max 根据邻域计算 + /// + private Image AdaptiveLogarithmicMapping(Image inputImage, + double gamma, double bias) + { + int width = inputImage.Width; + int height = inputImage.Height; + + var floatImage = inputImage.Convert(); + + // 归一化到 [0, 1] + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + floatImage.Data[y, x, 0] /= 255.0f; + + // 计算全局最大亮度 + float globalMax = 0; + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + if (floatImage.Data[y, x, 0] > globalMax) + globalMax = floatImage.Data[y, x, 0]; + + if (globalMax < 0.001f) globalMax = 0.001f; + + // 计算对数平均亮度 + double logAvg = 0; + int count = 0; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + float v = floatImage.Data[y, x, 0]; + if (v > 0.001f) + { + logAvg += Math.Log(v); + count++; + } + } + } + logAvg = Math.Exp(logAvg / Math.Max(count, 1)); + + // 自适应对数映射 + // bias 控制暗部和亮部的平衡 + double logBase = Math.Log(2.0 + 8.0 * Math.Pow(logAvg / globalMax, Math.Log(bias) / Math.Log(0.5))); + + var result = new Image(width, height); + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + float lum = floatImage.Data[y, x, 0]; + double mapped = Math.Log(1.0 + lum) / logBase; + result.Data[y, x, 0] = (float)mapped; + } + } + + // Gamma校正 + if (Math.Abs(gamma - 1.0) > 0.01) + { + double invGamma = 1.0 / gamma; + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + result.Data[y, x, 0] = (float)Math.Pow(Math.Max(0, result.Data[y, x, 0]), invGamma); + } + + var byteResult = NormalizeToByteImage(result); + + floatImage.Dispose(); + result.Dispose(); + + return byteResult; + } + + /// + /// Drago色调映射 + /// 使用自适应对数基底进行色调映射 + /// L_out = log_base(1 + L_in) / log_base(1 + L_max) + /// base = 2 + 8 * (L_in / L_max) ^ (ln(bias) / ln(0.5)) + /// + private Image DragoToneMapping(Image inputImage, + double gamma, double bias) + { + int width = inputImage.Width; + int height = inputImage.Height; + + var floatImage = inputImage.Convert(); + + // 归一化到 [0, 1] + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + floatImage.Data[y, x, 0] /= 255.0f; + + // 全局最大亮度 + float maxLum = 0; + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + if (floatImage.Data[y, x, 0] > maxLum) + maxLum = floatImage.Data[y, x, 0]; + + if (maxLum < 0.001f) maxLum = 0.001f; + + double biasP = Math.Log(bias) / Math.Log(0.5); + double divider = Math.Log10(1.0 + maxLum); + if (divider < 0.001) divider = 0.001; + + var result = new Image(width, height); + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + float lum = floatImage.Data[y, x, 0]; + // 自适应对数基底 + double adaptBase = 2.0 + 8.0 * Math.Pow(lum / maxLum, biasP); + double logAdapt = Math.Log(1.0 + lum) / Math.Log(adaptBase); + double mapped = logAdapt / divider; + result.Data[y, x, 0] = (float)Math.Max(0, Math.Min(1.0, mapped)); + } + } + + // Gamma校正 + if (Math.Abs(gamma - 1.0) > 0.01) + { + double invGamma = 1.0 / gamma; + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + result.Data[y, x, 0] = (float)Math.Pow(result.Data[y, x, 0], invGamma); + } + + var byteResult = NormalizeToByteImage(result); + + floatImage.Dispose(); + result.Dispose(); + + return byteResult; + } + + /// + /// 双边滤波色调映射 + /// 使用双边滤波分离基础层和细节层 + /// 双边滤波保边特性使得细节层更加精确 + /// + private Image BilateralToneMapping(Image inputImage, + double gamma, double sigmaSpace, double sigmaColor, double detailBoost) + { + int width = inputImage.Width; + int height = inputImage.Height; + + // 转换为浮点并取对数 + var floatImage = inputImage.Convert(); + var logImage = new Image(width, height); + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + logImage.Data[y, x, 0] = (float)Math.Log(floatImage.Data[y, x, 0] / 255.0f + 0.001); + + // 双边滤波提取基础层(保边平滑) + int diameter = (int)(sigmaSpace * 2) | 1; + if (diameter < 3) diameter = 3; + if (diameter > 31) diameter = 31; + + var baseLayer = new Image(width, height); + // 转换为 byte 进行双边滤波,再转回 float + var logNorm = NormalizeToByteImage(logImage); + var baseNorm = new Image(width, height); + CvInvoke.BilateralFilter(logNorm, baseNorm, diameter, sigmaColor, sigmaSpace); + + // 将基础层转回浮点对数域 + double logMin = double.MaxValue, logMax = double.MinValue; + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + { + float v = logImage.Data[y, x, 0]; + if (v < logMin) logMin = v; + if (v > logMax) logMax = v; + } + + double logRange = logMax - logMin; + if (logRange < 0.001) logRange = 0.001; + + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + baseLayer.Data[y, x, 0] = (float)(baseNorm.Data[y, x, 0] / 255.0 * logRange + logMin); + + // 细节层 = 对数图像 - 基础层 + var detailLayer = logImage - baseLayer; + + // 压缩基础层 + double baseMin = double.MaxValue, baseMax = double.MinValue; + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + { + float v = baseLayer.Data[y, x, 0]; + if (v < baseMin) baseMin = v; + if (v > baseMax) baseMax = v; + } + + double bRange = baseMax - baseMin; + if (bRange < 0.001) bRange = 0.001; + double targetRange = Math.Log(256.0); + double compression = targetRange / bRange; + + // 合成 + var combined = new Image(width, height); + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + { + float compBase = (float)((baseLayer.Data[y, x, 0] - baseMin) * compression + Math.Log(0.01)); + combined.Data[y, x, 0] = compBase + detailLayer.Data[y, x, 0] * (float)detailBoost; + } + + // 指数变换回线性域 + var linearResult = new Image(width, height); + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + linearResult.Data[y, x, 0] = (float)Math.Exp(combined.Data[y, x, 0]); + + // Gamma校正 + if (Math.Abs(gamma - 1.0) > 0.01) + { + double invGamma = 1.0 / gamma; + double maxVal = 0; + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + if (linearResult.Data[y, x, 0] > maxVal) maxVal = linearResult.Data[y, x, 0]; + + if (maxVal > 0) + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + linearResult.Data[y, x, 0] = (float)(Math.Pow(linearResult.Data[y, x, 0] / maxVal, invGamma) * maxVal); + } + + var result = NormalizeToByteImage(linearResult); + + floatImage.Dispose(); + logImage.Dispose(); + logNorm.Dispose(); + baseNorm.Dispose(); + baseLayer.Dispose(); + detailLayer.Dispose(); + combined.Dispose(); + linearResult.Dispose(); + + return result; + } + + /// + /// 归一化浮点图像到字节图像 + /// + private Image NormalizeToByteImage(Image floatImage) + { + double minVal = double.MaxValue; + double maxVal = double.MinValue; + + for (int y = 0; y < floatImage.Height; y++) + for (int x = 0; x < floatImage.Width; x++) + { + float val = floatImage.Data[y, x, 0]; + if (val < minVal) minVal = val; + if (val > maxVal) maxVal = val; + } + + var result = new Image(floatImage.Size); + double range = maxVal - minVal; + if (range > 0) + { + for (int y = 0; y < floatImage.Height; y++) + for (int x = 0; x < floatImage.Width; x++) + { + int normalized = (int)((floatImage.Data[y, x, 0] - minVal) / range * 255.0); + result.Data[y, x, 0] = (byte)Math.Max(0, Math.Min(255, normalized)); + } + } + + return result; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/图像增强/HierarchicalEnhancementProcessor.cs b/ImageProcessing.Processors/图像增强/HierarchicalEnhancementProcessor.cs new file mode 100644 index 0000000..db04325 --- /dev/null +++ b/ImageProcessing.Processors/图像增强/HierarchicalEnhancementProcessor.cs @@ -0,0 +1,213 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: HierarchicalEnhancementProcessor.cs +// 描述: 层次增强算子,基于多尺度高斯分解对不同尺度细节独立增强 +// 功能: +// - 将图像分解为多层细节层 + 基础层 +// - 对每层细节独立控制增益 +// - 支持基础层亮度调整和对比度限制 +// 算法: 多尺度高斯差分分解与重建 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// 层次增强算子,基于多尺度高斯差分对不同尺度的图像细节进行独立增强 +/// +public class HierarchicalEnhancementProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public HierarchicalEnhancementProcessor() + { + Name = LocalizationHelper.GetString("HierarchicalEnhancementProcessor_Name"); + Description = LocalizationHelper.GetString("HierarchicalEnhancementProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Levels", new ProcessorParameter( + "Levels", + LocalizationHelper.GetString("HierarchicalEnhancementProcessor_Levels"), + typeof(int), + 4, + 2, + 8, + LocalizationHelper.GetString("HierarchicalEnhancementProcessor_Levels_Desc"))); + + Parameters.Add("FineGain", new ProcessorParameter( + "FineGain", + LocalizationHelper.GetString("HierarchicalEnhancementProcessor_FineGain"), + typeof(double), + 2.0, + 0.0, + 10.0, + LocalizationHelper.GetString("HierarchicalEnhancementProcessor_FineGain_Desc"))); + + Parameters.Add("MediumGain", new ProcessorParameter( + "MediumGain", + LocalizationHelper.GetString("HierarchicalEnhancementProcessor_MediumGain"), + typeof(double), + 1.5, + 0.0, + 10.0, + LocalizationHelper.GetString("HierarchicalEnhancementProcessor_MediumGain_Desc"))); + + Parameters.Add("CoarseGain", new ProcessorParameter( + "CoarseGain", + LocalizationHelper.GetString("HierarchicalEnhancementProcessor_CoarseGain"), + typeof(double), + 1.0, + 0.0, + 10.0, + LocalizationHelper.GetString("HierarchicalEnhancementProcessor_CoarseGain_Desc"))); + + Parameters.Add("BaseGain", new ProcessorParameter( + "BaseGain", + LocalizationHelper.GetString("HierarchicalEnhancementProcessor_BaseGain"), + typeof(double), + 1.0, + 0.0, + 3.0, + LocalizationHelper.GetString("HierarchicalEnhancementProcessor_BaseGain_Desc"))); + + Parameters.Add("ClipLimit", new ProcessorParameter( + "ClipLimit", + LocalizationHelper.GetString("HierarchicalEnhancementProcessor_ClipLimit"), + typeof(double), + 0.0, + 0.0, + 50.0, + LocalizationHelper.GetString("HierarchicalEnhancementProcessor_ClipLimit_Desc"))); + + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + int levels = GetParameter("Levels"); + double fineGain = GetParameter("FineGain"); + double mediumGain = GetParameter("MediumGain"); + double coarseGain = GetParameter("CoarseGain"); + double baseGain = GetParameter("BaseGain"); + double clipLimit = GetParameter("ClipLimit"); + + _logger.Debug("Process: Levels={Levels}, Fine={Fine}, Medium={Medium}, Coarse={Coarse}, Base={Base}, Clip={Clip}", + levels, fineGain, mediumGain, coarseGain, baseGain, clipLimit); + + int h = inputImage.Height; + int w = inputImage.Width; + + // === 多尺度高斯差分分解(全部在原始分辨率上操作,无需金字塔上下采样) === + // 用递增 sigma 的高斯模糊生成平滑层序列:G0(原图), G1, G2, ..., G_n(基础层) + // 细节层 D_i = G_i - G_{i+1} + // 重建:output = sum(D_i * gain_i) + G_n * baseGain + + // 计算每层的高斯 sigma(指数递增) + var sigmas = new double[levels]; + for (int i = 0; i < levels; i++) + sigmas[i] = Math.Pow(2, i + 1); // 2, 4, 8, 16, ... + + // 生成平滑层序列(float 数组,避免 Emgu float Image 的问题) + var smoothLayers = new float[levels + 1][]; // [0]=原图, [1..n]=高斯模糊 + smoothLayers[0] = new float[h * w]; + var srcData = inputImage.Data; + Parallel.For(0, h, y => + { + int row = y * w; + for (int x = 0; x < w; x++) + smoothLayers[0][row + x] = srcData[y, x, 0]; + }); + + for (int i = 0; i < levels; i++) + { + int ksize = ((int)(sigmas[i] * 3)) | 1; // 确保奇数 + if (ksize < 3) ksize = 3; + + using var src = new Image(w, h); + // 从上一层 float 转 byte 做高斯模糊 + var prevLayer = smoothLayers[i]; + var sd = src.Data; + Parallel.For(0, h, y => + { + int row = y * w; + for (int x = 0; x < w; x++) + sd[y, x, 0] = (byte)Math.Clamp((int)Math.Round(prevLayer[row + x]), 0, 255); + }); + + using var dst = new Image(w, h); + CvInvoke.GaussianBlur(src, dst, new System.Drawing.Size(ksize, ksize), sigmas[i]); + + smoothLayers[i + 1] = new float[h * w]; + var dd = dst.Data; + var nextLayer = smoothLayers[i + 1]; + Parallel.For(0, h, y => + { + int row = y * w; + for (int x = 0; x < w; x++) + nextLayer[row + x] = dd[y, x, 0]; + }); + } + + // === 计算增益插值并直接重建 === + var gains = new double[levels]; + for (int i = 0; i < levels; i++) + { + double t = levels <= 1 ? 0.0 : (double)i / (levels - 1); + if (t <= 0.5) + { + double t2 = t * 2.0; + gains[i] = fineGain * (1.0 - t2) + mediumGain * t2; + } + else + { + double t2 = (t - 0.5) * 2.0; + gains[i] = mediumGain * (1.0 - t2) + coarseGain * t2; + } + } + + // 重建:output = baseGain * G_n + sum(gain_i * (G_i - G_{i+1})) + float fBaseGain = (float)baseGain; + float fClip = (float)clipLimit; + var baseLayerData = smoothLayers[levels]; + + var result = new Image(w, h); + var resultData = result.Data; + + // 预转换 gains 为 float + var fGains = new float[levels]; + for (int i = 0; i < levels; i++) + fGains[i] = (float)gains[i]; + + Parallel.For(0, h, y => + { + int row = y * w; + for (int x = 0; x < w; x++) + { + int idx = row + x; + float val = baseLayerData[idx] * fBaseGain; + + for (int i = 0; i < levels; i++) + { + float detail = smoothLayers[i][idx] - smoothLayers[i + 1][idx]; + detail *= fGains[i]; + if (fClip > 0) + detail = Math.Clamp(detail, -fClip, fClip); + val += detail; + } + + resultData[y, x, 0] = (byte)Math.Clamp((int)Math.Round(val), 0, 255); + } + }); + + _logger.Debug("Process completed: {Levels} levels, output={W}x{H}", levels, w, h); + return result; + } +} diff --git a/ImageProcessing.Processors/图像增强/HistogramEqualizationProcessor.cs b/ImageProcessing.Processors/图像增强/HistogramEqualizationProcessor.cs new file mode 100644 index 0000000..c964049 --- /dev/null +++ b/ImageProcessing.Processors/图像增强/HistogramEqualizationProcessor.cs @@ -0,0 +1,142 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: HistogramEqualizationProcessor.cs +// 描述: 直方图均衡化算子,用于增强图像对比度 +// 功能: +// - 全局直方图均衡化 +// - 自适应直方图均衡化(CLAHE) +// - 限制对比度增强 +// - 改善图像的整体对比度 +// 算法: 直方图均衡化、CLAHE +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// 直方图均衡化算子 +/// +public class HistogramEqualizationProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public HistogramEqualizationProcessor() + { + Name = LocalizationHelper.GetString("HistogramEqualizationProcessor_Name"); + Description = LocalizationHelper.GetString("HistogramEqualizationProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Method", new ProcessorParameter( + "Method", + LocalizationHelper.GetString("HistogramEqualizationProcessor_Method"), + typeof(string), + "Global", + null, + null, + LocalizationHelper.GetString("HistogramEqualizationProcessor_Method_Desc"), + new string[] { "Global", "CLAHE" })); + + Parameters.Add("ClipLimit", new ProcessorParameter( + "ClipLimit", + LocalizationHelper.GetString("HistogramEqualizationProcessor_ClipLimit"), + typeof(double), + 2.0, + 1.0, + 10.0, + LocalizationHelper.GetString("HistogramEqualizationProcessor_ClipLimit_Desc"))); + + Parameters.Add("TileSize", new ProcessorParameter( + "TileSize", + LocalizationHelper.GetString("HistogramEqualizationProcessor_TileSize"), + typeof(int), + 8, + 4, + 32, + LocalizationHelper.GetString("HistogramEqualizationProcessor_TileSize_Desc"))); + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + string method = GetParameter("Method"); + double clipLimit = GetParameter("ClipLimit"); + int tileSize = GetParameter("TileSize"); + + Image result; + + if (method == "CLAHE") + { + result = ApplyCLAHE(inputImage, clipLimit, tileSize); + } + else // Global + { + result = new Image(inputImage.Size); + CvInvoke.EqualizeHist(inputImage, result); + } + + _logger.Debug("Process: Method = {Method}, ClipLimit = {ClipLimit}, TileSize = {TileSize}", + method, clipLimit, tileSize); + return result; + } + + private Image ApplyCLAHE(Image inputImage, double clipLimit, int tileSize) + { + int width = inputImage.Width; + int height = inputImage.Height; + + int tilesX = (width + tileSize - 1) / tileSize; + int tilesY = (height + tileSize - 1) / tileSize; + + var result = new Image(width, height); + + // 对每个tile进行直方图均衡化 + for (int ty = 0; ty < tilesY; ty++) + { + for (int tx = 0; tx < tilesX; tx++) + { + int x = tx * tileSize; + int y = ty * tileSize; + int w = Math.Min(tileSize, width - x); + int h = Math.Min(tileSize, height - y); + + var roi = new System.Drawing.Rectangle(x, y, w, h); + inputImage.ROI = roi; + var tile = inputImage.Copy(); + inputImage.ROI = System.Drawing.Rectangle.Empty; + + // 应用直方图均衡化 + var equalizedTile = new Image(tile.Size); + CvInvoke.EqualizeHist(tile, equalizedTile); + + // 应用限制(简化版本) + var floatTile = tile.Convert(); + var floatEqualized = equalizedTile.Convert(); + var diff = floatEqualized - floatTile; + var limited = floatTile + diff * Math.Min(clipLimit / 10.0, 1.0); + var limitedByte = limited.Convert(); + + // 复制到结果图像 + result.ROI = roi; + limitedByte.CopyTo(result); + result.ROI = System.Drawing.Rectangle.Empty; + + tile.Dispose(); + equalizedTile.Dispose(); + floatTile.Dispose(); + floatEqualized.Dispose(); + diff.Dispose(); + limited.Dispose(); + limitedByte.Dispose(); + } + } + + return result; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/图像增强/HistogramOverlayProcessor.cs b/ImageProcessing.Processors/图像增强/HistogramOverlayProcessor.cs new file mode 100644 index 0000000..a9a545c --- /dev/null +++ b/ImageProcessing.Processors/图像增强/HistogramOverlayProcessor.cs @@ -0,0 +1,266 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: HistogramOverlayProcessor.cs +// 描述: 直方图叠加算子,计算灰度直方图并以蓝色柱状图绘制到结果图像左上角 +// 功能: +// - 计算输入图像的灰度直方图 +// - 将直方图绘制为蓝色半透明柱状图叠加到图像左上角 +// - 输出直方图统计表格数据 +// 算法: 灰度直方图统计 + 彩色图像叠加 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; +using System.Drawing; +using System.Text; + +namespace ImageProcessing.Processors; + +/// +/// 直方图叠加算子,计算灰度直方图并以蓝色柱状图绘制到结果图像左上角,同时输出统计表格 +/// +public class HistogramOverlayProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + // 固定参数 + private const int ChartWidth = 256; // 柱状图绘图区宽度 + private const int ChartHeight = 200; // 柱状图绘图区高度 + private const int AxisMarginLeft = 50; // Y轴标签预留宽度 + private const int AxisMarginBottom = 25; // X轴标签预留高度 + private const int Padding = 8; // 背景额外内边距 + private const int PaddingRight = 25; // 右侧额外内边距(容纳X轴末尾刻度文字) + private const int Margin = 10; // 距图像左上角边距 + private const float BgAlpha = 0.6f; + private const double FontScale = 0.35; + private const int FontThickness = 1; + + public HistogramOverlayProcessor() + { + Name = LocalizationHelper.GetString("HistogramOverlayProcessor_Name"); + Description = LocalizationHelper.GetString("HistogramOverlayProcessor_Description"); + } + + protected override void InitializeParameters() + { + // 无可调参数 + } + + public override Image Process(Image inputImage) + { + int h = inputImage.Height; + int w = inputImage.Width; + var srcData = inputImage.Data; + + // === 1. 计算灰度直方图 === + var hist = new int[256]; + for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) + hist[srcData[y, x, 0]]++; + + int maxCount = 0; + long totalPixels = (long)h * w; + for (int i = 0; i < 256; i++) + if (hist[i] > maxCount) maxCount = hist[i]; + + // === 2. 计算统计信息 === + double mean = 0, variance = 0; + int minVal = 255, maxVal = 0; + int modeVal = 0, modeCount = 0; + long medianTarget = totalPixels / 2, cumulative = 0; + int medianVal = 0; + bool medianFound = false; + + for (int i = 0; i < 256; i++) + { + if (hist[i] > 0) + { + if (i < minVal) minVal = i; + if (i > maxVal) maxVal = i; + } + if (hist[i] > modeCount) { modeCount = hist[i]; modeVal = i; } + mean += (double)i * hist[i]; + cumulative += hist[i]; + if (!medianFound && cumulative >= medianTarget) { medianVal = i; medianFound = true; } + } + mean /= totalPixels; + for (int i = 0; i < 256; i++) + variance += hist[i] * (i - mean) * (i - mean); + variance /= totalPixels; + double stdDev = Math.Sqrt(variance); + + // === 3. 输出表格数据 === + var sb = new StringBuilder(); + sb.AppendLine("=== 灰度直方图统计 ==="); + sb.AppendLine($"图像尺寸: {w} x {h}"); + sb.AppendLine($"总像素数: {totalPixels}"); + sb.AppendLine($"最小灰度: {minVal}"); + sb.AppendLine($"最大灰度: {maxVal}"); + sb.AppendLine($"平均灰度: {mean:F2}"); + sb.AppendLine($"中位灰度: {medianVal}"); + sb.AppendLine($"众数灰度: {modeVal} (出现 {modeCount} 次)"); + sb.AppendLine($"标准差: {stdDev:F2}"); + sb.AppendLine(); + sb.AppendLine("灰度值\t像素数\t占比(%)"); + for (int i = 0; i < 256; i++) + { + if (hist[i] > 0) + sb.AppendLine($"{i}\t{hist[i]}\t{(double)hist[i] / totalPixels * 100.0:F4}"); + } + + OutputData["HistogramTable"] = sb.ToString(); + OutputData["Histogram"] = hist; + + // === 4. 生成彩色叠加图像(蓝色柱状图 + XY轴坐标) === + var colorImage = inputImage.Convert(); + var colorData = colorImage.Data; + + // 布局:背景区域包含 Padding + Y轴标签 + 绘图区 + Padding(水平) + // Padding + 绘图区 + X轴标签 + Padding(垂直) + int totalW = Padding + AxisMarginLeft + ChartWidth + PaddingRight; + int totalH = Padding + ChartHeight + AxisMarginBottom + Padding; + int bgW = Math.Min(totalW, w - Margin); + int bgH = Math.Min(totalH, h - Margin); + + if (bgW > Padding + AxisMarginLeft && bgH > Padding + AxisMarginBottom) + { + int plotW = Math.Min(ChartWidth, bgW - Padding - AxisMarginLeft - PaddingRight); + int plotH = Math.Min(ChartHeight, bgH - Padding - AxisMarginBottom - Padding); + if (plotW <= 0 || plotH <= 0) goto SkipOverlay; + + // 绘图区左上角在图像中的坐标 + int plotX0 = Margin + Padding + AxisMarginLeft; + int plotY0 = Margin + Padding; + + // 计算每列柱高 + double binWidth = (double)plotW / 256.0; + var barHeights = new int[plotW]; + for (int px = 0; px < plotW; px++) + { + int bin = Math.Min((int)(px / binWidth), 255); + barHeights[px] = maxCount > 0 ? (int)((long)hist[bin] * (plotH - 1) / maxCount) : 0; + } + + float alpha = BgAlpha; + float inv = 1.0f - alpha; + + // 绘制半透明黑色背景(覆盖整个区域含坐标轴和内边距) + Parallel.For(0, bgH, dy => + { + int imgY = Margin + dy; + if (imgY >= h) return; + for (int dx = 0; dx < bgW; dx++) + { + int imgX = Margin + dx; + if (imgX >= w) break; + colorData[imgY, imgX, 0] = (byte)(int)(colorData[imgY, imgX, 0] * inv); + colorData[imgY, imgX, 1] = (byte)(int)(colorData[imgY, imgX, 1] * inv); + colorData[imgY, imgX, 2] = (byte)(int)(colorData[imgY, imgX, 2] * inv); + } + }); + + // 绘制蓝色柱状图 + Parallel.For(0, plotH, dy => + { + int imgY = plotY0 + dy; + if (imgY >= h) return; + int rowFromBottom = plotH - 1 - dy; + + for (int dx = 0; dx < plotW; dx++) + { + int imgX = plotX0 + dx; + if (imgX >= w) break; + + if (rowFromBottom < barHeights[dx]) + { + byte curB = colorData[imgY, imgX, 0]; + byte curG = colorData[imgY, imgX, 1]; + byte curR = colorData[imgY, imgX, 2]; + colorData[imgY, imgX, 0] = (byte)Math.Clamp(curB + (int)(255 * alpha), 0, 255); + colorData[imgY, imgX, 1] = (byte)Math.Clamp(curG + (int)(50 * alpha), 0, 255); + colorData[imgY, imgX, 2] = (byte)Math.Clamp(curR + (int)(50 * alpha), 0, 255); + } + } + }); + + // === 5. 绘制坐标轴线和刻度标注 === + var white = new MCvScalar(255, 255, 255); + var gray = new MCvScalar(180, 180, 180); + + // Y轴线 + CvInvoke.Line(colorImage, + new Point(plotX0, plotY0), + new Point(plotX0, plotY0 + plotH), + white, 1); + + // X轴线 + CvInvoke.Line(colorImage, + new Point(plotX0, plotY0 + plotH), + new Point(plotX0 + plotW, plotY0 + plotH), + white, 1); + + // X轴刻度: 0, 64, 128, 192, 255 + int[] xTicks = { 0, 64, 128, 192, 255 }; + foreach (int tick in xTicks) + { + int tx = plotX0 + (int)(tick * binWidth); + if (tx >= w) break; + CvInvoke.Line(colorImage, + new Point(tx, plotY0 + plotH), + new Point(tx, plotY0 + plotH + 4), + white, 1); + string label = tick.ToString(); + CvInvoke.PutText(colorImage, label, + new Point(tx - 8, plotY0 + plotH + 18), + FontFace.HersheySimplex, FontScale, white, FontThickness); + } + + // Y轴刻度: 0%, 25%, 50%, 75%, 100% + for (int i = 0; i <= 4; i++) + { + int val = maxCount * i / 4; + int ty = plotY0 + plotH - (int)((long)plotH * i / 4); + CvInvoke.Line(colorImage, + new Point(plotX0 - 4, ty), + new Point(plotX0, ty), + white, 1); + // 网格虚线 + if (i > 0 && i < 4) + { + for (int gx = plotX0 + 2; gx < plotX0 + plotW; gx += 6) + { + int gxEnd = Math.Min(gx + 2, plotX0 + plotW); + CvInvoke.Line(colorImage, + new Point(gx, ty), + new Point(gxEnd, ty), + gray, 1); + } + } + string label = FormatCount(val); + CvInvoke.PutText(colorImage, label, + new Point(Margin + Padding, ty + 4), + FontFace.HersheySimplex, FontScale, white, FontThickness); + } + } + + SkipOverlay: + OutputData["PseudoColorImage"] = colorImage; + + _logger.Debug("Process completed: histogram overlay, mean={Mean:F2}, stdDev={Std:F2}", mean, stdDev); + return inputImage.Clone(); + } + + /// + /// 格式化像素计数为紧凑字符串(如 12345 → "12.3K") + /// + private static string FormatCount(int count) + { + if (count >= 1_000_000) return $"{count / 1_000_000.0:F1}M"; + if (count >= 1_000) return $"{count / 1_000.0:F1}K"; + return count.ToString(); + } +} diff --git a/ImageProcessing.Processors/图像增强/RetinexProcessor.cs b/ImageProcessing.Processors/图像增强/RetinexProcessor.cs new file mode 100644 index 0000000..8414907 --- /dev/null +++ b/ImageProcessing.Processors/图像增强/RetinexProcessor.cs @@ -0,0 +1,320 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: RetinexProcessor.cs +// 描述: 基于Retinex的多尺度阴影校正算子 +// 功能: +// - 单尺度Retinex (SSR) +// - 多尺度Retinex (MSR) +// - 带色彩恢复的多尺度Retinex (MSRCR) +// - 光照不均匀校正 +// - 阴影去除 +// 算法: Retinex理论 - 将图像分解为反射分量和光照分量 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// Retinex多尺度阴影校正算子 +/// +public class RetinexProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public RetinexProcessor() + { + Name = LocalizationHelper.GetString("RetinexProcessor_Name"); + Description = LocalizationHelper.GetString("RetinexProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Method", new ProcessorParameter( + "Method", + LocalizationHelper.GetString("RetinexProcessor_Method"), + typeof(string), + "MSR", + null, + null, + LocalizationHelper.GetString("RetinexProcessor_Method_Desc"), + new string[] { "SSR", "MSR", "MSRCR" })); + + Parameters.Add("Sigma1", new ProcessorParameter( + "Sigma1", + LocalizationHelper.GetString("RetinexProcessor_Sigma1"), + typeof(double), + 15.0, + 1.0, + 100.0, + LocalizationHelper.GetString("RetinexProcessor_Sigma1_Desc"))); + + Parameters.Add("Sigma2", new ProcessorParameter( + "Sigma2", + LocalizationHelper.GetString("RetinexProcessor_Sigma2"), + typeof(double), + 80.0, + 1.0, + 200.0, + LocalizationHelper.GetString("RetinexProcessor_Sigma2_Desc"))); + + Parameters.Add("Sigma3", new ProcessorParameter( + "Sigma3", + LocalizationHelper.GetString("RetinexProcessor_Sigma3"), + typeof(double), + 250.0, + 1.0, + 500.0, + LocalizationHelper.GetString("RetinexProcessor_Sigma3_Desc"))); + + Parameters.Add("Gain", new ProcessorParameter( + "Gain", + LocalizationHelper.GetString("RetinexProcessor_Gain"), + typeof(double), + 1.0, + 0.1, + 5.0, + LocalizationHelper.GetString("RetinexProcessor_Gain_Desc"))); + + Parameters.Add("Offset", new ProcessorParameter( + "Offset", + LocalizationHelper.GetString("RetinexProcessor_Offset"), + typeof(int), + 0, + -100, + 100, + LocalizationHelper.GetString("RetinexProcessor_Offset_Desc"))); + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + string method = GetParameter("Method"); + double sigma1 = GetParameter("Sigma1"); + double sigma2 = GetParameter("Sigma2"); + double sigma3 = GetParameter("Sigma3"); + double gain = GetParameter("Gain"); + int offset = GetParameter("Offset"); + + Image result; + + if (method == "SSR") + { + // 单尺度Retinex + result = SingleScaleRetinex(inputImage, sigma2, gain, offset); + } + else if (method == "MSR") + { + // 多尺度Retinex + result = MultiScaleRetinex(inputImage, new[] { sigma1, sigma2, sigma3 }, gain, offset); + } + else // MSRCR + { + // 带色彩恢复的多尺度Retinex + result = MultiScaleRetinexCR(inputImage, new[] { sigma1, sigma2, sigma3 }, gain, offset); + } + + _logger.Debug("Process: Method = {Method}, Sigma1 = {Sigma1}, Sigma2 = {Sigma2}, Sigma3 = {Sigma3}, Gain = {Gain}, Offset = {Offset}", + method, sigma1, sigma2, sigma3, gain, offset); + return result; + } + + /// + /// 单尺度Retinex (SSR) + /// R(x,y) = log(I(x,y)) - log(I(x,y) * G(x,y)) + /// + private Image SingleScaleRetinex(Image inputImage, double sigma, double gain, int offset) + { + // 转换为浮点图像并添加小常数避免log(0) + Image floatImage = inputImage.Convert(); + floatImage = floatImage + 1.0f; + + // 计算log(I) + Image logImage = new Image(inputImage.Size); + for (int y = 0; y < inputImage.Height; y++) + { + for (int x = 0; x < inputImage.Width; x++) + { + logImage.Data[y, x, 0] = (float)Math.Log(floatImage.Data[y, x, 0]); + } + } + + // 高斯模糊得到光照分量 + Image blurred = new Image(inputImage.Size); + int kernelSize = (int)(sigma * 6) | 1; // 确保为奇数 + if (kernelSize < 3) kernelSize = 3; + CvInvoke.GaussianBlur(floatImage, blurred, new System.Drawing.Size(kernelSize, kernelSize), sigma); + + // 计算log(I * G) + Image logBlurred = new Image(inputImage.Size); + for (int y = 0; y < inputImage.Height; y++) + { + for (int x = 0; x < inputImage.Width; x++) + { + logBlurred.Data[y, x, 0] = (float)Math.Log(blurred.Data[y, x, 0]); + } + } + + // R = log(I) - log(I*G) + Image retinex = logImage - logBlurred; + + // 应用增益和偏移 + retinex = retinex * gain + offset; + + // 归一化到0-255 + Image result = NormalizeToByteImage(retinex); + + floatImage.Dispose(); + logImage.Dispose(); + blurred.Dispose(); + logBlurred.Dispose(); + retinex.Dispose(); + + return result; + } + + /// + /// 多尺度Retinex (MSR) + /// MSR = Σ(w_i * SSR_i) / N + /// + private Image MultiScaleRetinex(Image inputImage, double[] sigmas, double gain, int offset) + { + // 转换为浮点图像 + Image floatImage = inputImage.Convert(); + floatImage = floatImage + 1.0f; + + // 计算log(I) + Image logImage = new Image(inputImage.Size); + for (int y = 0; y < inputImage.Height; y++) + { + for (int x = 0; x < inputImage.Width; x++) + { + logImage.Data[y, x, 0] = (float)Math.Log(floatImage.Data[y, x, 0]); + } + } + + // 累加多个尺度的结果 + Image msrResult = new Image(inputImage.Size); + msrResult.SetZero(); + + foreach (double sigma in sigmas) + { + // 高斯模糊 + Image blurred = new Image(inputImage.Size); + int kernelSize = (int)(sigma * 6) | 1; + if (kernelSize < 3) kernelSize = 3; + CvInvoke.GaussianBlur(floatImage, blurred, new System.Drawing.Size(kernelSize, kernelSize), sigma); + + // 计算log(I*G) + Image logBlurred = new Image(inputImage.Size); + for (int y = 0; y < inputImage.Height; y++) + { + for (int x = 0; x < inputImage.Width; x++) + { + logBlurred.Data[y, x, 0] = (float)Math.Log(blurred.Data[y, x, 0]); + } + } + + // 累加 SSR + msrResult = msrResult + (logImage - logBlurred); + + blurred.Dispose(); + logBlurred.Dispose(); + } + + // 平均 + msrResult = msrResult / sigmas.Length; + + // 应用增益和偏移 + msrResult = msrResult * gain + offset; + + // 归一化 + Image result = NormalizeToByteImage(msrResult); + + floatImage.Dispose(); + logImage.Dispose(); + msrResult.Dispose(); + + return result; + } + + /// + /// 带色彩恢复的多尺度Retinex (MSRCR) + /// 对于灰度图像,使用简化版本 + /// + private Image MultiScaleRetinexCR(Image inputImage, double[] sigmas, double gain, int offset) + { + // 先执行MSR + Image msrResult = MultiScaleRetinex(inputImage, sigmas, gain, offset); + + // 对于灰度图像,色彩恢复简化为对比度增强 + Image floatMsr = msrResult.Convert(); + Image floatInput = inputImage.Convert(); + + // 简单的色彩恢复:增强局部对比度 + Image enhanced = new Image(inputImage.Size); + for (int y = 0; y < inputImage.Height; y++) + { + for (int x = 0; x < inputImage.Width; x++) + { + float msr = floatMsr.Data[y, x, 0]; + float original = floatInput.Data[y, x, 0]; + + // 色彩恢复因子 + float c = (float)Math.Log(original + 1.0) / (float)Math.Log(128.0); + enhanced.Data[y, x, 0] = msr * c; + } + } + + Image result = NormalizeToByteImage(enhanced); + + msrResult.Dispose(); + floatMsr.Dispose(); + floatInput.Dispose(); + enhanced.Dispose(); + + return result; + } + + /// + /// 归一化浮点图像到字节图像 + /// + private Image NormalizeToByteImage(Image floatImage) + { + // 找到最小值和最大值 + double minVal = double.MaxValue; + double maxVal = double.MinValue; + + for (int y = 0; y < floatImage.Height; y++) + { + for (int x = 0; x < floatImage.Width; x++) + { + float val = floatImage.Data[y, x, 0]; + if (val < minVal) minVal = val; + if (val > maxVal) maxVal = val; + } + } + + // 归一化到0-255 + Image result = new Image(floatImage.Size); + double range = maxVal - minVal; + if (range > 0) + { + for (int y = 0; y < floatImage.Height; y++) + { + for (int x = 0; x < floatImage.Width; x++) + { + float val = floatImage.Data[y, x, 0]; + int normalized = (int)((val - minVal) / range * 255.0); + result.Data[y, x, 0] = (byte)Math.Max(0, Math.Min(255, normalized)); + } + } + } + + return result; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/图像增强/SharpenProcessor.cs b/ImageProcessing.Processors/图像增强/SharpenProcessor.cs new file mode 100644 index 0000000..f46b24c --- /dev/null +++ b/ImageProcessing.Processors/图像增强/SharpenProcessor.cs @@ -0,0 +1,141 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: SharpenProcessor.cs +// 描述: 锐化算子,用于增强图像边缘和细节 +// 功能: +// - 拉普拉斯锐化 +// - 非锐化掩蔽(Unsharp Masking) +// - 可调节锐化强度 +// - 支持多种锐化核 +// 算法: 拉普拉斯算子、非锐化掩蔽 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// 锐化算子 +/// +public class SharpenProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public SharpenProcessor() + { + Name = LocalizationHelper.GetString("SharpenProcessor_Name"); + Description = LocalizationHelper.GetString("SharpenProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Method", new ProcessorParameter( + "Method", + LocalizationHelper.GetString("SharpenProcessor_Method"), + typeof(string), + "Laplacian", + null, + null, + LocalizationHelper.GetString("SharpenProcessor_Method_Desc"), + new string[] { "Laplacian", "UnsharpMask" })); + + Parameters.Add("Strength", new ProcessorParameter( + "Strength", + LocalizationHelper.GetString("SharpenProcessor_Strength"), + typeof(double), + 1.0, + 0.1, + 5.0, + LocalizationHelper.GetString("SharpenProcessor_Strength_Desc"))); + + Parameters.Add("KernelSize", new ProcessorParameter( + "KernelSize", + LocalizationHelper.GetString("SharpenProcessor_KernelSize"), + typeof(int), + 3, + 1, + 15, + LocalizationHelper.GetString("SharpenProcessor_KernelSize_Desc"))); + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + string method = GetParameter("Method"); + double strength = GetParameter("Strength"); + int kernelSize = GetParameter("KernelSize"); + + if (kernelSize % 2 == 0) kernelSize++; + + Image result; + + if (method == "UnsharpMask") + { + result = ApplyUnsharpMask(inputImage, kernelSize, strength); + } + else // Laplacian + { + result = ApplyLaplacianSharpening(inputImage, strength); + } + + _logger.Debug("Process: Method = {Method}, Strength = {Strength}, KernelSize = {KernelSize}", + method, strength, kernelSize); + return result; + } + + private Image ApplyLaplacianSharpening(Image inputImage, double strength) + { + // 计算拉普拉斯算子 + var laplacian = new Image(inputImage.Size); + CvInvoke.Laplacian(inputImage, laplacian, DepthType.Cv32F, 1); + + // 转换为字节类型 + var laplacianByte = laplacian.Convert(); + + // 将拉普拉斯结果加到原图上进行锐化 + var floatImage = inputImage.Convert(); + var sharpened = floatImage + laplacian * strength; + + // 限制范围并转换回字节类型 + var result = sharpened.Convert(); + + laplacian.Dispose(); + laplacianByte.Dispose(); + floatImage.Dispose(); + sharpened.Dispose(); + + return result; + } + + private Image ApplyUnsharpMask(Image inputImage, int kernelSize, double strength) + { + // 创建模糊图像 + var blurred = new Image(inputImage.Size); + CvInvoke.GaussianBlur(inputImage, blurred, + new System.Drawing.Size(kernelSize, kernelSize), 0); + + // 计算差异(细节) + var floatInput = inputImage.Convert(); + var floatBlurred = blurred.Convert(); + var detail = floatInput - floatBlurred; + + // 将细节加回原图 + var sharpened = floatInput + detail * strength; + + // 转换回字节类型 + var result = sharpened.Convert(); + + blurred.Dispose(); + floatInput.Dispose(); + floatBlurred.Dispose(); + detail.Dispose(); + sharpened.Dispose(); + + return result; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/图像增强/SubPixelZoomProcessor.cs b/ImageProcessing.Processors/图像增强/SubPixelZoomProcessor.cs new file mode 100644 index 0000000..b9a0ce5 --- /dev/null +++ b/ImageProcessing.Processors/图像增强/SubPixelZoomProcessor.cs @@ -0,0 +1,127 @@ +// ============================================================================ +// Copyright © 2016-2025 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: SubPixelZoomProcessor.cs +// 描述: 亚像素放大算子,通过高质量插值实现图像的亚像素级放大 +// 功能: +// - 支持任意倍率放大(含小数倍率如 1.5x、2.3x) +// - 多种插值方法(最近邻、双线性、双三次、Lanczos) +// - 可选锐化补偿(抵消插值模糊) +// - 可选指定输出尺寸 +// 算法: 基于 OpenCV Resize 的高质量插值放大 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; +using System.Drawing; + +namespace ImageProcessing.Processors; + +/// +/// 亚像素放大算子 +/// +public class SubPixelZoomProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public SubPixelZoomProcessor() + { + Name = LocalizationHelper.GetString("SubPixelZoomProcessor_Name"); + Description = LocalizationHelper.GetString("SubPixelZoomProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("ScaleFactor", new ProcessorParameter( + "ScaleFactor", + LocalizationHelper.GetString("SubPixelZoomProcessor_ScaleFactor"), + typeof(double), + 2.0, + 1.0, + 16.0, + LocalizationHelper.GetString("SubPixelZoomProcessor_ScaleFactor_Desc"))); + + Parameters.Add("Interpolation", new ProcessorParameter( + "Interpolation", + LocalizationHelper.GetString("SubPixelZoomProcessor_Interpolation"), + typeof(string), + "Lanczos", + null, + null, + LocalizationHelper.GetString("SubPixelZoomProcessor_Interpolation_Desc"), + new string[] { "Nearest", "Bilinear", "Bicubic", "Lanczos" })); + + Parameters.Add("SharpenAfter", new ProcessorParameter( + "SharpenAfter", + LocalizationHelper.GetString("SubPixelZoomProcessor_SharpenAfter"), + typeof(bool), + false, + null, + null, + LocalizationHelper.GetString("SubPixelZoomProcessor_SharpenAfter_Desc"))); + + Parameters.Add("SharpenStrength", new ProcessorParameter( + "SharpenStrength", + LocalizationHelper.GetString("SubPixelZoomProcessor_SharpenStrength"), + typeof(double), + 0.5, + 0.1, + 3.0, + LocalizationHelper.GetString("SubPixelZoomProcessor_SharpenStrength_Desc"))); + + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + double scaleFactor = GetParameter("ScaleFactor"); + string interpolation = GetParameter("Interpolation"); + bool sharpenAfter = GetParameter("SharpenAfter"); + double sharpenStrength = GetParameter("SharpenStrength"); + + Inter interMethod = interpolation switch + { + "Nearest" => Inter.Nearest, + "Bilinear" => Inter.Linear, + "Bicubic" => Inter.Cubic, + _ => Inter.Lanczos4 + }; + + int newWidth = (int)Math.Round(inputImage.Width * scaleFactor); + int newHeight = (int)Math.Round(inputImage.Height * scaleFactor); + + // 确保最小尺寸为 1 + newWidth = Math.Max(1, newWidth); + newHeight = Math.Max(1, newHeight); + + var result = new Image(newWidth, newHeight); + CvInvoke.Resize(inputImage, result, new Size(newWidth, newHeight), 0, 0, interMethod); + + // 锐化补偿 + if (sharpenAfter) + { + // Unsharp Masking: result = result + strength * (result - blur) + int ksize = Math.Max(3, (int)(scaleFactor * 2) | 1); // 奇数核 + using var blurred = result.SmoothGaussian(ksize); + + for (int y = 0; y < newHeight; y++) + { + for (int x = 0; x < newWidth; x++) + { + float val = result.Data[y, x, 0]; + float blur = blurred.Data[y, x, 0]; + float sharpened = val + (float)(sharpenStrength * (val - blur)); + result.Data[y, x, 0] = (byte)Math.Clamp((int)sharpened, 0, 255); + } + } + } + + _logger.Debug("Process: Scale={Scale}, Interp={Interp}, Size={W}x{H}, Sharpen={Sharpen}", + scaleFactor, interpolation, newWidth, newHeight, sharpenAfter); + + return result; + } +} diff --git a/ImageProcessing.Processors/图像增强/SuperResolutionProcessor.cs b/ImageProcessing.Processors/图像增强/SuperResolutionProcessor.cs new file mode 100644 index 0000000..dc57cc2 --- /dev/null +++ b/ImageProcessing.Processors/图像增强/SuperResolutionProcessor.cs @@ -0,0 +1,319 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: SuperResolutionProcessor.cs +// 描述: 基于深度学习的超分辨率算子 +// 功能: +// - 支持 EDSR 和 FSRCNN 超分辨率模型(ONNX 格式) +// - 支持 2x、3x、4x 放大倍率 +// - 灰度图像自动转换为三通道输入,推理后转回灰度 +// - 模型文件自动搜索,支持自定义路径 +// - 使用 Microsoft.ML.OnnxRuntime 进行推理 +// 算法: EDSR (Enhanced Deep Residual SR) / FSRCNN (Fast SR CNN) +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Microsoft.ML.OnnxRuntime; +using Microsoft.ML.OnnxRuntime.Tensors; +using Serilog; +using System.IO; + +namespace ImageProcessing.Processors; + +/// +/// 基于深度学习的超分辨率算子(EDSR / FSRCNN),使用 ONNX Runtime 推理 +/// +public class SuperResolutionProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + // 会话缓存,避免重复加载 + private static InferenceSession? _cachedSession; + private static string _cachedModelKey = string.Empty; + + public SuperResolutionProcessor() + { + Name = LocalizationHelper.GetString("SuperResolutionProcessor_Name"); + Description = LocalizationHelper.GetString("SuperResolutionProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Model", new ProcessorParameter( + "Model", + LocalizationHelper.GetString("SuperResolutionProcessor_Model"), + typeof(string), + "FSRCNN", + null, + null, + LocalizationHelper.GetString("SuperResolutionProcessor_Model_Desc"), + new string[] { "EDSR", "FSRCNN" })); + + Parameters.Add("Scale", new ProcessorParameter( + "Scale", + LocalizationHelper.GetString("SuperResolutionProcessor_Scale"), + typeof(string), + "2", + null, + null, + LocalizationHelper.GetString("SuperResolutionProcessor_Scale_Desc"), + new string[] { "2", "3", "4" })); + + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + string model = GetParameter("Model"); + int scale = int.Parse(GetParameter("Scale")); + + // 查找模型文件 + string modelPath = FindModelFile(model, scale); + if (string.IsNullOrEmpty(modelPath)) + { + _logger.Error("Model file not found: {Model}_x{Scale}.onnx", model, scale); + throw new FileNotFoundException( + $"超分辨率模型文件未找到: {model}_x{scale}.onnx\n" + + $"请将模型文件放置到以下任一目录:\n" + + $" 1. 程序目录/Models/\n" + + $" 2. 程序目录/\n" + + $"模型需要 ONNX 格式。\n" + + $"可使用 tf2onnx 从 .pb 转换:\n" + + $" pip install tf2onnx\n" + + $" python -m tf2onnx.convert --input {model}_x{scale}.pb --output {model}_x{scale}.onnx --inputs input:0 --outputs output:0"); + } + + // 加载或复用会话 + string modelKey = $"{model}_{scale}"; + InferenceSession session; + if (_cachedModelKey == modelKey && _cachedSession != null) + { + session = _cachedSession; + _logger.Debug("Reusing cached session: {ModelKey}", modelKey); + } + else + { + _cachedSession?.Dispose(); + var options = new SessionOptions(); + options.GraphOptimizationLevel = GraphOptimizationLevel.ORT_ENABLE_ALL; + try + { + options.AppendExecutionProvider_CUDA(0); + _logger.Information("Using CUDA GPU for inference"); + } + catch + { + _logger.Warning("CUDA not available, falling back to CPU"); + } + session = new InferenceSession(modelPath, options); + _cachedSession = session; + _cachedModelKey = modelKey; + // 记录实际使用的 Execution Provider + var providers = session.ModelMetadata?.CustomMetadataMap; + _logger.Information("Loaded ONNX model: {ModelPath}, Providers: {Providers}", + modelPath, string.Join(", ", session.GetType().Name)); + } + + int h = inputImage.Height; + int w = inputImage.Width; + _logger.Information("Input image size: {W}x{H}, Model: {Model}, Scale: {Scale}", w, h, model, scale); + + // 对大图使用分块推理策略,避免单次推理过慢/OOM + const int TileSize = 256; + bool useTiling = (model.StartsWith("EDSR", StringComparison.OrdinalIgnoreCase)) && (h > TileSize || w > TileSize); + + if (useTiling) + { + return ProcessTiled(session, inputImage, scale, TileSize); + } + + return ProcessSingle(session, inputImage, scale); + } + + /// + /// 单次推理(小图或 FSRCNN) + /// + private Image ProcessSingle(InferenceSession session, Image inputImage, int scale) + { + int h = inputImage.Height; + int w = inputImage.Width; + + // 获取模型输入信息 + string inputName = session.InputMetadata.Keys.First(); + var inputMeta = session.InputMetadata[inputName]; + int[] dims = inputMeta.Dimensions; + // dims 格式: [1, H, W, C] (NHWC),C 可能是 1 或 3 + int inputChannels = dims[^1]; // 最后一维是通道数 + + // 构建输入 tensor: [1, H, W, C] (NHWC) + // 使用底层数组 + Parallel.For 避免逐元素索引开销 + DenseTensor inputTensor; + if (inputChannels == 1) + { + // FSRCNN: 单通道灰度输入 + inputTensor = new DenseTensor(new[] { 1, h, w, 1 }); + float[] buf = inputTensor.Buffer.ToArray(); + var imgData = inputImage.Data; + Parallel.For(0, h, y => + { + int rowOffset = y * w; + for (int x = 0; x < w; x++) + buf[rowOffset + x] = imgData[y, x, 0]; + }); + inputTensor = new DenseTensor(buf, new[] { 1, h, w, 1 }); + } + else + { + // EDSR: 三通道 BGR 输入 + using var colorInput = new Image(w, h); + CvInvoke.CvtColor(inputImage, colorInput, ColorConversion.Gray2Bgr); + var buf = new float[h * w * 3]; + var imgData = colorInput.Data; + Parallel.For(0, h, y => + { + int rowOffset = y * w * 3; + for (int x = 0; x < w; x++) + { + int px = rowOffset + x * 3; + buf[px] = imgData[y, x, 0]; + buf[px + 1] = imgData[y, x, 1]; + buf[px + 2] = imgData[y, x, 2]; + } + }); + inputTensor = new DenseTensor(buf, new[] { 1, h, w, 3 }); + } + + // 推理 + var inputs = new List + { + NamedOnnxValue.CreateFromTensor(inputName, inputTensor) + }; + + using var results = session.Run(inputs); + var outputTensor = results.First().AsTensor(); + + // 输出 shape: [1, C, H*scale, W*scale] (NCHW,模型输出经过 Transpose) + var shape = outputTensor.Dimensions; + int outC = shape[1]; + int outH = shape[2]; + int outW = shape[3]; + + // 转换为灰度图像 + // 使用 Parallel.For + 直接内存操作 + Image result; + if (outC == 1) + { + // FSRCNN: 单通道输出 [1, 1, outH, outW] + result = new Image(outW, outH); + var outData = result.Data; + Parallel.For(0, outH, y => + { + for (int x = 0; x < outW; x++) + outData[y, x, 0] = (byte)Math.Clamp((int)outputTensor[0, 0, y, x], 0, 255); + }); + } + else + { + // EDSR: 三通道输出 [1, 3, outH, outW] → 灰度 + // 直接计算灰度值,跳过中间 BGR 图像分配 + result = new Image(outW, outH); + var outData = result.Data; + Parallel.For(0, outH, y => + { + for (int x = 0; x < outW; x++) + { + float b = outputTensor[0, 0, y, x]; + float g = outputTensor[0, 1, y, x]; + float r = outputTensor[0, 2, y, x]; + // BT.601 灰度公式: 0.299*R + 0.587*G + 0.114*B + int gray = (int)(0.299f * r + 0.587f * g + 0.114f * b); + outData[y, x, 0] = (byte)Math.Clamp(gray, 0, 255); + } + }); + } + + _logger.Debug("ProcessSingle: Scale={Scale}, Output={W}x{H}", scale, outW, outH); + + return result; + } + + /// + /// 分块推理(大图 EDSR),将图像切成小块分别推理后拼接 + /// + private Image ProcessTiled(InferenceSession session, Image inputImage, int scale, int tileSize) + { + int h = inputImage.Height; + int w = inputImage.Width; + int overlap = 8; // 重叠像素,减少拼接边缘伪影 + + var result = new Image(w * scale, h * scale); + + int tilesX = (int)Math.Ceiling((double)w / (tileSize - overlap)); + int tilesY = (int)Math.Ceiling((double)h / (tileSize - overlap)); + _logger.Information("Tiled processing: {TilesX}x{TilesY} tiles, tileSize={TileSize}", tilesX, tilesY, tileSize); + + for (int ty = 0; ty < tilesY; ty++) + { + for (int tx = 0; tx < tilesX; tx++) + { + int srcX = Math.Min(tx * (tileSize - overlap), w - tileSize); + int srcY = Math.Min(ty * (tileSize - overlap), h - tileSize); + srcX = Math.Max(srcX, 0); + srcY = Math.Max(srcY, 0); + int tw = Math.Min(tileSize, w - srcX); + int th = Math.Min(tileSize, h - srcY); + + // 裁剪 tile + inputImage.ROI = new System.Drawing.Rectangle(srcX, srcY, tw, th); + var tile = inputImage.Copy(); + inputImage.ROI = System.Drawing.Rectangle.Empty; + + // 推理单个 tile + var srTile = ProcessSingle(session, tile, scale); + tile.Dispose(); + + // 写入结果 + int dstX = srcX * scale; + int dstY = srcY * scale; + result.ROI = new System.Drawing.Rectangle(dstX, dstY, srTile.Width, srTile.Height); + srTile.CopyTo(result); + result.ROI = System.Drawing.Rectangle.Empty; + srTile.Dispose(); + } + } + + _logger.Debug("ProcessTiled: Scale={Scale}, Output={W}x{H}", scale, result.Width, result.Height); + return result; + } + + /// + /// 查找模型文件,按优先级搜索多个目录(.onnx 格式) + /// + private static string FindModelFile(string model, int scale) + { + string baseDir = AppDomain.CurrentDomain.BaseDirectory; + string fileName = $"{model}_x{scale}.onnx"; + string[] searchPaths = new[] + { + Path.Combine(baseDir, "Models", fileName), + Path.Combine(baseDir, fileName), + Path.Combine(Directory.GetCurrentDirectory(), "Models", fileName), + Path.Combine(Directory.GetCurrentDirectory(), fileName), + }; + + foreach (var path in searchPaths) + { + if (File.Exists(path)) + { + _logger.Debug("Found model file: {Path}", path); + return path; + } + } + + _logger.Warning("Model file not found: {Model}_x{Scale}.onnx", model, scale); + return string.Empty; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/形态学处理/MorphologyProcessor.cs b/ImageProcessing.Processors/形态学处理/MorphologyProcessor.cs new file mode 100644 index 0000000..5a9942a --- /dev/null +++ b/ImageProcessing.Processors/形态学处理/MorphologyProcessor.cs @@ -0,0 +1,102 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: MorphologyProcessor.cs +// 描述: 形态学处理算子,用于二值图像的形态学操作 +// 功能: +// - 腐蚀(Erode):收缩目标区域 +// - 膨胀(Dilate):扩张目标区域 +// - 开运算(Open):先腐蚀后膨胀,去除小目标 +// - 闭运算(Close):先膨胀后腐蚀,填充小孔洞 +// 算法: 数学形态学运算 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; +using System.Drawing; + +namespace ImageProcessing.Processors; + +/// +/// 形态学处理算子 +/// +public class MorphologyProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public MorphologyProcessor() + { + Name = LocalizationHelper.GetString("MorphologyProcessor_Name"); + Description = LocalizationHelper.GetString("MorphologyProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Operation", new ProcessorParameter( + "Operation", + LocalizationHelper.GetString("MorphologyProcessor_Operation"), + typeof(string), + "Erode", + null, + null, + LocalizationHelper.GetString("MorphologyProcessor_Operation_Desc"), + new string[] { "Erode", "Dilate", "Open", "Close" })); + + Parameters.Add("KernelSize", new ProcessorParameter( + "KernelSize", + LocalizationHelper.GetString("MorphologyProcessor_KernelSize"), + typeof(int), + 3, + 1, + 21, + LocalizationHelper.GetString("MorphologyProcessor_KernelSize_Desc"))); + + Parameters.Add("Iterations", new ProcessorParameter( + "Iterations", + LocalizationHelper.GetString("MorphologyProcessor_Iterations"), + typeof(int), + 1, + 1, + 10, + LocalizationHelper.GetString("MorphologyProcessor_Iterations_Desc"))); + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + string operation = GetParameter("Operation"); + int kernelSize = GetParameter("KernelSize"); + int iterations = GetParameter("Iterations"); + + var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(kernelSize, kernelSize), new Point(-1, -1)); + var result = inputImage.Clone(); + + switch (operation) + { + case "Erode": + CvInvoke.Erode(inputImage, result, kernel, new Point(-1, -1), + iterations, BorderType.Default, default); + break; + + case "Dilate": + CvInvoke.Dilate(inputImage, result, kernel, new Point(-1, -1), + iterations, BorderType.Default, default); + break; + + case "Open": + CvInvoke.MorphologyEx(inputImage, result, MorphOp.Open, kernel, + new Point(-1, -1), iterations, BorderType.Default, default); + break; + + case "Close": + CvInvoke.MorphologyEx(inputImage, result, MorphOp.Close, kernel, + new Point(-1, -1), iterations, BorderType.Default, default); + break; + } + _logger.Debug("Process:Operation = {operation},KernelSize = {kernelSize},Iterations = {iterations}", operation, kernelSize, iterations); + return result; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/数学运算/DifferenceProcessor.cs b/ImageProcessing.Processors/数学运算/DifferenceProcessor.cs new file mode 100644 index 0000000..93c6b54 --- /dev/null +++ b/ImageProcessing.Processors/数学运算/DifferenceProcessor.cs @@ -0,0 +1,128 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: DifferenceProcessor.cs +// 描述: 差分运算算子,用于边缘检测和变化检测 +// 功能: +// - 对图像进行差分运算 +// - 支持水平、垂直和对角线差分 +// - 可用于边缘检测 +// - 可选归一化输出 +// 算法: 像素级差分运算 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; +using System.Drawing; + +namespace ImageProcessing.Processors; + +/// +/// 差分运算算子 +/// +public class DifferenceProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public DifferenceProcessor() + { + Name = LocalizationHelper.GetString("DifferenceProcessor_Name"); + Description = LocalizationHelper.GetString("DifferenceProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Direction", new ProcessorParameter( + "Direction", + LocalizationHelper.GetString("DifferenceProcessor_Direction"), + typeof(string), + "Horizontal", + null, + null, + LocalizationHelper.GetString("DifferenceProcessor_Direction_Desc"), + new string[] { "Horizontal", "Vertical", "Both" })); + + Parameters.Add("Normalize", new ProcessorParameter( + "Normalize", + LocalizationHelper.GetString("DifferenceProcessor_Normalize"), + typeof(bool), + true, + null, + null, + LocalizationHelper.GetString("DifferenceProcessor_Normalize_Desc"))); + + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + string direction = GetParameter("Direction"); + bool normalize = GetParameter("Normalize"); + + int width = inputImage.Width; + int height = inputImage.Height; + + var floatImage = inputImage.Convert(); + var result = new Image(width, height); + + if (direction == "Horizontal") + { + // 水平差分: I(x+1,y) - I(x,y) + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width - 1; x++) + { + result.Data[y, x, 0] = floatImage.Data[y, x + 1, 0] - floatImage.Data[y, x, 0]; + } + result.Data[y, width - 1, 0] = 0; + } + } + else if (direction == "Vertical") + { + // 垂直差分: I(x,y+1) - I(x,y) + for (int y = 0; y < height - 1; y++) + { + for (int x = 0; x < width; x++) + { + result.Data[y, x, 0] = floatImage.Data[y + 1, x, 0] - floatImage.Data[y, x, 0]; + } + } + for (int x = 0; x < width; x++) + { + result.Data[height - 1, x, 0] = 0; + } + } + else // Both + { + // 梯度幅值: sqrt((dx)^2 + (dy)^2) + for (int y = 0; y < height - 1; y++) + { + for (int x = 0; x < width - 1; x++) + { + float dx = floatImage.Data[y, x + 1, 0] - floatImage.Data[y, x, 0]; + float dy = floatImage.Data[y + 1, x, 0] - floatImage.Data[y, x, 0]; + result.Data[y, x, 0] = (float)Math.Sqrt(dx * dx + dy * dy); + } + } + } + + if (normalize) + { + double minVal = 0, maxVal = 0; + Point minLoc = new Point(); + Point maxLoc = new Point(); + CvInvoke.MinMaxLoc(result, ref minVal, ref maxVal, ref minLoc, ref maxLoc); + + if (maxVal > minVal) + { + result = (result - minVal) * (255.0 / (maxVal - minVal)); + } + } + + floatImage.Dispose(); + _logger.Debug("Process: Direction = {Direction}, Normalize = {Normalize}", direction, normalize); + return result.Convert(); + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/数学运算/DivisionProcessor.cs b/ImageProcessing.Processors/数学运算/DivisionProcessor.cs new file mode 100644 index 0000000..2414c06 --- /dev/null +++ b/ImageProcessing.Processors/数学运算/DivisionProcessor.cs @@ -0,0 +1,90 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: DivisionProcessor.cs +// 描述: 除法运算算子,用于图像归一化处理 +// 功能: +// - 对图像像素值进行除法运算 +// - 支持缩放因子调整 +// - 可选归一化到0-255范围 +// - 常用于背景校正和图像归一化 +// 算法: 像素级除法运算 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; +using System.Drawing; + +namespace ImageProcessing.Processors; + +/// +/// 除法运算算子 +/// +public class DivisionProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public DivisionProcessor() + { + Name = LocalizationHelper.GetString("DivisionProcessor_Name"); + Description = LocalizationHelper.GetString("DivisionProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Divisor", new ProcessorParameter( + "Divisor", + LocalizationHelper.GetString("DivisionProcessor_Divisor"), + typeof(double), + 2.0, + 0.01, + 255.0, + LocalizationHelper.GetString("DivisionProcessor_Divisor_Desc"))); + + Parameters.Add("Scale", new ProcessorParameter( + "Scale", + LocalizationHelper.GetString("DivisionProcessor_Scale"), + typeof(double), + 1.0, + 0.1, + 10.0, + LocalizationHelper.GetString("DivisionProcessor_Scale_Desc"))); + + Parameters.Add("Normalize", new ProcessorParameter( + "Normalize", + LocalizationHelper.GetString("DivisionProcessor_Normalize"), + typeof(bool), + true, + null, + null, + LocalizationHelper.GetString("DivisionProcessor_Normalize_Desc"))); + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + double divisor = GetParameter("Divisor"); + double scale = GetParameter("Scale"); + bool normalize = GetParameter("Normalize"); + + var floatImage = inputImage.Convert(); + var result = floatImage / divisor * scale; + + if (normalize) + { + double minVal = 0, maxVal = 0; + Point minLoc = new Point(); + Point maxLoc = new Point(); + CvInvoke.MinMaxLoc(result, ref minVal, ref maxVal, ref minLoc, ref maxLoc); + + if (maxVal > minVal) + { + result = (result - minVal) * (255.0 / (maxVal - minVal)); + } + } + _logger.Debug("Process:Divisor = {0}, Scale = {1}, Normalize = {2}", divisor, scale, normalize); + return result.Convert(); + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/数学运算/IntegralProcessor.cs b/ImageProcessing.Processors/数学运算/IntegralProcessor.cs new file mode 100644 index 0000000..1cf7996 --- /dev/null +++ b/ImageProcessing.Processors/数学运算/IntegralProcessor.cs @@ -0,0 +1,95 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: IntegralProcessor.cs +// 描述: 积分运算算子,计算积分图像 +// 功能: +// - 计算积分图像(累加和) +// - 用于快速区域求和 +// - 支持归一化输出 +// 算法: 积分图像算法 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; +using System.Drawing; + +namespace ImageProcessing.Processors; + +/// +/// 积分运算算子 +/// +public class IntegralProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public IntegralProcessor() + { + Name = LocalizationHelper.GetString("IntegralProcessor_Name"); + Description = LocalizationHelper.GetString("IntegralProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Normalize", new ProcessorParameter( + "Normalize", + LocalizationHelper.GetString("IntegralProcessor_Normalize"), + typeof(bool), + true, + null, + null, + LocalizationHelper.GetString("IntegralProcessor_Normalize_Desc"))); + + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + bool normalize = GetParameter("Normalize"); + + int width = inputImage.Width; + int height = inputImage.Height; + + // 使用double类型避免溢出 + var integralImage = new Image(width, height); + + // 计算积分图像 + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + double sum = inputImage.Data[y, x, 0]; + + if (x > 0) + sum += integralImage.Data[y, x - 1, 0]; + if (y > 0) + sum += integralImage.Data[y - 1, x, 0]; + if (x > 0 && y > 0) + sum -= integralImage.Data[y - 1, x - 1, 0]; + + integralImage.Data[y, x, 0] = sum; + } + } + + var result = integralImage.Convert(); + + if (normalize) + { + double minVal = 0, maxVal = 0; + Point minLoc = new Point(); + Point maxLoc = new Point(); + CvInvoke.MinMaxLoc(result, ref minVal, ref maxVal, ref minLoc, ref maxLoc); + + if (maxVal > minVal) + { + result = (result - minVal) * (255.0 / (maxVal - minVal)); + } + } + + integralImage.Dispose(); + _logger.Debug("Process: Normalize = {Normalize}", normalize); + return result.Convert(); + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/数学运算/MultiplicationProcessor.cs b/ImageProcessing.Processors/数学运算/MultiplicationProcessor.cs new file mode 100644 index 0000000..46d758e --- /dev/null +++ b/ImageProcessing.Processors/数学运算/MultiplicationProcessor.cs @@ -0,0 +1,88 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: MultiplicationProcessor.cs +// 描述: 乘法运算算子,用于图像增强 +// 功能: +// - 对图像像素值进行乘法运算 +// - 支持增益调整 +// - 可选归一化输出 +// - 常用于图像增强和对比度调整 +// 算法: 像素级乘法运算 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; +using System.Drawing; + +namespace ImageProcessing.Processors; + +/// +/// 乘法运算算子 +/// +public class MultiplicationProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public MultiplicationProcessor() + { + Name = LocalizationHelper.GetString("MultiplicationProcessor_Name"); + Description = LocalizationHelper.GetString("MultiplicationProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Multiplier", new ProcessorParameter( + "Multiplier", + LocalizationHelper.GetString("MultiplicationProcessor_Multiplier"), + typeof(double), + 2.0, + 0.1, + 10.0, + LocalizationHelper.GetString("MultiplicationProcessor_Multiplier_Desc"))); + + Parameters.Add("Normalize", new ProcessorParameter( + "Normalize", + LocalizationHelper.GetString("MultiplicationProcessor_Normalize"), + typeof(bool), + true, + null, + null, + LocalizationHelper.GetString("MultiplicationProcessor_Normalize_Desc"))); + + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + double multiplier = GetParameter("Multiplier"); + bool normalize = GetParameter("Normalize"); + + var floatImage = inputImage.Convert(); + var result = floatImage * multiplier; + + if (normalize) + { + double minVal = 0, maxVal = 0; + Point minLoc = new Point(); + Point maxLoc = new Point(); + CvInvoke.MinMaxLoc(result, ref minVal, ref maxVal, ref minLoc, ref maxLoc); + + if (maxVal > minVal) + { + result = (result - minVal) * (255.0 / (maxVal - minVal)); + } + } + else + { + // 不归一化时,直接截断到0-255范围 + result = result.ThresholdBinary(new Gray(255), new Gray(255)); + } + + floatImage.Dispose(); + _logger.Debug("Process: Multiplier = {Multiplier}, Normalize = {Normalize}", multiplier, normalize); + return result.Convert(); + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/数学运算/OrProcessor.cs b/ImageProcessing.Processors/数学运算/OrProcessor.cs new file mode 100644 index 0000000..60e1a1d --- /dev/null +++ b/ImageProcessing.Processors/数学运算/OrProcessor.cs @@ -0,0 +1,65 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: OrProcessor.cs +// 描述: 或运算算子,用于图像逻辑运算 +// 功能: +// - 对图像进行按位或运算 +// - 支持与固定值或运算 +// - 可用于图像合并和掩码操作 +// 算法: 像素级按位或运算 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// 或运算算子 +/// +public class OrProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public OrProcessor() + { + Name = LocalizationHelper.GetString("OrProcessor_Name"); + Description = LocalizationHelper.GetString("OrProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Value", new ProcessorParameter( + "Value", + LocalizationHelper.GetString("OrProcessor_Value"), + typeof(int), + 0, + 0, + 255, + LocalizationHelper.GetString("OrProcessor_Value_Desc"))); + + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + int value = GetParameter("Value"); + + var result = inputImage.Clone(); + + // 对每个像素进行按位或运算 + for (int y = 0; y < inputImage.Height; y++) + { + for (int x = 0; x < inputImage.Width; x++) + { + result.Data[y, x, 0] = (byte)(inputImage.Data[y, x, 0] | value); + } + } + + _logger.Debug("Process: Value = {Value}", value); + return result; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/检测分析/AngleMeasurementProcessor.cs b/ImageProcessing.Processors/检测分析/AngleMeasurementProcessor.cs new file mode 100644 index 0000000..b3b4f43 --- /dev/null +++ b/ImageProcessing.Processors/检测分析/AngleMeasurementProcessor.cs @@ -0,0 +1,87 @@ +// ============================================================================ +// 文件名: AngleMeasurementProcessor.cs +// 描述: 角度测量算子 — 共端点的两条直线夹角 +// 功能: +// - 用户定义三个点:端点(顶点)、射线1终点、射线2终点 +// - 计算两条射线之间的夹角(0°~180°) +// - 在图像上绘制两条射线、角度弧线和标注 +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; +using System.Drawing; + +namespace ImageProcessing.Processors; + +public class AngleMeasurementProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public AngleMeasurementProcessor() + { + Name = LocalizationHelper.GetString("AngleMeasurementProcessor_Name"); + Description = LocalizationHelper.GetString("AngleMeasurementProcessor_Description"); + } + + protected override void InitializeParameters() + { + // 三个点坐标(由交互控件注入,使用 double 避免取整误差) + Parameters.Add("VX", new ProcessorParameter("VX", "VX", typeof(double), 250.0, null, null, "") { IsVisible = false }); + Parameters.Add("VY", new ProcessorParameter("VY", "VY", typeof(double), 250.0, null, null, "") { IsVisible = false }); + Parameters.Add("AX", new ProcessorParameter("AX", "AX", typeof(double), 100.0, null, null, "") { IsVisible = false }); + Parameters.Add("AY", new ProcessorParameter("AY", "AY", typeof(double), 250.0, null, null, "") { IsVisible = false }); + Parameters.Add("BX", new ProcessorParameter("BX", "BX", typeof(double), 250.0, null, null, "") { IsVisible = false }); + Parameters.Add("BY", new ProcessorParameter("BY", "BY", typeof(double), 100.0, null, null, "") { IsVisible = false }); + } + + public override Image Process(Image inputImage) + { + double vx = GetParameter("VX"), vy = GetParameter("VY"); + double ax = GetParameter("AX"), ay = GetParameter("AY"); + double bx = GetParameter("BX"), by = GetParameter("BY"); + + OutputData.Clear(); + + // 向量 VA 和 VB + double vax = ax - vx, vay = ay - vy; + double vbx = bx - vx, vby = by - vy; + + double lenA = Math.Sqrt(vax * vax + vay * vay); + double lenB = Math.Sqrt(vbx * vbx + vby * vby); + + double angleDeg = 0; + if (lenA > 0.001 && lenB > 0.001) + { + double dot = vax * vbx + vay * vby; + double cosAngle = Math.Clamp(dot / (lenA * lenB), -1.0, 1.0); + angleDeg = Math.Acos(cosAngle) * 180.0 / Math.PI; + } + + // 计算角度弧的起始角和扫过角(用于绘制弧线) + double angleA = Math.Atan2(vay, vax) * 180.0 / Math.PI; + double angleB = Math.Atan2(vby, vbx) * 180.0 / Math.PI; + + // 确保从 angleA 到 angleB 的扫过方向是较小的夹角 + double sweep = angleB - angleA; + if (sweep > 180) sweep -= 360; + if (sweep < -180) sweep += 360; + + string angleText = $"{angleDeg:F2} deg"; + + OutputData["AngleMeasurementResult"] = true; + OutputData["Vertex"] = new Point((int)Math.Round(vx), (int)Math.Round(vy)); + OutputData["PointA"] = new Point((int)Math.Round(ax), (int)Math.Round(ay)); + OutputData["PointB"] = new Point((int)Math.Round(bx), (int)Math.Round(by)); + OutputData["AngleDeg"] = angleDeg; + OutputData["ArcStartAngle"] = angleA; + OutputData["ArcSweepAngle"] = sweep; + OutputData["AngleText"] = angleText; + + _logger.Information("AngleMeasurement: Angle={Angle}, V=({VX},{VY}), A=({AX},{AY}), B=({BX},{BY})", + angleText, vx, vy, ax, ay, bx, by); + + return inputImage.Clone(); + } +} diff --git a/ImageProcessing.Processors/检测分析/BgaVoidRateProcessor.cs b/ImageProcessing.Processors/检测分析/BgaVoidRateProcessor.cs new file mode 100644 index 0000000..d99ffdc --- /dev/null +++ b/ImageProcessing.Processors/检测分析/BgaVoidRateProcessor.cs @@ -0,0 +1,403 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: BgaVoidRateProcessor.cs +// 描述: BGA 空洞率检测算子(两步自动检测法) +// +// 处理流程: +// 第一步 — 焊球定位: 高斯模糊 → Otsu反向二值化 → 闭运算 → 轮廓检测 → 圆度过滤 → 椭圆拟合 +// 第二步 — 气泡检测: 焊球轮廓掩码 → 双阈值分割 → 轮廓检测 → 面积过滤 → 气泡率计算 +// +// 支持多边形ROI限定检测区域,支持IPC-7095标准PASS/FAIL判定 +// 正片模式:焊球=暗区域,气泡=亮区域 +// +// 作者: 李伟 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; + +public class BgaVoidRateProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public BgaVoidRateProcessor() + { + Name = LocalizationHelper.GetString("BgaVoidRateProcessor_Name"); + Description = LocalizationHelper.GetString("BgaVoidRateProcessor_Description"); + } + + protected override void InitializeParameters() + { + // ── ROI限定区域 ── + Parameters.Add("RoiMode", new ProcessorParameter( + "RoiMode", + LocalizationHelper.GetString("BgaVoidRateProcessor_RoiMode"), + typeof(string), "None", null, null, + LocalizationHelper.GetString("BgaVoidRateProcessor_RoiMode_Desc"), + new string[] { "None", "Polygon" })); + + // 多边形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 }); + } + + // ── 第一步:BGA定位参数 ── + Parameters.Add("BgaMinArea", new ProcessorParameter( + "BgaMinArea", + LocalizationHelper.GetString("BgaVoidRateProcessor_BgaMinArea"), + typeof(int), 500, 10, 1000000, + LocalizationHelper.GetString("BgaVoidRateProcessor_BgaMinArea_Desc"))); + + Parameters.Add("BgaMaxArea", new ProcessorParameter( + "BgaMaxArea", + LocalizationHelper.GetString("BgaVoidRateProcessor_BgaMaxArea"), + typeof(int), 500000, 100, 10000000, + LocalizationHelper.GetString("BgaVoidRateProcessor_BgaMaxArea_Desc"))); + + Parameters.Add("BgaBlurSize", new ProcessorParameter( + "BgaBlurSize", + LocalizationHelper.GetString("BgaVoidRateProcessor_BgaBlurSize"), + typeof(int), 5, 1, 31, + LocalizationHelper.GetString("BgaVoidRateProcessor_BgaBlurSize_Desc"))); + + Parameters.Add("BgaCircularity", new ProcessorParameter( + "BgaCircularity", + LocalizationHelper.GetString("BgaVoidRateProcessor_BgaCircularity"), + typeof(double), 0.5, 0.0, 1.0, + LocalizationHelper.GetString("BgaVoidRateProcessor_BgaCircularity_Desc"))); + + // ── 第二步:气泡检测参数 ── + Parameters.Add("MinThreshold", new ProcessorParameter( + "MinThreshold", + LocalizationHelper.GetString("BgaVoidRateProcessor_MinThreshold"), + typeof(int), 128, 0, 255, + LocalizationHelper.GetString("BgaVoidRateProcessor_MinThreshold_Desc"))); + + Parameters.Add("MaxThreshold", new ProcessorParameter( + "MaxThreshold", + LocalizationHelper.GetString("BgaVoidRateProcessor_MaxThreshold"), + typeof(int), 255, 0, 255, + LocalizationHelper.GetString("BgaVoidRateProcessor_MaxThreshold_Desc"))); + + Parameters.Add("MinVoidArea", new ProcessorParameter( + "MinVoidArea", + LocalizationHelper.GetString("BgaVoidRateProcessor_MinVoidArea"), + typeof(int), 10, 1, 10000, + LocalizationHelper.GetString("BgaVoidRateProcessor_MinVoidArea_Desc"))); + + Parameters.Add("VoidLimit", new ProcessorParameter( + "VoidLimit", + LocalizationHelper.GetString("BgaVoidRateProcessor_VoidLimit"), + typeof(double), 25.0, 0.0, 100.0, + LocalizationHelper.GetString("BgaVoidRateProcessor_VoidLimit_Desc"))); + + Parameters.Add("Thickness", new ProcessorParameter( + "Thickness", + LocalizationHelper.GetString("BgaVoidRateProcessor_Thickness"), + typeof(int), 2, 1, 10, + LocalizationHelper.GetString("BgaVoidRateProcessor_Thickness_Desc"))); + } + + public override Image Process(Image inputImage) + { + string roiMode = GetParameter("RoiMode"); + int bgaMinArea = GetParameter("BgaMinArea"); + int bgaMaxArea = GetParameter("BgaMaxArea"); + int bgaBlurSize = GetParameter("BgaBlurSize"); + double bgaCircularity = GetParameter("BgaCircularity"); + int minThresh = GetParameter("MinThreshold"); + int maxThresh = GetParameter("MaxThreshold"); + int minVoidArea = GetParameter("MinVoidArea"); + double voidLimit = GetParameter("VoidLimit"); + int thickness = GetParameter("Thickness"); + + // 确保模糊核为奇数 + if (bgaBlurSize % 2 == 0) bgaBlurSize++; + + OutputData.Clear(); + int w = inputImage.Width, h = inputImage.Height; + + // 构建ROI掩码(限定检测区域) + Image? roiMask = null; + if (roiMode == "Polygon") + { + int polyCount = GetParameter("PolyCount"); + if (polyCount >= 3) + { + var pts = new Point[polyCount]; + for (int i = 0; i < polyCount; i++) + pts[i] = new Point(GetParameter($"PolyX{i}"), GetParameter($"PolyY{i}")); + roiMask = new Image(w, h); + using var vop = new VectorOfPoint(pts); + using var vvop = new VectorOfVectorOfPoint(vop); + CvInvoke.DrawContours(roiMask, vvop, 0, new MCvScalar(255), -1); + _logger.Debug("ROI Polygon: {Count} points", polyCount); + } + } + + OutputData["RoiMode"] = roiMode; + OutputData["RoiMask"] = roiMask; + + _logger.Debug("BgaVoidRate 两步法: BgaArea=[{Min},{Max}], Blur={Blur}, Circ={Circ}, Thresh=[{TMin},{TMax}]", + bgaMinArea, bgaMaxArea, bgaBlurSize, bgaCircularity, minThresh, maxThresh); + + // ================================================================ + // 第一步:自动检测BGA焊球位置 + // ================================================================ + var bgaResults = DetectBgaBalls(inputImage, bgaBlurSize, bgaMinArea, bgaMaxArea, bgaCircularity, roiMask); + + _logger.Information("第一步完成: 检测到 {Count} 个BGA焊球", bgaResults.Count); + + if (bgaResults.Count == 0) + { + OutputData["BgaVoidResult"] = true; + OutputData["BgaCount"] = 0; + OutputData["BgaBalls"] = bgaResults; + OutputData["VoidRate"] = 0.0; + OutputData["Classification"] = "N/A"; + OutputData["ResultText"] = "No BGA detected"; + OutputData["Thickness"] = thickness; + OutputData["VoidLimit"] = voidLimit; + OutputData["TotalBgaArea"] = 0; + OutputData["TotalVoidArea"] = 0; + OutputData["TotalVoidCount"] = 0; + roiMask?.Dispose(); + return inputImage.Clone(); + } + + // ================================================================ + // 第二步:在每个焊球区域内检测气泡 + // ================================================================ + int totalBgaArea = 0; + int totalVoidArea = 0; + int totalVoidCount = 0; + + foreach (var bga in bgaResults) + { + DetectVoidsInBga(inputImage, bga, minThresh, maxThresh, minVoidArea); + totalBgaArea += bga.BgaArea; + totalVoidArea += bga.VoidPixels; + totalVoidCount += bga.Voids.Count; + } + + double overallVoidRate = totalBgaArea > 0 ? (double)totalVoidArea / totalBgaArea * 100.0 : 0; + string classification = overallVoidRate <= voidLimit ? "PASS" : "FAIL"; + + // 检查每个焊球是否单独超标 + foreach (var bga in bgaResults) + { + bga.Classification = bga.VoidRate <= voidLimit ? "PASS" : "FAIL"; + } + + _logger.Information("第二步完成: 总气泡率={VoidRate:F1}%, 气泡数={Count}, 判定={Class}", + overallVoidRate, totalVoidCount, classification); + + // 输出数据 + OutputData["BgaVoidResult"] = true; + OutputData["BgaCount"] = bgaResults.Count; + OutputData["BgaBalls"] = bgaResults; + OutputData["VoidRate"] = overallVoidRate; + OutputData["FillRate"] = 100.0 - overallVoidRate; + OutputData["TotalBgaArea"] = totalBgaArea; + OutputData["TotalVoidArea"] = totalVoidArea; + OutputData["TotalVoidCount"] = totalVoidCount; + OutputData["VoidLimit"] = voidLimit; + OutputData["Classification"] = classification; + OutputData["Thickness"] = thickness; + OutputData["ResultText"] = $"Void: {overallVoidRate:F1}% | {classification} | BGA×{bgaResults.Count}"; + + roiMask?.Dispose(); + return inputImage.Clone(); + } + + /// + /// 第一步:自动检测BGA焊球位置 + /// 使用Otsu二值化 + 轮廓检测 + 圆度过滤 + 椭圆拟合 + /// + private List DetectBgaBalls(Image input, int blurSize, int minArea, int maxArea, double minCircularity, Image? roiMask) + { + var results = new List(); + int w = input.Width, h = input.Height; + + // 高斯模糊降噪 + var blurred = new Image(w, h); + CvInvoke.GaussianBlur(input, blurred, new Size(blurSize, blurSize), 0); + + // Otsu自动二值化(X-Ray正片:焊球=暗区域) + var binary = new Image(w, h); + CvInvoke.Threshold(blurred, binary, 0, 255, ThresholdType.Otsu | ThresholdType.BinaryInv); + + // 如果有ROI掩码,只保留ROI区域内的二值化结果 + if (roiMask != null) + { + CvInvoke.BitwiseAnd(binary, roiMask, binary); + } + + // 形态学闭运算填充小孔洞 + var kernel = CvInvoke.GetStructuringElement(ElementShape.Ellipse, new Size(5, 5), new Point(-1, -1)); + CvInvoke.MorphologyEx(binary, binary, MorphOp.Close, kernel, new Point(-1, -1), 2, BorderType.Default, new MCvScalar(0)); + + // 查找轮廓 + using var contours = new VectorOfVectorOfPoint(); + using var hierarchy = new Mat(); + CvInvoke.FindContours(binary, contours, hierarchy, RetrType.External, ChainApproxMethod.ChainApproxSimple); + + int bgaIndex = 0; + for (int i = 0; i < contours.Size; i++) + { + double area = CvInvoke.ContourArea(contours[i]); + if (area < minArea || area > maxArea) continue; + + // 圆度过滤: circularity = 4π × area / perimeter² + double perimeter = CvInvoke.ArcLength(contours[i], true); + if (perimeter < 1) continue; + double circularity = 4.0 * Math.PI * area / (perimeter * perimeter); + if (circularity < minCircularity) continue; + + // 需要至少5个点才能拟合椭圆 + if (contours[i].Size < 5) continue; + + var ellipse = CvInvoke.FitEllipse(contours[i]); + var moments = CvInvoke.Moments(contours[i]); + if (moments.M00 < 1) continue; + + bgaIndex++; + results.Add(new BgaBallInfo + { + Index = bgaIndex, + CenterX = moments.M10 / moments.M00, + CenterY = moments.M01 / moments.M00, + FittedEllipse = ellipse, + ContourPoints = contours[i].ToArray(), + BgaArea = (int)area, + Circularity = circularity + }); + } + + // 按面积从大到小排序 + results.Sort((a, b) => b.BgaArea.CompareTo(a.BgaArea)); + for (int i = 0; i < results.Count; i++) results[i].Index = i + 1; + + blurred.Dispose(); + binary.Dispose(); + kernel.Dispose(); + + return results; + } + + /// + /// 第二步:在单个BGA焊球区域内检测气泡 + /// 使用焊球轮廓作为掩码,双阈值分割气泡区域 + /// + private void DetectVoidsInBga(Image input, BgaBallInfo bga, int minThresh, int maxThresh, int minVoidArea) + { + int w = input.Width, h = input.Height; + + // 创建该焊球的掩码 + var mask = new Image(w, h); + using (var vop = new VectorOfPoint(bga.ContourPoints)) + using (var vvop = new VectorOfVectorOfPoint(vop)) + { + CvInvoke.DrawContours(mask, vvop, 0, new MCvScalar(255), -1); + } + + int bgaPixels = CvInvoke.CountNonZero(mask); + bga.BgaArea = bgaPixels; + + // 双阈值分割(正片模式:气泡=亮,灰度在[minThresh, maxThresh]范围内判为气泡) + var voidImg = new Image(w, h); + byte[,,] srcData = input.Data; + byte[,,] dstData = voidImg.Data; + byte[,,] maskData = mask.Data; + + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + if (maskData[y, x, 0] > 0) + { + byte val = srcData[y, x, 0]; + dstData[y, x, 0] = (val >= minThresh && val <= maxThresh) ? (byte)255 : (byte)0; + } + } + } + + int voidPixels = CvInvoke.CountNonZero(voidImg); + bga.VoidPixels = voidPixels; + bga.VoidRate = bgaPixels > 0 ? (double)voidPixels / bgaPixels * 100.0 : 0; + + // 检测每个气泡的轮廓 + using var contours = new VectorOfVectorOfPoint(); + using var hierarchy = new Mat(); + CvInvoke.FindContours(voidImg, contours, hierarchy, RetrType.External, ChainApproxMethod.ChainApproxSimple); + + for (int i = 0; i < contours.Size; i++) + { + double area = CvInvoke.ContourArea(contours[i]); + if (area < minVoidArea) continue; + + var moments = CvInvoke.Moments(contours[i]); + if (moments.M00 < 1) continue; + + bga.Voids.Add(new VoidInfo + { + Index = bga.Voids.Count + 1, + CenterX = moments.M10 / moments.M00, + CenterY = moments.M01 / moments.M00, + Area = area, + AreaPercent = bgaPixels > 0 ? area / bgaPixels * 100.0 : 0, + BoundingBox = CvInvoke.BoundingRectangle(contours[i]), + ContourPoints = contours[i].ToArray() + }); + } + + // 按面积从大到小排序 + bga.Voids.Sort((a, b) => b.Area.CompareTo(a.Area)); + for (int i = 0; i < bga.Voids.Count; i++) bga.Voids[i].Index = i + 1; + + mask.Dispose(); + voidImg.Dispose(); + } +} + +/// +/// 单个BGA焊球信息 +/// +public class BgaBallInfo +{ + public int Index { get; set; } + public double CenterX { get; set; } + public double CenterY { get; set; } + public RotatedRect FittedEllipse { get; set; } + public Point[] ContourPoints { get; set; } = Array.Empty(); + public int BgaArea { get; set; } + public double Circularity { get; set; } + public int VoidPixels { get; set; } + public double VoidRate { get; set; } + public string Classification { get; set; } = "N/A"; + public List Voids { get; set; } = new(); +} + +/// +/// 单个气泡信息 +/// +public class VoidInfo +{ + public int Index { get; set; } + public double CenterX { get; set; } + public double CenterY { get; set; } + public double Area { get; set; } + public double AreaPercent { get; set; } + public Rectangle BoundingBox { get; set; } + public Point[] ContourPoints { get; set; } = Array.Empty(); +} diff --git a/ImageProcessing.Processors/检测分析/ContourDetectionProcessor.cs b/ImageProcessing.Processors/检测分析/ContourDetectionProcessor.cs new file mode 100644 index 0000000..1d3ddcf --- /dev/null +++ b/ImageProcessing.Processors/检测分析/ContourDetectionProcessor.cs @@ -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; + +/// +/// 轮廓查找算子 +/// +public class ContourProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + 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 Process(Image inputImage) + { + string targetColor = GetParameter("TargetColor"); + bool useThreshold = GetParameter("UseThreshold"); + int thresholdValue = GetParameter("ThresholdValue"); + bool useOtsu = GetParameter("UseOtsu"); + double minArea = GetParameter("MinArea"); + double maxArea = GetParameter("MaxArea"); + int thickness = GetParameter("Thickness"); + + _logger.Debug("Process started: TargetColor = '{TargetColor}', UseThreshold = {UseThreshold}, ThresholdValue = {ThresholdValue}, UseOtsu = {UseOtsu}", + targetColor, useThreshold, thresholdValue, useOtsu); + + OutputData.Clear(); + + // 创建输入图像的副本用于处理 + Image processImage = inputImage.Clone(); + + // 步骤1:如果启用阈值分割,先进行二值化 + if (useThreshold) + { + _logger.Debug("Applying threshold processing"); + Image thresholdImage = new Image(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 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(); + } + } +} + +/// +/// 轮廓信息 +/// +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(); + public PointF CircleCenter { get; set; } + public float CircleRadius { get; set; } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/检测分析/EllipseDetectionProcessor.cs b/ImageProcessing.Processors/检测分析/EllipseDetectionProcessor.cs new file mode 100644 index 0000000..012ed26 --- /dev/null +++ b/ImageProcessing.Processors/检测分析/EllipseDetectionProcessor.cs @@ -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; + +/// +/// 椭圆检测结果 +/// +public class EllipseInfo +{ + /// 序号 + public int Index { get; set; } + /// 中心点X + public float CenterX { get; set; } + /// 中心点Y + public float CenterY { get; set; } + /// 长轴长度 + public float MajorAxis { get; set; } + /// 短轴长度 + public float MinorAxis { get; set; } + /// 旋转角度(度) + public float Angle { get; set; } + /// 面积 + public double Area { get; set; } + /// 周长 + public double Perimeter { get; set; } + /// 离心率 (0=圆, 接近1=扁椭圆) + public double Eccentricity { get; set; } + /// 拟合误差(像素) + public double FitError { get; set; } + /// 轮廓点集 + public Point[] ContourPoints { get; set; } = Array.Empty(); + /// 外接矩形 + public Rectangle BoundingBox { get; set; } +} + +/// +/// 椭圆检测器 +/// +public class EllipseDetector +{ + private static readonly ILogger _logger = Log.ForContext(); + + 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; + + /// 执行椭圆检测 + public List Detect(Image inputImage, Image? roiMask = null) + { + _logger.Debug("Ellipse detection started: UseOtsu={UseOtsu}, MinThreshold={Min}, MaxThreshold={Max}", + UseOtsu, MinThreshold, MaxThreshold); + var results = new List(); + + using var binary = new Image(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; + } +} + +/// +/// 椭圆检测算子 +/// +public class EllipseDetectionProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + 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 Process(Image inputImage) + { + int thickness = GetParameter("Thickness"); + + _logger.Debug("Ellipse detection started"); + OutputData.Clear(); + + // 构建多边形ROI掩码 + int polyCount = GetParameter("PolyCount"); + Image? roiMask = null; + if (polyCount >= 3) + { + var pts = new Point[polyCount]; + for (int i = 0; i < polyCount; i++) + pts[i] = new Point(GetParameter($"PolyX{i}"), GetParameter($"PolyY{i}")); + roiMask = new Image(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("MinThreshold"), + MaxThreshold = GetParameter("MaxThreshold"), + UseOtsu = GetParameter("UseOtsu"), + MinContourPoints = GetParameter("MinContourPoints"), + MinArea = GetParameter("MinArea"), + MaxArea = GetParameter("MaxArea"), + MaxEccentricity = GetParameter("MaxEccentricity"), + MaxFitError = GetParameter("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(); + } +} diff --git a/ImageProcessing.Processors/检测分析/FillRateProcessor.cs b/ImageProcessing.Processors/检测分析/FillRateProcessor.cs new file mode 100644 index 0000000..82deeb2 --- /dev/null +++ b/ImageProcessing.Processors/检测分析/FillRateProcessor.cs @@ -0,0 +1,133 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: FillRateProcessor.cs +// 描述: 通孔填锡率测量算子(倾斜投影几何法),基于四椭圆ROI +// 功能: +// - 样品倾斜约45°放置,利用投影位移关系计算填锡率 +// - 四个椭圆定义: +// E1 = 通孔底部轮廓 +// E2 = 通孔顶部轮廓 +// E3 = 填锡起点(与E1重合,代表0%填锡) +// E4 = 填锡终点(锡实际填充到的高度) +// - 填锡率 = |E4中心 - E3中心| / |E2中心 - E1中心| × 100% +// - 纯几何方法,不依赖灰度分析 +// - IPC-610 THT 分级判定(Class 1/2/3) +// 算法: 倾斜投影位移比例 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; +using System.Drawing; + +namespace ImageProcessing.Processors; + +/// +/// 通孔填锡率测量算子(倾斜投影几何法) +/// +public class FillRateProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public FillRateProcessor() + { + Name = LocalizationHelper.GetString("FillRateProcessor_Name"); + Description = LocalizationHelper.GetString("FillRateProcessor_Description"); + } + + protected override void InitializeParameters() + { + // 四个椭圆(由交互控件注入,UI不可见) + AddEllipseParams("E1", 200, 250, 60, 50, 0); // 底部 + AddEllipseParams("E2", 220, 180, 60, 50, 0); // 顶部 + AddEllipseParams("E3", 200, 250, 60, 50, 0); // 填锡起点(=E1) + AddEllipseParams("E4", 210, 220, 55, 45, 0); // 填锡终点 + + Parameters.Add("THTLimit", new ProcessorParameter( + "THTLimit", + LocalizationHelper.GetString("FillRateProcessor_THTLimit"), + typeof(double), 75.0, 0.0, 100.0, + LocalizationHelper.GetString("FillRateProcessor_THTLimit_Desc"))); + + Parameters.Add("Thickness", new ProcessorParameter( + "Thickness", + LocalizationHelper.GetString("FillRateProcessor_Thickness"), + typeof(int), 2, 1, 10, + LocalizationHelper.GetString("FillRateProcessor_Thickness_Desc"))); + } + + private void AddEllipseParams(string prefix, int cx, int cy, double a, double b, double angle) + { + Parameters.Add($"{prefix}_CX", new ProcessorParameter($"{prefix}_CX", $"{prefix}_CX", typeof(int), cx, null, null, "") { IsVisible = false }); + Parameters.Add($"{prefix}_CY", new ProcessorParameter($"{prefix}_CY", $"{prefix}_CY", typeof(int), cy, null, null, "") { IsVisible = false }); + Parameters.Add($"{prefix}_A", new ProcessorParameter($"{prefix}_A", $"{prefix}_A", typeof(double), a, null, null, "") { IsVisible = false }); + Parameters.Add($"{prefix}_B", new ProcessorParameter($"{prefix}_B", $"{prefix}_B", typeof(double), b, null, null, "") { IsVisible = false }); + Parameters.Add($"{prefix}_Angle", new ProcessorParameter($"{prefix}_Angle", $"{prefix}_Angle", typeof(double), angle, null, null, "") { IsVisible = false }); + } + + public override Image Process(Image inputImage) + { + double thtLimit = GetParameter("THTLimit"); + int thickness = GetParameter("Thickness"); + + // 获取四个椭圆中心 + int e1cx = GetParameter("E1_CX"), e1cy = GetParameter("E1_CY"); + int e2cx = GetParameter("E2_CX"), e2cy = GetParameter("E2_CY"); + int e3cx = GetParameter("E3_CX"), e3cy = GetParameter("E3_CY"); + int e4cx = GetParameter("E4_CX"), e4cy = GetParameter("E4_CY"); + + // 获取椭圆轴参数(用于绘制) + double e1a = GetParameter("E1_A"), e1b = GetParameter("E1_B"), e1ang = GetParameter("E1_Angle"); + double e2a = GetParameter("E2_A"), e2b = GetParameter("E2_B"), e2ang = GetParameter("E2_Angle"); + double e3a = GetParameter("E3_A"), e3b = GetParameter("E3_B"), e3ang = GetParameter("E3_Angle"); + double e4a = GetParameter("E4_A"), e4b = GetParameter("E4_B"), e4ang = GetParameter("E4_Angle"); + + _logger.Debug("FillRate: E1=({E1X},{E1Y}), E2=({E2X},{E2Y}), E3=({E3X},{E3Y}), E4=({E4X},{E4Y})", + e1cx, e1cy, e2cx, e2cy, e3cx, e3cy, e4cx, e4cy); + + OutputData.Clear(); + + // 计算通孔全高度的投影位移(E1底部 → E2顶部) + double fullDx = e2cx - e1cx; + double fullDy = e2cy - e1cy; + double fullDistance = Math.Sqrt(fullDx * fullDx + fullDy * fullDy); + + // 计算填锡高度的投影位移(E3起点 → E4终点) + double fillDx = e4cx - e3cx; + double fillDy = e4cy - e3cy; + double fillDistance = Math.Sqrt(fillDx * fillDx + fillDy * fillDy); + + // 填锡率 = 填锡位移 / 全高度位移 + double fillRate = fullDistance > 0 ? (fillDistance / fullDistance) * 100.0 : 0; + fillRate = Math.Clamp(fillRate, 0, 100); + + // 判定 + string classification = fillRate >= thtLimit ? "PASS" : "FAIL"; + + // 存储结果 + OutputData["FillRateResult"] = true; + OutputData["FillRate"] = fillRate; + OutputData["VoidRate"] = 100.0 - fillRate; + OutputData["FullDistance"] = fullDistance; + OutputData["FillDistance"] = fillDistance; + OutputData["THTLimit"] = thtLimit; + OutputData["Classification"] = classification; + OutputData["Thickness"] = thickness; + + // 椭圆几何(用于绘制) + OutputData["E1"] = (new Point(e1cx, e1cy), new Size((int)e1a, (int)e1b), e1ang); + OutputData["E2"] = (new Point(e2cx, e2cy), new Size((int)e2a, (int)e2b), e2ang); + OutputData["E3"] = (new Point(e3cx, e3cy), new Size((int)e3a, (int)e3b), e3ang); + OutputData["E4"] = (new Point(e4cx, e4cy), new Size((int)e4a, (int)e4b), e4ang); + + string resultText = $"{fillRate:F1}% | {classification}"; + OutputData["ResultText"] = resultText; + + _logger.Information("FillRate (geometric): {Rate}%, {Class}, FullDist={FD:F1}, FillDist={FiD:F1}", + fillRate, classification, fullDistance, fillDistance); + + return inputImage.Clone(); + } +} diff --git a/ImageProcessing.Processors/检测分析/LineMeasurementProcessor.cs b/ImageProcessing.Processors/检测分析/LineMeasurementProcessor.cs new file mode 100644 index 0000000..e681620 --- /dev/null +++ b/ImageProcessing.Processors/检测分析/LineMeasurementProcessor.cs @@ -0,0 +1,150 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: LineMeasurementProcessor.cs +// 描述: 直线测量算子,用于测量图像中两点之间的距离 +// 功能: +// - 用户指定两个点坐标(像素坐标) +// - 计算两点之间的欧氏距离(像素单位) +// - 支持像素尺寸标定,输出实际物理距离 +// - 在图像上绘制测量线和标注 +// - 输出测量结果供后续处理使用 +// 算法: 欧氏距离计算 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; +using System.Drawing; + +namespace ImageProcessing.Processors; + +/// +/// 直线测量算子 - 测量两点之间的距离 +/// +public class LineMeasurementProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public LineMeasurementProcessor() + { + Name = LocalizationHelper.GetString("LineMeasurementProcessor_Name"); + Description = LocalizationHelper.GetString("LineMeasurementProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("X1", new ProcessorParameter( + "X1", + LocalizationHelper.GetString("LineMeasurementProcessor_X1"), + typeof(int), 100, null, null, + LocalizationHelper.GetString("LineMeasurementProcessor_X1_Desc")) + { IsVisible = false }); + + Parameters.Add("Y1", new ProcessorParameter( + "Y1", + LocalizationHelper.GetString("LineMeasurementProcessor_Y1"), + typeof(int), 100, null, null, + LocalizationHelper.GetString("LineMeasurementProcessor_Y1_Desc")) + { IsVisible = false }); + + Parameters.Add("X2", new ProcessorParameter( + "X2", + LocalizationHelper.GetString("LineMeasurementProcessor_X2"), + typeof(int), 400, null, null, + LocalizationHelper.GetString("LineMeasurementProcessor_X2_Desc")) + { IsVisible = false }); + + Parameters.Add("Y2", new ProcessorParameter( + "Y2", + LocalizationHelper.GetString("LineMeasurementProcessor_Y2"), + typeof(int), 400, null, null, + LocalizationHelper.GetString("LineMeasurementProcessor_Y2_Desc")) + { IsVisible = false }); + + Parameters.Add("PixelSize", new ProcessorParameter( + "PixelSize", + LocalizationHelper.GetString("LineMeasurementProcessor_PixelSize"), + typeof(double), 1.0, null, null, + LocalizationHelper.GetString("LineMeasurementProcessor_PixelSize_Desc"))); + + Parameters.Add("Unit", new ProcessorParameter( + "Unit", + LocalizationHelper.GetString("LineMeasurementProcessor_Unit"), + typeof(string), "px", null, null, + LocalizationHelper.GetString("LineMeasurementProcessor_Unit_Desc"), + new string[] { "px", "mm", "μm", "cm" })); + + Parameters.Add("Thickness", new ProcessorParameter( + "Thickness", + LocalizationHelper.GetString("LineMeasurementProcessor_Thickness"), + typeof(int), 2, 1, 10, + LocalizationHelper.GetString("LineMeasurementProcessor_Thickness_Desc"))); + + Parameters.Add("ShowLabel", new ProcessorParameter( + "ShowLabel", + LocalizationHelper.GetString("LineMeasurementProcessor_ShowLabel"), + typeof(bool), true, null, null, + LocalizationHelper.GetString("LineMeasurementProcessor_ShowLabel_Desc"))); + } + + public override Image Process(Image inputImage) + { + int x1 = GetParameter("X1"); + int y1 = GetParameter("Y1"); + int x2 = GetParameter("X2"); + int y2 = GetParameter("Y2"); + double pixelSize = GetParameter("PixelSize"); + string unit = GetParameter("Unit"); + int thickness = GetParameter("Thickness"); + bool showLabel = GetParameter("ShowLabel"); + + _logger.Debug("LineMeasurement: ({X1},{Y1}) -> ({X2},{Y2}), PixelSize={PixelSize}, Unit={Unit}", + x1, y1, x2, y2, pixelSize, unit); + + OutputData.Clear(); + + // 限制坐标在图像范围内 + x1 = Math.Clamp(x1, 0, inputImage.Width - 1); + y1 = Math.Clamp(y1, 0, inputImage.Height - 1); + x2 = Math.Clamp(x2, 0, inputImage.Width - 1); + y2 = Math.Clamp(y2, 0, inputImage.Height - 1); + + // 计算像素距离 + double dx = x2 - x1; + double dy = y2 - y1; + double pixelDistance = Math.Sqrt(dx * dx + dy * dy); + + // 计算实际距离 + double actualDistance = pixelDistance * pixelSize; + + // 计算角度(相对于水平方向) + double angleRad = Math.Atan2(dy, dx); + double angleDeg = angleRad * 180.0 / Math.PI; + + // 存储测量结果 + OutputData["MeasurementType"] = "Line"; + OutputData["Point1"] = new Point(x1, y1); + OutputData["Point2"] = new Point(x2, y2); + OutputData["PixelDistance"] = pixelDistance; + OutputData["ActualDistance"] = actualDistance; + OutputData["Unit"] = unit; + OutputData["Angle"] = angleDeg; + OutputData["Thickness"] = thickness; + OutputData["ShowLabel"] = showLabel; + + // 构建测量信息文本 + string distanceText = unit == "px" + ? $"{pixelDistance:F2} px" + : $"{actualDistance:F4} {unit} ({pixelDistance:F2} px)"; + + OutputData["MeasurementText"] = distanceText; + + _logger.Information("LineMeasurement completed: Distance={Distance}, Angle={Angle:F2}°", + distanceText, angleDeg); + + return inputImage.Clone(); + } +} diff --git a/ImageProcessing.Processors/检测分析/PointToLineProcessor.cs b/ImageProcessing.Processors/检测分析/PointToLineProcessor.cs new file mode 100644 index 0000000..6dbd43a --- /dev/null +++ b/ImageProcessing.Processors/检测分析/PointToLineProcessor.cs @@ -0,0 +1,116 @@ +// ============================================================================ +// 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 ImageProcessing.Core; +using Serilog; +using System.Drawing; + +namespace 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(); + } +} diff --git a/ImageProcessing.Processors/检测分析/VoidMeasurementProcessor.cs b/ImageProcessing.Processors/检测分析/VoidMeasurementProcessor.cs new file mode 100644 index 0000000..09c3aa2 --- /dev/null +++ b/ImageProcessing.Processors/检测分析/VoidMeasurementProcessor.cs @@ -0,0 +1,230 @@ +// ============================================================================ +// 文件名: VoidMeasurementProcessor.cs +// 描述: 空隙测量算子 +// +// 处理流程: +// 1. 构建多边形ROI掩码,计算ROI面积 +// 2. 在ROI内进行双阈值分割提取气泡区域 +// 3. 形态学膨胀合并相邻气泡 +// 4. 轮廓检测,计算每个气泡面积 +// 5. 计算空隙率 = 总气泡面积 / ROI面积 +// ============================================================================ + +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; + +public class VoidMeasurementProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public VoidMeasurementProcessor() + { + Name = LocalizationHelper.GetString("VoidMeasurementProcessor_Name"); + Description = LocalizationHelper.GetString("VoidMeasurementProcessor_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("VoidMeasurementProcessor_MinThreshold"), + typeof(int), 128, 0, 255, + LocalizationHelper.GetString("VoidMeasurementProcessor_MinThreshold_Desc"))); + + Parameters.Add("MaxThreshold", new ProcessorParameter( + "MaxThreshold", + LocalizationHelper.GetString("VoidMeasurementProcessor_MaxThreshold"), + typeof(int), 255, 0, 255, + LocalizationHelper.GetString("VoidMeasurementProcessor_MaxThreshold_Desc"))); + + Parameters.Add("MinVoidArea", new ProcessorParameter( + "MinVoidArea", + LocalizationHelper.GetString("VoidMeasurementProcessor_MinVoidArea"), + typeof(int), 10, 1, 100000, + LocalizationHelper.GetString("VoidMeasurementProcessor_MinVoidArea_Desc"))); + + Parameters.Add("MergeRadius", new ProcessorParameter( + "MergeRadius", + LocalizationHelper.GetString("VoidMeasurementProcessor_MergeRadius"), + typeof(int), 3, 0, 30, + LocalizationHelper.GetString("VoidMeasurementProcessor_MergeRadius_Desc"))); + + Parameters.Add("BlurSize", new ProcessorParameter( + "BlurSize", + LocalizationHelper.GetString("VoidMeasurementProcessor_BlurSize"), + typeof(int), 3, 1, 31, + LocalizationHelper.GetString("VoidMeasurementProcessor_BlurSize_Desc"))); + + Parameters.Add("VoidLimit", new ProcessorParameter( + "VoidLimit", + LocalizationHelper.GetString("VoidMeasurementProcessor_VoidLimit"), + typeof(double), 25.0, 0.0, 100.0, + LocalizationHelper.GetString("VoidMeasurementProcessor_VoidLimit_Desc"))); + } + + public override Image Process(Image inputImage) + { + int minThresh = GetParameter("MinThreshold"); + int maxThresh = GetParameter("MaxThreshold"); + int minVoidArea = GetParameter("MinVoidArea"); + int mergeRadius = GetParameter("MergeRadius"); + int blurSize = GetParameter("BlurSize"); + double voidLimit = GetParameter("VoidLimit"); + + if (blurSize % 2 == 0) blurSize++; + + OutputData.Clear(); + int w = inputImage.Width, h = inputImage.Height; + + // ── 构建多边形ROI掩码 ── + int polyCount = GetParameter("PolyCount"); + Image? roiMask = null; + Point[]? roiPoints = null; + + if (polyCount >= 3) + { + roiPoints = new Point[polyCount]; + for (int i = 0; i < polyCount; i++) + roiPoints[i] = new Point(GetParameter($"PolyX{i}"), GetParameter($"PolyY{i}")); + roiMask = new Image(w, h); + using var vop = new VectorOfPoint(roiPoints); + using var vvop = new VectorOfVectorOfPoint(vop); + CvInvoke.DrawContours(roiMask, vvop, 0, new MCvScalar(255), -1); + } + else + { + // 无ROI时使用全图 + roiMask = new Image(w, h); + roiMask.SetValue(new Gray(255)); + } + + int roiArea = CvInvoke.CountNonZero(roiMask); + + _logger.Debug("VoidMeasurement: ROI area={Area}, Thresh=[{Min},{Max}], MergeR={MR}", + roiArea, minThresh, maxThresh, mergeRadius); + + // ── 高斯模糊降噪 ── + var blurred = new Image(w, h); + CvInvoke.GaussianBlur(inputImage, blurred, new Size(blurSize, blurSize), 0); + + // ── 双阈值分割提取气泡(亮区域) ── + var voidImg = new Image(w, h); + byte[,,] srcData = blurred.Data; + byte[,,] dstData = voidImg.Data; + byte[,,] maskData = roiMask.Data; + + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + if (maskData[y, x, 0] > 0) + { + byte val = srcData[y, x, 0]; + dstData[y, x, 0] = (val >= minThresh && val <= maxThresh) ? (byte)255 : (byte)0; + } + } + } + + // ── 形态学膨胀合并相邻气泡 ── + if (mergeRadius > 0) + { + int kernelSize = mergeRadius * 2 + 1; + using var kernel = CvInvoke.GetStructuringElement(ElementShape.Ellipse, + new Size(kernelSize, kernelSize), new Point(-1, -1)); + CvInvoke.Dilate(voidImg, voidImg, kernel, new Point(-1, -1), 1, BorderType.Default, new MCvScalar(0)); + // 与ROI掩码取交集,防止膨胀超出ROI + CvInvoke.BitwiseAnd(voidImg, roiMask, voidImg); + } + + // ── 轮廓检测 ── + using var contours = new VectorOfVectorOfPoint(); + using var hierarchy = new Mat(); + CvInvoke.FindContours(voidImg, contours, hierarchy, RetrType.External, ChainApproxMethod.ChainApproxSimple); + + var voids = new List(); + int totalVoidArea = 0; + + for (int i = 0; i < contours.Size; i++) + { + double area = CvInvoke.ContourArea(contours[i]); + if (area < minVoidArea) continue; + + var moments = CvInvoke.Moments(contours[i]); + if (moments.M00 < 1) continue; + + int intArea = (int)Math.Round(area); + totalVoidArea += intArea; + + voids.Add(new VoidRegionInfo + { + Index = voids.Count + 1, + CenterX = moments.M10 / moments.M00, + CenterY = moments.M01 / moments.M00, + Area = intArea, + AreaPercent = roiArea > 0 ? area / roiArea * 100.0 : 0, + BoundingBox = CvInvoke.BoundingRectangle(contours[i]), + ContourPoints = contours[i].ToArray() + }); + } + + // 按面积从大到小排序 + voids.Sort((a, b) => b.Area.CompareTo(a.Area)); + for (int i = 0; i < voids.Count; i++) voids[i].Index = i + 1; + + double voidRate = roiArea > 0 ? (double)totalVoidArea / roiArea * 100.0 : 0; + string classification = voidRate <= voidLimit ? "PASS" : "FAIL"; + int maxVoidArea = voids.Count > 0 ? voids[0].Area : 0; + + _logger.Information("VoidMeasurement: VoidRate={Rate:F1}%, Voids={Count}, MaxArea={Max}, {Class}", + voidRate, voids.Count, maxVoidArea, classification); + + // ── 输出数据 ── + OutputData["VoidMeasurementResult"] = true; + OutputData["RoiArea"] = roiArea; + OutputData["RoiPoints"] = roiPoints; + OutputData["TotalVoidArea"] = totalVoidArea; + OutputData["VoidRate"] = voidRate; + OutputData["VoidLimit"] = voidLimit; + OutputData["VoidCount"] = voids.Count; + OutputData["MaxVoidArea"] = maxVoidArea; + OutputData["Classification"] = classification; + OutputData["Voids"] = voids; + OutputData["ResultText"] = $"Void: {voidRate:F1}% | {classification} | {voids.Count} voids | ROI: {roiArea}px"; + + blurred.Dispose(); + voidImg.Dispose(); + roiMask.Dispose(); + + return inputImage.Clone(); + } +} + +/// +/// 单个空隙区域信息 +/// +public class VoidRegionInfo +{ + public int Index { get; set; } + public double CenterX { get; set; } + public double CenterY { get; set; } + public int Area { get; set; } + public double AreaPercent { get; set; } + public Rectangle BoundingBox { get; set; } + public Point[] ContourPoints { get; set; } = Array.Empty(); +} diff --git a/ImageProcessing.Processors/滤波处理/BandPassFilterProcessor.cs b/ImageProcessing.Processors/滤波处理/BandPassFilterProcessor.cs new file mode 100644 index 0000000..d902cb9 --- /dev/null +++ b/ImageProcessing.Processors/滤波处理/BandPassFilterProcessor.cs @@ -0,0 +1,197 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: BandPassFilterProcessor.cs +// 描述: 带通滤波器算子,用于频域图像处理 +// 功能: +// - 在频域中保留特定频率范围的信号 +// - 支持理想、巴特沃斯、高斯三种滤波器类型 +// - 可调节低频和高频截止频率 +// - 通过FFT实现频域滤波 +// 算法: 基于离散傅里叶变换(DFT)的频域滤波 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; +using System.Drawing; + +namespace ImageProcessing.Processors; + +/// +/// 带通滤波器算子 +/// +public class BandPassFilterProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public BandPassFilterProcessor() + { + Name = LocalizationHelper.GetString("BandPassFilterProcessor_Name"); + Description = LocalizationHelper.GetString("BandPassFilterProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("LowCutoff", new ProcessorParameter( + "LowCutoff", + LocalizationHelper.GetString("BandPassFilterProcessor_LowCutoff"), + typeof(int), + 10, + 1, + 200, + LocalizationHelper.GetString("BandPassFilterProcessor_LowCutoff_Desc"))); + + Parameters.Add("HighCutoff", new ProcessorParameter( + "HighCutoff", + LocalizationHelper.GetString("BandPassFilterProcessor_HighCutoff"), + typeof(int), + 50, + 2, + 500, + LocalizationHelper.GetString("BandPassFilterProcessor_HighCutoff_Desc"))); + + Parameters.Add("FilterType", new ProcessorParameter( + "FilterType", + LocalizationHelper.GetString("BandPassFilterProcessor_FilterType"), + typeof(string), + "Ideal", + null, + null, + LocalizationHelper.GetString("BandPassFilterProcessor_FilterType_Desc"), + new string[] { "Ideal", "Butterworth", "Gaussian" })); + + Parameters.Add("Order", new ProcessorParameter( + "Order", + LocalizationHelper.GetString("BandPassFilterProcessor_Order"), + typeof(int), + 2, + 1, + 10, + LocalizationHelper.GetString("BandPassFilterProcessor_Order_Desc"))); + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + int lowCutoff = GetParameter("LowCutoff"); + int highCutoff = GetParameter("HighCutoff"); + string filterType = GetParameter("FilterType"); + int order = GetParameter("Order"); + + if (highCutoff <= lowCutoff) + { + highCutoff = lowCutoff + 10; + } + + var floatImage = inputImage.Convert(); + var imaginaryImage = new Image(floatImage.Size); + imaginaryImage.SetZero(); + + using (var planes = new Emgu.CV.Util.VectorOfMat()) + { + planes.Push(floatImage.Mat); + planes.Push(imaginaryImage.Mat); + + Mat complexMat = new Mat(); + CvInvoke.Merge(planes, complexMat); + + Mat dftMat = new Mat(); + CvInvoke.Dft(complexMat, dftMat, DxtType.Forward, 0); + + var mask = CreateBandPassMask(floatImage.Size, lowCutoff, highCutoff, filterType, order); + + using (var dftPlanes = new Emgu.CV.Util.VectorOfMat()) + { + CvInvoke.Split(dftMat, dftPlanes); + + Mat real = dftPlanes[0]; + Mat imag = dftPlanes[1]; + + CvInvoke.Multiply(real, mask.Mat, real); + CvInvoke.Multiply(imag, mask.Mat, imag); + + using (var filteredPlanes = new Emgu.CV.Util.VectorOfMat()) + { + filteredPlanes.Push(real); + filteredPlanes.Push(imag); + + Mat filteredDft = new Mat(); + CvInvoke.Merge(filteredPlanes, filteredDft); + + Mat idftMat = new Mat(); + CvInvoke.Dft(filteredDft, idftMat, DxtType.Inverse | DxtType.Scale, 0); + + using (var idftPlanes = new Emgu.CV.Util.VectorOfMat()) + { + CvInvoke.Split(idftMat, idftPlanes); + + var result = new Image(floatImage.Size); + idftPlanes[0].CopyTo(result); + + double minVal = 0, maxVal = 0; + Point minLoc = new Point(); + Point maxLoc = new Point(); + CvInvoke.MinMaxLoc(result, ref minVal, ref maxVal, ref minLoc, ref maxLoc); + + if (maxVal > minVal) + { + result = (result - minVal) * (255.0 / (maxVal - minVal)); + } + + complexMat.Dispose(); + dftMat.Dispose(); + filteredDft.Dispose(); + idftMat.Dispose(); + _logger.Debug("Process: LowCutoff = {0}, HighCutoff = {1}, FilterType = {2}, Order = {3}", lowCutoff, highCutoff, filterType, order); + return result.Convert(); + } + } + } + } + } + + private Image CreateBandPassMask(Size size, int lowCutoff, int highCutoff, string filterType, int order) + { + var mask = new Image(size); + int cx = size.Width / 2; + int cy = size.Height / 2; + + for (int y = 0; y < size.Height; y++) + { + for (int x = 0; x < size.Width; x++) + { + double dx = x - cx; + double dy = y - cy; + double distance = Math.Sqrt(dx * dx + dy * dy); + + float value = 0; + + switch (filterType) + { + case "理想": + value = (distance >= lowCutoff && distance <= highCutoff) ? 1.0f : 0.0f; + break; + + case "巴特沃斯": + double highPass = 1.0 / (1.0 + Math.Pow(lowCutoff / (distance + 0.001), 2 * order)); + double lowPass = 1.0 / (1.0 + Math.Pow(distance / (highCutoff + 0.001), 2 * order)); + value = (float)(highPass * lowPass); + break; + + case "高斯": + double highPassGaussian = 1.0 - Math.Exp(-distance * distance / (2.0 * lowCutoff * lowCutoff)); + double lowPassGaussian = Math.Exp(-distance * distance / (2.0 * highCutoff * highCutoff)); + value = (float)(highPassGaussian * lowPassGaussian); + break; + } + + mask.Data[y, x, 0] = value; + } + } + + return mask; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/滤波处理/BilateralFilterProcessor.cs b/ImageProcessing.Processors/滤波处理/BilateralFilterProcessor.cs new file mode 100644 index 0000000..0c5ca2a --- /dev/null +++ b/ImageProcessing.Processors/滤波处理/BilateralFilterProcessor.cs @@ -0,0 +1,78 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: BilateralFilterProcessor.cs +// 描述: 双边滤波算子,用于保边降噪 +// 功能: +// - 双边滤波 +// - 保持边缘清晰的同时平滑图像 +// - 可调节核大小和标准差 +// 算法: 双边滤波 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// 双边滤波算子 +/// +public class BilateralFilterProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public BilateralFilterProcessor() + { + Name = LocalizationHelper.GetString("BilateralFilterProcessor_Name"); + Description = LocalizationHelper.GetString("BilateralFilterProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Diameter", new ProcessorParameter( + "Diameter", + LocalizationHelper.GetString("BilateralFilterProcessor_Diameter"), + typeof(int), + 9, + 1, + 31, + LocalizationHelper.GetString("BilateralFilterProcessor_Diameter_Desc"))); + + Parameters.Add("SigmaColor", new ProcessorParameter( + "SigmaColor", + LocalizationHelper.GetString("BilateralFilterProcessor_SigmaColor"), + typeof(double), + 75.0, + 1.0, + 200.0, + LocalizationHelper.GetString("BilateralFilterProcessor_SigmaColor_Desc"))); + + Parameters.Add("SigmaSpace", new ProcessorParameter( + "SigmaSpace", + LocalizationHelper.GetString("BilateralFilterProcessor_SigmaSpace"), + typeof(double), + 75.0, + 1.0, + 200.0, + LocalizationHelper.GetString("BilateralFilterProcessor_SigmaSpace_Desc"))); + + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + int diameter = GetParameter("Diameter"); + double sigmaColor = GetParameter("SigmaColor"); + double sigmaSpace = GetParameter("SigmaSpace"); + + var result = inputImage.Clone(); + CvInvoke.BilateralFilter(inputImage, result, diameter, sigmaColor, sigmaSpace); + + _logger.Debug("Process: Diameter = {Diameter}, SigmaColor = {SigmaColor}, SigmaSpace = {SigmaSpace}", + diameter, sigmaColor, sigmaSpace); + return result; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/滤波处理/GaussianBlurProcessor.cs b/ImageProcessing.Processors/滤波处理/GaussianBlurProcessor.cs new file mode 100644 index 0000000..1e2540e --- /dev/null +++ b/ImageProcessing.Processors/滤波处理/GaussianBlurProcessor.cs @@ -0,0 +1,69 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: GaussianBlurProcessor.cs +// 描述: 高斯模糊算子,用于图像平滑和降噪 +// 功能: +// - 高斯核卷积平滑 +// - 可调节核大小和标准差 +// - 有效去除高斯噪声 +// - 保持边缘相对清晰 +// 算法: 高斯滤波器卷积 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// 高斯模糊算子 +/// +public class GaussianBlurProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public GaussianBlurProcessor() + { + Name = LocalizationHelper.GetString("GaussianBlurProcessor_Name"); + Description = LocalizationHelper.GetString("GaussianBlurProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("KernelSize", new ProcessorParameter( + "KernelSize", + LocalizationHelper.GetString("GaussianBlurProcessor_KernelSize"), + typeof(int), + 5, + 1, + 31, + LocalizationHelper.GetString("GaussianBlurProcessor_KernelSize_Desc"))); + + Parameters.Add("Sigma", new ProcessorParameter( + "Sigma", + LocalizationHelper.GetString("GaussianBlurProcessor_Sigma"), + typeof(double), + 1.5, + 0.1, + 10.0, + LocalizationHelper.GetString("GaussianBlurProcessor_Sigma_Desc"))); + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + int kernelSize = GetParameter("KernelSize"); + double sigma = GetParameter("Sigma"); + + if (kernelSize % 2 == 0) kernelSize++; + + var result = inputImage.Clone(); + CvInvoke.GaussianBlur(inputImage, result, + new System.Drawing.Size(kernelSize, kernelSize), sigma); + _logger.Debug("Process: KernelSize = {KernelSize}, Sigma = {Sigma}", kernelSize, sigma); + return result; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/滤波处理/HighPassFilterProcessor.cs b/ImageProcessing.Processors/滤波处理/HighPassFilterProcessor.cs new file mode 100644 index 0000000..172cfd2 --- /dev/null +++ b/ImageProcessing.Processors/滤波处理/HighPassFilterProcessor.cs @@ -0,0 +1,148 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: HighPassFilterProcessor.cs +// 描述: 高通滤波算子,用于边缘增强 +// 功能: +// - 高通滤波(频域) +// - 边缘增强 +// - 去除低频信息 +// - 可调节截止频率 +// 算法: 高斯高通滤波器(频域) +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// 高通滤波算子 +/// +public class HighPassFilterProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public HighPassFilterProcessor() + { + Name = LocalizationHelper.GetString("HighPassFilterProcessor_Name"); + Description = LocalizationHelper.GetString("HighPassFilterProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("CutoffFrequency", new ProcessorParameter( + "CutoffFrequency", + LocalizationHelper.GetString("HighPassFilterProcessor_CutoffFrequency"), + typeof(double), + 30.0, + 1.0, + 200.0, + LocalizationHelper.GetString("HighPassFilterProcessor_CutoffFrequency_Desc"))); + + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + double cutoffFrequency = GetParameter("CutoffFrequency"); + + int rows = inputImage.Rows; + int cols = inputImage.Cols; + + // 转换为浮点型 + Image floatImage = inputImage.Convert(); + + // 创建复数图像用于FFT + Mat complexImage = new Mat(); + using (var planes = new Emgu.CV.Util.VectorOfMat()) + { + planes.Push(floatImage.Mat); + planes.Push(Mat.Zeros(rows, cols, DepthType.Cv32F, 1)); + CvInvoke.Merge(planes, complexImage); + } + + // 执行DFT + Mat dftImage = new Mat(); + CvInvoke.Dft(complexImage, dftImage, DxtType.Forward); + + // 分离实部和虚部 + using (var dftPlanes = new Emgu.CV.Util.VectorOfMat()) + { + CvInvoke.Split(dftImage, dftPlanes); + + Mat real = dftPlanes[0]; + Mat imag = dftPlanes[1]; + + // 创建高通滤波器 + Mat filter = CreateHighPassFilter(rows, cols, cutoffFrequency); + + // 应用滤波器 + CvInvoke.Multiply(real, filter, real); + CvInvoke.Multiply(imag, filter, imag); + + // 合并并执行逆DFT + using (var filteredPlanes = new Emgu.CV.Util.VectorOfMat()) + { + filteredPlanes.Push(real); + filteredPlanes.Push(imag); + + Mat filteredDft = new Mat(); + CvInvoke.Merge(filteredPlanes, filteredDft); + + Mat ifftImage = new Mat(); + CvInvoke.Dft(filteredDft, ifftImage, DxtType.Inverse | DxtType.Scale); + + // 分离实部 + using (var ifftPlanes = new Emgu.CV.Util.VectorOfMat()) + { + CvInvoke.Split(ifftImage, ifftPlanes); + + // 转换回byte类型 + Mat resultMat = new Mat(); + ifftPlanes[0].ConvertTo(resultMat, DepthType.Cv8U); + + Image result = resultMat.ToImage(); + + // 释放资源 + floatImage.Dispose(); + complexImage.Dispose(); + dftImage.Dispose(); + filter.Dispose(); + filteredDft.Dispose(); + ifftImage.Dispose(); + resultMat.Dispose(); + + _logger.Debug("Process: CutoffFrequency = {CutoffFrequency}", cutoffFrequency); + return result; + } + } + } + } + + /// + /// 创建高斯高通滤波器 + /// + private Mat CreateHighPassFilter(int rows, int cols, double d0) + { + var filter = new Image(cols, rows); + + int centerX = cols / 2; + int centerY = rows / 2; + + for (int i = 0; i < rows; i++) + { + for (int j = 0; j < cols; j++) + { + double distance = Math.Sqrt(Math.Pow(i - centerY, 2) + Math.Pow(j - centerX, 2)); + float value = (float)(1 - Math.Exp(-(distance * distance) / (2 * d0 * d0))); + filter.Data[i, j, 0] = value; + } + } + + return filter.Mat; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/滤波处理/LowPassFilterProcessor.cs b/ImageProcessing.Processors/滤波处理/LowPassFilterProcessor.cs new file mode 100644 index 0000000..89d0159 --- /dev/null +++ b/ImageProcessing.Processors/滤波处理/LowPassFilterProcessor.cs @@ -0,0 +1,148 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: LowPassFilterProcessor.cs +// 描述: 低通滤波算子,用于去除高频噪声 +// 功能: +// - 低通滤波(频域) +// - 去除高频噪声 +// - 平滑图像 +// - 可调节截止频率 +// 算法: 高斯低通滤波器(频域) +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// 低通滤波算子 +/// +public class LowPassFilterProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public LowPassFilterProcessor() + { + Name = LocalizationHelper.GetString("LowPassFilterProcessor_Name"); + Description = LocalizationHelper.GetString("LowPassFilterProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("CutoffFrequency", new ProcessorParameter( + "CutoffFrequency", + LocalizationHelper.GetString("LowPassFilterProcessor_CutoffFrequency"), + typeof(double), + 30.0, + 1.0, + 200.0, + LocalizationHelper.GetString("LowPassFilterProcessor_CutoffFrequency_Desc"))); + + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + double cutoffFrequency = GetParameter("CutoffFrequency"); + + int rows = inputImage.Rows; + int cols = inputImage.Cols; + + // 转换为浮点型 + Image floatImage = inputImage.Convert(); + + // 创建复数图像用于FFT + Mat complexImage = new Mat(); + using (var planes = new Emgu.CV.Util.VectorOfMat()) + { + planes.Push(floatImage.Mat); + planes.Push(Mat.Zeros(rows, cols, DepthType.Cv32F, 1)); + CvInvoke.Merge(planes, complexImage); + } + + // 执行DFT + Mat dftImage = new Mat(); + CvInvoke.Dft(complexImage, dftImage, DxtType.Forward); + + // 分离实部和虚部 + using (var dftPlanes = new Emgu.CV.Util.VectorOfMat()) + { + CvInvoke.Split(dftImage, dftPlanes); + + Mat real = dftPlanes[0]; + Mat imag = dftPlanes[1]; + + // 创建低通滤波器 + Mat filter = CreateLowPassFilter(rows, cols, cutoffFrequency); + + // 应用滤波器 + CvInvoke.Multiply(real, filter, real); + CvInvoke.Multiply(imag, filter, imag); + + // 合并并执行逆DFT + using (var filteredPlanes = new Emgu.CV.Util.VectorOfMat()) + { + filteredPlanes.Push(real); + filteredPlanes.Push(imag); + + Mat filteredDft = new Mat(); + CvInvoke.Merge(filteredPlanes, filteredDft); + + Mat ifftImage = new Mat(); + CvInvoke.Dft(filteredDft, ifftImage, DxtType.Inverse | DxtType.Scale); + + // 分离实部 + using (var ifftPlanes = new Emgu.CV.Util.VectorOfMat()) + { + CvInvoke.Split(ifftImage, ifftPlanes); + + // 转换回byte类型 + Mat resultMat = new Mat(); + ifftPlanes[0].ConvertTo(resultMat, DepthType.Cv8U); + + Image result = resultMat.ToImage(); + + // 释放资源 + floatImage.Dispose(); + complexImage.Dispose(); + dftImage.Dispose(); + filter.Dispose(); + filteredDft.Dispose(); + ifftImage.Dispose(); + resultMat.Dispose(); + + _logger.Debug("Process: CutoffFrequency = {CutoffFrequency}", cutoffFrequency); + return result; + } + } + } + } + + /// + /// 创建高斯低通滤波器 + /// + private Mat CreateLowPassFilter(int rows, int cols, double d0) + { + var filter = new Image(cols, rows); + + int centerX = cols / 2; + int centerY = rows / 2; + + for (int i = 0; i < rows; i++) + { + for (int j = 0; j < cols; j++) + { + double distance = Math.Sqrt(Math.Pow(i - centerY, 2) + Math.Pow(j - centerX, 2)); + float value = (float)Math.Exp(-(distance * distance) / (2 * d0 * d0)); + filter.Data[i, j, 0] = value; + } + } + + return filter.Mat; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/滤波处理/MeanFilterProcessor.cs b/ImageProcessing.Processors/滤波处理/MeanFilterProcessor.cs new file mode 100644 index 0000000..49d68f6 --- /dev/null +++ b/ImageProcessing.Processors/滤波处理/MeanFilterProcessor.cs @@ -0,0 +1,61 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: MeanFilterProcessor.cs +// 描述: 均值滤波算子,用于图像平滑 +// 功能: +// - 均值滤波 +// - 简单快速的平滑方法 +// - 可调节核大小 +// 算法: 均值滤波 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; +using System.Drawing; + +namespace ImageProcessing.Processors; + +/// +/// 均值滤波算子 +/// +public class MeanFilterProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public MeanFilterProcessor() + { + Name = LocalizationHelper.GetString("MeanFilterProcessor_Name"); + Description = LocalizationHelper.GetString("MeanFilterProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("KernelSize", new ProcessorParameter( + "KernelSize", + LocalizationHelper.GetString("MeanFilterProcessor_KernelSize"), + typeof(int), + 5, + 1, + 31, + LocalizationHelper.GetString("MeanFilterProcessor_KernelSize_Desc"))); + + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + int kernelSize = GetParameter("KernelSize"); + + // 确保核大小为奇数 + if (kernelSize % 2 == 0) kernelSize++; + + var result = inputImage.Clone(); + CvInvoke.Blur(inputImage, result, new Size(kernelSize, kernelSize), new Point(-1, -1)); + + _logger.Debug("Process: KernelSize = {KernelSize}", kernelSize); + return result; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/滤波处理/MedianFilterProcessor.cs b/ImageProcessing.Processors/滤波处理/MedianFilterProcessor.cs new file mode 100644 index 0000000..b3ddf0e --- /dev/null +++ b/ImageProcessing.Processors/滤波处理/MedianFilterProcessor.cs @@ -0,0 +1,61 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: MedianFilterProcessor.cs +// 描述: 中值滤波算子,用于去除椒盐噪声 +// 功能: +// - 中值滤波 +// - 有效去除椒盐噪声 +// - 保持边缘清晰 +// - 可调节核大小 +// 算法: 中值滤波 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// 中值滤波算子 +/// +public class MedianFilterProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public MedianFilterProcessor() + { + Name = LocalizationHelper.GetString("MedianFilterProcessor_Name"); + Description = LocalizationHelper.GetString("MedianFilterProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("KernelSize", new ProcessorParameter( + "KernelSize", + LocalizationHelper.GetString("MedianFilterProcessor_KernelSize"), + typeof(int), + 5, + 1, + 31, + LocalizationHelper.GetString("MedianFilterProcessor_KernelSize_Desc"))); + + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + int kernelSize = GetParameter("KernelSize"); + + // 确保核大小为奇数 + if (kernelSize % 2 == 0) kernelSize++; + + var result = inputImage.Clone(); + CvInvoke.MedianBlur(inputImage, result, kernelSize); + + _logger.Debug("Process: KernelSize = {KernelSize}", kernelSize); + return result; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/滤波处理/ShockFilterProcessor.cs b/ImageProcessing.Processors/滤波处理/ShockFilterProcessor.cs new file mode 100644 index 0000000..c692dd2 --- /dev/null +++ b/ImageProcessing.Processors/滤波处理/ShockFilterProcessor.cs @@ -0,0 +1,123 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: ShockFilterProcessor.cs +// 描述: 冲击滤波算子,用于图像锐化和边缘增强 +// 功能: +// - 基于PDE的图像锐化 +// - 增强边缘同时保持平滑区域 +// - 可调节迭代次数和滤波强度 +// - 适用于模糊图像的恢复 +// 算法: 冲击滤波器(Shock Filter)基于偏微分方程 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// 冲击滤波算子 +/// +public class ShockFilterProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public ShockFilterProcessor() + { + Name = LocalizationHelper.GetString("ShockFilterProcessor_Name"); + Description = LocalizationHelper.GetString("ShockFilterProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Iterations", new ProcessorParameter( + "Iterations", + LocalizationHelper.GetString("ShockFilterProcessor_Iterations"), + typeof(int), + 5, + 1, + 20, + LocalizationHelper.GetString("ShockFilterProcessor_Iterations_Desc"))); + + Parameters.Add("Theta", new ProcessorParameter( + "Theta", + LocalizationHelper.GetString("ShockFilterProcessor_Theta"), + typeof(double), + 0.5, + 0.0, + 2.0, + LocalizationHelper.GetString("ShockFilterProcessor_Theta_Desc"))); + + Parameters.Add("Dt", new ProcessorParameter( + "Dt", + LocalizationHelper.GetString("ShockFilterProcessor_Dt"), + typeof(double), + 0.25, + 0.1, + 1.0, + LocalizationHelper.GetString("ShockFilterProcessor_Dt_Desc"))); + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + int iterations = GetParameter("Iterations"); + double theta = GetParameter("Theta"); + double dt = GetParameter("Dt"); + var result = inputImage.Convert(); + for (int iter = 0; iter < iterations; iter++) + { + result = ShockFilterIteration(result, theta, dt); + } + _logger.Debug("Process: Iterations = {Iterations}, Theta = {Theta}, Dt = {Dt}", iterations, theta, dt); + return result.Convert(); + } + + private Image ShockFilterIteration(Image input, double theta, double dt) + { + int width = input.Width; + int height = input.Height; + var output = new Image(width, height); + + for (int y = 1; y < height - 1; y++) + { + for (int x = 1; x < width - 1; x++) + { + float dx = (input.Data[y, x + 1, 0] - input.Data[y, x - 1, 0]) / 2.0f; + float dy = (input.Data[y + 1, x, 0] - input.Data[y - 1, x, 0]) / 2.0f; + float gradMag = (float)Math.Sqrt(dx * dx + dy * dy); + + float dxx = input.Data[y, x + 1, 0] - 2 * input.Data[y, x, 0] + input.Data[y, x - 1, 0]; + float dyy = input.Data[y + 1, x, 0] - 2 * input.Data[y, x, 0] + input.Data[y - 1, x, 0]; + float laplacian = dxx + dyy; + + float sign = laplacian > 0 ? 1.0f : -1.0f; + + if (gradMag > theta) + { + output.Data[y, x, 0] = input.Data[y, x, 0] - (float)(dt * sign * gradMag); + } + else + { + output.Data[y, x, 0] = input.Data[y, x, 0]; + } + } + } + + for (int x = 0; x < width; x++) + { + output.Data[0, x, 0] = input.Data[0, x, 0]; + output.Data[height - 1, x, 0] = input.Data[height - 1, x, 0]; + } + for (int y = 0; y < height; y++) + { + output.Data[y, 0, 0] = input.Data[y, 0, 0]; + output.Data[y, width - 1, 0] = input.Data[y, width - 1, 0]; + } + + return output; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/边缘检测/HorizontalEdgeProcessor.cs b/ImageProcessing.Processors/边缘检测/HorizontalEdgeProcessor.cs new file mode 100644 index 0000000..4d7485d --- /dev/null +++ b/ImageProcessing.Processors/边缘检测/HorizontalEdgeProcessor.cs @@ -0,0 +1,199 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: HorizontalEdgeProcessor.cs +// 描述: 水平边缘检测算子,专门用于检测水平方向的边缘 +// 功能: +// - 检测水平边缘 +// - 支持Prewitt和Sobel算子 +// - 可调节检测灵敏度 +// - 适用于检测水平线条和纹理 +// 算法: Prewitt/Sobel水平算子 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// 水平边缘检测算子 +/// +public class HorizontalEdgeProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public HorizontalEdgeProcessor() + { + Name = LocalizationHelper.GetString("HorizontalEdgeProcessor_Name"); + Description = LocalizationHelper.GetString("HorizontalEdgeProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Method", new ProcessorParameter( + "Method", + LocalizationHelper.GetString("HorizontalEdgeProcessor_Method"), + typeof(string), + "Sobel", + null, + null, + LocalizationHelper.GetString("HorizontalEdgeProcessor_Method_Desc"), + new string[] { "Sobel", "Prewitt", "Simple" })); + + Parameters.Add("Sensitivity", new ProcessorParameter( + "Sensitivity", + LocalizationHelper.GetString("HorizontalEdgeProcessor_Sensitivity"), + typeof(double), + 1.0, + 0.1, + 5.0, + LocalizationHelper.GetString("HorizontalEdgeProcessor_Sensitivity_Desc"))); + + Parameters.Add("Threshold", new ProcessorParameter( + "Threshold", + LocalizationHelper.GetString("HorizontalEdgeProcessor_Threshold"), + typeof(int), + 20, + 0, + 255, + LocalizationHelper.GetString("HorizontalEdgeProcessor_Threshold_Desc"))); + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + string method = GetParameter("Method"); + double sensitivity = GetParameter("Sensitivity"); + int threshold = GetParameter("Threshold"); + + Image result; + + if (method == "Sobel") + { + result = ApplySobel(inputImage, sensitivity, threshold); + } + else if (method == "Prewitt") + { + result = ApplyPrewitt(inputImage, sensitivity, threshold); + } + else // Simple + { + result = ApplySimple(inputImage, sensitivity, threshold); + } + + _logger.Debug("Process: Method = {Method}, Sensitivity = {Sensitivity}, Threshold = {Threshold}", + method, sensitivity, threshold); + return result; + } + + private Image ApplySobel(Image inputImage, double sensitivity, int threshold) + { + // 使用Sobel算子检测水平边缘(Y方向导数) + Image sobelY = new Image(inputImage.Size); + CvInvoke.Sobel(inputImage, sobelY, DepthType.Cv32F, 0, 1, 3); + + // 转换为绝对值并应用灵敏度 + Image result = new Image(inputImage.Size); + CvInvoke.ConvertScaleAbs(sobelY, result, sensitivity, 0); + + // 应用阈值 + if (threshold > 0) + { + CvInvoke.Threshold(result, result, threshold, 255, ThresholdType.Binary); + CvInvoke.Threshold(result, result, 0, 255, ThresholdType.ToZero); + } + + sobelY.Dispose(); + return result; + } + + private Image ApplyPrewitt(Image inputImage, double sensitivity, int threshold) + { + // Prewitt水平算子 + // [ 1 1 1] + // [ 0 0 0] + // [-1 -1 -1] + + int width = inputImage.Width; + int height = inputImage.Height; + byte[,,] inputData = inputImage.Data; + + Image result = new Image(width, height); + byte[,,] outputData = result.Data; + + for (int y = 1; y < height - 1; y++) + { + for (int x = 1; x < width - 1; x++) + { + int sum = 0; + + // 上行 + sum += inputData[y - 1, x - 1, 0]; + sum += inputData[y - 1, x, 0]; + sum += inputData[y - 1, x + 1, 0]; + + // 下行 + sum -= inputData[y + 1, x - 1, 0]; + sum -= inputData[y + 1, x, 0]; + sum -= inputData[y + 1, x + 1, 0]; + + // 取绝对值并应用灵敏度 + int value = (int)(Math.Abs(sum) * sensitivity); + + // 应用阈值 + if (value > threshold) + { + outputData[y, x, 0] = (byte)Math.Min(255, value); + } + else + { + outputData[y, x, 0] = 0; + } + } + } + + return result; + } + + private Image ApplySimple(Image inputImage, double sensitivity, int threshold) + { + // 简单差分算子 + // [ 1 1 1] + // [ 0 0 0] + // [-1 -1 -1] + // 但权重更简单 + + int width = inputImage.Width; + int height = inputImage.Height; + byte[,,] inputData = inputImage.Data; + + Image result = new Image(width, height); + byte[,,] outputData = result.Data; + + for (int y = 1; y < height - 1; y++) + { + for (int x = 0; x < width; x++) + { + // 简单的上下差分 + int diff = inputData[y - 1, x, 0] - inputData[y + 1, x, 0]; + int value = (int)(Math.Abs(diff) * sensitivity); + + // 应用阈值 + if (value > threshold) + { + outputData[y, x, 0] = (byte)Math.Min(255, value); + } + else + { + outputData[y, x, 0] = 0; + } + } + } + + return result; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/边缘检测/KirschEdgeProcessor.cs b/ImageProcessing.Processors/边缘检测/KirschEdgeProcessor.cs new file mode 100644 index 0000000..8f1b24d --- /dev/null +++ b/ImageProcessing.Processors/边缘检测/KirschEdgeProcessor.cs @@ -0,0 +1,133 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: KirschEdgeProcessor.cs +// 描述: Kirsch边缘检测算子,用于检测图像边缘 +// 功能: +// - Kirsch算子边缘检测 +// - 8个方向的边缘检测 +// - 输出最大响应方向的边缘 +// - 对噪声敏感度低 +// 算法: Kirsch算子(8方向模板) +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// Kirsch边缘检测算子 +/// +public class KirschEdgeProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + // Kirsch算子的8个方向模板 + private static readonly int[][,] KirschKernels = new int[8][,] + { + // N + new int[,] { { 5, 5, 5 }, { -3, 0, -3 }, { -3, -3, -3 } }, + // NW + new int[,] { { 5, 5, -3 }, { 5, 0, -3 }, { -3, -3, -3 } }, + // W + new int[,] { { 5, -3, -3 }, { 5, 0, -3 }, { 5, -3, -3 } }, + // SW + new int[,] { { -3, -3, -3 }, { 5, 0, -3 }, { 5, 5, -3 } }, + // S + new int[,] { { -3, -3, -3 }, { -3, 0, -3 }, { 5, 5, 5 } }, + // SE + new int[,] { { -3, -3, -3 }, { -3, 0, 5 }, { -3, 5, 5 } }, + // E + new int[,] { { -3, -3, 5 }, { -3, 0, 5 }, { -3, -3, 5 } }, + // NE + new int[,] { { -3, 5, 5 }, { -3, 0, 5 }, { -3, -3, -3 } } + }; + + public KirschEdgeProcessor() + { + Name = LocalizationHelper.GetString("KirschEdgeProcessor_Name"); + Description = LocalizationHelper.GetString("KirschEdgeProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Threshold", new ProcessorParameter( + "Threshold", + LocalizationHelper.GetString("KirschEdgeProcessor_Threshold"), + typeof(int), + 100, + 0, + 1000, + LocalizationHelper.GetString("KirschEdgeProcessor_Threshold_Desc"))); + + Parameters.Add("Scale", new ProcessorParameter( + "Scale", + LocalizationHelper.GetString("KirschEdgeProcessor_Scale"), + typeof(double), + 1.0, + 0.1, + 5.0, + LocalizationHelper.GetString("KirschEdgeProcessor_Scale_Desc"))); + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + int threshold = GetParameter("Threshold"); + double scale = GetParameter("Scale"); + + int width = inputImage.Width; + int height = inputImage.Height; + byte[,,] inputData = inputImage.Data; + + Image result = new Image(width, height); + byte[,,] outputData = result.Data; + + // 对每个像素应用8个Kirsch模板,取最大响应 + for (int y = 1; y < height - 1; y++) + { + for (int x = 1; x < width - 1; x++) + { + int maxResponse = 0; + + // 对8个方向分别计算 + for (int k = 0; k < 8; k++) + { + int sum = 0; + for (int ky = 0; ky < 3; ky++) + { + for (int kx = 0; kx < 3; kx++) + { + int pixelValue = inputData[y + ky - 1, x + kx - 1, 0]; + sum += pixelValue * KirschKernels[k][ky, kx]; + } + } + + // 取绝对值 + sum = Math.Abs(sum); + if (sum > maxResponse) + { + maxResponse = sum; + } + } + + // 应用阈值和缩放 + if (maxResponse > threshold) + { + int value = (int)(maxResponse * scale); + outputData[y, x, 0] = (byte)Math.Min(255, Math.Max(0, value)); + } + else + { + outputData[y, x, 0] = 0; + } + } + } + + _logger.Debug("Process: Threshold = {Threshold}, Scale = {Scale}", threshold, scale); + return result; + } +} \ No newline at end of file diff --git a/ImageProcessing.Processors/边缘检测/SobelEdgeProcessor.cs b/ImageProcessing.Processors/边缘检测/SobelEdgeProcessor.cs new file mode 100644 index 0000000..8e49386 --- /dev/null +++ b/ImageProcessing.Processors/边缘检测/SobelEdgeProcessor.cs @@ -0,0 +1,135 @@ +// ============================================================================ +// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved. +// 文件名: SobelEdgeProcessor.cs +// 描述: Sobel边缘检测算子,用于检测图像边缘 +// 功能: +// - Sobel算子边缘检测 +// - 支持X方向、Y方向和组合检测 +// - 可调节核大小 +// - 输出边缘强度图 +// 算法: Sobel算子 +// 作者: 李伟 wei.lw.li@hexagon.com +// ============================================================================ + +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using ImageProcessing.Core; +using Serilog; + +namespace ImageProcessing.Processors; + +/// +/// Sobel边缘检测算子 +/// +public class SobelEdgeProcessor : ImageProcessorBase +{ + private static readonly ILogger _logger = Log.ForContext(); + + public SobelEdgeProcessor() + { + Name = LocalizationHelper.GetString("SobelEdgeProcessor_Name"); + Description = LocalizationHelper.GetString("SobelEdgeProcessor_Description"); + } + + protected override void InitializeParameters() + { + Parameters.Add("Direction", new ProcessorParameter( + "Direction", + LocalizationHelper.GetString("SobelEdgeProcessor_Direction"), + typeof(string), + "Both", + null, + null, + LocalizationHelper.GetString("SobelEdgeProcessor_Direction_Desc"), + new string[] { "Both", "Horizontal", "Vertical" })); + + Parameters.Add("KernelSize", new ProcessorParameter( + "KernelSize", + LocalizationHelper.GetString("SobelEdgeProcessor_KernelSize"), + typeof(int), + 3, + 1, + 7, + LocalizationHelper.GetString("SobelEdgeProcessor_KernelSize_Desc"))); + + Parameters.Add("Scale", new ProcessorParameter( + "Scale", + LocalizationHelper.GetString("SobelEdgeProcessor_Scale"), + typeof(double), + 1.0, + 0.1, + 5.0, + LocalizationHelper.GetString("SobelEdgeProcessor_Scale_Desc"))); + _logger.Debug("InitializeParameters"); + } + + public override Image Process(Image inputImage) + { + string direction = GetParameter("Direction"); + int kernelSize = GetParameter("KernelSize"); + double scale = GetParameter("Scale"); + + // 确保核大小为奇数 + if (kernelSize % 2 == 0) kernelSize++; + if (kernelSize > 7) kernelSize = 7; + if (kernelSize < 1) kernelSize = 1; + + Image sobelX = new Image(inputImage.Size); + Image sobelY = new Image(inputImage.Size); + Image result = new Image(inputImage.Size); + + if (direction == "Horizontal" || direction == "Both") + { + // X方向(水平边缘) + CvInvoke.Sobel(inputImage, sobelX, DepthType.Cv32F, 1, 0, kernelSize); + } + + if (direction == "Vertical" || direction == "Both") + { + // Y方向(垂直边缘) + CvInvoke.Sobel(inputImage, sobelY, DepthType.Cv32F, 0, 1, kernelSize); + } + + if (direction == "Both") + { + // 计算梯度幅值:sqrt(Gx^2 + Gy^2) + Image magnitude = new Image(inputImage.Size); + + // 手动计算幅值 + for (int y = 0; y < inputImage.Height; y++) + { + for (int x = 0; x < inputImage.Width; x++) + { + float gx = sobelX.Data[y, x, 0]; + float gy = sobelY.Data[y, x, 0]; + magnitude.Data[y, x, 0] = (float)Math.Sqrt(gx * gx + gy * gy); + } + } + + // 应用缩放并转换为字节类型 + var scaled = magnitude * scale; + result = scaled.Convert(); + + magnitude.Dispose(); + scaled.Dispose(); + } + else if (direction == "Horizontal") + { + // 只使用X方向 + CvInvoke.ConvertScaleAbs(sobelX, result, scale, 0); + } + else // Vertical + { + // 只使用Y方向 + CvInvoke.ConvertScaleAbs(sobelY, result, scale, 0); + } + + sobelX.Dispose(); + sobelY.Dispose(); + + _logger.Debug("Process: Direction = {Direction}, KernelSize = {KernelSize}, Scale = {Scale}", + direction, kernelSize, scale); + return result; + } +} \ No newline at end of file diff --git a/ImageROIControl/ControlThumb.cs b/ImageROIControl/ControlThumb.cs new file mode 100644 index 0000000..36abc49 --- /dev/null +++ b/ImageROIControl/ControlThumb.cs @@ -0,0 +1,43 @@ +using System; +using System.Windows; +using System.Windows.Controls.Primitives; + +namespace ImageROIControl +{ + /// + /// ROI控制点 + /// + public class ControlThumb : Thumb + { + private static readonly Style? thumbStyle; + + static ControlThumb() + { + try + { + ResourceDictionary dictionary = new ResourceDictionary(); + dictionary.Source = new Uri("pack://application:,,,/ImageROIControl;component/Themes/Generic.xaml", UriKind.Absolute); + thumbStyle = (Style?)dictionary["AreaControlThumbStyle"]; + } + catch + { + // 如果样式加载失败,使用默认样式 + thumbStyle = null; + } + } + + public ControlThumb() + { + if (thumbStyle != null) + { + Style = thumbStyle; + } + else + { + // 默认样式 + Width = 12; + Height = 12; + } + } + } +} \ No newline at end of file diff --git a/ImageROIControl/Controls/ImageROICanvas.cs b/ImageROIControl/Controls/ImageROICanvas.cs new file mode 100644 index 0000000..c33354c --- /dev/null +++ b/ImageROIControl/Controls/ImageROICanvas.cs @@ -0,0 +1,270 @@ +using ImageROIControl.Models; +using System.Collections.ObjectModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace ImageROIControl.Controls +{ + /// + /// 图像ROI画布控件,支持图像显示、ROI编辑、缩放和平移 + /// + public class ImageROICanvas : Control + { + private Canvas? roiCanvas; + private bool isDragging = false; + private Point mouseDownPoint = new Point(); + private const double ZoomStep = 1.2; + private const double MinZoom = 0.1; + private const double MaxZoom = 10.0; + + static ImageROICanvas() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageROICanvas), + new FrameworkPropertyMetadata(typeof(ImageROICanvas))); + } + + public ImageROICanvas() + { + Loaded += OnLoaded; + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + roiCanvas = GetTemplateChild("PART_Canvas") as Canvas; + } + + #region Dependency Properties + + public static readonly DependencyProperty ImageSourceProperty = + DependencyProperty.Register(nameof(ImageSource), typeof(ImageSource), typeof(ImageROICanvas), + new PropertyMetadata(null, OnImageSourceChanged)); + + public ImageSource? ImageSource + { + get => (ImageSource?)GetValue(ImageSourceProperty); + set => SetValue(ImageSourceProperty, value); + } + + private static void OnImageSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var control = (ImageROICanvas)d; + if (e.NewValue is BitmapSource bitmap && control.roiCanvas != null) + { + control.ImageWidth = bitmap.PixelWidth; + control.ImageHeight = bitmap.PixelHeight; + } + } + + public static readonly DependencyProperty ROIItemsProperty = + DependencyProperty.Register(nameof(ROIItems), typeof(ObservableCollection), typeof(ImageROICanvas), + new PropertyMetadata(null)); + + public ObservableCollection? ROIItems + { + get => (ObservableCollection?)GetValue(ROIItemsProperty); + set => SetValue(ROIItemsProperty, value); + } + + public static readonly DependencyProperty ZoomScaleProperty = + DependencyProperty.Register(nameof(ZoomScale), typeof(double), typeof(ImageROICanvas), + new PropertyMetadata(1.0)); + + public double ZoomScale + { + get => (double)GetValue(ZoomScaleProperty); + set => SetValue(ZoomScaleProperty, value); + } + + public static readonly DependencyProperty ZoomCenterProperty = + DependencyProperty.Register(nameof(ZoomCenter), typeof(Point), typeof(ImageROICanvas), + new PropertyMetadata(new Point())); + + public Point ZoomCenter + { + get => (Point)GetValue(ZoomCenterProperty); + set => SetValue(ZoomCenterProperty, value); + } + + public static readonly DependencyProperty PanningOffsetXProperty = + DependencyProperty.Register(nameof(PanningOffsetX), typeof(double), typeof(ImageROICanvas), + new PropertyMetadata(0.0)); + + public double PanningOffsetX + { + get => (double)GetValue(PanningOffsetXProperty); + set => SetValue(PanningOffsetXProperty, value); + } + + public static readonly DependencyProperty PanningOffsetYProperty = + DependencyProperty.Register(nameof(PanningOffsetY), typeof(double), typeof(ImageROICanvas), + new PropertyMetadata(0.0)); + + public double PanningOffsetY + { + get => (double)GetValue(PanningOffsetYProperty); + set => SetValue(PanningOffsetYProperty, value); + } + + public static readonly DependencyProperty ImageWidthProperty = + DependencyProperty.Register(nameof(ImageWidth), typeof(double), typeof(ImageROICanvas), + new PropertyMetadata(800.0)); + + public double ImageWidth + { + get => (double)GetValue(ImageWidthProperty); + set => SetValue(ImageWidthProperty, value); + } + + public static readonly DependencyProperty ImageHeightProperty = + DependencyProperty.Register(nameof(ImageHeight), typeof(double), typeof(ImageROICanvas), + new PropertyMetadata(600.0)); + + public double ImageHeight + { + get => (double)GetValue(ImageHeightProperty); + set => SetValue(ImageHeightProperty, value); + } + + public static readonly DependencyProperty SelectedROIProperty = + DependencyProperty.Register(nameof(SelectedROI), typeof(ROIShape), typeof(ImageROICanvas), + new PropertyMetadata(null)); + + public ROIShape? SelectedROI + { + get => (ROIShape?)GetValue(SelectedROIProperty); + set => SetValue(SelectedROIProperty, value); + } + + #endregion Dependency Properties + + #region Mouse Events + + protected override void OnMouseWheel(MouseWheelEventArgs e) + { + base.OnMouseWheel(e); + + Point mousePos = e.GetPosition(this); + + if (e.Delta > 0) + { + ZoomIn(mousePos); + } + else + { + ZoomOut(mousePos); + } + + e.Handled = true; + } + + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) + { + base.OnMouseLeftButtonDown(e); + mouseDownPoint = e.GetPosition(this); + isDragging = false; + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + if (e.LeftButton == MouseButtonState.Pressed) + { + Point mousePoint = e.GetPosition(this); + double mouseMoveLength = (mousePoint - mouseDownPoint).Length; + + if (mouseMoveLength > 10 / ZoomScale) + { + isDragging = true; + PanningOffsetX += mousePoint.X - mouseDownPoint.X; + PanningOffsetY += mousePoint.Y - mouseDownPoint.Y; + mouseDownPoint = mousePoint; + } + } + } + + protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) + { + base.OnMouseLeftButtonUp(e); + + if (!isDragging) + { + // 处理点击事件(如添加多边形顶点) + Point clickPoint = e.GetPosition(roiCanvas); + OnCanvasClicked(clickPoint); + } + + isDragging = false; + } + + #endregion Mouse Events + + #region Zoom Methods + + public void ZoomIn(Point center) + { + double newZoom = ZoomScale * ZoomStep; + if (newZoom <= MaxZoom) + { + ZoomCenter = center; + ZoomScale = newZoom; + } + } + + public void ZoomOut(Point center) + { + double newZoom = ZoomScale / ZoomStep; + if (newZoom >= MinZoom) + { + ZoomCenter = center; + ZoomScale = newZoom; + } + } + + public void ResetZoom() + { + ZoomScale = 1.0; + PanningOffsetX = 0; + PanningOffsetY = 0; + ZoomCenter = new Point(); + } + + #endregion Zoom Methods + + #region Events + + public static readonly RoutedEvent CanvasClickedEvent = + EventManager.RegisterRoutedEvent(nameof(CanvasClicked), RoutingStrategy.Bubble, + typeof(RoutedEventHandler), typeof(ImageROICanvas)); + + public event RoutedEventHandler CanvasClicked + { + add { AddHandler(CanvasClickedEvent, value); } + remove { RemoveHandler(CanvasClickedEvent, value); } + } + + protected virtual void OnCanvasClicked(Point position) + { + var args = new CanvasClickedEventArgs(CanvasClickedEvent, position); + RaiseEvent(args); + } + + #endregion Events + } + + /// + /// 画布点击事件参数 + /// + public class CanvasClickedEventArgs : RoutedEventArgs + { + public Point Position { get; } + + public CanvasClickedEventArgs(RoutedEvent routedEvent, Point position) : base(routedEvent) + { + Position = position; + } + } +} \ No newline at end of file diff --git a/ImageROIControl/Controls/PolygonRoiCanvas.xaml b/ImageROIControl/Controls/PolygonRoiCanvas.xaml new file mode 100644 index 0000000..9be318d --- /dev/null +++ b/ImageROIControl/Controls/PolygonRoiCanvas.xaml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + +