diff --git a/Wpf_AiSportsMicrospace/App.xaml.cs b/Wpf_AiSportsMicrospace/App.xaml.cs index 4fbe65e..fb87b50 100644 --- a/Wpf_AiSportsMicrospace/App.xaml.cs +++ b/Wpf_AiSportsMicrospace/App.xaml.cs @@ -43,7 +43,8 @@ public partial class App : Application services.AddDbContext(options => options.UseSqlite(connectionString)); - services.AddSingleton(); + //services.AddSingleton(); + services.AddSingleton
(); }) .Build(); @@ -57,8 +58,16 @@ public partial class App : Application } // 用 DI 创建窗口 - var mainWindow = AppHost.Services.GetRequiredService(); - mainWindow.Show(); + + //var mainWindow = AppHost.Services.GetRequiredService(); + //mainWindow.Show(); + + var mainWin = AppHost.Services.GetRequiredService
(); + Current.MainWindow = mainWin; + mainWin.Show(); + + var newPage = new Home(); + mainWin.SwitchPage(newPage, true); } } diff --git a/Wpf_AiSportsMicrospace/Common/AppSettings.cs b/Wpf_AiSportsMicrospace/Common/AppSettings.cs new file mode 100644 index 0000000..26d57e4 --- /dev/null +++ b/Wpf_AiSportsMicrospace/Common/AppSettings.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Common +{ + public static class AppSettings + { + public static string SchoolCode = "202501060001"; + public static string BackendUrl = "http://localhost:9992/api/StudentFace"; + } +} diff --git a/Wpf_AiSportsMicrospace/Common/HttpManager.cs b/Wpf_AiSportsMicrospace/Common/HttpManager.cs new file mode 100644 index 0000000..11751f8 --- /dev/null +++ b/Wpf_AiSportsMicrospace/Common/HttpManager.cs @@ -0,0 +1,67 @@ +using System.Net.Http; +using System.Text; + +namespace Wpf_AiSportsMicrospace.Common +{ + public class HttpManager + { + public static async Task HttpPostAsync(string url, string postData = null, string contentType = "application/json", int timeOut = 30, Dictionary headers = null) + { + using (var client = new HttpClient()) + { + client.Timeout = TimeSpan.FromSeconds(timeOut); + + if (headers != null) + { + foreach (var header in headers) + { + client.DefaultRequestHeaders.Add(header.Key, header.Value); + } + } + + HttpContent content = null; + if (!string.IsNullOrEmpty(postData)) + { + content = new StringContent(postData, Encoding.UTF8, contentType); + } + + try + { + var response = await client.PostAsync(url, content); + response.EnsureSuccessStatusCode(); + return await response.Content.ReadAsStringAsync(); + } + catch (Exception ex) + { + return ex.Message; + } + } + } + public static async Task HttpGetAsync(string url, Dictionary headers = null) + { + using (var client = new HttpClient()) + { + if (headers != null) + { + foreach (var header in headers) + { + client.DefaultRequestHeaders.Add(header.Key, header.Value); + } + } + + try + { + var response = await client.GetAsync(url); + + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadAsStringAsync(); + } + catch (Exception ex) + { + return ex.Message; + } + } + } + } +} diff --git a/Wpf_AiSportsMicrospace/Common/SportOperate.cs b/Wpf_AiSportsMicrospace/Common/SportOperate.cs index 5be2d76..7148e16 100644 --- a/Wpf_AiSportsMicrospace/Common/SportOperate.cs +++ b/Wpf_AiSportsMicrospace/Common/SportOperate.cs @@ -56,7 +56,7 @@ namespace Wpf_AiSportsMicrospace.Common { try { - _webcamClient = WebcamClient.CreateRTSP("172.17.30.65", "admin", "yd708090", 554u); + _webcamClient = WebcamClient.CreateRTSP("172.17.30.64", "admin", "yd708090", 554u); } catch (Exception) { diff --git a/Wpf_AiSportsMicrospace/Common/Utils.cs b/Wpf_AiSportsMicrospace/Common/Utils.cs index e7d30c7..998cf66 100644 --- a/Wpf_AiSportsMicrospace/Common/Utils.cs +++ b/Wpf_AiSportsMicrospace/Common/Utils.cs @@ -1,15 +1,22 @@ -using System; -using System.Collections.Generic; +using Common; +using Dto; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; +using System.Text.Json; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; using WpfAnimatedGif; +using Color = System.Windows.Media.Color; +using SharpImage = SixLabors.ImageSharp; namespace Wpf_AiSportsMicrospace.Common { @@ -96,7 +103,6 @@ namespace Wpf_AiSportsMicrospace.Common return total; } - //public static Uri countDownGif = new Uri("/Resources/Img/Album/1.gif", UriKind.Relative); //倒计时动画 @@ -132,7 +138,7 @@ namespace Wpf_AiSportsMicrospace.Common Height = 1080, }; ImageBehavior.SetAnimatedSource(image, uri); - ImageBehavior.SetAutoStart(image , true); + ImageBehavior.SetAutoStart(image, true); grid.Children.Add(image); needAnimationGrid.Children.Add(grid); @@ -151,62 +157,143 @@ namespace Wpf_AiSportsMicrospace.Common return grid; } - //预加载资源 - private static readonly Dictionary _cache = new Dictionary(); - private static bool _isPreloaded = false; + private static readonly Dictionary _cache = new Dictionary(); + private static bool _isPreloaded = false; - // 预加载所有需要的 GIF - public static void PreloadGifs() - { - if (_isPreloaded) return; + // 预加载所有需要的 GIF + public static void PreloadGifs() + { + if (_isPreloaded) return; var gifPaths = new[] { "/time_3.gif", // 添加其他需要预加载的 GIF 路径 }; - foreach (var path in gifPaths) - { - LoadGifToCache(path); - } - - _isPreloaded = true; + foreach (var path in gifPaths) + { + LoadGifToCache(path); } - private static void LoadGifToCache(string relativePath) + _isPreloaded = true; + } + + private static void LoadGifToCache(string relativePath) + { + try { - try - { string projectRoot = Path.Combine(AppContext.BaseDirectory, @"..\..\.."); string topPath = Path.Combine(projectRoot, "Resources", "Img", "gif"); var bitmapImage = new BitmapImage(); - bitmapImage.BeginInit(); - bitmapImage.UriSource = new Uri(topPath + relativePath, UriKind.Relative); - bitmapImage.CacheOption = BitmapCacheOption.OnLoad; // 立即加载到内存 - bitmapImage.CreateOptions = BitmapCreateOptions.IgnoreImageCache; // 忽略磁盘缓存 - bitmapImage.EndInit(); - bitmapImage.Freeze(); // 冻结对象,使其线程安全 - + bitmapImage.BeginInit(); + bitmapImage.UriSource = new Uri(topPath + relativePath, UriKind.Relative); + bitmapImage.CacheOption = BitmapCacheOption.OnLoad; // 立即加载到内存 + bitmapImage.CreateOptions = BitmapCreateOptions.IgnoreImageCache; // 忽略磁盘缓存 + bitmapImage.EndInit(); + bitmapImage.Freeze(); // 冻结对象,使其线程安全 + _cache[relativePath] = bitmapImage; - } - catch (Exception ex) - { - Debug.WriteLine($"预加载GIF失败 {relativePath}: {ex.Message}"); - } } - - public static BitmapImage GetGif(string relativePath) + catch (Exception ex) { - if (_cache.TryGetValue(relativePath, out var cachedImage)) - { - return cachedImage; - } - - // 如果缓存中没有,立即加载 - LoadGifToCache(relativePath); - return _cache.GetValueOrDefault(relativePath); + Debug.WriteLine($"预加载GIF失败 {relativePath}: {ex.Message}"); } + } + + public static BitmapImage GetGif(string relativePath) + { + if (_cache.TryGetValue(relativePath, out var cachedImage)) + { + return cachedImage; + } + + // 如果缓存中没有,立即加载 + LoadGifToCache(relativePath); + return _cache.GetValueOrDefault(relativePath); + } + + public static async Task> FacialRecognition(byte[] buffer, double[] xNorms) + { + if (xNorms == null || xNorms.Length < 2) + throw new ArgumentException("xNorms 至少需要两个元素"); + + using var image = SharpImage.Image.Load(buffer); + + int imgWidth = image.Width; + int imgHeight = image.Height; + int rectHeight = imgHeight / 2; + + // 计算分割边界 + var sortedX = xNorms.OrderBy(x => x).ToList(); + var splitXs = new List(); + for (int i = 0; i < sortedX.Count - 1; i++) + splitXs.Add((sortedX[i] + sortedX[i + 1]) / 2.0); + splitXs.Insert(0, 0.0); + splitXs.Add(1.0); + + var studentInfos = new List(); + + for (int i = 0; i < 6 && i < splitXs.Count - 1; i++) + { + int x1 = (int)(splitXs[i] * imgWidth); + int x2 = (int)(splitXs[i + 1] * imgWidth); + int w = x2 - x1; + + var cropRect = new Rectangle(x1, 0, w, rectHeight); + + using var cropped = image.Clone(ctx => ctx.Crop(cropRect)); + + // 转为 Base64 + using var ms = new MemoryStream(); + cropped.Save(ms, new JpegEncoder()); + string base64 = Convert.ToBase64String(ms.ToArray()); + + // 调用人脸识别方法(假设返回姓名+头像Base64) + StudentInfoDto studentInfo = await FaceRecognitionAsync(base64); + studentInfos.Add(studentInfo); + } + + return studentInfos; + } + + /// + /// 调用人脸识别接口,返回学生信息 + /// + public static async Task FaceRecognitionAsync(string base64Image) + { + var param = new + { + AppSettings.SchoolCode, + Base64 = base64Image + }; + + string json = JsonSerializer.Serialize(param); + + // 调用 HttpPostAsync + string response = await HttpManager.HttpPostAsync(AppSettings.BackendUrl, json); + + if (string.IsNullOrEmpty(response)) + return null; + + try + { + // 反序列化为接口返回对象 + var faceInfo = JsonSerializer.Deserialize(response, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + + if (faceInfo == null) + return null; + + return faceInfo; + } + catch + { + return null; + } + } } } diff --git a/Wpf_AiSportsMicrospace/Dto/StudentInfoDto.cs b/Wpf_AiSportsMicrospace/Dto/StudentInfoDto.cs new file mode 100644 index 0000000..882aa8a --- /dev/null +++ b/Wpf_AiSportsMicrospace/Dto/StudentInfoDto.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Dto +{ + /// + /// 学生信息 + /// + public class StudentInfoDto + { + public string Name { get; set; } + public string Photo { get; set; } + public string StudentNo { get; set; } + public string SchoolCode { get; set; } + public string GradeName { get; set; } + public string ClassName { get; set; } + public int Age { get; set; } + public string Sex { get; set; } + } +} diff --git a/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs b/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs index e58fbcd..bc47647 100644 --- a/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs +++ b/Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs @@ -70,6 +70,12 @@ namespace Wpf_AiSportsMicrospace.Views } private async void UserControl_Loaded(object sender, RoutedEventArgs e) { + // 绑定人脸识别事件 + _mainWin.FacialRecognitionEvent += async (sender, buffer) => + { + var studentList = await Utils.FacialRecognition(buffer, new double[] { 0.50, 0.78 }); + }; + DrawCirclesWithText(); // 播放音乐 diff --git a/Wpf_AiSportsMicrospace/Views/Main.xaml.cs b/Wpf_AiSportsMicrospace/Views/Main.xaml.cs index f2cfa02..ae587da 100644 --- a/Wpf_AiSportsMicrospace/Views/Main.xaml.cs +++ b/Wpf_AiSportsMicrospace/Views/Main.xaml.cs @@ -1,24 +1,20 @@ -using System; + using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Drawing; +using System.IO; 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; +using Brushes = System.Windows.Media.Brushes; namespace Wpf_AiSportsMicrospace.Views { @@ -41,6 +37,9 @@ namespace Wpf_AiSportsMicrospace.Views private bool _isReconnecting = false; private readonly object _reconnectLock = new(); + public event EventHandler> HumanFrameUpdated; + public event EventHandler FacialRecognitionEvent; + public Main() { InitializeComponent(); @@ -108,6 +107,13 @@ namespace Wpf_AiSportsMicrospace.Views // return; var buffer = frame.GetImageBuffer(ImageFormat.Jpeg).ToArray(); + + // 触发全局事件 + Application.Current.Dispatcher.BeginInvoke(() => + { + FacialRecognitionEvent?.Invoke(this, buffer); + }); + var humanResult = _humanPredictor.Predicting(buffer, frame.Number); var humans = humanResult?.Humans?.ToList(); @@ -126,8 +132,6 @@ namespace Wpf_AiSportsMicrospace.Views } } - public event EventHandler> HumanFrameUpdated; - private void StartHeartbeatMonitor() { Task.Run(async () => @@ -237,7 +241,7 @@ namespace Wpf_AiSportsMicrospace.Views } // 强制WPF加载所有资源 - page.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + page.Measure(new System.Windows.Size(double.PositiveInfinity, double.PositiveInfinity)); page.Arrange(new Rect(0, 0, page.DesiredSize.Width, page.DesiredSize.Height)); } @@ -250,7 +254,7 @@ namespace Wpf_AiSportsMicrospace.Views transformGroup.Children.Add(scale); transformGroup.Children.Add(skew); transformGroup.Children.Add(translate); - newPage.RenderTransformOrigin = new Point(fromRight ? 0 : 1, 0.5); + newPage.RenderTransformOrigin = new System.Windows.Point(fromRight ? 0 : 1, 0.5); newPage.RenderTransform = transformGroup; // 动画 @@ -274,7 +278,7 @@ namespace Wpf_AiSportsMicrospace.Views PreloadPageResources(newPage); // 每次都创建新的 Image 实例 - var maskTop = new Image + var maskTop = new System.Windows.Controls.Image { Source = new BitmapImage(new Uri("/Resources/Img/gif/top_animation_image.png", UriKind.Relative)), Stretch = Stretch.Fill, @@ -283,7 +287,7 @@ namespace Wpf_AiSportsMicrospace.Views Width = 1920, Height = 1000 }; - var maskBottom = new Image + var maskBottom = new System.Windows.Controls.Image { Source = new BitmapImage(new Uri("/Resources/Img/gif/bottom_animation_image.png", UriKind.Relative)), Stretch = Stretch.Fill,