CNC高级模块的运行后的可视化

This commit is contained in:
zhengxuan.zhang
2026-05-19 13:11:47 +08:00
parent 80c86e2ed7
commit eb6ee48a5e
4 changed files with 147 additions and 1 deletions
@@ -216,6 +216,9 @@ public class BgaVoidRateProcessor : ImageProcessorBase
OutputData["Thickness"] = thickness;
OutputData["ResultText"] = $"Void: {overallVoidRate:F1}% | {classification} | BGA×{bgaResults.Count}";
// 渲染带标注的彩色结果图像
OutputData["RenderedResultImage"] = RenderAnnotatedResult(inputImage, bgaResults, voidLimit, thickness);
roiMask?.Dispose();
return inputImage.Clone();
}
@@ -372,6 +375,67 @@ public class BgaVoidRateProcessor : ImageProcessorBase
mask.Dispose();
voidImg.Dispose();
}
/// <summary>
/// 渲染带标注的彩色结果图像(轮廓、编号、气泡填充、总览信息)
/// </summary>
private Image<Bgr, byte> RenderAnnotatedResult(Image<Gray, byte> grayImage, List<BgaBallInfo> bgaResults, double voidLimit, int thickness)
{
var colorImage = new Image<Bgr, byte>(grayImage.Width, grayImage.Height);
CvInvoke.CvtColor(grayImage, colorImage, ColorConversion.Gray2Bgr);
if (bgaResults.Count == 0)
return colorImage;
// 半透明气泡填充
var overlay = colorImage.Clone();
foreach (var bga in bgaResults)
{
var fillColor = new MCvScalar(0, 200, 255);
foreach (var v in bga.Voids)
{
if (v.ContourPoints.Length > 0)
{
using var vop = new VectorOfPoint(v.ContourPoints);
using var vvop = new VectorOfVectorOfPoint(vop);
CvInvoke.DrawContours(overlay, vvop, 0, fillColor, -1);
}
}
}
CvInvoke.AddWeighted(overlay, 0.4, colorImage, 0.6, 0, colorImage);
overlay.Dispose();
// 绘制焊球轮廓 + 编号
int ngCount = 0;
foreach (var bga in bgaResults)
{
var bgaColor = bga.Classification == "PASS"
? new MCvScalar(0, 255, 0) : new MCvScalar(0, 0, 255);
if (bga.Classification != "PASS") ngCount++;
if (bga.ContourPoints.Length > 0)
{
using var vop = new VectorOfPoint(bga.ContourPoints);
using var vvop = new VectorOfVectorOfPoint(vop);
CvInvoke.DrawContours(colorImage, vvop, 0, bgaColor, thickness);
}
var bbox = CvInvoke.BoundingRectangle(new VectorOfPoint(bga.ContourPoints));
CvInvoke.PutText(colorImage, $"#{bga.Index}",
new Point(bbox.X + bbox.Width / 2 - 10, bbox.Bottom + 16),
FontFace.HersheySimplex, 0.45, new MCvScalar(255, 100, 0), 2);
}
// 左上角总览
int okCount = bgaResults.Count - ngCount;
var overallColor = ngCount > 0 ? new MCvScalar(0, 0, 255) : new MCvScalar(0, 255, 0);
CvInvoke.PutText(colorImage,
$"Total: {bgaResults.Count} | OK: {okCount} | NG: {ngCount}",
new Point(10, 25),
FontFace.HersheySimplex, 0.55, overallColor, 2);
return colorImage;
}
}
/// <summary>
@@ -207,12 +207,65 @@ public class VoidMeasurementProcessor : ImageProcessorBase
OutputData["Voids"] = voids;
OutputData["ResultText"] = $"Void: {voidRate:F1}% | {classification} | {voids.Count} voids | ROI: {roiArea}px";
// 渲染带标注的彩色结果图像
OutputData["RenderedResultImage"] = RenderAnnotatedResult(inputImage, voids, voidRate, voidLimit, classification);
blurred.Dispose();
voidImg.Dispose();
roiMask.Dispose();
return inputImage.Clone();
}
/// <summary>
/// 渲染带标注的彩色结果图像(轮廓、编号、半透明填充、总览信息)
/// </summary>
private Image<Bgr, byte> RenderAnnotatedResult(Image<Gray, byte> grayImage, List<VoidRegionInfo> voids, double voidRate, double voidLimit, string classification)
{
var colorImage = new Image<Bgr, byte>(grayImage.Width, grayImage.Height);
CvInvoke.CvtColor(grayImage, colorImage, ColorConversion.Gray2Bgr);
if (voids.Count == 0)
return colorImage;
// 半透明气泡填充
var overlay = colorImage.Clone();
foreach (var v in voids)
{
if (v.ContourPoints.Length > 0)
{
using var vop = new VectorOfPoint(v.ContourPoints);
using var vvop = new VectorOfVectorOfPoint(vop);
CvInvoke.DrawContours(overlay, vvop, 0, new MCvScalar(0, 200, 255), -1);
}
}
CvInvoke.AddWeighted(overlay, 0.4, colorImage, 0.6, 0, colorImage);
overlay.Dispose();
// 绘制轮廓 + 编号
foreach (var v in voids)
{
if (v.ContourPoints.Length > 0)
{
using var vop = new VectorOfPoint(v.ContourPoints);
using var vvop = new VectorOfVectorOfPoint(vop);
CvInvoke.DrawContours(colorImage, vvop, 0, new MCvScalar(0, 255, 255), 1);
}
CvInvoke.PutText(colorImage, $"#{v.Index}",
new Point((int)v.CenterX - 8, (int)v.CenterY + 5),
FontFace.HersheySimplex, 0.35, new MCvScalar(255, 100, 0), 1);
}
// 左上角总览
var overallColor = classification == "PASS"
? new MCvScalar(0, 255, 0) : new MCvScalar(0, 0, 255);
CvInvoke.PutText(colorImage,
$"Void: {voidRate:F1}% | Limit: {voidLimit:F0}% | {voids.Count} voids | {classification}",
new Point(10, 25),
FontFace.HersheySimplex, 0.5, overallColor, 2);
return colorImage;
}
}
/// <summary>
@@ -67,5 +67,17 @@ namespace XplorePlane.Services
return BitmapSource.Create(width, height, 96, 96, PixelFormats.Gray8, null, pixels, stride);
}
public static BitmapSource ToBitmapSourceFromBgr(Image<Bgr, byte> emguImage)
{
if (emguImage == null) throw new ArgumentNullException(nameof(emguImage));
int width = emguImage.Width;
int height = emguImage.Height;
byte[] pixels = emguImage.Bytes;
int stride = pixels.Length / height;
return BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgr24, null, pixels, stride);
}
}
}
@@ -258,13 +258,30 @@ namespace XplorePlane.Services
var processedEmgu = processor.Process(emguImage);
progress?.Report(0.9);
var result = ImageConverter.ToBitmapSource(processedEmgu);
BitmapSource result;
// 如果处理器输出了渲染后的彩色结果图像,优先使用它
if (processor.OutputData.TryGetValue("RenderedResultImage", out var renderedObj)
&& renderedObj is Emgu.CV.Image<Emgu.CV.Structure.Bgr, byte> renderedImage)
{
result = ImageConverter.ToBitmapSourceFromBgr(renderedImage);
renderedImage.Dispose();
}
else
{
result = ImageConverter.ToBitmapSource(processedEmgu);
}
result.Freeze();
progress?.Report(1.0);
var snapshot = new Dictionary<string, object>(processor.OutputData.Count);
foreach (var kv in processor.OutputData)
{
// 不将大型图像对象序列化到快照中
if (kv.Key == "RenderedResultImage") continue;
snapshot[kv.Key] = kv.Value;
}
return (result, snapshot);
}