将 uint8 字节数组转换为任何 WPF 渲染对象
Convert uint8 byte array to any WPF rendering object
目前我已经使用 C++ CLR 为 c# 构建了包装器。 C++ clr class 从摄像机获取帧作为 uint8[] 和 returns 到 c++ class 中的 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);
}
}
}
您可能想要另一个 class 来跟踪这些原始图像字节
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;
}
感谢您的快速回复!通过使用一半的答案来解决。不喜欢计时器,所以只是从 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:
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:
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;
现在可以使用了,再次感谢:)
目前我已经使用 C++ CLR 为 c# 构建了包装器。 C++ clr class 从摄像机获取帧作为 uint8[] 和 returns 到 c++ class 中的 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);
}
}
}
您可能想要另一个 class 来跟踪这些原始图像字节
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;
}
感谢您的快速回复!通过使用一半的答案来解决。不喜欢计时器,所以只是从 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:
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:
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;
现在可以使用了,再次感谢:)