C# - 如何快速反转图像字节?

3

我正在尝试确定沿Y轴翻转图像的最佳方式。每个像素有4个字节,每组4个字节需要保持在一起,但是需要进行移位。到目前为止,这是我能想到的最好的方法。

对于1280x960的图片,这只需要0.1-0.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);
    }

Revision #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);
    }

1
使用'ref'是毫无意义的。 - SLaks
你是否考虑使用unsafe代码? - Ben
这并不是一个确切的答案,但通常这些问题在渲染图像时最好在着色器中解决(如果您正在使用一个使用着色器的上下文进行渲染)。 - Phylliida
6个回答

4

我遇到了几乎相同的问题,但在我的情况下,我需要翻转图像以将其保存到.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;
    }

3

我尝试了几种方法 - 使用临时缓冲区仅复制字节,以及整个步幅的缓冲区复制。虽然看起来稍微快一点,但并没有太大的区别。 - Crutt

2

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


我在听着。 :) 对GPU/DirectX方面做得不多,能给我一些资源或示例吗? - Crutt
这真的取决于您的情况 - 您要实现的确切目标、平台、可以使用的语言等。Jeremiah Morrill在这里发布了有关在MFT中使用GPU效果的博客文章:http://jeremiahmorrill.wordpress.com/2012/04/25/gpu-accelerated-media-effects-in-windows-8-metro/。这是Windows 8 Metro Style应用程序的示例,但我认为通过一些研究,您可以找到如何在运行在Windows 7上的常规C++应用程序中执行完全相同的操作。这是针对像素着色器的,尽管在您的情况下,您可能不需要像素着色器。 - Filip Skakun
你可以使用IMFMediaEngine :: TransferVideoFrame(http://msdn.microsoft.com/en-us/library/windows/desktop/hh448021(v=vs.85).aspx)将视频渲染到纹理中,并在Direct3D场景中使用该纹理,应用于沿Y轴翻转的矩形。我刚开始尝试使用DirectX,手头没有样本,但是修改一些基本的DirectX示例应该相当简单。结果,您可能可以同时播放4个反向视频,并具有良好的渲染性能,使瓶颈在IO和GPU冷却方面。 - Filip Skakun
感谢您提供的额外信息。接下来的几天,我会更深入地研究一下,因为我认为将工作卸载到GPU上将有助于我处理其他需要大量CPU的部分。 - Crutt

1

还有几个随机的事情可以尝试并测量:

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

新的目标比原地更慢。我将尝试构建索引数组,但我有一种感觉它不会更快。谢谢您的建议。 - Crutt
使用索引和Buffer.BlockCopy进行原地操作可以将时间减半(通常为0.05秒至0.1秒)。虽然可以接受,但仍希望找到更快的方法。 :) - Crutt

1

那是我的最初方法,速度非常慢。 - Crutt
看起来你正在使用数组索引来访问数据。根据这篇文章的描述,使用“不安全”的指针可能会更快(你表示“不安全”是可以接受的):http://www.bobpowell.net/lockingbits.htm - Steve Wellens
实际上,我只是重新审视了一下并再次尝试了一下,看起来使用Marshal.Copy将字节复制到/从位图并执行RotateFlip确实比我当前的实现表现要好得多。同时也会发布当前版本 - 请随意批评,因为它的内存使用情况非常混乱。 - Crutt

0

另一个选项可能是使用XNA框架对图像进行操作。这里有一个小例子如何在XNA中调整大小并保存Texture2D?。我不知道它有多快,但我可以看出来,它应该非常快,因为这些函数应该用于具有高帧率的游戏。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接