Files
XplorePlane/XP.ImageProcessing.Processors/Core/PixelDepthHelper.cs
T
XplorePlane Developer 957157ae80 fix: 修复编译错误、BGA向导预览、算子重复插入及状态栏消息
## Compilation fixes
- Fix XAML namespace reference XplorePlane.Converters → XplorePlane.Helpers (EventLogView, StateDisplayView)
- Add missing using for ISampleTypeRepository / JsonSampleTypeRepository in App.xaml.cs
- Fix ILoggerService.Warn() calls using Exception as first arg instead of message string
- Serialize CncProgram to JSON when passing to MatrixLayout (expects string, not CncProgram object)
- Suppress CS8632 nullable annotation warnings project-wide

## BGA wizard fixes
- Fix preview not showing on step 3: force RefreshBgaPreview() when entering final step
- Add step validation in CanNext() to prevent skipping to preview with invalid inputs
- Notify NextCommand.CanExecuteChanged when inputs change
- Fix RunProgress runtime binding error: set Mode=OneWay for ProgressBar.Value

## Operator toolbox: prevent duplicate insertion
- Add static OperatorTarget property for direct dispatch to active pipeline
- PipelineEditorView registers as toolbox target on load (floating window only)
- CNC page sets initial target; PipelineEditorWindow resets to CNC pipeline on close
- '+' button inserts only to the active pipeline instead of broadcasting to all subscribers

## Pipeline editor status bar messages
- Add StatusBarMessageEvent / StatusBarMessagePayload to CommonEvents.cs
- PipelineEditorViewModel publishes status on all ops (add/remove/reorder/save/execute)
- MainViewModel subscribes and displays on main window bottom status bar
- Auto-clear after timeout (3s info, 5s error)

## docs
- Merge three architecture docs into consolidated XplorePlane-架构与结构说明.md
2026-07-24 15:59:16 +08:00

