bbb3bf5326
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空检查
154 lines
5.6 KiB
C#
154 lines
5.6 KiB
C#
// ============================================================================
|
||
// 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;
|
||
}
|
||
|
||
}
|