实时视频场景下C#中Bitmap转OnnxRuntime Tensor提速方案求助
优化OnnxRuntime图像转Tensor的性能方案
你的问题很典型——用GetPixel逐像素遍历确实是性能瓶颈,因为每次调用GetPixel都会涉及托管代码到非托管GDI+内存的跨边界操作,对于视频帧这种高分辨率图像来说,300ms的耗时完全在意料之中。下面是几个能大幅提升转换速度的方案,按优先级排序:
1. 使用Bitmap.LockBits直接操作内存(最快,无额外依赖)
LockBits可以把Bitmap的像素数据锁定在内存中,直接通过指针或Span访问原始字节,避免了逐像素的跨边界开销,性能能提升一个数量级以上。
示例代码(支持24位和32位RGB图像)
public unsafe Tensor<float> ConvertImageToFloatTensorFast(Bitmap image) { // 保持和原代码一致的Tensor维度:[batch, height, width, channel] var tensor = new DenseTensor<float>(new[] { 1, image.Height, image.Width, 3 }); // 锁定图像内存,获取像素数据指针 var rect = new Rectangle(0, 0, image.Width, image.Height); var bitmapData = image.LockBits(rect, ImageLockMode.ReadOnly, image.PixelFormat); try { int bytesPerPixel = Image.GetPixelFormatSize(image.PixelFormat) / 8; int stride = bitmapData.Stride; byte* ptr = (byte*)bitmapData.Scan0; // 遍历每一行每一列 for (int y = 0; y < image.Height; y++) { byte* row = ptr + (y * stride); for (int x = 0; x < image.Width; x++) { // 注意:GDI+的Bitmap默认是BGR存储顺序,不是RGB byte b = row[x * bytesPerPixel]; byte g = row[x * bytesPerPixel + 1]; byte r = row[x * bytesPerPixel + 2]; // 归一化到0-1范围,填充到Tensor tensor[0, y, x, 0] = r / 255f; tensor[0, y, x, 1] = g / 255f; tensor[0, y, x, 2] = b / 255f; } } } finally { // 必须解锁,否则会导致内存泄漏 image.UnlockBits(bitmapData); } return tensor; }
关键注意点:
- 要在项目属性的Build标签中开启
Allow unsafe code选项 - 如果是32位带Alpha通道的图像,要跳过Alpha字节(
x*bytesPerPixel+3的位置) - 不要忘记在
finally块中解锁图像内存,避免资源泄漏
2. 使用现代图像处理库(代码更简洁,性能接近LockBits)
如果你的项目基于.NET Core/.NET 5+,推荐使用ImageSharp或SkiaSharp这类跨平台、高性能的图像处理库,它们的API设计更高效,不需要unsafe代码,同时避免了GDI+的局限性。
ImageSharp示例代码:
using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; public Tensor<float> ConvertImageToFloatTensorWithImageSharp(Image<Rgb24> image) { var tensor = new DenseTensor<float>(new[] { 1, image.Height, image.Width, 3 }); // 直接遍历ImageSharp的像素,无额外跨边界开销 image.ProcessPixelRows(accessor => { for (int y = 0; y < accessor.Height; y++) { var row = accessor.GetRowSpan(y); for (int x = 0; x < row.Length; x++) { var pixel = row[x]; tensor[0, y, x, 0] = pixel.R / 255f; tensor[0, y, x, 1] = pixel.G / 255f; tensor[0, y, x, 2] = pixel.B / 255f; } } }); return tensor; }
优势:
- 完全类型安全,不需要unsafe代码
- 跨平台支持(Windows/Linux/macOS)
- 内置缩放、裁剪等预处理功能,可以和Tensor转换合并操作,进一步降低端到端耗时
3. 额外优化建议
- 内存复用:如果视频帧尺寸固定,预先分配好Tensor对象,避免每次转换都创建新的
DenseTensor,减少内存分配和GC开销 - 预处理合并:如果模型要求输入尺寸和视频帧不一致,尽量在转换Tensor前完成缩放、裁剪操作(用LockBits或ImageSharp的API),避免先转Tensor再处理的冗余步骤
- 并行处理:可以尝试用
Parallel.For遍历像素行(注意Tensor的线程安全性,DenseTensor本身支持多线程写入不同位置),进一步压榨多核性能
用上述方案,转换时间应该能降到几十毫秒甚至更低,完全满足实时视频场景的需求。
内容的提问来源于stack exchange,提问作者Ignacio
相关产品推荐
相关产品推荐

