Files
XplorePlane/XP.Camera/Converters/PixelConverter.cs
T
李伟 843c4d67a6 feat: 集成海康威视相机接口
- 新增 HikvisionCameraController 实现 ICameraController
- CameraFactory 支持 Basler/Hikvision 动态切换(config.json 配置)
- PixelConverter 支持 Bayer RG/GR/GB/BG 8-bit 解码
- 修复采集链断裂问题(finally 中触发下一帧)
- 相机设置面板:宽高和像素格式改为只读显示
- NavigationPropertyPanelViewModel 日志和状态文本改为英文
- 添加 MvCameraControl.Net.dll 到 ExternalLibraries
2026-05-18 13:11:26 +08:00

180 lines
6.8 KiB
C#

using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace XP.Camera;
/// <summary>
/// 提供像素数据到 WPF BitmapSource 的转换工具方法。
/// </summary>
public static class PixelConverter
{
/// <summary>
/// 将原始像素数据转换为 WPF 的 BitmapSource 对象。
/// 支持 Mono8、BGR8、RGB8、BGRA8 以及 Bayer 8-bit 格式(自动解码为 BGR24)。
/// 返回的 BitmapSource 已调用 Freeze(),可跨线程访问。
/// </summary>
public static BitmapSource ToBitmapSource(byte[] pixelData, int width, int height, string pixelFormat)
{
ArgumentNullException.ThrowIfNull(pixelData);
if (width <= 0) throw new ArgumentException("Width must be a positive integer.", nameof(width));
if (height <= 0) throw new ArgumentException("Height must be a positive integer.", nameof(height));
ArgumentNullException.ThrowIfNull(pixelFormat);
string normalized = NormalizePixelFormat(pixelFormat);
// Bayer 格式需要解码
if (normalized.StartsWith("Bayer"))
{
byte[] bgrData = DemosaicBayer(pixelData, width, height, normalized);
var bmp = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgr24, null, bgrData, width * 3);
bmp.Freeze();
return bmp;
}
var (format, stride) = normalized switch
{
"Mono8" => (PixelFormats.Gray8, width),
"BGR8" => (PixelFormats.Bgr24, width * 3),
"BGRA8" => (PixelFormats.Bgra32, width * 4),
"RGB8" => (PixelFormats.Rgb24, width * 3),
_ => throw new NotSupportedException($"Pixel format '{pixelFormat}' is not supported.")
};
var bitmap = BitmapSource.Create(width, height, 96, 96, format, null, pixelData, stride);
bitmap.Freeze();
return bitmap;
}
/// <summary>
/// 将不同 SDK 的像素格式名称统一为标准名称。
/// </summary>
private static string NormalizePixelFormat(string pixelFormat)
{
if (pixelFormat is "Mono8" or "BGR8" or "BGRA8" or "RGB8")
return pixelFormat;
var upper = pixelFormat.ToUpperInvariant();
if (upper.Contains("MONO8")) return "Mono8";
if (upper.Contains("BGR8")) return "BGR8";
if (upper.Contains("BGRA8")) return "BGRA8";
if (upper.Contains("RGB8") && !upper.Contains("BAYER")) return "RGB8";
// Bayer 格式
if (upper.Contains("BAYERRG8") || upper.Contains("BAYER_RG8")) return "BayerRG8";
if (upper.Contains("BAYERGR8") || upper.Contains("BAYER_GR8")) return "BayerGR8";
if (upper.Contains("BAYERGB8") || upper.Contains("BAYER_GB8")) return "BayerGB8";
if (upper.Contains("BAYERBG8") || upper.Contains("BAYER_BG8")) return "BayerBG8";
return pixelFormat;
}
/// <summary>
/// 简单 Bayer 解码(双线性插值),输出 BGR24。
/// </summary>
private static byte[] DemosaicBayer(byte[] bayer, int width, int height, string pattern)
{
// pattern: BayerRG8, BayerGR8, BayerGB8, BayerBG8
// RG: R G GR: G R GB: G B BG: B G
// G B B G R G G R
int rRow, rCol; // 红色像素在2x2块中的位置
switch (pattern)
{
case "BayerRG8": rRow = 0; rCol = 0; break;
case "BayerGR8": rRow = 0; rCol = 1; break;
case "BayerGB8": rRow = 1; rCol = 0; break;
case "BayerBG8": rRow = 1; rCol = 1; break;
default: rRow = 0; rCol = 0; break;
}
byte[] bgr = new byte[width * height * 3];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int srcIdx = y * width + x;
int dstIdx = (y * width + x) * 3;
// 确定当前像素在 Bayer 模式中的角色
int py = (y + rRow) % 2; // 0=红行, 1=蓝行
int px = (x + rCol) % 2; // 0=红列/蓝列, 1=绿列
byte r, g, b;
if (py == 0 && px == 0)
{
// 红色像素位置
r = bayer[srcIdx];
g = AvgNeighbors4(bayer, width, height, x, y);
b = AvgDiagonal(bayer, width, height, x, y);
}
else if (py == 1 && px == 1)
{
// 蓝色像素位置
b = bayer[srcIdx];
g = AvgNeighbors4(bayer, width, height, x, y);
r = AvgDiagonal(bayer, width, height, x, y);
}
else if (py == 0 && px == 1)
{
// 绿色像素(红行)
g = bayer[srcIdx];
r = AvgHorizontal(bayer, width, x, y);
b = AvgVertical(bayer, width, height, x, y);
}
else
{
// 绿色像素(蓝行)
g = bayer[srcIdx];
b = AvgHorizontal(bayer, width, x, y);
r = AvgVertical(bayer, width, height, x, y);
}
bgr[dstIdx] = b;
bgr[dstIdx + 1] = g;
bgr[dstIdx + 2] = r;
}
}
return bgr;
}
private static byte AvgNeighbors4(byte[] data, int w, int h, int x, int y)
{
int sum = 0, count = 0;
if (x > 0) { sum += data[y * w + x - 1]; count++; }
if (x < w - 1) { sum += data[y * w + x + 1]; count++; }
if (y > 0) { sum += data[(y - 1) * w + x]; count++; }
if (y < h - 1) { sum += data[(y + 1) * w + x]; count++; }
return count > 0 ? (byte)(sum / count) : (byte)0;
}
private static byte AvgDiagonal(byte[] data, int w, int h, int x, int y)
{
int sum = 0, count = 0;
if (x > 0 && y > 0) { sum += data[(y - 1) * w + x - 1]; count++; }
if (x < w - 1 && y > 0) { sum += data[(y - 1) * w + x + 1]; count++; }
if (x > 0 && y < h - 1) { sum += data[(y + 1) * w + x - 1]; count++; }
if (x < w - 1 && y < h - 1) { sum += data[(y + 1) * w + x + 1]; count++; }
return count > 0 ? (byte)(sum / count) : (byte)0;
}
private static byte AvgHorizontal(byte[] data, int w, int x, int y)
{
int sum = 0, count = 0;
if (x > 0) { sum += data[y * w + x - 1]; count++; }
if (x < w - 1) { sum += data[y * w + x + 1]; count++; }
return count > 0 ? (byte)(sum / count) : (byte)0;
}
private static byte AvgVertical(byte[] data, int w, int h, int x, int y)
{
int sum = 0, count = 0;
if (y > 0) { sum += data[(y - 1) * w + x]; count++; }
if (y < h - 1) { sum += data[(y + 1) * w + x]; count++; }
return count > 0 ? (byte)(sum / count) : (byte)0;
}
}