This commit is contained in:
tanglong 2025-09-15 14:45:54 +08:00
parent 1cbf9dcb1b
commit 6d65003c42
5 changed files with 280 additions and 9 deletions

View File

@ -18,7 +18,6 @@
DisplayMemberPath="Name" DisplayMemberPath="Name"
SelectedValuePath="Key" SelectedValuePath="Key"
SelectionChanged="sportList_SelectionChanged" d:IsHidden="True"/> SelectionChanged="sportList_SelectionChanged" d:IsHidden="True"/>
<TextBox x:Name="host" Margin="0,10,0,0" Text="192.168.3.64"/> <TextBox x:Name="host" Margin="0,10,0,0" Text="192.168.3.64"/>
<TextBox x:Name="userName" Margin="0,10,0,0" Text="admin"/> <TextBox x:Name="userName" Margin="0,10,0,0" Text="admin"/>
<TextBox x:Name="password" Margin="0,10,0,0" Text="yd708090"/> <TextBox x:Name="password" Margin="0,10,0,0" Text="yd708090"/>

View File

@ -1,17 +1,12 @@
using Emgu.CV.Reg; using Emgu.CV.Reg;
using SkiaSharp;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing;
using System.IO; using System.IO;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Text;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Wpf_AiSportsMicrospace.Common; using Wpf_AiSportsMicrospace.Common;
using Yztob.AiSports.Common; using Yztob.AiSports.Common;
using Yztob.AiSports.Inferences.Abstractions; using Yztob.AiSports.Inferences.Abstractions;
@ -35,6 +30,7 @@ public partial class MainWindow : Window
private readonly List<SportDescriptor> _sports; private readonly List<SportDescriptor> _sports;
private SportBase _sport; private SportBase _sport;
private readonly SportDetectionQueue _detectQueue; private readonly SportDetectionQueue _detectQueue;
private RTSPPreviewRenderer _rtspRenderer;
#endregion #endregion
@ -54,6 +50,13 @@ public partial class MainWindow : Window
this.sportList.ItemsSource = _sports; this.sportList.ItemsSource = _sports;
this.sportList.SelectedIndex = 0; this.sportList.SelectedIndex = 0;
this.OnSelectedSport(); this.OnSelectedSport();
// 确保 Canvas 已经加载完成
_rtspRenderer = new RTSPPreviewRenderer(rtspPreviewCanvas)
{
Adaptive = true
};
} }
private void sportList_SelectionChanged(object sender, SelectionChangedEventArgs e) private void sportList_SelectionChanged(object sender, SelectionChangedEventArgs e)
@ -136,9 +139,22 @@ public partial class MainWindow : Window
private async void OnFrameExtracted(VideoFrame frame) 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); 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<HumanInferenceResult>(() => _humanPredictor.Predicting(buffer, frame.Number)); var humanResult = await Task.Run<HumanInferenceResult>(() => _humanPredictor.Predicting(buffer, frame.Number));
@ -163,4 +179,50 @@ public partial class MainWindow : Window
//VoiceBroadcast.PlayTick(); //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<BoundingBox>());
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;
});
}
} }

View File

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

View File

@ -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);
}
/// <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;
}
}

View File

@ -21,6 +21,7 @@
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.7" /> <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.7" />
<PackageReference Include="SkiaSharp" Version="3.119.0" /> <PackageReference Include="SkiaSharp" Version="3.119.0" />
<PackageReference Include="System.Drawing.Common" Version="9.0.9" />
<PackageReference Include="System.Speech" Version="9.0.9" /> <PackageReference Include="System.Speech" Version="9.0.9" />
</ItemGroup> </ItemGroup>