From 0cdfe7c23d117ebccb200110d60b15783ec2eefd Mon Sep 17 00:00:00 2001
From: tanglong <842690096@qq.com>
Date: Tue, 16 Sep 2025 13:47:40 +0800
Subject: [PATCH] =?UTF-8?q?=E7=82=B9=E4=BD=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Wpf_AiSportsMicrospace/MainWindow.xaml | 9 +-
Wpf_AiSportsMicrospace/MainWindow.xaml.cs | 206 ++++++++++++------
Wpf_AiSportsMicrospace/RTSPPreview.cs | 143 ------------
Wpf_AiSportsMicrospace/RTSPPreviewRenderer.cs | 66 ------
.../Wpf_AiSportsMicrospace.csproj | 3 +-
5 files changed, 152 insertions(+), 275 deletions(-)
delete mode 100644 Wpf_AiSportsMicrospace/RTSPPreview.cs
delete mode 100644 Wpf_AiSportsMicrospace/RTSPPreviewRenderer.cs
diff --git a/Wpf_AiSportsMicrospace/MainWindow.xaml b/Wpf_AiSportsMicrospace/MainWindow.xaml
index d00a1bd..30dd2fe 100644
--- a/Wpf_AiSportsMicrospace/MainWindow.xaml
+++ b/Wpf_AiSportsMicrospace/MainWindow.xaml
@@ -4,6 +4,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Wpf_AiSportsMicrospace"
+ Loaded="Window_Loaded" Closing="Window_Closing"
mc:Ignorable="d" Title="AI运动微空间" Height="700" Width="1200">
@@ -31,9 +32,9 @@
-
-
+
+
+
+
diff --git a/Wpf_AiSportsMicrospace/MainWindow.xaml.cs b/Wpf_AiSportsMicrospace/MainWindow.xaml.cs
index 7873411..3f40171 100644
--- a/Wpf_AiSportsMicrospace/MainWindow.xaml.cs
+++ b/Wpf_AiSportsMicrospace/MainWindow.xaml.cs
@@ -1,15 +1,19 @@
using Emgu.CV.Reg;
+using SharpDX.Direct3D9;
using SkiaSharp;
using System.Diagnostics;
-using System.Drawing;
+using System.Drawing.Imaging;
using System.IO;
using System.Net.NetworkInformation;
+using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
+using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using System.Windows.Threading;
using Wpf_AiSportsMicrospace.Common;
-using Yztob.AiSports.Common;
using Yztob.AiSports.Inferences.Abstractions;
using Yztob.AiSports.Inferences.Things;
using Yztob.AiSports.Postures.Sports;
@@ -31,7 +35,9 @@ public partial class MainWindow : Window
private readonly List _sports;
private SportBase _sport;
private readonly SportDetectionQueue _detectQueue;
- private RTSPPreviewRenderer _rtspRenderer;
+
+ private WriteableBitmap _videoBitmap;
+ private int _lastFrameNumber = -1;
#endregion
@@ -51,13 +57,28 @@ public partial class MainWindow : Window
this.sportList.ItemsSource = _sports;
this.sportList.SelectedIndex = 0;
this.OnSelectedSport();
-
- // 确保 Canvas 已经加载完成
- _rtspRenderer = new RTSPPreviewRenderer(rtspPreviewCanvas)
+ }
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ Application.Current.Dispatcher.InvokeAsync(() =>
{
- Adaptive = true
- };
+ DrawJumpRope3DPointsWithGlow();
+ }, System.Windows.Threading.DispatcherPriority.Loaded);
+ videoImage.SizeChanged += (s, ev) =>
+ {
+ DrawJumpRope3DPointsWithGlow();
+ };
+ }
+
+ private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
+ {
+ base.OnClosed(e);
+
+ // 释放 WriteableBitmap
+ _videoBitmap = null;
+ videoImage.Source = null;
+ overlayCanvas = null;
}
private void sportList_SelectionChanged(object sender, SelectionChangedEventArgs e)
@@ -138,28 +159,62 @@ public partial class MainWindow : Window
}
private async void OnFrameExtracted(VideoFrame frame)
{
+ if (frame == null) return;
+
+ // 跳帧:每秒只渲染 10~15 帧,降低 CPU 占用
+ if (_lastFrameNumber != -1 && frame.Number - _lastFrameNumber < 2) return;
+ _lastFrameNumber = (int)frame.Number;
+
//获得帧二进制流,保存图像、人体识别竺
var buffer = frame.GetImageBuffer(Yztob.AiSports.Sensors.Things.ImageFormat.Jpeg).ToArray();
- await File.WriteAllBytesAsync($"./temps/{frame.Number}.jpg", buffer);
+ //await File.WriteAllBytesAsync($"./temps/{frame.Number}.jpg", buffer);
- //// 2️⃣ 转换成 BGRA32
- //int width, height;
- //var bgraBuffer = ConvertJpegToBGRA32(buffer, out width, out height);
+ // === 显示到 WPF ===
+ BitmapImage bitmap = null;
+ await Task.Run(() =>
+ {
+ using var ms = new MemoryStream(buffer);
+ bitmap = new BitmapImage();
+ bitmap.BeginInit();
+ bitmap.CacheOption = BitmapCacheOption.OnLoad;
+ bitmap.StreamSource = ms;
+ bitmap.EndInit();
+ bitmap.Freeze(); // 跨线程安全
+ });
- //// 3️⃣ 更新 WPF Canvas
- //Application.Current.Dispatcher.Invoke(() =>
- //{
- // _rtspRenderer.UpdateFrame(bgraBuffer, width, height);
- //});
- //_ = Task.Run(async () =>
- //{
- // await HumanPredictingAsync(frame.Number, buffer);
- //});
+ // UI 线程显示
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ if (_videoBitmap == null ||
+ _videoBitmap.PixelWidth != bitmap.PixelWidth ||
+ _videoBitmap.PixelHeight != bitmap.PixelHeight)
+ {
+ _videoBitmap = new WriteableBitmap(bitmap.PixelWidth, bitmap.PixelHeight,
+ 96, 96, PixelFormats.Bgra32, null);
+ videoImage.Source = _videoBitmap;
+ }
+
+ _videoBitmap.Lock();
+ bitmap.CopyPixels(new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight),
+ _videoBitmap.BackBuffer,
+ _videoBitmap.BackBufferStride * bitmap.PixelHeight,
+ _videoBitmap.BackBufferStride);
+ _videoBitmap.AddDirtyRect(new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight));
+ _videoBitmap.Unlock();
+
+ //// 延迟一帧等待控件布局完成
+ //Application.Current.Dispatcher.InvokeAsync(() =>
+ //{
+ // DrawJumpRopePointsOnVideoImage();
+ //}, System.Windows.Threading.DispatcherPriority.Loaded);
+ });
+
+
//可以进一步进行人体识别等
- var humanResult = await Task.Run(() => _humanPredictor.Predicting(buffer, frame.Number));
+ //var humanResult = await Task.Run(() => _humanPredictor.Predicting(buffer, frame.Number));
- var human = humanResult?.Humans?.FirstOrDefault();
- _detectQueue.Enqueue(frame.Number, human, null);
+ //var human = humanResult?.Humans?.FirstOrDefault();
+ //_detectQueue.Enqueue(frame.Number, human, null);
}
private void OnSportTick(int counts, int times)
{
@@ -178,51 +233,80 @@ public partial class MainWindow : Window
//VoiceBroadcast.PlayTick();
}
- private byte[] ConvertJpegToBGRA32(byte[] jpegBuffer, out int width, out int height)
+
+ private void DrawJumpRope3DPointsWithGlow()
{
- using var ms = new MemoryStream(jpegBuffer);
- using var bmp = new Bitmap(ms);
+ if (videoImage == null || overlayCanvas == null) return;
- width = bmp.Width;
- height = bmp.Height;
+ overlayCanvas.Children.Clear();
- var rect = new Rectangle(0, 0, width, height);
- var bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
+ double imgWidth = videoImage.ActualWidth;
+ double imgHeight = videoImage.ActualHeight;
- int bytes = Math.Abs(bmpData.Stride) * height;
- byte[] bgra = new byte[bytes];
- System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, bgra, 0, bytes);
+ if (imgWidth <= 0 || imgHeight <= 0) return;
- bmp.UnlockBits(bmpData);
- return bgra;
- }
+ overlayCanvas.Width = imgWidth;
+ overlayCanvas.Height = imgHeight;
- private async Task HumanPredictingAsync(long frameNumber, byte[] buffer)
+ // 前排 3 人(近景)
+ double frontRadius = 40;
+ var frontPositions = new List<(double XNorm, double YNorm)>
{
- var humanResultTask = Task.Run(() => _humanPredictor.Predicting(buffer, frameNumber));
- var objectsTask = _sport.Equipment
- ? Task.Run(() => _objectDetector.Detecting(buffer))
- : Task.FromResult(new List());
+ (0.2, 0.70), (0.5, 0.70), (0.8, 0.70) // 前排略高
+ };
- 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(() =>
+ foreach (var pos in frontPositions)
{
- //_humanGraphicsRenderer.ScaleRatio = _rtspRenderer.ScaleRatio;
- //_humanGraphicsRenderer.OffsetX = _rtspRenderer.OffsetX;
- //_humanGraphicsRenderer.OffsetY = _rtspRenderer.OffsetY;
- _humanGraphicsRenderer.Humans = humanResult?.Humans;
- _humanGraphicsRenderer.Objects = objects;
- });
+ double x = pos.XNorm * imgWidth;
+ double y = pos.YNorm * imgHeight;
+ AddGlowEllipse(x, y, frontRadius, 0.8, overlayCanvas);
+ }
+
+ // 后排 4 人(远景,更靠下面,大一点)
+ double backRadius = 50;
+ var backPositions = new List<(double XNorm, double YNorm)>
+ {
+ (0.1, 0.88), (0.35, 0.88), (0.65, 0.88), (0.9, 0.88) // 后排靠底
+ };
+
+ foreach (var pos in backPositions)
+ {
+ double x = pos.XNorm * imgWidth;
+ double y = pos.YNorm * imgHeight;
+ AddGlowEllipse(x, y, backRadius, 0.6, overlayCanvas);
+ }
}
+
+ ///
+ /// 添加带渐变光的圆圈
+ ///
+ private void AddGlowEllipse(double centerX, double centerY, double radius, double opacity, Canvas canvas)
+ {
+ var ellipse = new Ellipse
+ {
+ Width = radius * 2,
+ Height = radius, // 压扁成椭圆模拟地面
+ Stroke = Brushes.White,
+ StrokeThickness = 2,
+ Opacity = opacity,
+ Fill = new RadialGradientBrush
+ {
+ GradientOrigin = new Point(0.5, 0.5),
+ Center = new Point(0.5, 0.5),
+ RadiusX = 0.5,
+ RadiusY = 0.5,
+ GradientStops = new GradientStopCollection
+ {
+ new GradientStop(Color.FromArgb(180, 255, 0, 0), 0), // 中心红色
+ new GradientStop(Color.FromArgb(0, 255, 0, 0), 1) // 外部透明
+ }
+ }
+ };
+
+ Canvas.SetLeft(ellipse, centerX - radius);
+ Canvas.SetTop(ellipse, centerY - radius / 2);
+ canvas.Children.Add(ellipse);
+ }
+
+
}
\ No newline at end of file
diff --git a/Wpf_AiSportsMicrospace/RTSPPreview.cs b/Wpf_AiSportsMicrospace/RTSPPreview.cs
deleted file mode 100644
index 5372c00..0000000
--- a/Wpf_AiSportsMicrospace/RTSPPreview.cs
+++ /dev/null
@@ -1,143 +0,0 @@
-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
deleted file mode 100644
index ac2c86a..0000000
--- a/Wpf_AiSportsMicrospace/RTSPPreviewRenderer.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-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 0291eca..311ea4d 100644
--- a/Wpf_AiSportsMicrospace/Wpf_AiSportsMicrospace.csproj
+++ b/Wpf_AiSportsMicrospace/Wpf_AiSportsMicrospace.csproj
@@ -9,7 +9,7 @@
-
+
@@ -18,6 +18,7 @@
+