气泡测量第三步:魔棒flood fill,灰度阈值连通区域标记,空隙率实时计算
This commit is contained in:
@@ -1183,12 +1183,131 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
||||
|
||||
private void ApplyBrushAt(Point pos)
|
||||
{
|
||||
// 画笔/橡皮:暂时空实现,下一步实现
|
||||
// 画笔/橡皮:下一步实现
|
||||
}
|
||||
|
||||
private void UpdateBubbleResult()
|
||||
{
|
||||
// 计算空隙率:暂时空实现,下一步实现
|
||||
if (_bubbleMask == null || !_bubbleRoi.HasValue) return;
|
||||
var roi = _bubbleRoi.Value;
|
||||
int w = _bubbleMask.PixelWidth, h = _bubbleMask.PixelHeight;
|
||||
int stride = w * 4;
|
||||
var pixels = new byte[stride * h];
|
||||
_bubbleMask.CopyPixels(pixels, stride, 0);
|
||||
|
||||
int roiX0 = Math.Max(0, (int)roi.X), roiY0 = Math.Max(0, (int)roi.Y);
|
||||
int roiX1 = Math.Min(w, (int)roi.Right), roiY1 = Math.Min(h, (int)roi.Bottom);
|
||||
|
||||
int roiArea = 0, voidArea = 0;
|
||||
for (int y = roiY0; y < roiY1; y++)
|
||||
for (int x = roiX0; x < roiX1; x++)
|
||||
{
|
||||
roiArea++;
|
||||
int idx = (y * w + x) * 4;
|
||||
if (pixels[idx + 3] > 0) voidArea++; // alpha > 0 表示已标记
|
||||
}
|
||||
|
||||
double voidRate = roiArea > 0 ? voidArea * 100.0 / roiArea : 0;
|
||||
RaiseMeasureCompleted(roi.TopLeft, roi.BottomRight, voidRate, 1, "BubbleVoid");
|
||||
}
|
||||
|
||||
/// <summary>魔棒:在点击位置做 flood fill</summary>
|
||||
public void WandFloodFill(Point pos)
|
||||
{
|
||||
if (_bubbleMask == null || !_bubbleRoi.HasValue || ImageSource == null) return;
|
||||
|
||||
var roi = _bubbleRoi.Value;
|
||||
int px = (int)pos.X, py = (int)pos.Y;
|
||||
if (!roi.Contains(pos)) return;
|
||||
|
||||
// 获取灰度像素
|
||||
var gray = GetGrayscalePixels();
|
||||
if (gray == null) return;
|
||||
|
||||
int w = _bubbleMask.PixelWidth, h = _bubbleMask.PixelHeight;
|
||||
if (px < 0 || px >= w || py < 0 || py >= h) return;
|
||||
|
||||
int seedVal = gray[py * w + px];
|
||||
int lo = _bubbleThreshold, hi = _bubbleThreshold;
|
||||
|
||||
// BFS flood fill
|
||||
var visited = new bool[w * h];
|
||||
var queue = new System.Collections.Generic.Queue<(int x, int y)>();
|
||||
queue.Enqueue((px, py));
|
||||
visited[py * w + px] = true;
|
||||
|
||||
int roiX0 = Math.Max(0, (int)roi.X), roiY0 = Math.Max(0, (int)roi.Y);
|
||||
int roiX1 = Math.Min(w, (int)roi.Right), roiY1 = Math.Min(h, (int)roi.Bottom);
|
||||
|
||||
var filled = new System.Collections.Generic.List<(int x, int y)>();
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var (cx, cy) = queue.Dequeue();
|
||||
int val = gray[cy * w + cx];
|
||||
|
||||
// 阈值判断:与种子点灰度差在阈值范围内
|
||||
if (Math.Abs(val - seedVal) > lo) continue;
|
||||
// 必须在 ROI 内
|
||||
if (cx < roiX0 || cx >= roiX1 || cy < roiY0 || cy >= roiY1) continue;
|
||||
|
||||
filled.Add((cx, cy));
|
||||
|
||||
// 四邻域
|
||||
int[] dx = { -1, 1, 0, 0 }, dy = { 0, 0, -1, 1 };
|
||||
for (int d = 0; d < 4; d++)
|
||||
{
|
||||
int nx = cx + dx[d], ny = cy + dy[d];
|
||||
if (nx >= roiX0 && nx < roiX1 && ny >= roiY0 && ny < roiY1 && !visited[ny * w + nx])
|
||||
{
|
||||
visited[ny * w + nx] = true;
|
||||
queue.Enqueue((nx, ny));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (filled.Count == 0) return;
|
||||
|
||||
// 写入 mask(橙色半透明)
|
||||
int stride = w * 4;
|
||||
var maskPixels = new byte[stride * h];
|
||||
_bubbleMask.CopyPixels(maskPixels, stride, 0);
|
||||
|
||||
foreach (var (fx, fy) in filled)
|
||||
{
|
||||
int idx = (fy * w + fx) * 4;
|
||||
maskPixels[idx + 0] = 0; // B
|
||||
maskPixels[idx + 1] = 140; // G
|
||||
maskPixels[idx + 2] = 255; // R (橙色)
|
||||
maskPixels[idx + 3] = 180; // A
|
||||
}
|
||||
|
||||
_bubbleMask.WritePixels(new System.Windows.Int32Rect(0, 0, w, h), maskPixels, stride, 0);
|
||||
UpdateBubbleResult();
|
||||
}
|
||||
|
||||
/// <summary>从 ImageSource 提取灰度像素数组</summary>
|
||||
private byte[] GetGrayscalePixels()
|
||||
{
|
||||
if (ImageSource is not BitmapSource bmp) return null;
|
||||
|
||||
int w = bmp.PixelWidth, h = bmp.PixelHeight;
|
||||
if (w != (int)CanvasWidth || h != (int)CanvasHeight) return null;
|
||||
|
||||
// 转为 Bgra32
|
||||
var converted = new FormatConvertedBitmap(bmp, PixelFormats.Bgra32, null, 0);
|
||||
int stride = w * 4;
|
||||
var pixels = new byte[stride * h];
|
||||
converted.CopyPixels(pixels, stride, 0);
|
||||
|
||||
// 提取灰度
|
||||
var gray = new byte[w * h];
|
||||
for (int i = 0; i < w * h; i++)
|
||||
{
|
||||
int idx = i * 4;
|
||||
gray[i] = (byte)(pixels[idx + 2] * 0.299 + pixels[idx + 1] * 0.587 + pixels[idx] * 0.114);
|
||||
}
|
||||
return gray;
|
||||
}
|
||||
|
||||
/// <summary>清除气泡测量所有状态</summary>
|
||||
@@ -1668,7 +1787,14 @@ namespace XP.ImageProcessing.RoiControl.Controls
|
||||
{
|
||||
Point clickPosition = e.GetPosition(mainCanvas);
|
||||
if (IsMeasuring)
|
||||
{
|
||||
HandleMeasureClick(clickPosition);
|
||||
// 魔棒点击
|
||||
if (CurrentMeasureMode == Models.MeasureMode.BubbleMeasure && _bubbleTool == BubbleSubTool.Wand && _bubbleRoi.HasValue)
|
||||
{
|
||||
WandFloodFill(clickPosition);
|
||||
}
|
||||
}
|
||||
OnCanvasClicked(clickPosition);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user