不安全的每像素访问,30毫秒访问1756000像素

时间:2009-05-21 14:42:39

标签: c# performance gdi+

所以我一直在我的网站上分享关于上述主题标题的一些想法,关于快速,不安全的像素访问。一位先生给了我一个粗略的例子,说明他是如何用C ++做的,但这对C#没有帮助,除非我可以互操作,而且互操作也很快。我在互联网上找到了一个使用MSDN帮助编写的课程,以便不安全地访问像素。这门课速度非常快,但速度不够快。这是班级:

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace DCOMProductions.Desktop.ScreenViewer {
public unsafe class UnsafeBitmap {
    Bitmap bitmap;

    // three elements used for MakeGreyUnsafe
    int width;
    BitmapData bitmapData = null;
    Byte* pBase = null;

    public UnsafeBitmap(Bitmap bitmap) {
        this.bitmap = new Bitmap(bitmap);
    }

    public UnsafeBitmap(int width, int height) {
        this.bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
    }

    public void Dispose() {
        bitmap.Dispose();
    }

    public Bitmap Bitmap {
        get {
            return (bitmap);
        }
    }

    private Point PixelSize {
        get {
            GraphicsUnit unit = GraphicsUnit.Pixel;
            RectangleF bounds = bitmap.GetBounds(ref unit);

            return new Point((int)bounds.Width, (int)bounds.Height);
        }
    }

    public void LockBitmap() {
        GraphicsUnit unit = GraphicsUnit.Pixel;
        RectangleF boundsF = bitmap.GetBounds(ref unit);
        Rectangle bounds = new Rectangle((int)boundsF.X,
      (int)boundsF.Y,
      (int)boundsF.Width,
      (int)boundsF.Height);

        // Figure out the number of bytes in a row
        // This is rounded up to be a multiple of 4
        // bytes, since a scan line in an image must always be a multiple of 4 bytes
        // in length. 
        width = (int)boundsF.Width * sizeof(Pixel);
        if (width % 4 != 0) {
            width = 4 * (width / 4 + 1);
        }
        bitmapData =
      bitmap.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

        pBase = (Byte*)bitmapData.Scan0.ToPointer();
    }

    public Pixel GetPixel(int x, int y) {
        Pixel returnValue = *PixelAt(x, y);
        return returnValue;
    }

    public void SetPixel(int x, int y, Pixel colour) {
        Pixel* pixel = PixelAt(x, y);
        *pixel = colour;
    }

    public void UnlockBitmap() {
        bitmap.UnlockBits(bitmapData);
        bitmapData = null;
        pBase = null;
    }
    public Pixel* PixelAt(int x, int y) {
        return (Pixel*)(pBase + y * width + x * sizeof(Pixel));
    }
}

}

基本上我正在做的是复制整个屏幕并将每个像素与旧副本进行比较。在1680x1050位图上,使用以下代码大约需要300毫秒。

private Bitmap GetInvalidFrame(Bitmap frame) {
        Stopwatch sp = new Stopwatch();
        sp.Start();

        if (m_FrameBackBuffer == null) {
            return frame;
        }

        Int32 pixelsToRead = frame.Width * frame.Height;
        Int32 x = 0, y = 0;

        UnsafeBitmap unsafeBitmap = new UnsafeBitmap(frame);
        UnsafeBitmap unsafeBuffBitmap = new UnsafeBitmap(m_FrameBackBuffer);
        UnsafeBitmap retVal = new UnsafeBitmap(frame.Width, frame.Height);

        unsafeBitmap.LockBitmap();
        unsafeBuffBitmap.LockBitmap();
        retVal.LockBitmap();

        do {
            for (x = 0; x < frame.Width; x++) {
                Pixel newPixel = unsafeBitmap.GetPixel(x, y);
                Pixel oldPixel = unsafeBuffBitmap.GetPixel(x, y);

                if (newPixel.Alpha != oldPixel.Alpha || newPixel.Red != oldPixel.Red || newPixel.Green != oldPixel.Green || newPixel.Blue != oldPixel.Blue) {
                   retVal.SetPixel(x, y, newPixel);
                }
                else {
                    // Skip pixel
                }
            }

            y++;
        } while (y != frame.Height);

        unsafeBitmap.UnlockBitmap();
        unsafeBuffBitmap.UnlockBitmap();
        retVal.UnlockBitmap();

        sp.Stop();

        System.Diagnostics.Debug.WriteLine(sp.Elapsed.Milliseconds.ToString());

        sp.Reset();

        return retVal.Bitmap;
    }

