点位
This commit is contained in:
parent
b3feab76d6
commit
0cdfe7c23d
@ -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">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
@ -31,9 +32,9 @@
|
||||
</StackPanel>
|
||||
|
||||
<!-- 右侧视频预览 -->
|
||||
<Canvas x:Name="rtspPreviewCanvas" Grid.Column="1" Background="Black">
|
||||
<Image x:Name="rtspPreviewImage" Stretch="Uniform" />
|
||||
</Canvas>
|
||||
|
||||
<Grid Grid.Column="1" Background="Black">
|
||||
<Image x:Name="videoImage" Stretch="Uniform" />
|
||||
<Canvas x:Name="overlayCanvas" IsHitTestVisible="False" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
@ -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<SportDescriptor> _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<HumanInferenceResult>(() => _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<BoundingBox>());
|
||||
(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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加带渐变光的圆圈
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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<VideoFrame> OnExtracted { get; set; }
|
||||
public Action<VideoFrameWithBuffer> 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
/// <summary>更新帧到 Canvas</summary>
|
||||
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;
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="15.0.1" />
|
||||
<PackageReference Include="AutoMapper" Version="14.0.0" />
|
||||
<PackageReference Include="Emgu.CV" Version="4.12.0.5764" />
|
||||
<PackageReference Include="Emgu.CV.runtime.windows" Version="4.12.0.5764" />
|
||||
<PackageReference Include="FFmpeg.AutoGen" Version="7.1.1" />
|
||||
@ -18,6 +18,7 @@
|
||||
<PackageReference Include="Microsoft.Management.Infrastructure" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.ML" Version="4.0.2" />
|
||||
<PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.22.1" />
|
||||
<PackageReference Include="SharpDX.Direct3D9" Version="4.2.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.7" />
|
||||
<PackageReference Include="SkiaSharp" Version="3.119.0" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user