转换uint8字节数组到任何WPF渲染对象

Convert uint8 byte array to any WPF rendering object

本文关键字:WPF 对象 任何 uint8 字节 字节数 数组 转换      更新时间:2023-10-16

目前我已经使用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的方法。谢谢!

有很多方法可以做到这一点,我建议你看看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或一些这样的字节缓冲区来存储图像,代码将是类似的。这里选择的图像格式(和内存对齐)需要与BitmapSource匹配。在WPF应用程序中创建函数

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;
}

感谢您的快速回复!用一半的答案就解决了。不喜欢计时器,所以从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;

现在它工作了,再次感谢:)