是否有任何可能的方法/手段/方法可以将其加速到约30ms?我可以使用Graphics.CopyFromScreen()在大约30ms内复制屏幕,这样每秒产生大约30帧。但是,程序的运行速度与其较慢的程序一样快,因此GetInvalidFrame中的300ms延迟会将其减慢到每秒约1-3帧。这对于会议软件来说并不好。

任何建议,方法,正确方向的指示都绝对精彩!此外,用于在客户端绘制位图的代码也在下面。

评论Dmitriy的回答/评论:

#region RootWorkItem

    private ScreenClient m_RootWorkItem;
    /// <summary>
    /// Gets the RootWorkItem
    /// </summary>
    public ScreenClient RootWorkItem {
        get {
            if (m_RootWorkItem == null) {
                m_RootWorkItem = new ScreenClient();
                m_RootWorkItem.FrameRead += new EventHandler<FrameEventArgs>(RootWorkItem_FrameRead);
            }
            return m_RootWorkItem;
        }
    }

    #endregion

    private void RootWorkItem_FrameRead(Object sender, FrameEventArgs e) {
        if (e.Frame != null) {
            if (uxSurface.Image != null) {
                Bitmap frame = (Bitmap)uxSurface.Image;

                Graphics g = Graphics.FromImage(frame);
                g.DrawImage(e.Frame, 0, 0); // Draw only updated pixels

                uxSurface.Image = frame;
            }
            else {
                uxSurface.Image = e.Frame; // Draw initial, full image
            }
        }
        else {
            uxSurface.Image = null;
        }
    }

5 个答案:

答案 0 :(得分:16)

使用整数而不是像素和单循环的不安全方法:

private static Bitmap GetInvalidFrame(Bitmap oldFrame, Bitmap newFrame)
{
    if (oldFrame.Size != newFrame.Size)
    {
        throw new ArgumentException();
    }
    Bitmap result = new Bitmap(oldFrame.Width, oldFrame.Height, oldFrame.PixelFormat);

    Rectangle lockArea = new Rectangle(Point.Empty, oldFrame.Size);
    PixelFormat format = PixelFormat.Format32bppArgb;
    BitmapData oldData = oldFrame.LockBits(lockArea, ImageLockMode.ReadOnly, format);
    BitmapData newData = newFrame.LockBits(lockArea, ImageLockMode.ReadOnly, format);
    BitmapData resultData = result.LockBits(lockArea, ImageLockMode.WriteOnly, format);

    int len = resultData.Height * Math.Abs(resultData.Stride) / 4;

    unsafe
    {
        int* pOld = (int*)oldData.Scan0;
        int* pNew = (int*)newData.Scan0;
        int* pResult = (int*)resultData.Scan0;

        for (int i = 0; i < len; i++)
        {
            int oldValue = *pOld++;
            int newValue = *pNew++;
            *pResult++ = oldValue != newValue ? newValue : 0 /* replace with 0xff << 24 if you need non-transparent black pixel */;
            // *pResult++ = *pOld++ ^ *pNew++; // if you can use XORs.
        }
    }

    oldFrame.UnlockBits(oldData);
    newFrame.UnlockBits(newData);
    result.UnlockBits(resultData);

    return result;
}

我认为你真的可以在这里使用XORed帧,我希望这可以在双方都有更好的表现。

    private static void XorFrames(Bitmap leftFrame, Bitmap rightFrame)
    {
        if (leftFrame.Size != rightFrame.Size)
        {
            throw new ArgumentException();
        }

        Rectangle lockArea = new Rectangle(Point.Empty, leftFrame.Size);
        PixelFormat format = PixelFormat.Format32bppArgb;
        BitmapData leftData = leftFrame.LockBits(lockArea, ImageLockMode.ReadWrite, format);
        BitmapData rightData = rightFrame.LockBits(lockArea, ImageLockMode.ReadOnly, format);

        int len = leftData.Height * Math.Abs(rightData.Stride) / 4;

        unsafe
        {
            int* pLeft = (int*)leftData.Scan0;
            int* pRight = (int*)rightData.Scan0;

            for (int i = 0; i < len; i++)
            {
                *pLeft++ ^= *pRight++;
            }
        }

        leftFrame.UnlockBits(leftData);
        rightFrame.UnlockBits(rightData);
    }

您可以通过以下方式在双方使用此程序:
在服务器端,您需要评估旧框架和新框架之间的差异,将其发送到客户端并用新框架替换旧框架。服务器代码应如下所示:

  XorFrames(oldFrame, newFrame); // oldFrame ^= newFrame
  Send(oldFrame); // send XOR of two frames
  oldFrame = newFrame;

在客户端,您需要使用从服务器收到的xor帧更新当前帧:

  XorFrames((Bitmap)uxSurface.Image, e.Frame);

