【问题标题】:C# - Fastest way of Interpolating a large byte array (RGB to RGBA)C# - 插入大字节数组的最快方法(RGB 到 RGBA)
【发布时间】:2018-04-02 23:16:23
【问题描述】:

我正在将相机中的帧上传到 GPU 上的纹理进行处理(使用 SharpDX)。我的问题是 ATM 是我的帧以 24 位 RGB 的形式输入,但 DX11 不再具有 24 位 RGB 纹理格式,只有 32 位 RGBA。在每 3 个字节之后,我需要添加另一个值为 255 的字节(不透明)。我已经尝试过这种遍历字节数组的方法来添加它,但它太贵了。使用 GDI 位图进行转换也很昂贵。

                int count = 0;
                for (int i = 0; i < frameDataBGRA.Length - 3; i+=4)
                {

                    frameDataBGRA[i] = frameData[i - count];
                    frameDataBGRA[i + 1] = frameData[(i + 1) - count];
                    frameDataBGRA[i + 2] = frameData[(i + 2) - count];
                    frameDataBGRA[i + 3] = 255;
                    count++;
    }

【问题讨论】:

  • 无论哪种方式都会很昂贵,但我要做的是将其分成 3 或 4 块并在并行线程中处理。
  • 对每个像素进行迭代并为每个像素保留单独的索引会更清楚,将 ARGB 递增 4 并将 RGB 递增 3。此外,不确定“帧”是什么意思,但除非这意味着“扫描线”你忽略了你的步幅四舍五入。
  • @Nyerguds 哪个需要更长的时间?
  • 并非如此。每个循环将是两个加法,而不是您现在使用“计数”进行的三个减法。无论如何都需要考虑步幅。
  • 基于 Ron 的评论,但我会稍微改变一下。你可以做多线程,每张图片做 1 个线程。如果您有一个 8 线程 CPU,那么该转换将并行完成。另一件事是,如果你真的需要那种像素格式,你仍然可以利用旧的 DX9 库。

标签: c# bitmap sharpdx


【解决方案1】:

假设您可以使用 unsafe 进行编译,那么在这种情况下使用指针会给您带来显着的提升。

首先创建两个结构体以打包方式保存数据:

[StructLayout(LayoutKind.Sequential)]
public struct RGBA
{
    public byte r;
    public byte g;
    public byte b;
    public byte a;
}

[StructLayout(LayoutKind.Sequential)]
public struct RGB
{
    public byte r;
    public byte g;
    public byte b;
}

第一个版本:

    static void Process_Pointer_PerChannel(int pixelCount, byte[] rgbData, byte[] rgbaData)
    {
        fixed (byte* rgbPtr = &rgbData[0])
        {
            fixed (byte* rgbaPtr = &rgbaData[0])
            {
                RGB* rgb = (RGB*)rgbPtr;
                RGBA* rgba = (RGBA*)rgbaPtr;
                for (int i = 0; i < pixelCount; i++)
                {
                    rgba->r = rgb->r;
                    rgba->g = rgb->g;
                    rgba->b = rgb->b;
                    rgba->a = 255;
                    rgb++;
                    rgba++;
                }
            }
        }
    }

这样就避免了很多索引,直接传递数据。

另一个版本稍微快一点,直接装箱:

    static void Process_Pointer_Cast(int pixelCount, byte[] rgbData, byte[] rgbaData)
    {
        fixed (byte* rgbPtr = &rgbData[0])
        {
            fixed (byte* rgbaPtr = &rgbaData[0])
            {
                RGB* rgb = (RGB*)rgbPtr;
                RGBA* rgba = (RGBA*)rgbaPtr;
                for (int i = 0; i < pixelCount; i++)
                {
                    RGB* cp = (RGB*)rgba;
                    *cp = *rgb;
                    rgba->a = 255;
                    rgb++;
                    rgba++;
                }
            }
        }
    }

一个小的额外优化(这是微不足道的),如果你一直保持相同的数组并重复使用它,你可以将它初始化一次,将 alpha 设置为 255,例如:

    static void InitRGBA_Alpha(int pixelCount, byte[] rgbaData)
    {
        for (int i = 0; i < pixelCount; i++)
        {
            rgbaData[i * 4 + 3] = 255;
        }
    }

