如何优化将 byte[] 转换为 4d 张量(或数组)的方法?

How to optimize a method that transforms a byte[] to a 4d tensor (or array)?

我正在开发一个使用网络摄像头实时删除用户背景的程序。

为了进行背景减法,我使用了 ML 模型。该模型的输入是形状为 BCHW(batch、channel、height、width)的 4D 张量,其中 RGB 值介于 0 和 1 之间。我同时只使用一个图像,所以 batch = 1,并且它有效地使它成为一个 3D 张量。

所以,我必须编写一些代码来将字节数组(来自网络摄像头的输入图像)转换为张量。

假设您有这样的 byte[]RGB RGB RGB RGB RGB RGB RGB RGB RGB,

那么代码应该将其转换为:RRRRRRRRR GGGGGGGGG BBBBBBBBB.

为此我想出了以下方法:

public Tensor<float> ImageToTensor(byte[] pixels, int height, int width)
{
    var tensorSize = 1 * 3 * height * width;

    float[] data = new float[tensorSize];

    var channelSize = width * height;
    var redChannel = 0;
    var greenChannel = channelSize;
    var blueChannel = (channelSize * 2);

    Parallel.For(0, height, y =>
    {
        int rowStart = y * width * 3;
        int rowWidth = width * 3;

        var row = new Span<byte>(pixels, rowStart, rowWidth);

        for (int i = 0; i < row.Length; i += 3)
        {
            byte red = row[i];
            byte green = row[i + 1];
            byte blue = row[i + 2];

            var index = i / 3 + y * width;

            data[redChannel + index] = red / 255f;
            data[greenChannel + index] = green / 255f;
            data[blueChannel + index] = blue / 255f;
        }
    });

    Memory<float> memory = new Memory<float>(data);

    return new DenseTensor<float>(memory, new[] { 1, 3, height, width });
}

我使用 Benchmarkdotnet 对 1920 x 1080 图像进行了基准测试,大约需要 5 毫秒。

虽然这不是很多,但 ML 模型大约需要 8 毫秒,而且我还有一个 post 处理步骤。

目标是 运行 60 FPS 的视频流。这意味着整个过程只需 16.6ms。由于 ML 模型需要 8 毫秒,我只剩下大约 8 到 9 毫秒用于预处理和 post 处理步骤。

post和预处理步骤大致相同,但post-处理步骤稍微复杂一点,大约需要两倍的时间。所以,我目前在 5ms + 8ms + 10ms = 23ms.

6 - 7 毫秒太多了。

所以,我的问题是,如何优化上面的代码? 我已经尝试了很多东西,但似乎没有什么能真正改善它。

改进代码:

public Tensor<float> ImageToTensor(byte[] pixels, int height, int width)
{
    var tensorSize = 1 * 3 * height * width;

    float[] data = new float[tensorSize];

    var channelSize = width * height;
    var redChannel = 0;
    var greenChannel = channelSize;
    var blueChannel = (channelSize * 2);

    int floatSlots = Vector<float>.Count;

    Parallel.For(0, height, y =>
    {
        int rowStart = y * width * 3;
        int rowWidth = width * 3;

        var row = new Span<byte>(pixels, rowStart, rowWidth);

        int numOfVectors = row.Length / (floatSlots * 3);
        int ceiling = numOfVectors * (floatSlots * 3);

        var reds = new float[floatSlots];
        var greens = new float[floatSlots];
        var blues = new float[floatSlots];

        for (int i = 0; i < ceiling; i += (floatSlots * 3))
        {
            for (int j = 0; j < (3 * floatSlots); j += 3)
            {
                reds[j / 3] = row[j] / 255f;
                greens[j / 3] = row[j + 1] / 255f;
                blues[j / 3] = row[j + 2] / 255f;
            }

            var vecRed = new Vector<float>(reds);
            var vecGreen = new Vector<float>(greens);
            var vecBlue = new Vector<float>(blues);

            vecRed.CopyTo(data, i + redChannel);
            vecGreen.CopyTo(data, i + greenChannel);
            vecBlue.CopyTo(data, i + blueChannel);
        }

        for (int i = ceiling; i < row.Length; i += 3)
        {
            byte red = row[i];
            byte green = row[i + 1];
            byte blue = row[i + 2];

            var index = i / 3 + y * width;

            data[redChannel + index] = red / 255f;
            data[greenChannel + index] = green / 255f;
            data[blueChannel + index] = blue / 255f;
        }
    });

    Memory<float> memory = new Memory<float>(data);

    return new DenseTensor<float>(memory, new[] { 1, 3, height, width });
}

我通过使用 SIMD 向量改进了我的代码,这使代码速度提高了 20%。它从 5 毫秒变为 4 毫秒。

您的代码看起来相当不错,所以我不希望性能有任何奇迹,但您可以尝试一些事情。

与其将 i 递增 3,我建议在方法内部递增 1 并乘以 3,这样可以消除除法,乘法是

您还可以考虑 SIMD intrinstics, that might help quite a bit, but I have no idea how you should write it. You might also consider looking for optimized implementations, like Intel performance Primitives or OpenCV. These should hopefully already be highly optimized, but might require a .Net wrapper 或 pInvoke 调用。即使您不想使用第三方解决方案,也可能值得进行测试以了解性能的上限。

您也可以考虑改用 c++。根据我的经验,虽然大多数 .net 代码从直接的 c++ 端口中获益甚微,但 c++ 编译器具有更广泛的可用优化范围,可能 有助于像这样的紧密循环。

如果其他方法都不起作用,您可以考虑直接使用硬件来解决问题。 IE。更多更快的内核。这可能比花费大量时间进行优化更便宜,至少如果这是一个不打算用于 mass-market.

的专门实现的话