This commit is contained in:
tanglong 2025-11-10 15:12:53 +08:00
parent 9f56229c59
commit c1c7570e8e
8 changed files with 266 additions and 60 deletions

View File

@ -43,7 +43,8 @@ public partial class App : Application
services.AddDbContext<AppDbContext>(options =>
options.UseSqlite(connectionString));
services.AddSingleton<MainWindow>();
//services.AddSingleton<MainWindow>();
services.AddSingleton<Main>();
})
.Build();
@ -57,8 +58,16 @@ public partial class App : Application
}
// 用 DI 创建窗口
var mainWindow = AppHost.Services.GetRequiredService<MainWindow>();
mainWindow.Show();
//var mainWindow = AppHost.Services.GetRequiredService<MainWindow>();
//mainWindow.Show();
var mainWin = AppHost.Services.GetRequiredService<Main>();
Current.MainWindow = mainWin;
mainWin.Show();
var newPage = new Home();
mainWin.SwitchPage(newPage, true);
}
}

View File

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

View File

@ -0,0 +1,67 @@
using System.Net.Http;
using System.Text;
namespace Wpf_AiSportsMicrospace.Common
{
public class HttpManager
{
public static async Task<string> HttpPostAsync(string url, string postData = null, string contentType = "application/json", int timeOut = 30, Dictionary<string, string> 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<string> HttpGetAsync(string url, Dictionary<string, string> 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;
}
}
}
}
}

View File

@ -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)
{

View File

@ -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<string, BitmapImage> _cache = new Dictionary<string, BitmapImage>();
private static bool _isPreloaded = false;
private static readonly Dictionary<string, BitmapImage> _cache = new Dictionary<string, BitmapImage>();
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<List<StudentInfoDto>> FacialRecognition(byte[] buffer, double[] xNorms)
{
if (xNorms == null || xNorms.Length < 2)
throw new ArgumentException("xNorms 至少需要两个元素");
using var image = SharpImage.Image.Load<Rgba32>(buffer);
int imgWidth = image.Width;
int imgHeight = image.Height;
int rectHeight = imgHeight / 2;
// 计算分割边界
var sortedX = xNorms.OrderBy(x => x).ToList();
var splitXs = new List<double>();
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<StudentInfoDto>();
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;
}
/// <summary>
/// 调用人脸识别接口,返回学生信息
/// </summary>
public static async Task<StudentInfoDto> 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<StudentInfoDto>(response, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
if (faceInfo == null)
return null;
return faceInfo;
}
catch
{
return null;
}
}
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Dto
{
/// <summary>
/// 学生信息
/// </summary>
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; }
}
}

View File

@ -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();
// 播放音乐

View File

@ -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<List<Human>> HumanFrameUpdated;
public event EventHandler<Byte[]> 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<List<Human>> 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,