【问题标题】:Convert uint8 byte array to any WPF rendering object将 uint8 字节数组转换为任何 WPF 呈现对象
【发布时间】:2015-08-01 09:41:00
【问题描述】:

目前我已经使用 C++ CLR 为 c# 构建了包装器。 C++ clr 类从摄像机获取帧作为 uint8[] 并返回到 C++ 类中的 OnVideoFrame 事件。我有这样的初始化程序:

ZeroMemory(&bmi_, sizeof(bmi_));
bmi_.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi_.bmiHeader.biPlanes = 1;
bmi_.bmiHeader.biBitCount = 32;
bmi_.bmiHeader.biCompression = BI_RGB;
bmi_.bmiHeader.biWidth = width;
bmi_.bmiHeader.biHeight = -height;
bmi_.bmiHeader.biSizeImage = width * height * (bmi_.bmiHeader.biBitCount >> 3);
image_.reset(new uint8[bmi_.bmiHeader.biSizeImage]);

所以问题是:

我已经在 image_ 中有数据,但是如何将该数据发送到 c# 并将其转换为任何 WPF 位图对象以播放我的本地视频流?我正在寻找有关性能的最佳方法以及我使用 WPF 时遇到的问题。我已经有了基本 win32 应用的解决方案:

RECT rcClient;
GetClientRect(VideoRendererInternal->WindowHandle, &rcClient);
PAINTSTRUCT ps;
HDC hdc = BeginPaint(VideoRendererInternal->WindowHandle, &ps);
StretchDIBits(hdc,
    0, 0, rcClient.right, rcClient.bottom,  // destination rect
    0, 0, VideoRendererInternal->bmi_.bmiHeader.biWidth, -VideoRendererInternal->bmi_.bmiHeader.biHeight,  // source rect
    VideoRendererInternal->image_.get(), &VideoRendererInternal->bmi_, DIB_RGB_COLORS, SRCCOPY);
EndPaint(VideoRendererInternal->WindowHandle, &ps);

但找不到 WPF 的任何解决方案。谢谢!

