实时
This commit is contained in:
parent
1cbf9dcb1b
commit
6d65003c42
@ -18,7 +18,6 @@
|
||||
DisplayMemberPath="Name"
|
||||
SelectedValuePath="Key"
|
||||
SelectionChanged="sportList_SelectionChanged" d:IsHidden="True"/>
|
||||
|
||||
<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="password" Margin="0,10,0,0" Text="yd708090"/>
|
||||
|
@ -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<SportDescriptor> _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<HumanInferenceResult>(() => _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<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;
|
||||
});
|
||||
}
|
||||
}
|
143
Wpf_AiSportsMicrospace/RTSPPreview.cs
Normal file
143
Wpf_AiSportsMicrospace/RTSPPreview.cs
Normal 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;
|
||||
}
|
||||
}
|
66
Wpf_AiSportsMicrospace/RTSPPreviewRenderer.cs
Normal file
66
Wpf_AiSportsMicrospace/RTSPPreviewRenderer.cs
Normal 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;
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.7" />
|
||||
<PackageReference Include="SkiaSharp" Version="3.119.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.9" />
|
||||
<PackageReference Include="System.Speech" Version="9.0.9" />
|
||||
</ItemGroup>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user