0
点赞
收藏
分享

微信扫一扫

自动驾驶记忆泊车功能规范

【WPF】使用 WriteableBitmap 提升 Image 性能

前言

WriteableBitmap 背景

WriteableBitmap 继承至 System.Windows.Media.Imaging.BitmapSource

“巨硬” 官方介绍:  WriteableBitmap 类

WriteableBitmap 渲染原理

  • 在调用 WriteableBitmapAddDirtyRect 方法的时候,实际上是调用 MILSwDoubleBufferedBitmap.AddDirtyRect,这是 WPF 专门为 WriteableBitmap 而提供的非托管代码的双缓冲位图的实现。

  • WriteableBitmap 内部数组修改完毕之后,需要调用 Unlock 来解锁内部缓冲区的访问,这时会提交所有的修改。

WriteableBitmap 使用技巧

  1. WriteableBitmap 的性能瓶颈源于对脏区的重新渲染。
    • 脏区为 0 或者不在可视化树渲染,则不消耗性能。
    • 只要有脏区,渲染过程就会开始成为性能瓶颈。
      • CPU 占用基础值就很高了。
      • 脏区越大,CPU 占用越高,但增幅不大。
  2. 内存拷贝不是 WriteableBitmap 的性能瓶颈。
    • 建议使用 Windows API 或者 .NET API 来拷贝内存数据。

特殊的应用场景,可以适当调整下自己写代码的策略:

  • 如果你希望有较大脏区的情况下降低 CPU 占用,可以考虑降低 WriteableBitmap 脏区的刷新率。
  • 如果你希望 WriteableBitmap 有较低的渲染延迟,则考虑减小脏区。

案例

测试 Demo 使用 OpenCvSharp 将视频帧读取出来,将视频帧图像数据通过 WriteableBitmap 渲染到界面的 Image 控件。

核心源码

  • 核心代码,利用双缓存区更新位图图像信息
private void ShowImage()
{
    Bitmap.Lock();

    bitmap = frame.ToBitmap();

    bitmapData = bitmap.LockBits(new Rectangle(new System.Drawing.Point(0, 0), bitmap.Size),
        System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);

    Bitmap.WritePixels(rect, bitmapData.Scan0, bitmapData.Height * bitmapData.Stride, bitmapData.Stride, 0, 0);

    bitmap.UnlockBits(bitmapData);
    bitmap.Dispose();

    Bitmap.Unlock();
}

完整的 ViewModel 代码

public class MainWindowViewModel : Prism.Mvvm.BindableBase
{
    #region 属性、变量、命令

    private WriteableBitmap _bitmap;
    /// <summary>
    /// UI绑定的资源对象
    /// </summary>                
    public WriteableBitmap Bitmap
    {
        get => _bitmap;
        set => SetProperty(ref _bitmap, value);
    }

    /// <summary>
    /// OpenCvSharp 视频捕获对象
    /// </summary>
    private static VideoCapture videoCapture;

    /// <summary>
    /// 视频帧
    /// </summary>
    private static Mat frame = new Mat();

    private static BitmapData bitmapData = new BitmapData();
	
	private static Bitmap bitmap;
    
    Int32Rect rect;

    static int width = 0, height = 0;

    /// <summary>
    /// 打开文件
    /// </summary>
    public DelegateCommand OpenFileCommand { get; set; }

    public DelegateCommand MNCommand { get; set; }

    #endregion

    public MainWindowViewModel()
    {
        videoCapture = new VideoCapture();

        OpenFileCommand = new DelegateCommand(OpenFile);
        MNCommand = new DelegateCommand(MN);
    }

    #region 私有方法

    private void OpenFile()
    {
        OpenFileDialog open = new OpenFileDialog()
        {
            Multiselect = false,
            Title = "请选择文件",
            Filter = "视频文件(*.mp4, *.wmv, *.mkv, *.flv)|*.mp4;*.wmv;*.mkv;*.flv|所有文件(*.*)|*.*"
        };

        if (open.ShowDialog() is true)
        {           
            ShowMove(open.FileName);
        }
    }

    /// <summary>
    /// 获取视频
    /// </summary>
    /// <param name="fileName">文件路径</param>
    private void ShowMove(string fileName)
    {
        videoCapture.Open(fileName, VideoCaptureAPIs.ANY);

        if (videoCapture.IsOpened())
        {
            var timer = (int)Math.Round(1000 / videoCapture.Fps) - 8;
            width = videoCapture.FrameWidth;
            height = videoCapture.FrameHeight;

            Bitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgra32, null);
            rect = new Int32Rect(0, 0, Bitmap.PixelWidth, Bitmap.PixelHeight);

            while (true)
            {
                videoCapture.Read(frame);
                if (!frame.Empty())
                {
                    ShowImage();
                    Cv2.WaitKey(timer);
                }
            }
        }
    }

    private void ShowImage()
    {
        Bitmap.Lock();

        bitmap = frame.ToBitmap();

        bitmapData = bitmap.LockBits(new Rectangle(new System.Drawing.Point(0, 0), bitmap.Size),
            System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);

        Bitmap.WritePixels(rect, bitmapData.Scan0, bitmapData.Height * bitmapData.Stride, bitmapData.Stride, 0, 0);

        bitmap.UnlockBits(bitmapData);
        bitmap.Dispose();

        Bitmap.Unlock();
    }
}

测试结果

测试结果,经供参考,更精准的性能测试请使用专业工具。

  • VS Debug模式下的性能监测,以及Windows任务管理器中的资源占用,可以看出各项资源的使用是比较稳定的。
  • 发布之后独立运行资源的占用应该会有5%的降低。

使用 WriteableBitmap 提升 Image 性能

举报

相关推荐

0 条评论