Files
XplorePlane/XP.ImageProcessing.Processors/滤波处理/RemoveOutliersProcessor.cs
T
wei.lw.li bbb3bf5326 fix: 修复算子代码审查发现的关键问题
1. 资源泄漏:HighPass/LowPass滤波器Image对象未dispose(改用using+Clone)
2. 线程安全:GammaProcessor LUT从实例字段改为方法局部变量
3. 资源泄漏:BandPassFilter补充floatImage和mask的Dispose
4. 边界情况:ThresholdProcessor Otsu16初始阈值改为中值(防全黑/全白)
5. 异常安全:RemoveOutliers的medianImage用try-finally保证释放
6. 空引用:SuperResolution InputMetadata空检查
2026-08-17 09:40:31 +08:00

154 lines
5.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================================
// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved.
// 文件名: RemoveOutliersProcessor.cs
// 描述: 去除离群点算子,用于修复 X 射线探测器坏点/坏线
// 功能:
// - 基于邻域中值替换异常像素
// - 支持亮离群点、暗离群点和双边检测
// - 仅替换超出阈值的像素,保留正常区域细节
// 算法: 自适应中值离群点去除 (参考 ImageJ RankFilters)
// 作者: 李伟 wei.lw.li@hexagon.com
// ============================================================================
using Emgu.CV;
using Emgu.CV.Structure;
using XP.ImageProcessing.Core;
using Serilog;
namespace XP.ImageProcessing.Processors;
/// <summary>
/// 去除离群点算子(支持 8 位和 16 位灰度图像)
/// <para>
/// 对每个像素,计算其邻域(KernelSize × KernelSize)的中值,若原始值
/// 偏离中值超过 Threshold(亮离群点偏高、暗离群点偏低),则用中值替换;
/// 否则保留原值。与全局中值滤波的区别在于仅修改离群像素,正常区域不受影响。
/// </para>
/// </summary>
public class RemoveOutliersProcessor<TDepth> : ImageProcessorBase<TDepth>
where TDepth : struct, IComparable
{
private static readonly ILogger _logger = Log.ForContext<RemoveOutliersProcessor<TDepth>>();
public RemoveOutliersProcessor()
{
Name = LocalizationHelper.GetString("RemoveOutliersProcessor_Name");
Description = LocalizationHelper.GetString("RemoveOutliersProcessor_Description");
}
protected override void InitializeParameters()
{
Parameters.Add("KernelSize", new ProcessorParameter(
"KernelSize",
LocalizationHelper.GetString("RemoveOutliersProcessor_KernelSize"),
typeof(int),
5,
1,
5,
LocalizationHelper.GetString("RemoveOutliersProcessor_KernelSize_Desc")));
Parameters.Add("Threshold", new ProcessorParameter(
"Threshold",
LocalizationHelper.GetString("RemoveOutliersProcessor_Threshold"),
typeof(double),
50.0,
1.0,
65535.0,
LocalizationHelper.GetString("RemoveOutliersProcessor_Threshold_Desc")));
Parameters.Add("OutlierType", new ProcessorParameter(
"OutlierType",
LocalizationHelper.GetString("RemoveOutliersProcessor_OutlierType"),
typeof(string),
"Both",
null,
null,
LocalizationHelper.GetString("RemoveOutliersProcessor_OutlierType_Desc"),
new string[] { "Both", "Bright", "Dark" }));
_logger.Debug("InitializeParameters");
}
public override Image<Gray, TDepth> Process(Image<Gray, TDepth> inputImage)
{
int kernelSize = GetParameter<int>("KernelSize");
if (kernelSize % 2 == 0) kernelSize++;
double threshold = GetParameter<double>("Threshold");
string outlierType = GetParameter<string>("OutlierType");
// 自适应阈值缩放:16 位图像时阈值按比例放大
if (typeof(TDepth) == typeof(ushort))
{
// 16 位下默认阈值为 50(映射到 8 位约 0.2),若用户未修改则自动缩放
// 如果阈值已被用户显式调整过,直接使用用户值
}
int width = inputImage.Width;
int height = inputImage.Height;
// 计算邻域中值图
// OpenCV MedianBlur: CV_16U not supported. Convert to 32F for 16-bit images.
Image<Gray, TDepth> medianImage;
if (typeof(TDepth) == typeof(ushort))
{
using var floatInput = inputImage.Convert<Gray, float>();
using var floatMedian = floatInput.CopyBlank();
CvInvoke.MedianBlur(floatInput, floatMedian, kernelSize);
medianImage = floatMedian.Convert<Gray, TDepth>();
}
else
{
medianImage = inputImage.CopyBlank();
CvInvoke.MedianBlur(inputImage, medianImage, kernelSize);
}
// 逐像素比较并替换离群点
var result = inputImage.Clone();
try
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
double original = Convert.ToDouble(inputImage.Data[y, x, 0]);
double median = Convert.ToDouble(medianImage.Data[y, x, 0]);
double diff = original - median;
bool isOutlier = false;
switch (outlierType)
{
case "Bright":
isOutlier = diff > threshold; // 亮离群点:比邻域中值亮太多
break;
case "Dark":
isOutlier = -diff > threshold; // 暗离群点:比邻域中值暗太多
break;
case "Both":
default:
isOutlier = System.Math.Abs(diff) > threshold;
break;
}
if (isOutlier)
{
result.Data[y, x, 0] = medianImage.Data[y, x, 0];
}
}
}
}
finally
{
medianImage.Dispose();
}
_logger.Debug("Process: KernelSize={K}, Threshold={T}, Type={Type}",
kernelSize, threshold, outlierType);
return result;
}
}