答案 1 :(得分:3)

是的,您可以使用unsafe代码完成此操作。

BitmapData d = l.LockBits(new Rectangle(0, 0, l.Width, l.Height), ImageLockMode.ReadOnly,l.PixelFormat);
IntPtr scan = d.Scan0;
unsafe
{
    byte* p = (byte*)(void*)scan;
    //dostuff               
}

查看http://www.codeproject.com/KB/GDI-plus/csharpgraphicfilters11.aspx了解此类内容的一些基本示例。我的代码基于此。

注意: 这将比你的快得多的原因之一是你分别比较每个通道而不是仅使用一个操作比较整个字节。同样,改变PixelAt为你提供一个字节来促进这一点可能会给你一个改进。

答案 2 :(得分:3)

这里:Utilizing the GPU with c#提到了一些使用C#GPU的库。

答案 3 :(得分:1)

您可以只执行2位图的基本内存比较,而不是检查每个像素。在C中,类似于memcmp()。

这将为您提供更快速的测试,让您知道图像是否相同。只有当你知道它们不同时才需要使用更昂贵的代码来帮助你确定它们的不同之处(如果你甚至需要知道的话)。

我不是C#的人,所以我不知道访问原始内存是多么容易。

答案 4 :(得分:0)

能够切掉约60ms。我认为这需要GPU。我没有看到使用CPU的任何解决方案,即使通过一次比较多个字节/像素,除非有人可以掀起代码示例以向我显示。仍然在200-260ms左右,对于30fps来说太慢了。

private static BitmapData m_OldData;
private static BitmapData m_NewData;
private static unsafe Byte* m_OldPBase;
private static unsafe Byte* m_NewPBase;
private static unsafe Pixel* m_OldPixel;
private static unsafe Pixel* m_NewPixel;
private static Int32 m_X;
private static Int32 m_Y;
private static Stopwatch m_Watch = new Stopwatch();
private static GraphicsUnit m_GraphicsUnit = GraphicsUnit.Pixel;
private static RectangleF m_OldBoundsF;
private static RectangleF m_NewBoundsF;
private static Rectangle m_OldBounds;
private static Rectangle m_NewBounds;
private static Pixel m_TransparentPixel = new Pixel() { Alpha = 0x00, Red = 0, Green = 0, Blue = 0 };

private Bitmap GetInvalidFrame(Bitmap frame) {
    if (m_FrameBackBuffer == null) {
        return frame;
    }

    m_Watch.Start();

    unsafe {
        m_OldBoundsF = m_FrameBackBuffer.GetBounds(ref m_GraphicsUnit);
        m_OldBounds = new Rectangle((Int32)m_OldBoundsF.X, (Int32)m_OldBoundsF.Y, (Int32)m_OldBoundsF.Width, (Int32)m_OldBoundsF.Height);
        m_OldData = m_FrameBackBuffer.LockBits(m_OldBounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

        m_NewBoundsF = m_FrameBackBuffer.GetBounds(ref m_GraphicsUnit);
        m_NewBounds = new Rectangle((Int32)m_NewBoundsF.X, (Int32)m_NewBoundsF.Y, (Int32)m_NewBoundsF.Width, (Int32)m_NewBoundsF.Height);
        m_NewData = frame.LockBits(m_NewBounds, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

        m_OldPBase = (Byte*)m_OldData.Scan0.ToPointer();
        m_NewPBase = (Byte*)m_NewData.Scan0.ToPointer();

        do {
            for (m_X = 0; m_X < frame.Width; m_X++) {

                m_OldPixel = (Pixel*)(m_OldPBase + m_Y * m_OldData.Stride + 1 + m_X * sizeof(Pixel));
                m_NewPixel = (Pixel*)(m_NewPBase + m_Y * m_NewData.Stride + 1 + m_X * sizeof(Pixel));

                if (m_OldPixel->Alpha == m_NewPixel->Alpha // AccessViolationException accessing Property in get {}
                    || m_OldPixel->Red == m_NewPixel->Red
                    || m_OldPixel->Green == m_NewPixel->Green
                    || m_OldPixel->Blue == m_NewPixel->Blue) {

                    // Set the transparent pixel
                    *m_NewPixel = m_TransparentPixel;
                }
            }

            m_Y++; //Debug.WriteLine(String.Format("X: {0}, Y: {1}", m_X, m_Y));
        } while (m_Y < frame.Height);
    }

    m_Y = 0;

    m_Watch.Stop();
    Debug.WriteLine("Time elapsed: " + m_Watch.ElapsedMilliseconds.ToString());
    m_Watch.Reset();

    return frame;
}