将 uint8 字节数组转换为任何 WPF 呈现对象

Posted

技术标签:

【中文标题】将 uint8 字节数组转换为任何 WPF 呈现对象【英文标题】:Convert uint8 byte array to any WPF rendering object 【发布时间】: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 的任何解决方案。谢谢!

【问题讨论】:

【参考方案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;

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

【讨论】:

以上是关于将 uint8 字节数组转换为任何 WPF 呈现对象的主要内容,如果未能解决你的问题,请参考以下文章

将 uint8_t 数组转换为 C 中的 uint16_t 值

如何将 bitset 转换为 bytes/uint8 数组?

如何在 C 中将字节数组转换为十六进制字符串?

在 Swift 中将 bytes/UInt8 数组转换为 Int

在Swift中将bytes / UInt8数组转换为Int

在 GoLang 中将字节切片“[]uint8”转换为 float64