216 lines
7.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================================
// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved.
// 文件名: PixelDepthHelper.cs
// 描述: 像素深度辅助工具,封装 8 位 / 16 位图像的常用类型转换操作
// 作者: 李伟 wei.lw.li@hexagon.com
// ============================================================================
using Emgu.CV;
using Emgu.CV.Structure;
namespace XP.ImageProcessing.Core;
/// <summary>
/// 像素深度辅助工具,封装 byte / ushort 之间的通用操作。
/// </summary>
public static class PixelDepthHelper
{
/// <summary>
/// 返回当前深度的最大像素值(byte → 255ushort → 65535)。
/// </summary>
public static int MaxValue<TDepth>() where TDepth : struct, IComparable
=> typeof(TDepth) == typeof(ushort) ? 65535 : 255;
/// <summary>
/// 将任意深度灰度图转换为 8 位灰度图(用于需要 CV_8U 输入的 OpenCV 函数)。
/// 16 位图按比例缩小到 [0, 255]。
/// </summary>
public static Image<Gray, byte> ToByteImage<TDepth>(Image<Gray, TDepth> src)
where TDepth : struct, IComparable
{
if (typeof(TDepth) == typeof(byte))
return (src as Image<Gray, byte>)!.Clone();
// 16-bit → 8-bit:右移 8 位(等价于除以 256)
var result = new Image<Gray, byte>(src.Width, src.Height);
var srcData = src.Data as ushort[,,];
if (srcData == null)
return src.Convert<Gray, byte>();
int h = src.Height, w = src.Width;
Parallel.For(0, h, y =>
{
for (int x = 0; x < w; x++)
result.Data[y, x, 0] = (byte)(srcData[y, x, 0] >> 8);
});
return result;
}
/// <summary>
/// 将 8 位图升位到目标深度(byte 直接 Cloneushort 左移 8 位)。
/// </summary>
public static Image<Gray, TDepth> FromByteImage<TDepth>(Image<Gray, byte> src)
where TDepth : struct, IComparable
{
if (typeof(TDepth) == typeof(byte))
return (src.Clone() as Image<Gray, TDepth>)!;
var result = new Image<Gray, ushort>(src.Width, src.Height);
int h = src.Height, w = src.Width;
Parallel.For(0, h, y =>
{
for (int x = 0; x < w; x++)
result.Data[y, x, 0] = (ushort)(src.Data[y, x, 0] << 8);
});
return (result as Image<Gray, TDepth>)!;
}
/// <summary>
/// 将 float 图像归一化到目标深度范围后转换。
/// 注意:此方法做 min-max 归一化,适用于边缘检测、频域滤波等值域不确定的场景。
/// 对于线性变换(对比度/亮度调整)请使用 <see cref="FromFloatImageClamped{TDepth}"/>。
/// </summary>
public static Image<Gray, TDepth> FromFloatImage<TDepth>(Image<Gray, float> src)
where TDepth : struct, IComparable
{
int maxVal = MaxValue<TDepth>();
int h = src.Height, w = src.Width;
// 求最小/最大值
double minV = double.MaxValue, maxV = double.MinValue;
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
float v = src.Data[y, x, 0];
if (v < minV) minV = v;
if (v > maxV) maxV = v;
}
}
double range = maxV - minV;
if (typeof(TDepth) == typeof(byte))
{
var result = new Image<Gray, byte>(w, h);
if (range > 1e-9)
{
Parallel.For(0, h, y =>
{
for (int x = 0; x < w; x++)
result.Data[y, x, 0] = (byte)Math.Clamp((int)((src.Data[y, x, 0] - minV) / range * 255.0), 0, 255);
});
}
return (result as Image<Gray, TDepth>)!;
}
else
{
var result = new Image<Gray, ushort>(w, h);
if (range > 1e-9)
{
Parallel.For(0, h, y =>
{
for (int x = 0; x < w; x++)
result.Data[y, x, 0] = (ushort)Math.Clamp((int)((src.Data[y, x, 0] - minV) / range * 65535.0), 0, 65535);
});
}
return (result as Image<Gray, TDepth>)!;
}
}
/// <summary>
/// 将 float 图像直接 clamp 到目标深度范围后转换(不做归一化)。
/// 适用于线性变换(对比度、亮度、乘法)等期望保持绝对像素值语义的场景。
/// 超出 [0, maxValue] 的值被截断。
/// </summary>
public static Image<Gray, TDepth> FromFloatImageClamped<TDepth>(Image<Gray, float> src)
where TDepth : struct, IComparable
{
int maxVal = MaxValue<TDepth>();
int h = src.Height, w = src.Width;
if (typeof(TDepth) == typeof(byte))
{
var result = new Image<Gray, byte>(w, h);
Parallel.For(0, h, y =>
{
for (int x = 0; x < w; x++)
result.Data[y, x, 0] = (byte)Math.Clamp((int)Math.Round(src.Data[y, x, 0]), 0, 255);
});
return (result as Image<Gray, TDepth>)!;
}
else
{
var result = new Image<Gray, ushort>(w, h);
Parallel.For(0, h, y =>
{
for (int x = 0; x < w; x++)
result.Data[y, x, 0] = (ushort)Math.Clamp((int)Math.Round(src.Data[y, x, 0]), 0, 65535);
});
return (result as Image<Gray, TDepth>)!;
}
}
/// <summary>
/// 将目标深度图像转换为 float 图像(值域归一化到 0-1)。
/// </summary>
public static Image<Gray, float> ToFloatNormalized<TDepth>(Image<Gray, TDepth> src)
where TDepth : struct, IComparable
{
double maxVal = MaxValue<TDepth>();
int h = src.Height, w = src.Width;
var result = new Image<Gray, float>(w, h);
if (typeof(TDepth) == typeof(byte))
{
var srcByte = src.Data as byte[,,];
Parallel.For(0, h, y =>
{
for (int x = 0; x < w; x++)
result.Data[y, x, 0] = srcByte![y, x, 0] / 255f;
});
}
else
{
var srcUshort = src.Data as ushort[,,];
Parallel.For(0, h, y =>
{
for (int x = 0; x < w; x++)
result.Data[y, x, 0] = srcUshort![y, x, 0] / 65535f;
});
}
return result;
}
/// <summary>
/// 读取单个像素值(统一返回 int)。
/// </summary>
public static int ReadPixel<TDepth>(Image<Gray, TDepth> img, int y, int x)
where TDepth : struct, IComparable
{
if (typeof(TDepth) == typeof(byte))
return (img.Data as byte[,,])![y, x, 0];
return (img.Data as ushort[,,])![y, x, 0];
}
/// <summary>
/// 写入单个像素值(从 int 转换)。
/// </summary>
public static void WritePixel<TDepth>(Image<Gray, TDepth> img, int y, int x, int value)
where TDepth : struct, IComparable
{
if (typeof(TDepth) == typeof(byte))
(img.Data as byte[,,])![y, x, 0] = (byte)Math.Clamp(value, 0, 255);
else
(img.Data as ushort[,,])![y, x, 0] = (ushort)Math.Clamp(value, 0, 65535);
}
/// <summary>
/// 将参数中的阈值按目标深度归一化:
/// 8 位参数范围 0-25516 位参数范围 0-65535。
/// 如果旧参数最大值是 255,则对 16 位图自动按比例放大(×256)。
/// </summary>
public static int ScaleThreshold<TDepth>(int threshold8bit) where TDepth : struct, IComparable
=> typeof(TDepth) == typeof(ushort) ? threshold8bit * 256 : threshold8bit;
}