优化:修复Logger类型错误,重写CLAHE算法,像素遍历改用unsafe指针加速

This commit is contained in:
李伟
2026-05-26 13:28:52 +08:00
parent 82b7c32147
commit 030433cc92
5 changed files with 140 additions and 53 deletions
@@ -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;
}
}
}
@@ -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<Gray, byte>(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<Gray, byte>(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<Gray, byte>(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;
}
}
@@ -291,15 +291,23 @@ public class QfnLeadPadVoidProcessor : ImageProcessorBase
// 双阈值分割(X-Ray正片:焊点=暗区域,灰度在[threshLow, threshHigh]范围内判为焊点)
var binary = new Image<Gray, byte>(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<Gray, byte>(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;
}
}
}
}
@@ -125,18 +125,28 @@ public class VoidMeasurementProcessor : ImageProcessorBase
// ── 双阈值分割提取气泡(亮区域) ──
var voidImg = new Image<Gray, byte>(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;
}
}
}
}
@@ -23,7 +23,7 @@ namespace XP.ImageProcessing.Processors;
/// </summary>
public class GaussianBlurProcessor : ImageProcessorBase
{
private static readonly ILogger _logger = Log.ForContext<GammaProcessor>();
private static readonly ILogger _logger = Log.ForContext<GaussianBlurProcessor>();
public GaussianBlurProcessor()
{