【问题标题】:C# - reverse image bytes quickly?C# - 快速反转图像字节?
【发布时间】:2012-05-06 17:36:29
【问题描述】:

我正在尝试确定在 Y 轴上翻转图像的最佳方式。对于每个像素,有 4 个字节,每组 4 个字节需要按顺序保持在一起,但要移位。这是迄今为止我想出的最好的。

对于 1280x960 的图像,这仅需要 0.1-.2 秒,但对于视频,这样的性能是严重的。有什么建议吗?

初步实施

        private void ReverseFrameInPlace(int width, int height, int bytesPerPixel, ref byte[] framePixels)
    {
        System.Diagnostics.Stopwatch s = System.Diagnostics.Stopwatch.StartNew();

        int stride = width * bytesPerPixel;
        int halfStride = stride / 2;
        int byteJump = bytesPerPixel * 2;
        int length = stride * height;
        byte pix;

        for (int i = 0, a = stride, b = stride - bytesPerPixel;
            i < length; i++)
        {
            if (b % bytesPerPixel == 0)
            {
                b -= byteJump;
            }
            if (i > 0 && i % halfStride == 0)
            {
                i = a;
                a += stride;
                b = a - bytesPerPixel;
                if (i >= length)
                {
                    break;
                }
            }

            pix = framePixels[i];
            framePixels[i] = framePixels[b];
            framePixels[b++] = pix;
        }

        s.Stop();
        System.Console.WriteLine("ReverseFrameInPlace: {0}", s.Elapsed);
    }

修订 #1

根据 SLaks 和 Alexei 对索引和 Buffer.BlockCopy 进行了修订。还添加了一个 Parallel.For,因为索引允许它。

    int[] pixelIndexF = null;
    int[] pixelIndexB = null;
    private void ReverseFrameInPlace(int width, int height, int bytesPerPixel, byte[] framePixels)
    {
        System.Diagnostics.Stopwatch s = System.Diagnostics.Stopwatch.StartNew();

        if (pixelIndexF == null)// || pixelIndex.Length != (width * height))
        {
            int stride = width * bytesPerPixel;
            int length = stride * height;

            pixelIndexF = new int[width * height / 2];
            pixelIndexB = new int[width * height / 2];
            for (int i = 0, a = stride, b = stride, index = 0;
                i < length; i++)
            {
                b -= bytesPerPixel;
                if (i > 0 && i % (width / 2 )== 0)
                {
                    //i = a;
                    i += width / 2;
                    a += stride;
                    b = a - bytesPerPixel;
                    if (index >= pixelIndexF.Length)
                    {
                        break;
                    }
                }
                pixelIndexF[index] = i * bytesPerPixel;
                pixelIndexB[index++] = b;
            }
        }

        Parallel.For(0, pixelIndexF.Length, new Action<int>(delegate(int i)
        {
            byte[] buffer = new byte[bytesPerPixel];
            Buffer.BlockCopy(framePixels, pixelIndexF[i], buffer, 0, bytesPerPixel);
            Buffer.BlockCopy(framePixels, pixelIndexB[i], framePixels, pixelIndexF[i], bytesPerPixel);
            Buffer.BlockCopy(buffer, 0, framePixels, pixelIndexB[i], bytesPerPixel);
        }));

        s.Stop();
        System.Console.WriteLine("ReverseFrameInPlace: {0}", s.Elapsed);
    }

修订 #2

    private void ReverseFrameInPlace(int width, int height, System.Drawing.Imaging.PixelFormat pixelFormat, byte[] framePixels)
    {
        System.Diagnostics.Stopwatch s = System.Diagnostics.Stopwatch.StartNew();

        System.Drawing.Rectangle imageBounds = new System.Drawing.Rectangle(0,0,width, height);

        //create destination bitmap, get handle
        System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(width, height, pixelFormat);
        System.Drawing.Imaging.BitmapData bitmapData = bitmap.LockBits(imageBounds, System.Drawing.Imaging.ImageLockMode.ReadWrite, bitmap.PixelFormat);
        IntPtr ptr = bitmapData.Scan0;

        //byte[] to bmap
        System.Runtime.InteropServices.Marshal.Copy(framePixels, 0, ptr, framePixels.Length);
        bitmap.UnlockBits(bitmapData);

        //flip
        bitmap.RotateFlip(System.Drawing.RotateFlipType.RotateNoneFlipX);

        //get handle for bitmap to byte[]
        bitmapData = bitmap.LockBits(imageBounds, System.Drawing.Imaging.ImageLockMode.ReadWrite, bitmap.PixelFormat);
        ptr = bitmapData.Scan0;
        System.Runtime.InteropServices.Marshal.Copy(ptr, framePixels, 0, framePixels.Length);
        bitmap.UnlockBits(bitmapData);

        s.Stop();
        System.Console.WriteLine("ReverseFrameInPlace: {0}", s.Elapsed);
    }

