394 lines
15 KiB
C#
394 lines
15 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
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.Animation;
|
|
using System.Windows.Media.Imaging;
|
|
using System.Windows.Media.Media3D;
|
|
using Wpf_AiSportsMicrospace.Common;
|
|
using WpfAnimatedGif;
|
|
using Yztob.AiSports.Inferences.Abstractions;
|
|
using Yztob.AiSports.Inferences.Things;
|
|
using Yztob.AiSports.Sensors.Abstractions;
|
|
using Yztob.AiSports.Sensors.Things;
|
|
|
|
namespace Wpf_AiSportsMicrospace.Views
|
|
{
|
|
/// <summary>
|
|
/// Main.xaml 的交互逻辑
|
|
/// </summary>
|
|
public partial class Main : Window
|
|
{
|
|
private IHumanPredictor _humanPredictor;
|
|
private HumanGraphicsRenderer _humanGraphicsRenderer;
|
|
public SportOperate SportOperate { get; private set; }
|
|
public WebcamClient WebcamClient { get; private set; }
|
|
private readonly SportDetectionQueue _detectQueue;
|
|
private readonly ConcurrentQueue<VideoFrame> _frameQueue = new();
|
|
private readonly CancellationTokenSource _cts = new();
|
|
private DateTime _lastFrameTime = DateTime.Now;
|
|
private const int HeartbeatTimeoutMs = 5000; // 5秒内未收到帧则视为断线
|
|
private const int RetryIntervalMs = 3000; // 每3秒重试一次
|
|
private const int MaxRetryCount = 5;
|
|
private bool _isReconnecting = false;
|
|
private readonly object _reconnectLock = new();
|
|
|
|
public Main()
|
|
{
|
|
InitializeComponent();
|
|
|
|
var options = new InferenceOptions()
|
|
{
|
|
GpuEnabled = true
|
|
};
|
|
Yztob.AiSports.Common.SportAppSettingService.Set("inferences", options);
|
|
|
|
_humanPredictor = HumanPredictorFactory.Create(HumanPredictorType.MultiMedium);
|
|
_humanGraphicsRenderer = new HumanGraphicsRenderer();
|
|
_humanGraphicsRenderer.DrawLabel = false;
|
|
_detectQueue = new SportDetectionQueue();
|
|
|
|
SportOperate = new SportOperate();
|
|
WebcamClient = SportOperate.CreateRTSP();
|
|
|
|
// 开始抽帧线程
|
|
StartFrameProcessing();
|
|
// 启动心跳检测线程
|
|
//StartHeartbeatMonitor();
|
|
// 默认显示首页
|
|
MainContent.Content = new Home();
|
|
|
|
ImageBehavior.AddAnimationCompletedHandler(time3, TimeDown);
|
|
}
|
|
private void StartFrameProcessing()
|
|
{
|
|
if (WebcamClient == null)
|
|
{
|
|
HandyControl.Controls.MessageBox.Show("摄像头网络异常,请重新连接!");
|
|
return;
|
|
}
|
|
|
|
WebcamClient.OnExtractFrame += frame =>
|
|
{
|
|
_frameQueue.Enqueue(frame);
|
|
};
|
|
|
|
WebcamClient.StartExtract();
|
|
|
|
Task.Run(() =>
|
|
{
|
|
while (!_cts.Token.IsCancellationRequested)
|
|
{
|
|
if (_frameQueue.TryDequeue(out var frame))
|
|
{
|
|
ProcessFrame(frame);
|
|
}
|
|
else
|
|
{
|
|
// 避免 CPU 占满
|
|
Thread.Sleep(5);
|
|
}
|
|
}
|
|
}, _cts.Token);
|
|
}
|
|
|
|
private void ProcessFrame(VideoFrame frame)
|
|
{
|
|
try
|
|
{
|
|
//if (frame.Number % 2 != 0)
|
|
// return;
|
|
|
|
var buffer = frame.GetImageBuffer(ImageFormat.Jpeg).ToArray();
|
|
var humanResult = _humanPredictor.Predicting(buffer, frame.Number);
|
|
|
|
var humans = humanResult?.Humans?.ToList();
|
|
if (humans == null || humans.Count == 0)
|
|
return;
|
|
|
|
// 触发全局事件
|
|
Application.Current.Dispatcher.BeginInvoke(() =>
|
|
{
|
|
HumanFrameUpdated?.Invoke(this, humans);
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine("ProcessFrame error: " + ex.Message);
|
|
}
|
|
}
|
|
|
|
public event EventHandler<List<Human>> HumanFrameUpdated;
|
|
|
|
private void StartHeartbeatMonitor()
|
|
{
|
|
Task.Run(async () =>
|
|
{
|
|
while (!_cts.Token.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
var elapsed = (DateTime.Now - _lastFrameTime).TotalMilliseconds;
|
|
if (elapsed > HeartbeatTimeoutMs && !_isReconnecting)
|
|
{
|
|
Console.WriteLine("检测到摄像头断线,准备重连...");
|
|
await TryReconnectAsync();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"HeartbeatMonitor error: {ex.Message}");
|
|
}
|
|
|
|
await Task.Delay(HeartbeatTimeoutMs / 2, _cts.Token);
|
|
}
|
|
}, _cts.Token);
|
|
}
|
|
private async Task TryReconnectAsync()
|
|
{
|
|
lock (_reconnectLock)
|
|
{
|
|
if (_isReconnecting)
|
|
return;
|
|
_isReconnecting = true;
|
|
}
|
|
|
|
int retryCount = 0;
|
|
bool success = false;
|
|
|
|
while (retryCount < MaxRetryCount && !_cts.Token.IsCancellationRequested)
|
|
{
|
|
retryCount++;
|
|
Console.WriteLine($"尝试第 {retryCount} 次重连摄像头...");
|
|
|
|
try
|
|
{
|
|
WebcamClient?.StopExtract();
|
|
WebcamClient = SportOperate.CreateRTSP();
|
|
|
|
if (WebcamClient != null)
|
|
{
|
|
WebcamClient.OnExtractFrame += frame =>
|
|
{
|
|
_lastFrameTime = DateTime.Now;
|
|
_frameQueue.Enqueue(frame);
|
|
};
|
|
WebcamClient.StartExtract();
|
|
success = true;
|
|
Console.WriteLine("摄像头重连成功!");
|
|
break;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"重连失败: {ex.Message}");
|
|
}
|
|
|
|
await Task.Delay(RetryIntervalMs);
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
Application.Current.Dispatcher.Invoke(() =>
|
|
{
|
|
HandyControl.Controls.MessageBox.Show("摄像头连接失败,请检查网络或设备!");
|
|
});
|
|
}
|
|
|
|
_isReconnecting = false;
|
|
}
|
|
|
|
public void SwitchPage(UserControl newPage, bool fromRight)
|
|
{
|
|
if (MainContent.Content == newPage) return;
|
|
|
|
var oldPage = MainContent.Content as UserControl;
|
|
|
|
// 预加载新页面资源
|
|
PreloadPageResources(newPage);
|
|
|
|
// 确保最终状态
|
|
newPage.RenderTransform = null;
|
|
newPage.Opacity = 1;
|
|
|
|
// 切换到只有新页面
|
|
MainContent.Content = newPage;
|
|
|
|
// 清理资源
|
|
if (oldPage is IDisposable disposable)
|
|
disposable.Dispose();
|
|
}
|
|
|
|
private void PreloadPageResources(UserControl page)
|
|
{
|
|
// 先从原来的父容器中移除页面(如果存在)
|
|
if (page.Parent != null)
|
|
{
|
|
var parent = page.Parent as Panel;
|
|
parent?.Children.Remove(page);
|
|
}
|
|
|
|
// 强制WPF加载所有资源
|
|
page.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
|
page.Arrange(new Rect(0, 0, page.DesiredSize.Width, page.DesiredSize.Height));
|
|
}
|
|
|
|
private void AnimateNewPageEnhanced(UserControl newPage, bool fromRight)
|
|
{
|
|
var transformGroup = new TransformGroup();
|
|
var scale = new ScaleTransform(0.7, 0.7);
|
|
var skew = new SkewTransform(fromRight ? -45 : 45, 0);
|
|
var translate = new TranslateTransform(fromRight ? ActualWidth : -ActualWidth, 0);
|
|
transformGroup.Children.Add(scale);
|
|
transformGroup.Children.Add(skew);
|
|
transformGroup.Children.Add(translate);
|
|
newPage.RenderTransformOrigin = new Point(fromRight ? 0 : 1, 0.5);
|
|
newPage.RenderTransform = transformGroup;
|
|
|
|
// 动画
|
|
var scaleAnim = new DoubleAnimation(0.7, 1, TimeSpan.FromMilliseconds(600)) { EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut } };
|
|
var skewAnim = new DoubleAnimation(fromRight ? -45 : 45, 0, TimeSpan.FromMilliseconds(600)) { EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut } };
|
|
var fadeAnim = new DoubleAnimation(0.2, 1, TimeSpan.FromMilliseconds(600));
|
|
var translateAnim = new DoubleAnimation(fromRight ? ActualWidth : -ActualWidth, 0, TimeSpan.FromMilliseconds(600)) { EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut } };
|
|
|
|
scale.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnim);
|
|
scale.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnim);
|
|
skew.BeginAnimation(SkewTransform.AngleYProperty, skewAnim);
|
|
translate.BeginAnimation(TranslateTransform.XProperty, translateAnim);
|
|
newPage.BeginAnimation(OpacityProperty, fadeAnim);
|
|
}
|
|
|
|
public void SwitchPageWithMaskAnimation(UserControl newPage, bool fromRight, double durationSeconds = 1.2)
|
|
{
|
|
if (MainContent.Content == newPage) return;
|
|
|
|
var oldPage = MainContent.Content as UserControl;
|
|
PreloadPageResources(newPage);
|
|
|
|
// 每次都创建新的 Image 实例
|
|
var maskTop = new Image
|
|
{
|
|
Source = new BitmapImage(new Uri("/Resources/Img/gif/top_animation_image.png", UriKind.Relative)),
|
|
Stretch = Stretch.Fill,
|
|
HorizontalAlignment = HorizontalAlignment.Stretch,
|
|
VerticalAlignment = VerticalAlignment.Top,
|
|
Width = 1920,
|
|
Height = 1000
|
|
};
|
|
var maskBottom = new Image
|
|
{
|
|
Source = new BitmapImage(new Uri("/Resources/Img/gif/bottom_animation_image.png", UriKind.Relative)),
|
|
Stretch = Stretch.Fill,
|
|
HorizontalAlignment = HorizontalAlignment.Stretch,
|
|
VerticalAlignment = VerticalAlignment.Bottom,
|
|
Width = 1920,
|
|
Height = 1000
|
|
};
|
|
|
|
var maskGrid = new Grid { Background = Brushes.Transparent, Width = 1920, Height = 1080 };
|
|
maskGrid.Children.Add(maskTop);
|
|
maskGrid.Children.Add(maskBottom);
|
|
|
|
if (MainContent.Parent is Grid parentGrid)
|
|
{
|
|
parentGrid.Children.Add(maskGrid);
|
|
Grid.SetZIndex(maskGrid, 9999);
|
|
}
|
|
else
|
|
{
|
|
var win = Window.GetWindow(this);
|
|
if (win != null && win.Content is Grid winGrid)
|
|
{
|
|
winGrid.Children.Add(maskGrid);
|
|
Grid.SetZIndex(maskGrid, 9999);
|
|
}
|
|
}
|
|
|
|
double height = ActualHeight;
|
|
double maskHeight = 1000;
|
|
//double maskHeight = height / 2 - 150;
|
|
|
|
//maskTop.Height = maskHeight;
|
|
//maskBottom.Height = maskHeight;
|
|
|
|
maskTop.RenderTransform = new TranslateTransform(0, -maskHeight);
|
|
maskBottom.RenderTransform = new TranslateTransform(0, maskHeight);
|
|
|
|
var duration = TimeSpan.FromSeconds(durationSeconds);
|
|
var ease = new CubicEase { EasingMode = EasingMode.EaseInOut };
|
|
|
|
var topInAnim = new DoubleAnimation(-maskHeight, 0, duration) { EasingFunction = ease };
|
|
var bottomInAnim = new DoubleAnimation(maskHeight, 0, duration) { EasingFunction = ease };
|
|
//var topOutAnim = new DoubleAnimation(0, maskHeight, duration) { EasingFunction = ease };
|
|
//var bottomOutAnim = new DoubleAnimation(0, -maskHeight, duration) { EasingFunction = ease };
|
|
var bottomOutAnim = new DoubleAnimation(0, maskHeight, duration) { EasingFunction = ease };
|
|
var topOutAnim = new DoubleAnimation(0, -maskHeight, duration) { EasingFunction = ease };
|
|
|
|
topInAnim.Completed += (s, e) =>
|
|
{
|
|
MainContent.Content = newPage;
|
|
maskTop.RenderTransform.BeginAnimation(TranslateTransform.YProperty, topOutAnim);
|
|
maskBottom.RenderTransform.BeginAnimation(TranslateTransform.YProperty, bottomOutAnim);
|
|
};
|
|
|
|
topOutAnim.Completed += (s, e) =>
|
|
{
|
|
if (maskGrid.Parent is Grid grid)
|
|
grid.Children.Remove(maskGrid);
|
|
if (oldPage is IDisposable disposable)
|
|
disposable.Dispose();
|
|
};
|
|
|
|
maskTop.RenderTransform.BeginAnimation(TranslateTransform.YProperty, topInAnim);
|
|
maskBottom.RenderTransform.BeginAnimation(TranslateTransform.YProperty, bottomInAnim);
|
|
}
|
|
|
|
public void ShowElement(string name, int time = 600)
|
|
{
|
|
var Element = FindName(name) as FrameworkElement;
|
|
if (Element == null) return;
|
|
|
|
Element.Visibility = Visibility.Visible;
|
|
var fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(time));
|
|
Element.BeginAnimation(UIElement.OpacityProperty, fadeIn);
|
|
}
|
|
|
|
int num = 1;
|
|
public void ShowCountDownAnimation()
|
|
{
|
|
ImageBehavior.SetRepeatBehavior(time3, new RepeatBehavior(2));
|
|
ImageBehavior.SetRepeatBehavior(time3, new RepeatBehavior(1));
|
|
CountTimeGrid.Visibility = Visibility.Visible;
|
|
time3.Visibility = Visibility.Visible;
|
|
ImageBehavior.GetAnimationController(time3).Play();
|
|
}
|
|
|
|
private void TimeDown(object? sender, RoutedEventArgs? e)
|
|
{
|
|
//MessageBox.Show("播放完啦");// 弹提示框
|
|
ImageBehavior.GetAnimationController(time3).Pause();
|
|
CountTimeGrid.Visibility = Visibility.Hidden;
|
|
time3.Visibility = Visibility.Hidden;
|
|
num++;
|
|
}
|
|
|
|
private void Window_MouseDoubleClick(object sender, MouseButtonEventArgs e)
|
|
{
|
|
// 确认是左键双击
|
|
if (e.ChangedButton == MouseButton.Right)
|
|
{
|
|
Application.Current.Shutdown();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
} |