那么由于你永远不会改变这个通道,其他函数就不需要再写入它了:

    static void Process_Pointer_Cast_NoAlpha (int pixelCount, byte[] rgbData, byte[] rgbaData)
    {
        fixed (byte* rgbPtr = &rgbData[0])
        {
            fixed (byte* rgbaPtr = &rgbaData[0])
            {
                RGB* rgb = (RGB*)rgbPtr;
                RGBA* rgba = (RGBA*)rgbaPtr;
                for (int i = 0; i < pixelCount; i++)
                {
                    RGB* cp = (RGB*)rgba;
                    *cp = *rgb;
                    rgb++;
                    rgba++;
                }
            }
        }
    }

在我的测试中(运行 1920*1080 图像,100 次迭代),我得到(i7,x64 发布版本,平均运行时间)

  • 你的版本:6.81ms
  • Process_Pointer_PerChannel:4.3 毫秒
  • Process_Pointer_Cast : 3.8ms
  • Process_Pointer_Cast_NoAlpha:3.5 毫秒

请注意,当然,所有这些功能也可以很容易地分块,并且部分可以在多线程版本中运行。

如果你需要更高的性能,你有两个选择(有点超出问题的范围)

  • 将图像上传到字节地址缓冲区(以 rgb 格式),并在计算着色器中执行纹理转换。这涉及到一些位移和一些格式的摆弄,但实现起来相当简单。
  • 通常相机图像采用 Yuv 格式(对 u 和 v 进行下采样),因此在该颜色空间中上传图像并在像素着色器或计算着色器中转换为 rgba 会更快。如果您的相机 sdk 允许以该原生格式获取像素数据,那就是可行的方法。

【讨论】:

    【解决方案2】:

    @catflier:干得好,但它可以走得更快一点。 ;-)

    我的硬件上的复制次数:

    • 基本版本:5.48ms
    • Process_Pointer_PerChannel:2.84 毫秒
    • Process_Pointer_Cast:2.16 毫秒
    • Process_Pointer_Cast_NoAlpha:1.60 毫秒

    我的实验:

    • 快速转换:1.45 毫秒
    • FastConvert4:1.13 毫秒(此处:像素数必须能被 4 整除,但通常没问题)

    提高速度的东西:

    • 您的 RGB 结构必须始终读取每个像素 3 个单个字节,但读取整个 uint(4 个字节)并忽略最后一个字节会更快
    • 然后可以将 alpha 值直接添加到 uint 位计算中
    • 现代处理器通常可以比自增指针更快地寻址具有偏移位置的固定指针。
    • x64 模式下的偏移量变量也应该直接使用 64 位数据值(long 而非 int),这样可以减少访问的开销
    • 内部循环的部分推出再次提高了一些性能

    代码:

    static void FastConvert(int pixelCount, byte[] rgbData, byte[] rgbaData)
    {
      fixed (byte* rgbP = &rgbData[0], rgbaP = &rgbaData[0])
      {
        for (long i = 0, offsetRgb = 0; i < pixelCount; i++, offsetRgb += 3)
        {
          ((uint*)rgbaP)[i] = *(uint*)(rgbP + offsetRgb) | 0xff000000;
        }
      }
    }
    
    static void FastConvert4Loop(long pixelCount, byte* rgbP, byte* rgbaP)
    {
      for (long i = 0, offsetRgb = 0; i < pixelCount; i += 4, offsetRgb += 12)
      {
        uint c1 = *(uint*)(rgbP + offsetRgb);
        uint c2 = *(uint*)(rgbP + offsetRgb + 3);
        uint c3 = *(uint*)(rgbP + offsetRgb + 6);
        uint c4 = *(uint*)(rgbP + offsetRgb + 9);
        ((uint*)rgbaP)[i] = c1 | 0xff000000;
        ((uint*)rgbaP)[i + 1] = c2 | 0xff000000;
        ((uint*)rgbaP)[i + 2] = c3 | 0xff000000;
        ((uint*)rgbaP)[i + 3] = c4 | 0xff000000;
      }
    }
    
    static void FastConvert4(int pixelCount, byte[] rgbData, byte[] rgbaData)
    {
      if ((pixelCount & 3) != 0) throw new ArgumentException();
      fixed (byte* rgbP = &rgbData[0], rgbaP = &rgbaData[0])
      {
        FastConvert4Loop(pixelCount, rgbP, rgbaP);
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-26
      • 1970-01-01
      • 1970-01-01
      • 2011-01-03
      • 2016-02-06
      • 1970-01-01
      相关资源
      最近更新 更多