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空检查
This commit is contained in:
@@ -35,20 +35,20 @@ public class ThresholdProcessor<TDepth> : ImageProcessorBase<TDepth>
|
||||
|
||||
protected override void InitializeParameters()
|
||||
{
|
||||
// 参数范围必须跟随当前算子的位深。主流程使用 ushort,因此默认阈值也按
|
||||
// 16 位满量程计算,避免把 8 位的 64/192 直接套用到 0~65535。
|
||||
int quarterValue = MaxPixelValue / 4;
|
||||
int threeQuarterValue = MaxPixelValue * 3 / 4;
|
||||
Parameters.Add("MinThreshold", new ProcessorParameter(
|
||||
// 参数范围必须跟随当前算子的位深。主流程使用 ushort,因此默认阈值也按
|
||||
// 16 位满量程计算,避免把 8 位的 64/192 直接套用到 0~65535。
|
||||
int quarterValue = MaxPixelValue / 4;
|
||||
int threeQuarterValue = MaxPixelValue * 3 / 4;
|
||||
Parameters.Add("MinThreshold", new ProcessorParameter(
|
||||
"MinThreshold",
|
||||
LocalizationHelper.GetString("ThresholdProcessor_MinThreshold"),
|
||||
typeof(int), quarterValue, 0, MaxPixelValue,
|
||||
typeof(int), quarterValue, 0, MaxPixelValue,
|
||||
LocalizationHelper.GetString("ThresholdProcessor_MinThreshold_Desc")));
|
||||
|
||||
Parameters.Add("MaxThreshold", new ProcessorParameter(
|
||||
"MaxThreshold",
|
||||
LocalizationHelper.GetString("ThresholdProcessor_MaxThreshold"),
|
||||
typeof(int), threeQuarterValue, 0, MaxPixelValue,
|
||||
typeof(int), threeQuarterValue, 0, MaxPixelValue,
|
||||
LocalizationHelper.GetString("ThresholdProcessor_MaxThreshold_Desc")));
|
||||
|
||||
Parameters.Add("UseOtsu", new ProcessorParameter(
|
||||
@@ -128,6 +128,8 @@ public class ThresholdProcessor<TDepth> : ImageProcessorBase<TDepth>
|
||||
histogram[data[y, x, 0]]++;
|
||||
|
||||
long totalPixels = (long)w * h;
|
||||
if (totalPixels == 0) return maxVal / 2;
|
||||
|
||||
double totalSum = 0;
|
||||
for (int i = 0; i < levels; i++)
|
||||
totalSum += (double)i * histogram[i];
|
||||
@@ -135,7 +137,7 @@ public class ThresholdProcessor<TDepth> : ImageProcessorBase<TDepth>
|
||||
double bgSum = 0;
|
||||
long bgPixels = 0;
|
||||
double maxVariance = -1;
|
||||
int bestThreshold = 0;
|
||||
int bestThreshold = maxVal / 2; // Default to midpoint if no valid threshold found
|
||||
|
||||
for (int t = 0; t < levels; t++)
|
||||
{
|
||||
@@ -159,4 +161,4 @@ public class ThresholdProcessor<TDepth> : ImageProcessorBase<TDepth>
|
||||
|
||||
return bestThreshold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,6 @@ namespace XP.ImageProcessing.Processors;
|
||||
public class GammaProcessor<TDepth> : ImageProcessorBase<TDepth>
|
||||
where TDepth : struct, IComparable
|
||||
{
|
||||
private byte[] _lookupTable8 = new byte[256];
|
||||
private ushort[] _lookupTable16 = new ushort[65536];
|
||||
private static readonly ILogger _logger = Log.ForContext<GammaProcessor<TDepth>>();
|
||||
|
||||
public GammaProcessor()
|
||||
@@ -64,49 +62,53 @@ public class GammaProcessor<TDepth> : ImageProcessorBase<TDepth>
|
||||
|
||||
if (typeof(TDepth) == typeof(ushort))
|
||||
{
|
||||
BuildLUT16(gamma, gain);
|
||||
var lut16 = BuildLUT16(gamma, gain);
|
||||
var img16 = inputImage as Image<Gray, ushort>;
|
||||
var result = new Image<Gray, ushort>(inputImage.Width, inputImage.Height);
|
||||
Parallel.For(0, inputImage.Height, y =>
|
||||
{
|
||||
for (int x = 0; x < inputImage.Width; x++)
|
||||
result.Data[y, x, 0] = _lookupTable16[img16!.Data[y, x, 0]];
|
||||
result.Data[y, x, 0] = lut16[img16!.Data[y, x, 0]];
|
||||
});
|
||||
_logger.Debug("Process(16bit): Gamma={G}, Gain={Gain}", gamma, gain);
|
||||
return (result as Image<Gray, TDepth>)!;
|
||||
}
|
||||
else
|
||||
{
|
||||
BuildLUT8(gamma, gain);
|
||||
var lut8 = BuildLUT8(gamma, gain);
|
||||
var result = (inputImage as Image<Gray, byte>)!.Clone();
|
||||
int h = inputImage.Height, w = inputImage.Width;
|
||||
for (int y = 0; y < h; y++)
|
||||
for (int x = 0; x < w; x++)
|
||||
result.Data[y, x, 0] = _lookupTable8[result.Data[y, x, 0]];
|
||||
result.Data[y, x, 0] = lut8[result.Data[y, x, 0]];
|
||||
_logger.Debug("Process(8bit): Gamma={G}, Gain={Gain}", gamma, gain);
|
||||
return (result as Image<Gray, TDepth>)!;
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildLUT8(double gamma, double gain)
|
||||
private static byte[] BuildLUT8(double gamma, double gain)
|
||||
{
|
||||
var lut = new byte[256];
|
||||
double invGamma = 1.0 / gamma;
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
double normalized = i / 255.0;
|
||||
double corrected = Math.Pow(normalized, invGamma) * gain;
|
||||
_lookupTable8[i] = (byte)Math.Clamp((int)(corrected * 255.0), 0, 255);
|
||||
lut[i] = (byte)Math.Clamp((int)(corrected * 255.0), 0, 255);
|
||||
}
|
||||
return lut;
|
||||
}
|
||||
|
||||
private void BuildLUT16(double gamma, double gain)
|
||||
private static ushort[] BuildLUT16(double gamma, double gain)
|
||||
{
|
||||
var lut = new ushort[65536];
|
||||
double invGamma = 1.0 / gamma;
|
||||
for (int i = 0; i < 65536; i++)
|
||||
{
|
||||
double normalized = i / 65535.0;
|
||||
double corrected = Math.Pow(normalized, invGamma) * gain;
|
||||
_lookupTable16[i] = (ushort)Math.Clamp((int)(corrected * 65535.0), 0, 65535);
|
||||
lut[i] = (ushort)Math.Clamp((int)(corrected * 65535.0), 0, 65535);
|
||||
}
|
||||
return lut;
|
||||
}
|
||||
}
|
||||
@@ -145,7 +145,8 @@ public class SuperResolutionProcessor : ImageProcessorBase<byte>
|
||||
int w = inputImage.Width;
|
||||
|
||||
// 获取模型输入信息
|
||||
string inputName = session.InputMetadata.Keys.First();
|
||||
string inputName = session.InputMetadata.Keys.FirstOrDefault()
|
||||
?? throw new InvalidOperationException("ONNX model has no input metadata");
|
||||
var inputMeta = session.InputMetadata[inputName];
|
||||
int[] dims = inputMeta.Dimensions;
|
||||
// dims 格式: [1, H, W, C] (NHWC),C 可能是 1 或 3
|
||||
|
||||
@@ -85,7 +85,7 @@ public class BandPassFilterProcessor<TDepth> : ImageProcessorBase<TDepth>
|
||||
if (highCutoff <= lowCutoff) highCutoff = lowCutoff + 10;
|
||||
|
||||
var floatImage = inputImage.Convert<Gray, float>();
|
||||
var imaginaryImage = new Image<Gray, float>(floatImage.Size);
|
||||
using var imaginaryImage = new Image<Gray, float>(floatImage.Size);
|
||||
imaginaryImage.SetZero();
|
||||
|
||||
using (var planes = new Emgu.CV.Util.VectorOfMat())
|
||||
@@ -139,6 +139,8 @@ public class BandPassFilterProcessor<TDepth> : ImageProcessorBase<TDepth>
|
||||
result = (result - minVal) * (255.0 / (maxVal - minVal));
|
||||
}
|
||||
|
||||
floatImage.Dispose();
|
||||
mask.Dispose();
|
||||
complexMat.Dispose();
|
||||
dftMat.Dispose();
|
||||
filteredDft.Dispose();
|
||||
|
||||
@@ -126,7 +126,7 @@ public class HighPassFilterProcessor<TDepth> : ImageProcessorBase<TDepth>
|
||||
/// </summary>
|
||||
private Mat CreateHighPassFilter(int rows, int cols, double d0)
|
||||
{
|
||||
var filter = new Image<Gray, float>(cols, rows);
|
||||
using var filter = new Image<Gray, float>(cols, rows);
|
||||
|
||||
int centerX = cols / 2;
|
||||
int centerY = rows / 2;
|
||||
@@ -141,6 +141,6 @@ public class HighPassFilterProcessor<TDepth> : ImageProcessorBase<TDepth>
|
||||
}
|
||||
}
|
||||
|
||||
return filter.Mat;
|
||||
return filter.Mat.Clone();
|
||||
}
|
||||
}
|
||||
@@ -122,7 +122,7 @@ public class LowPassFilterProcessor<TDepth> : ImageProcessorBase<TDepth>
|
||||
/// </summary>
|
||||
private Mat CreateLowPassFilter(int rows, int cols, double d0)
|
||||
{
|
||||
var filter = new Image<Gray, float>(cols, rows);
|
||||
using var filter = new Image<Gray, float>(cols, rows);
|
||||
|
||||
int centerX = cols / 2;
|
||||
int centerY = rows / 2;
|
||||
@@ -137,6 +137,6 @@ public class LowPassFilterProcessor<TDepth> : ImageProcessorBase<TDepth>
|
||||
}
|
||||
}
|
||||
|
||||
return filter.Mat;
|
||||
return filter.Mat.Clone();
|
||||
}
|
||||
}
|
||||
@@ -106,38 +106,43 @@ public class RemoveOutliersProcessor<TDepth> : ImageProcessorBase<TDepth>
|
||||
// 逐像素比较并替换离群点
|
||||
var result = inputImage.Clone();
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
try
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
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)
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
case "Bright":
|
||||
isOutlier = diff > threshold; // 亮离群点:比邻域中值亮太多
|
||||
break;
|
||||
case "Dark":
|
||||
isOutlier = -diff > threshold; // 暗离群点:比邻域中值暗太多
|
||||
break;
|
||||
case "Both":
|
||||
default:
|
||||
isOutlier = System.Math.Abs(diff) > threshold;
|
||||
break;
|
||||
}
|
||||
double original = Convert.ToDouble(inputImage.Data[y, x, 0]);
|
||||
double median = Convert.ToDouble(medianImage.Data[y, x, 0]);
|
||||
double diff = original - median;
|
||||
|
||||
if (isOutlier)
|
||||
{
|
||||
result.Data[y, x, 0] = medianImage.Data[y, x, 0];
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
medianImage.Dispose();
|
||||
finally
|
||||
{
|
||||
medianImage.Dispose();
|
||||
}
|
||||
|
||||
_logger.Debug("Process: KernelSize={K}, Threshold={T}, Type={Type}",
|
||||
kernelSize, threshold, outlierType);
|
||||
|
||||
Reference in New Issue
Block a user