【问题讨论】:

  • 使用ref没有意义。
  • unsafe 代码适合您吗?
  • 这并不完全是一个答案,但通常这些问题在渲染这些图像时最好在着色器中解决(如果您在使用着色器的上下文中渲染它们)

标签: c# image bytearray reverse


【解决方案1】:

尝试在每个 4 字节范围内调用 Buffer.BlockCopy;那应该更快。

【讨论】:

  • 我尝试了几种方法 - 使用临时缓冲区仅用于复制的字节以及整个步幅的缓冲区副本。虽然看起来稍微快了一点,但并没有太大区别。
【解决方案2】:

尝试和测量更多随机的东西:

  • 预构建索引数组以复制到一行(如 [12,13,14,15, 8,9,10,11, 4,5,6,7, 0,1,2,3]而不是在每一行上执行一些复杂的 if。
  • 尝试复制到新目的地而不是原地。

【讨论】:

  • 新目的地比原地慢。我将尝试构建一个索引数组,但我觉得它不会更快。感谢您的建议。
  • 在适当的位置,使用索引和 Buffer.BlockCopy 大约可以将时间缩短一半(通常为 0.05s-0.1s)。可以忍受,但仍然希望更快地找到一些东西。 :)
【解决方案3】:

【讨论】:

  • 这是我最初的方法,它爬了。
  • 看起来您正在使用数组索引来访问数据。如本文所述,使用“不安全”指针(您表示不安全是可以的)可能会更快:bobpowell.net/lockingbits.htm
  • 实际上,我只是重新审视它并再次尝试,它看起来像使用 Marshal.Copy 将字节复制到位图或从位图复制字节并执行 RotateFlip 确实比我当前的实现好得多有。也发布当前的 - 随意批评,它的内存使用无处不在。
【解决方案4】:

您可以使用任何技术在 CPU 上并行执行,或者使用像素着色器并在 GPU 上执行。如果您这样做只是为了显示翻转的视频 - 您最好使用 DirectX 并简单地在 GPU 上进行转换。

【讨论】:

  • 我在听。 :) 没有做过很多 GPU/DirectX,能指出我的任何资源或示例吗?
  • 这真的取决于你的场景——你想要达到的确切目标、平台、你可以使用的语言等。Jeremiah Morrill 刚刚在此处发布了关于在 MFT 中使用 GPU 效果的博客:jeremiahmorrill.wordpress.com/2012/04/25/…。这是 Windows 8 Metro Style Apps 的示例,但我认为通过一些研究,您可以找到如何在 Windows 7 上运行的常规 C++ 应用程序中执行完全相同的操作。这是针对像素着色器的,尽管在您的场景中您可能不需要像素着色器。
  • 您可以使用 IMFMediaEngine::TransferVideoFrame (msdn.microsoft.com/en-us/library/windows/desktop/…) 将视频渲染为纹理,并在 Direct3D 场景中使用该纹理,其中纹理应用于围绕 Y 轴翻转的矩形。我自己刚开始使用 DirectX,手头没有示例,但是修改一些基本的 DirectX 示例来实现这一点应该相当简单。结果,您一次可以播放 4 个反向视频,并具有良好的渲染性能,从而在 IO 和 GPU 冷却方面留下瓶颈。
  • 感谢您提供更多信息。在接下来的几天里,我会更深入地研究它,因为我认为将工作卸载到 GPU 将有助于我处理我知道会占用 CPU 密集型的其他部分。
【解决方案5】:

另一种选择可能是使用 XNA 框架对图像进行操作。有一个How to resize and save a Texture2D in XNA? 的小例子。我不知道它有多快,但考虑到这些功能应该用于高 fps 的游戏中,我可以看到它应该有多快。

【讨论】:

    【解决方案6】:

    我遇到了几乎相同的问题,但在我的情况下,我需要翻转图像以将其保存到 .avi 容器中。 我改用了 Array.Copy() 方法,令人惊讶的是,它似乎比其他方法更快(至少在我的机器上)。我使用的源图像是 720 x 576 像素,每像素 3 个字节。此方法花费了 0.001 - 0.01 秒,而您的两次修订都花费了大约 0.06 秒。

        private byte[] ReverseFrameInPlace2(int stride, byte[] framePixels)
        {
            System.Diagnostics.Stopwatch s = System.Diagnostics.Stopwatch.StartNew();
            var reversedFramePixels = new byte[framePixels.Length];
            var lines = framePixels.Length / stride;
    
            for (var line = 0; line < lines; line++)
            {
                Array.Copy(framePixels, framePixels.Length - ((line + 1) * stride), reversedFramePixels, line * stride, stride);
            }
    
            s.Stop();
            System.Console.WriteLine("ReverseFrameInPlace2: {0}", s.Elapsed);
            return reversedFramePixels;
        }
    

    【讨论】:

      猜你喜欢
      • 2022-10-23
      • 2016-01-06
      • 2021-09-30
      • 2023-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-14
      • 1970-01-01
      相关资源
      最近更新 更多