来自LockBits消耗内存的BitmapData

本文关键字:BitmapData 内存 LockBits 来自 | 更新日期: 2023-09-27 17:52:49

我有一个c#例程来获取YUV422位图并转换为RGB:

    private unsafe void YUV422toRGB(byte[] YUV422, int YUVstride, ref Bitmap RGB)
    {
        //Found http://pastebin.com/MFsDnUCq after I wrote this.
        int row, col, index;
        byte y1, y2, u, v;
        int r1, r2, g1, g2, b1, b2;
        int c1, c2, d, e;
        byte* RGBbytes;
        int RGBindex;
        //http://bobpowell.net/lockingbits.aspx
        //It looks as though this bmp guy is consuming memory to the point
        //where I must force the garbage collector or the program will crash.
        //Why?
        System.Drawing.Imaging.BitmapData bmp =
            RGB.LockBits(
            new Rectangle(0, 0, RGB.Width, RGB.Height),
            System.Drawing.Imaging.ImageLockMode.WriteOnly,
            RGB.PixelFormat);
        RGBbytes = (byte*)bmp.Scan0;
        RGBindex = 0;
        index = 0;
        for (row = 0; row < RGB.Height; row++)
        {
            for (col = 0; col < YUVstride; col += 4)
            {
                u = YUV422[index + 0];
                y1 = YUV422[index + 1];
                v = YUV422[index + 2];
                y2 = YUV422[index + 3];
                index += 4;
                c1 = y1 - 16;
                c2 = y2 - 16;
                d = u - 128;
                e = v - 128;
                int c298 = 298 * c1;
                r1 = (c298 + 409 * e + 128) >> 8;
                g1 = (c298 - 100 * d - 208 * e + 128) >> 8;
                b1 = (c298 + 516 * d + 128) >> 8;
                c298 = 298 * c2;
                r2 = (c298 + 409 * e + 128) >> 8;
                g2 = (c298 - 100 * d - 208 * e + 128) >> 8;
                b2 = (c298 + 516 * d + 128) >> 8;
                //Now for clamping.
                //From http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
                //min(x, y) = y ^ ((x ^ y) & -(x < y))
                //max(x, y) = x ^ ((x ^ y) & -(x < y))
                //We want min(x, 255) followed by max(x, 0).
                //The problem is that x < y in C# is a bool which cannot be converted to int.
                //But effectively, -(x < y) is -1 if x < y and 0 otherwise.
                //we can do this by looking at the first bit of x-y
                //min(x, y) = y ^ ((x ^ y) & ((x - y) >> 31))
                //max(x, y) = x ^ ((x ^ y) & ((x - y) >> 31))
                //There appears to be 10% or so speed increase with the bithack.
                //r1 = Math.Max(0, Math.Min(r1, 255));
                //r2 = Math.Max(0, Math.Min(r2, 255));
                //g1 = Math.Max(0, Math.Min(g1, 255));
                //g2 = Math.Max(0, Math.Min(g2, 255));
                //b1 = Math.Max(0, Math.Min(b1, 255));
                //b2 = Math.Max(0, Math.Min(b2, 255));
                r1 = 255 ^ ((r1 ^ 255) & ((r1 - 255) >> 31));
                g1 = 255 ^ ((g1 ^ 255) & ((g1 - 255) >> 31));
                b1 = 255 ^ ((b1 ^ 255) & ((b1 - 255) >> 31));
                r2 = 255 ^ ((r2 ^ 255) & ((r2 - 255) >> 31));
                g2 = 255 ^ ((g2 ^ 255) & ((g2 - 255) >> 31));
                b2 = 255 ^ ((b2 ^ 255) & ((b2 - 255) >> 31));
                r1 = r1 ^ ((r1 ^ 0) & ((r1 - 0) >> 31));
                g1 = g1 ^ ((g1 ^ 0) & ((g1 - 0) >> 31));
                b1 = b1 ^ ((b1 ^ 0) & ((b1 - 0) >> 31));
                r2 = r2 ^ ((r2 ^ 0) & ((r2 - 0) >> 31));
                g2 = g2 ^ ((g2 ^ 0) & ((g2 - 0) >> 31));
                b2 = b2 ^ ((b2 ^ 0) & ((b2 - 0) >> 31));
                RGBbytes[RGBindex + 0] = (byte)b1;
                RGBbytes[RGBindex + 1] = (byte)g1;
                RGBbytes[RGBindex + 2] = (byte)r1;
                RGBbytes[RGBindex + 3] = (byte)b2;
                RGBbytes[RGBindex + 4] = (byte)g2;
                RGBbytes[RGBindex + 5] = (byte)r2;
                RGBindex += 6;
            }
        }
        RGB.UnlockBits(bmp);
    }

