实时
This commit is contained in:
parent
1cbf9dcb1b
commit
6d65003c42
@ -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"/>
|
||||||
|
@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
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" 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>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user