diff --git a/XP.ImageProcessing.Processors/图像变换/ThresholdProcessor.cs b/XP.ImageProcessing.Processors/图像变换/ThresholdProcessor.cs index f170dff..239552c 100644 --- a/XP.ImageProcessing.Processors/图像变换/ThresholdProcessor.cs +++ b/XP.ImageProcessing.Processors/图像变换/ThresholdProcessor.cs @@ -73,27 +73,30 @@ public class ThresholdProcessor : ImageProcessorBase 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++) + unsafe { - for (int x = 0; x < width; x++) + byte* srcPtr = (byte*)inputImage.Mat.DataPointer; + byte* dstPtr = (byte*)result.Mat.DataPointer; + int srcStep = inputImage.Mat.Step; + int dstStep = result.Mat.Step; + + for (int y = 0; y < height; y++) { - byte pixelValue = inputData[y, x, 0]; - outputData[y, x, 0] = (pixelValue >= minThreshold && pixelValue <= maxThreshold) - ? (byte)255 - : (byte)0; + byte* srcRow = srcPtr + y * srcStep; + byte* dstRow = dstPtr + y * dstStep; + for (int x = 0; x < width; x++) + { + byte val = srcRow[x]; + dstRow[x] = (val >= minThreshold && val <= maxThreshold) ? (byte)255 : (byte)0; + } } } diff --git a/XP.ImageProcessing.Processors/图像增强/ContrastProcessor.cs b/XP.ImageProcessing.Processors/图像增强/ContrastProcessor.cs index d7219e8..5328eab 100644 --- a/XP.ImageProcessing.Processors/图像增强/ContrastProcessor.cs +++ b/XP.ImageProcessing.Processors/图像增强/ContrastProcessor.cs @@ -135,38 +135,94 @@ public class ContrastProcessor : ImageProcessorBase int tileSize = 8; int width = inputImage.Width; int height = inputImage.Height; + byte[,,] srcData = inputImage.Data; + // 计算分块数 int tilesX = (width + tileSize - 1) / tileSize; int tilesY = (height + tileSize - 1) / tileSize; + int actualTileW = (width + tilesX - 1) / tilesX; + int actualTileH = (height + tilesY - 1) / tilesY; - var result = new Image(width, height); - + // 为每个 tile 计算带 clip limit 的均衡化映射表 + var luts = new byte[tilesY, tilesX, 256]; 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); + int x0 = tx * actualTileW; + int y0 = ty * actualTileH; + int x1 = Math.Min(x0 + actualTileW, width); + int y1 = Math.Min(y0 + actualTileH, height); + int tilePixels = (x1 - x0) * (y1 - y0); - var roi = new System.Drawing.Rectangle(x, y, w, h); - inputImage.ROI = roi; - var tile = inputImage.Copy(); - inputImage.ROI = System.Drawing.Rectangle.Empty; + // 构建直方图 + var hist = new int[256]; + for (int y = y0; y < y1; y++) + for (int x = x0; x < x1; x++) + hist[srcData[y, x, 0]]++; - var equalizedTile = new Image(tile.Size); - CvInvoke.EqualizeHist(tile, equalizedTile); + // Clip limit 裁剪并重新分配 + int clipThreshold = (int)(clipLimit * tilePixels / 256); + if (clipThreshold > 0) + { + int excess = 0; + for (int i = 0; i < 256; i++) + { + if (hist[i] > clipThreshold) + { + excess += hist[i] - clipThreshold; + hist[i] = clipThreshold; + } + } + int avgInc = excess / 256; + int remainder = excess - avgInc * 256; + for (int i = 0; i < 256; i++) + hist[i] += avgInc + (i < remainder ? 1 : 0); + } - result.ROI = roi; - equalizedTile.CopyTo(result); - result.ROI = System.Drawing.Rectangle.Empty; - - tile.Dispose(); - equalizedTile.Dispose(); + // 构建 CDF 映射表 + int sum = 0; + for (int i = 0; i < 256; i++) + { + sum += hist[i]; + luts[ty, tx, i] = (byte)Math.Clamp(sum * 255 / tilePixels, 0, 255); + } } } - _logger.Debug("ApplyCLAHE"); + + // 双线性插值生成结果 + var result = new Image(width, height); + byte[,,] dstData = result.Data; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + // 计算当前像素所在 tile 的中心坐标 + double fx = (double)x / actualTileW - 0.5; + double fy = (double)y / actualTileH - 0.5; + int tx0 = Math.Max(0, (int)fx); + int ty0 = Math.Max(0, (int)fy); + int tx1 = Math.Min(tx0 + 1, tilesX - 1); + int ty1 = Math.Min(ty0 + 1, tilesY - 1); + double ax = fx - tx0; + double ay = fy - ty0; + ax = Math.Clamp(ax, 0, 1); + ay = Math.Clamp(ay, 0, 1); + + byte val = srcData[y, x, 0]; + double v00 = luts[ty0, tx0, val]; + double v10 = luts[ty0, tx1, val]; + double v01 = luts[ty1, tx0, val]; + double v11 = luts[ty1, tx1, val]; + + double interpolated = v00 * (1 - ax) * (1 - ay) + v10 * ax * (1 - ay) + + v01 * (1 - ax) * ay + v11 * ax * ay; + dstData[y, x, 0] = (byte)Math.Clamp((int)(interpolated + 0.5), 0, 255); + } + } + + _logger.Debug("ApplyCLAHE: ClipLimit={ClipLimit}, TileSize={TileSize}", clipLimit, tileSize); return result; } } \ No newline at end of file diff --git a/XP.ImageProcessing.Processors/检测分析/QfnLeadPadVoidProcessor.cs b/XP.ImageProcessing.Processors/检测分析/QfnLeadPadVoidProcessor.cs index 5977eae..f309525 100644 --- a/XP.ImageProcessing.Processors/检测分析/QfnLeadPadVoidProcessor.cs +++ b/XP.ImageProcessing.Processors/检测分析/QfnLeadPadVoidProcessor.cs @@ -291,15 +291,23 @@ public class QfnLeadPadVoidProcessor : ImageProcessorBase // 双阈值分割(X-Ray正片:焊点=暗区域,灰度在[threshLow, threshHigh]范围内判为焊点) var binary = new Image(w, h); - byte[,,] srcData = blurred.Data; - byte[,,] dstData = binary.Data; - for (int y = 0; y < h; y++) + unsafe { - for (int x = 0; x < w; x++) + byte* srcPtr = (byte*)blurred.Mat.DataPointer; + byte* dstPtr = (byte*)binary.Mat.DataPointer; + int srcStep = blurred.Mat.Step; + int dstStep = binary.Mat.Step; + + for (int y = 0; y < h; y++) { - byte val = srcData[y, x, 0]; - dstData[y, x, 0] = (val >= threshLow && val <= threshHigh) ? (byte)255 : (byte)0; + byte* srcRow = srcPtr + y * srcStep; + byte* dstRow = dstPtr + y * dstStep; + for (int x = 0; x < w; x++) + { + byte val = srcRow[x]; + dstRow[x] = (val >= threshLow && val <= threshHigh) ? (byte)255 : (byte)0; + } } } @@ -410,18 +418,28 @@ public class QfnLeadPadVoidProcessor : ImageProcessorBase // 双阈值分割(正片模式:空洞=亮区域,灰度在[voidThreshLow, voidThreshHigh]范围内判为空洞) 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++) + unsafe { - for (int x = 0; x < w; x++) + byte* srcPtr = (byte*)input.Mat.DataPointer; + byte* dstPtr = (byte*)voidImg.Mat.DataPointer; + byte* mskPtr = (byte*)mask.Mat.DataPointer; + int srcStep = input.Mat.Step; + int dstStep = voidImg.Mat.Step; + int mskStep = mask.Mat.Step; + + for (int y = 0; y < h; y++) { - if (maskData[y, x, 0] > 0) + byte* srcRow = srcPtr + y * srcStep; + byte* dstRow = dstPtr + y * dstStep; + byte* mskRow = mskPtr + y * mskStep; + for (int x = 0; x < w; x++) { - byte val = srcData[y, x, 0]; - dstData[y, x, 0] = (val >= voidThreshLow && val <= voidThreshHigh) ? (byte)255 : (byte)0; + if (mskRow[x] > 0) + { + byte val = srcRow[x]; + dstRow[x] = (val >= voidThreshLow && val <= voidThreshHigh) ? (byte)255 : (byte)0; + } } } } diff --git a/XP.ImageProcessing.Processors/检测分析/VoidMeasurementProcessor.cs b/XP.ImageProcessing.Processors/检测分析/VoidMeasurementProcessor.cs index b63d591..a5ecc82 100644 --- a/XP.ImageProcessing.Processors/检测分析/VoidMeasurementProcessor.cs +++ b/XP.ImageProcessing.Processors/检测分析/VoidMeasurementProcessor.cs @@ -125,18 +125,28 @@ public class VoidMeasurementProcessor : ImageProcessorBase // ── 双阈值分割提取气泡(亮区域) ── 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++) + unsafe { - for (int x = 0; x < w; x++) + byte* srcPtr = (byte*)blurred.Mat.DataPointer; + byte* dstPtr = (byte*)voidImg.Mat.DataPointer; + byte* mskPtr = (byte*)roiMask.Mat.DataPointer; + int srcStep = blurred.Mat.Step; + int dstStep = voidImg.Mat.Step; + int mskStep = roiMask.Mat.Step; + + for (int y = 0; y < h; y++) { - if (maskData[y, x, 0] > 0) + byte* srcRow = srcPtr + y * srcStep; + byte* dstRow = dstPtr + y * dstStep; + byte* mskRow = mskPtr + y * mskStep; + for (int x = 0; x < w; x++) { - byte val = srcData[y, x, 0]; - dstData[y, x, 0] = (val >= minThresh && val <= maxThresh) ? (byte)255 : (byte)0; + if (mskRow[x] > 0) + { + byte val = srcRow[x]; + dstRow[x] = (val >= minThresh && val <= maxThresh) ? (byte)255 : (byte)0; + } } } } diff --git a/XP.ImageProcessing.Processors/滤波处理/GaussianBlurProcessor.cs b/XP.ImageProcessing.Processors/滤波处理/GaussianBlurProcessor.cs index ff34434..0ff8bd2 100644 --- a/XP.ImageProcessing.Processors/滤波处理/GaussianBlurProcessor.cs +++ b/XP.ImageProcessing.Processors/滤波处理/GaussianBlurProcessor.cs @@ -23,7 +23,7 @@ namespace XP.ImageProcessing.Processors; /// public class GaussianBlurProcessor : ImageProcessorBase { - private static readonly ILogger _logger = Log.ForContext(); + private static readonly ILogger _logger = Log.ForContext(); public GaussianBlurProcessor() {