diff --git a/Wpf_AiSportsMicrospace/MainWindow.xaml b/Wpf_AiSportsMicrospace/MainWindow.xaml
index e3d086b..8bbde93 100644
--- a/Wpf_AiSportsMicrospace/MainWindow.xaml
+++ b/Wpf_AiSportsMicrospace/MainWindow.xaml
@@ -18,7 +18,6 @@
DisplayMemberPath="Name"
SelectedValuePath="Key"
SelectionChanged="sportList_SelectionChanged" d:IsHidden="True"/>
-
diff --git a/Wpf_AiSportsMicrospace/MainWindow.xaml.cs b/Wpf_AiSportsMicrospace/MainWindow.xaml.cs
index 81f60a8..76e710a 100644
--- a/Wpf_AiSportsMicrospace/MainWindow.xaml.cs
+++ b/Wpf_AiSportsMicrospace/MainWindow.xaml.cs
@@ -1,17 +1,12 @@
using Emgu.CV.Reg;
+using SkiaSharp;
using System.Diagnostics;
+using System.Drawing;
using System.IO;
using System.Net.NetworkInformation;
-using System.Text;
using System.Windows;
using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Navigation;
-using System.Windows.Shapes;
using Wpf_AiSportsMicrospace.Common;
using Yztob.AiSports.Common;
using Yztob.AiSports.Inferences.Abstractions;
@@ -35,6 +30,7 @@ public partial class MainWindow : Window
private readonly List _sports;
private SportBase _sport;
private readonly SportDetectionQueue _detectQueue;
+ private RTSPPreviewRenderer _rtspRenderer;
#endregion
@@ -54,6 +50,13 @@ public partial class MainWindow : Window
this.sportList.ItemsSource = _sports;
this.sportList.SelectedIndex = 0;
this.OnSelectedSport();
+
+ // 确保 Canvas 已经加载完成
+ _rtspRenderer = new RTSPPreviewRenderer(rtspPreviewCanvas)
+ {
+ Adaptive = true
+ };
+
}
private void sportList_SelectionChanged(object sender, SelectionChangedEventArgs e)
@@ -136,9 +139,22 @@ public partial class MainWindow : Window
private async void OnFrameExtracted(VideoFrame frame)
{
//获得帧二进制流,保存图像、人体识别竺
- var buffer = frame.GetImageBuffer(ImageFormat.Jpeg).ToArray();
+ var buffer = frame.GetImageBuffer(Yztob.AiSports.Sensors.Things.ImageFormat.Jpeg).ToArray();
await File.WriteAllBytesAsync($"./temps/{frame.Number}.jpg", buffer);
+ //// 2️⃣ 转换成 BGRA32
+ //int width, height;
+ //var bgraBuffer = ConvertJpegToBGRA32(buffer, out width, out height);
+
+ //// 3️⃣ 更新 WPF Canvas
+ //Application.Current.Dispatcher.Invoke(() =>
+ //{
+ // _rtspRenderer.UpdateFrame(bgraBuffer, width, height);
+ //});
+ //_ = Task.Run(async () =>
+ //{
+ // await HumanPredictingAsync(frame.Number, buffer);
+ //});
//可以进一步进行人体识别等
var humanResult = await Task.Run(() => _humanPredictor.Predicting(buffer, frame.Number));
@@ -163,4 +179,50 @@ public partial class MainWindow : Window
//VoiceBroadcast.PlayTick();
}
+ private byte[] ConvertJpegToBGRA32(byte[] jpegBuffer, out int width, out int height)
+ {
+ using var ms = new MemoryStream(jpegBuffer);
+ using var bmp = new Bitmap(ms);
+
+ width = bmp.Width;
+ height = bmp.Height;
+
+ var rect = new Rectangle(0, 0, width, height);
+ var bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
+
+ int bytes = Math.Abs(bmpData.Stride) * height;
+ byte[] bgra = new byte[bytes];
+ System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, bgra, 0, bytes);
+
+ bmp.UnlockBits(bmpData);
+ return bgra;
+ }
+ private async Task HumanPredictingAsync(long frameNumber, byte[] buffer)
+ {
+ var humanResultTask = Task.Run(() => _humanPredictor.Predicting(buffer, frameNumber));
+ var objectsTask = _sport.Equipment
+ ? Task.Run(() => _objectDetector.Detecting(buffer))
+ : Task.FromResult(new List());
+
+ await Task.WhenAll(humanResultTask, objectsTask);
+
+ var humanResult = humanResultTask.Result;
+ var objects = objectsTask.Result;
+
+ if (_sport.MeasureApparatus != null)
+ objects.AddRange(_sport.MeasureApparatus.Apparatuses);
+
+ var human = humanResult?.Humans?.FirstOrDefault();
+ _detectQueue.Enqueue(frameNumber, human, objects);
+
+ // 更新渲染器属性(UI线程)
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ //_humanGraphicsRenderer.ScaleRatio = _rtspRenderer.ScaleRatio;
+ //_humanGraphicsRenderer.OffsetX = _rtspRenderer.OffsetX;
+ //_humanGraphicsRenderer.OffsetY = _rtspRenderer.OffsetY;
+ _humanGraphicsRenderer.Humans = humanResult?.Humans;
+ _humanGraphicsRenderer.Objects = objects;
+ });
+ }
}
\ No newline at end of file
diff --git a/Wpf_AiSportsMicrospace/RTSPPreview.cs b/Wpf_AiSportsMicrospace/RTSPPreview.cs
new file mode 100644
index 0000000..5372c00
--- /dev/null
+++ b/Wpf_AiSportsMicrospace/RTSPPreview.cs
@@ -0,0 +1,143 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Controls;
+using System.Windows.Media.Imaging;
+using System.Windows.Media;
+using System.Windows;
+using Yztob.AiSports.Sensors.Things;
+
+namespace Wpf_AiSportsMicrospace
+{
+ public class RTSPPreview : UserControl, IDisposable
+ {
+ private Image _imageControl;
+ private WriteableBitmap _bitmap;
+ private bool _isPlaying = false;
+
+ public RTSPPreview()
+ {
+ _imageControl = new Image();
+ this.Content = _imageControl;
+ this.Loaded += RTSPPreview_Loaded;
+ }
+
+ private void RTSPPreview_Loaded(object sender, RoutedEventArgs e)
+ {
+ if (Adaptive)
+ {
+ _imageControl.Stretch = Stretch.Uniform;
+ }
+ else
+ {
+ _imageControl.Stretch = Stretch.Fill;
+ }
+ }
+
+ #region 属性
+
+ [Browsable(true)]
+ [DefaultValue(true)]
+ [Description("是否自应用大小,保持同比缩放。")]
+ public bool Adaptive { get; set; } = true;
+
+ public string Host { get; set; }
+ public uint Port { get; set; }
+ public string UserName { get; set; }
+ public string Password { get; set; }
+
+ public Action OnExtracted { get; set; }
+ public Action OnFrameProcessing { get; set; }
+
+ public bool IsPlaying => _isPlaying;
+
+ public float OffsetX { get; private set; }
+ public float OffsetY { get; private set; }
+ public float ScaleRatio { get; private set; }
+
+ #endregion
+
+ #region 播放/停止/保存
+
+ public void Play()
+ {
+ _isPlaying = true;
+ // TODO: RTSP 播放初始化
+ // 可使用 FFmpeg.AutoGen 或 LibVLCSharp
+ }
+
+ public void Stop()
+ {
+ _isPlaying = false;
+ // TODO: 停止播放
+ }
+
+ public bool SaveFrameToJpeg(string path)
+ {
+ if (_bitmap == null) return false;
+
+ try
+ {
+ BitmapEncoder encoder = new JpegBitmapEncoder();
+ encoder.Frames.Add(BitmapFrame.Create(_bitmap));
+
+ using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
+ {
+ encoder.Save(fs);
+ }
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ #endregion
+
+ #region 更新帧
+
+ public void UpdateFrame(byte[] pixelData, int width, int height)
+ {
+ // 假设 pixelData 为 BGRA32
+ if (_bitmap == null || _bitmap.PixelWidth != width || _bitmap.PixelHeight != height)
+ {
+ _bitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgra32, null);
+ _imageControl.Source = _bitmap;
+ }
+
+ _bitmap.Lock();
+ _bitmap.WritePixels(new Int32Rect(0, 0, width, height), pixelData, width * 4, 0);
+ _bitmap.AddDirtyRect(new Int32Rect(0, 0, width, height));
+ _bitmap.Unlock();
+
+ OnFrameProcessing?.Invoke(new VideoFrameWithBuffer
+ {
+ Width = width,
+ Height = height,
+ Buffer = pixelData
+ });
+ }
+
+ #endregion
+
+ public void Dispose()
+ {
+ Stop();
+ _bitmap = null;
+ }
+ }
+
+ // 数据类示例
+
+ public class VideoFrameWithBuffer
+ {
+ public int Width;
+ public int Height;
+ public byte[] Buffer;
+ }
+}
diff --git a/Wpf_AiSportsMicrospace/RTSPPreviewRenderer.cs b/Wpf_AiSportsMicrospace/RTSPPreviewRenderer.cs
new file mode 100644
index 0000000..ac2c86a
--- /dev/null
+++ b/Wpf_AiSportsMicrospace/RTSPPreviewRenderer.cs
@@ -0,0 +1,66 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+public class RTSPPreviewRenderer : IDisposable
+{
+ private Canvas _canvas;
+ private Image _imageControl;
+ private WriteableBitmap _bitmap;
+ public bool Adaptive { get; set; } = true;
+
+ public RTSPPreviewRenderer(Canvas canvas)
+ {
+ _canvas = canvas ?? throw new ArgumentNullException(nameof(canvas));
+
+ _imageControl = new Image();
+ _canvas.Children.Add(_imageControl);
+
+ Canvas.SetLeft(_imageControl, 0);
+ Canvas.SetTop(_imageControl, 0);
+ }
+
+ /// 更新帧到 Canvas
+ public void UpdateFrame(byte[] pixelData, int width, int height)
+ {
+ if (_bitmap == null || _bitmap.PixelWidth != width || _bitmap.PixelHeight != height)
+ {
+ _bitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgra32, null);
+ _imageControl.Source = _bitmap;
+ }
+
+ _bitmap.Lock();
+ _bitmap.WritePixels(new System.Windows.Int32Rect(0, 0, width, height), pixelData, width * 4, 0);
+ _bitmap.AddDirtyRect(new System.Windows.Int32Rect(0, 0, width, height));
+ _bitmap.Unlock();
+
+ // 自动适应 Canvas
+ if (Adaptive)
+ {
+ double scaleX = _canvas.ActualWidth / width;
+ double scaleY = _canvas.ActualHeight / height;
+ double scale = Math.Min(scaleX, scaleY);
+
+ _imageControl.Width = width * scale;
+ _imageControl.Height = height * scale;
+
+ Canvas.SetLeft(_imageControl, (_canvas.ActualWidth - _imageControl.Width) / 2);
+ Canvas.SetTop(_imageControl, (_canvas.ActualHeight - _imageControl.Height) / 2);
+ }
+ else
+ {
+ _imageControl.Width = _canvas.ActualWidth;
+ _imageControl.Height = _canvas.ActualHeight;
+ Canvas.SetLeft(_imageControl, 0);
+ Canvas.SetTop(_imageControl, 0);
+ }
+ }
+
+ public void Dispose()
+ {
+ _bitmap = null;
+ }
+}
diff --git a/Wpf_AiSportsMicrospace/Wpf_AiSportsMicrospace.csproj b/Wpf_AiSportsMicrospace/Wpf_AiSportsMicrospace.csproj
index 7a5ec6d..0291eca 100644
--- a/Wpf_AiSportsMicrospace/Wpf_AiSportsMicrospace.csproj
+++ b/Wpf_AiSportsMicrospace/Wpf_AiSportsMicrospace.csproj
@@ -21,6 +21,7 @@
+