You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实时视频场景下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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 07:58:13