【问题讨论】:

    标签: c# c++ bitmap clr wrapper


    【解决方案1】:

    有很多方法可以做到这一点,我建议你看看https://channel9.msdn.com/Events/Build/2015/3-82 他们正是这样做的。这是使用 WPF 从 c++ 显示图像的另一种解决方案。

    使用 MVVM,您需要一个 XAML 中的图像,您可以使用任何位图源设置它。这样做 WPF 将重绘图像。这在后台高度优化(在 4k @ 60fps 下测试)。

    <Image x:Name="MainFrameBitmap" 
    Height="{Binding ImageProcessingModel.MainFrameBitmap.Height}" 
    Width="{Binding ImageProcessingModel.MainFrameBitmap.Width}" 
    Source="{Binding ImageProcessingModel.MainFrameBitmap, UpdateSourceTrigger=PropertyChanged}" Cursor="Cross" >
    

    在您的 ImageProcessingModel 中,您可能希望使用 pinvoke 或通过管道从 c++ 土地获取字节。此处的计时器很有用,因为您不想阻止 UI 执行其操作。 WPF 能够以 120fps 进行本机渲染,因此如果帧源不是周期性的或以低帧速率运行,这会很有帮助。

    public class ImageProcessingModel : INotifyPropertyChanged
    {
        public BitmapSource MainFrameBitmap
        {
            get
            {
                return _mainFrameBitmap;
            }
            private set
            {
                _mainFrameBitmap = value;
                OnPropertyChanged("MainFrameBitmap");
            }
        }
    
        public ImageProcessingModel(int updatePeriodInMilliseconds)
        {
            dispatcherTimer = new DispatcherTimer();
            dispatcherTimer.Tick += ServiceUiDispatcherTimerTick;
            dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, updatePeriodInMilliseconds);
        }
    
    
        private void ServiceUiDispatcherTimerTick(object sender, EventArgs e)
        {
            try
            {
                MainFrameBitmap = ReadBitmap(FrameWidth, FrameHeight, Stride);
            }
            catch (Exception ex)
            {
                Console.WriteLine(@"UI Runtime Exception:" + ex.Message);
            }
        }
    
    }
    

    您可能希望有另一个类来跟踪这些原始图像字节

    byte[] _imageData
    public BitmapSource ReadBitmap(int w, int h, int s )
    {
        int size = w*h*3 + 54;
    
        _imageData = new byte[size];
    
        // Read from pipe Read(_temp, 0, size);
        // Or use PInvoke to get a reference and manage that
    
        return BitmapSource.Create(w, h, 96, 96, PixelFormats.Rgb24, null, _imageData, s);
    }
    

    在 c++ 领域,您需要为任何给定的帧构造字节,这取决于您使用的框架。下面的示例使用 OpenCV,但如果使用 RGBQUAD 或一些这样的字节缓冲区来存储图像,代码将是相似的。此处选择的图像格式(和内存对齐方式)需要与 WPF 应用中的 BitmapSource.Create 函数相匹配。

    std::vector<uchar> buffer
    DWORD UIConnection::writeToPipe(cv::Mat inputFrame){
    
        int buffSize = (inputFrame.rows * inputFrame.cols * 3) + 54;
    
        cv::Mat serviceImage;
        inputFrame.copyTo(serviceImage);
    
        flip(serviceImage, serviceImage, 1);
        imencode(".bmp", serviceImage, buffer);
        reverse(buffer.begin(), buffer.end());
    
        WriteFile(hPipe, &buffer[0], buffSize * sizeof(uchar), bytesWritten, NULL);
    
        return *bytesWritten;
    }
    

    【讨论】:

      【解决方案2】:

      感谢您的快速回复!通过使用一半的答案来解决。不喜欢计时器,所以从 c++ 中创建了一个委托来发送 IntPtr:

      public delegate void OnNewFrame(IntPtr bitmapBuffer, int bitmapBufferLength);
      ...
      public:
      event OnNewFrame ^OnNewFrameEvent;`
      

      然后:

      System::IntPtr buffer = System::IntPtr(VideoRendererInternal->image_.get());    
      OnNewFrameEvent(buffer, bufferSize);
      

      在 C# 方面:

      ...
      renderer.OnNewFrameEvent += new OnNewFrame(OnNewVideoFrame);
      }
      
      private void OnNewVideoFrame(IntPtr bitmapBuffer, int bitmapBufferLength)
      {
          FrameDispatcher.Instance.DispatchFrame("LocalVideo", bitmapBuffer, bitmapBufferLength);
      }
      

      帧调度类:

      class FrameDispatcher
      {
      
          private static FrameDispatcher StaticInstance;
          private Dictionary<String, FrameRenderer> Renderers = new Dictionary<string, FrameRenderer>();
      
          public static FrameDispatcher Instance {
      
              get {
      
                  if (StaticInstance == null)
                  {
                      StaticInstance = new FrameDispatcher();
                  }
      
                  return StaticInstance;
      
              }
      
          }
      
          public void DispatchFrame(String id, IntPtr bitmapBuffer, int bitmapBufferLength)
          {
      
              if (Renderers.ContainsKey(id))
              {
      
                  BitmapSource bitmap = BitmapSource.Create(640, 480, 96, 96, PixelFormats.Pbgra32, null, bitmapBuffer, bitmapBufferLength, 640 * 8);                
                  Renderers[id].SetBitmap(bitmap);                
      
              }
      
          }
      
          public void RegisterFrameRenderer(FrameRenderer renderer, String id)
          {
              Renderers.Add(id, renderer);
          }
      
          public void UnregisterFrameRenderer(String id)
          {
              Renderers.Remove(id);
          }
      
      }
      

      帧渲染类:

         class FrameRenderer : INotifyPropertyChanged
      {
      
          private BitmapSource _Bitmap;
      
          public BitmapSource Bitmap
          {
              get
              {
                  return _Bitmap;
              }
      
              private set
              {
                  _Bitmap = value; 
                  OnPropertyChanged("Bitmap");
              }
          }
      
          public event PropertyChangedEventHandler PropertyChanged;       
      
          private void OnPropertyChanged(String info)
          {
      
              PropertyChangedEventHandler handler = PropertyChanged;
      
              if (handler != null)
              {               
                  try
                  {
                      PropertyChanged(this, new PropertyChangedEventArgs(info));
      
                  }
                  catch (Exception ex)
                  {
                      Console.WriteLine(@"UI Runtime Exception:" + ex.Message);
                  }
      
              }
          }
      
          public void SetBitmap(BitmapSource bitmap) {
      
              bitmap.Freeze();
              Bitmap = bitmap;
              GC.Collect();
      
          }
      
      }
      

      并在 MainWindow 控制器中注册 FrameRenderer:

      Renderer = new FrameRenderer();
      FrameDispatcher.Instance.RegisterFrameRenderer(Renderer, "LocalVideo");
      
      DataContext = Renderer;
      

      现在可以了,再次感谢 :)

      【讨论】:

        猜你喜欢
        • 2022-01-11
        • 2017-06-08
        • 2011-06-19
        • 2011-08-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多