Files
2026-07-24 18:53:53 +08:00

226 lines
8.3 KiB
C#
Raw Permalink 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);
});
}
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-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;
}