226 lines
8.3 KiB
C#
226 lines
8.3 KiB
C#
// ============================================================================
|
||
// 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 → 255,ushort → 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 直接 Clone,ushort 左移 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);
|
||
});
|
||
}
|
||
else
|
||
{
|
||
byte v = (byte)Math.Clamp((int)Math.Round(minV), 0, 255);
|
||
result.SetValue(new Gray(v));
|
||
}
|
||
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);
|
||
});
|
||
}
|
||
else
|
||
{
|
||
ushort v = (ushort)Math.Clamp((int)Math.Round(minV), 0, 65535);
|
||
result.SetValue(new Gray(v));
|
||
}
|
||
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-255,16 位参数范围 0-65535。
|
||
/// 如果旧参数最大值是 255,则对 16 位图自动按比例放大(×256)。
|
||
/// </summary>
|
||
public static int ScaleThreshold<TDepth>(int threshold8bit) where TDepth : struct, IComparable
|
||
=> typeof(TDepth) == typeof(ushort) ? threshold8bit * 256 : threshold8bit;
|
||
}
|