在经典的"嘿,为什么我的程序总是在30秒后崩溃"之后,我意识到垃圾收集器没有跟上(我每秒调用这个函数10次,并且始终通过引用传递位图)。

逐步执行代码,我看到RGB.LockBits是增加RAM使用的那个。我每10次调用添加一个GC.Collect()(每秒一次),现在一切都很好。

我在这里做错了什么吗?UnlockBits不应该自己清理吗?

来自LockBits消耗内存的BitmapData

通过代码的步骤,我看到了RGB。增加内存使用的是LockBits。我添加了GC.Collect()每十个调用一次(每秒一次),现在一切都好了。

你所说的基本上是一个正常的垃圾回收将回收所有东西,问题是垃圾回收器没有在你期望它执行的时候执行。

您的机器内存不足吗?您是否存在实际的内存压力,迫使垃圾收集器触发并回收空间?或者您是否有几个gb的空闲内存,以至于暂停所有线程来回收内存只是浪费cpu资源?听起来你的机器只是耸耸肩说:"嗯,我这里有更多的内存。一切如常。"

汉斯·帕桑特是对的。这个问题是一个位图被复制了,尽管我仍然不清楚为什么这会导致垃圾收集器显然无法完成它的工作。

我在这个程序中有一些后端类,它们从相机中获取图像并将位图转换为适当的格式(例如,我发布的YUV422到RGB转换)。被传递的位图被重用。

然后我有一些UI类,其中一个是一个窗口,它显示一个新的帧在一个定时器运行在大约10赫兹。在这里,你必须复制位图,因为后端东西会用一个新帧代替它,等等。

更新显示的例程很简单:

    public void SetImage(Bitmap Image)
    {
        if (this.IsDisposed) return;
        if (this.InvokeRequired)
            this.Invoke((MethodInvoker)delegate { SetImage(Image); });
        else
            picFrame.Image = (Bitmap)Image.Clone();
    }

一个摄像头提供640x480x8位(黑白)图像;在后端,我还使用lockbits将从API调用中获得的像素数组复制到. net位图。即使两个摄像头(和显示窗口)同时运行,我的内存也没有问题。

1920x1080 YUV422图像显然使垃圾收集器"饱和"(不管你怎么称呼它),所以这个版本的显示例程修复了这个问题:

    private Bitmap DisplayImage = null;
    public void SetImage(Bitmap Image)
    {
        if (this.IsDisposed) return;
        if (this.InvokeRequired)
            this.Invoke((MethodInvoker)delegate { SetImage(Image); });
        else
        {
            if (DisplayImage != null)
                DisplayImage.Dispose();
            DisplayImage = (Bitmap)Image.Clone();
            picFrame.Image = DisplayImage;
        }
    }

让我震惊的是,观察黑白相机的内存使用情况只显示它被固定在一个很低的数字上,而彩色相机却飙升到2gb,没有任何停止的迹象。我曾见过垃圾回收器的边缘情况,在这种情况下,你会看到内存缓慢上升,然后在垃圾回收器启动时突然下降。

这里明显的区别当然是,即使是两个黑白相机也只有614kB x 10fps,而彩色相机大约是它的10倍。我甚至不打算开始猜测为什么前者没有垃圾收集器的问题,而后者有。