843c4d67a6
- 新增 HikvisionCameraController 实现 ICameraController - CameraFactory 支持 Basler/Hikvision 动态切换(config.json 配置) - PixelConverter 支持 Bayer RG/GR/GB/BG 8-bit 解码 - 修复采集链断裂问题(finally 中触发下一帧) - 相机设置面板:宽高和像素格式改为只读显示 - NavigationPropertyPanelViewModel 日志和状态文本改为英文 - 添加 MvCameraControl.Net.dll 到 ExternalLibraries
180 lines
6.8 KiB
C#
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;
|
|
}
|
|
}
|