diff --git a/AiSportsMicrospaceDB/Entities/BasicConfig.cs b/AiSportsMicrospaceDB/Entities/BasicConfig.cs
index 6771b82..020f307 100644
--- a/AiSportsMicrospaceDB/Entities/BasicConfig.cs
+++ b/AiSportsMicrospaceDB/Entities/BasicConfig.cs
@@ -51,5 +51,13 @@ namespace AiSportsMicrospaceDB.Entities
[Comment("密码")]
[Column(TypeName = "nvarchar(20)")]
public string Password { get; set; }
+
+ ///
+ ///端口
+ ///
+ [Display(Name = "端口")]
+ [Comment("端口")]
+ [Column(TypeName = "nvarchar(20)")]
+ public string Port { get; set; }
}
}
diff --git a/Wpf_AiSportsMicrospace/Common/SportOperate.cs b/Wpf_AiSportsMicrospace/Common/SportOperate.cs
index f68e49c..c5ae17d 100644
--- a/Wpf_AiSportsMicrospace/Common/SportOperate.cs
+++ b/Wpf_AiSportsMicrospace/Common/SportOperate.cs
@@ -60,7 +60,7 @@ namespace Wpf_AiSportsMicrospace.Common
return null;
}
- _webcamClient = WebcamClient.CreateRTSP(baseData.Host, baseData.UserName, baseData.Password, 554u);
+ _webcamClient = WebcamClient.CreateRTSP(baseData.Host, baseData.UserName, baseData.Password);
}
catch (Exception)
{
diff --git a/Wpf_AiSportsMicrospace/MainWindow.xaml b/Wpf_AiSportsMicrospace/MainWindow.xaml
index 1e0865f..e7a6391 100644
--- a/Wpf_AiSportsMicrospace/MainWindow.xaml
+++ b/Wpf_AiSportsMicrospace/MainWindow.xaml
@@ -7,32 +7,8 @@
Loaded="Window_Loaded" Closing="Window_Closing"
mc:Ignorable="d" Title="AI运动微空间" Height="700" Width="1200">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/Wpf_AiSportsMicrospace/MainWindow.xaml.cs b/Wpf_AiSportsMicrospace/MainWindow.xaml.cs
index e3738a0..590c6ba 100644
--- a/Wpf_AiSportsMicrospace/MainWindow.xaml.cs
+++ b/Wpf_AiSportsMicrospace/MainWindow.xaml.cs
@@ -3,6 +3,7 @@ using Common;
using Emgu.CV.Reg;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
using SharpDX.Direct3D9;
using SkiaSharp;
using System.Diagnostics;
@@ -36,37 +37,15 @@ namespace Wpf_AiSportsMicrospace;
public partial class MainWindow : Window
{
#region 私有成员
- private IHumanPredictor _humanPredictor;
- private IObjectDetector _objectDetector;
- private HumanGraphicsRenderer _humanGraphicsRenderer;
- private readonly List _sports;
- private SportBase _sport;
- private readonly SportDetectionQueue _detectQueue;
-
private WriteableBitmap _videoBitmap;
private int _lastFrameNumber = -1;
-
- private readonly AppDbContext _context;
#endregion
public MainWindow(AppDbContext context)
{
InitializeComponent();
- _context = context;
-
- // 输出到控制台
- _humanPredictor = HumanPredictorFactory.Create(HumanPredictorType.SingleHigh);
- _objectDetector = ObjectDetectorFactory.CreateSportGoodsDetector();
- _humanGraphicsRenderer = new HumanGraphicsRenderer();
- _humanGraphicsRenderer.DrawLabel = false;
-
- _sports = SportBase.GetSports();
-
- _detectQueue = new SportDetectionQueue();
- this.sportList.ItemsSource = _sports;
- this.sportList.SelectedIndex = 0;
- this.OnSelectedSport();
}
+
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Application.Current.Dispatcher.InvokeAsync(() =>
@@ -82,109 +61,25 @@ public partial class MainWindow : Window
using var scope = App.AppHost.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService();
var baseData = db.BasicConfig.FirstOrDefault(x => x.Code == AppSettings.Code);
- if (baseData != null)
+
+ if (baseData == null)
{
- host.Text = baseData.Host;
- userName.Text = baseData.UserName;
- password.Text = baseData.Password;
+ this.ShowError("相机主机地址/IP不能为空。");
+ return;
}
+
+ var _webcamClient = WebcamClient.CreateRTSP(baseData.Host, baseData.UserName, baseData.Password);
+
+ //处理抽帧回调
+ _webcamClient.OnExtractFrame += this.OnFrameExtracted;
+ _webcamClient.StartExtract();//开始抽帧
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
base.OnClosed(e);
-
- // 释放 WriteableBitmap
- //_videoBitmap = null;
- //videoImage.Source = null;
- //overlayCanvas = null;
}
- private void sportList_SelectionChanged(object sender, SelectionChangedEventArgs e)
- {
- this.OnSelectedSport();
- }
- private void OnSelectedSport()
- {
- var sport = SportBase.Create(_sports[this.sportList.SelectedIndex].Key);
- sport.OnTicked += this.OnSportTick;
- if (_sport?.IsCounting == true)
- sport.Start();
-
- _sport?.Stop();
- _detectQueue.Stop();
- _sport = sport;
- _detectQueue.Sport = _sport;
- }
-
- private void ping_Click(object sender, RoutedEventArgs e)
- {
- //var config = _context.BasicConfig.FirstOrDefault();
-
- //if (config != null)
- //{
- // //var ip = config.Ip;
- //}
-
- var host = this.host.Text.Trim();
- if (string.IsNullOrWhiteSpace(host))
- {
- this.ShowError("相机主机地址/IP不能为空。");
- return;
- }
-
- var pinger = new Ping();
- var reply = pinger.Send(host, 10);
- if (reply.Status == IPStatus.Success)
- this.ShowInformation("与相机通信正常。");
- else
- this.ShowError($"与相机通信失败,错误:{reply.Status}。");
- }
-
- private void startOrStop_Click(object sender, RoutedEventArgs e)
- {
- var host = this.host.Text.Trim();
- if (string.IsNullOrWhiteSpace(host))
- {
- this.ShowError("相机主机地址/IP不能为空。");
- return;
- }
-
- var userName = this.userName.Text.Trim();
- if (string.IsNullOrWhiteSpace(userName))
- {
- this.ShowError("用户名不能为空。");
- return;
- }
-
- var password = this.password.Text.Trim();
- if (string.IsNullOrWhiteSpace(password))
- {
- this.ShowError("密码不能为空。");
- return;
- }
- var port = 554u;//RTSP协议默认端口是554
-
- var tempPath = "./temps";
- if (!Directory.Exists(tempPath))
- Directory.CreateDirectory(tempPath);
-
- _sport.Reset();
- _sport.Start();
- _detectQueue.Start();
-
- var _webcamClient = WebcamClient.CreateRTSP(host, userName, password, port);
-
- //处理抽帧回调
- _webcamClient.OnExtractFrame += this.OnFrameExtracted;
- _webcamClient.StartExtract();//开始抽帧
-
-
-
- this.startOrStop.Content = "停止(&S)";
- this.sportCounts.Text = "0";
- this.sportTimes.Text = "00'00\"";
- }
private async void OnFrameExtracted(VideoFrame frame)
{
if (frame == null) return;
@@ -232,254 +127,83 @@ public partial class MainWindow : Window
_videoBitmap.AddDirtyRect(new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight));
_videoBitmap.Unlock();
});
-
- //var actualPositions = new List<(double X, double Y)>
- //{
- // (290, 510),
- // (500, 700),
- // (810, 710),
- // (120, 880),
- // (350, 880),
- // (660, 880)
- //};
-
- ////判断是否出圈
- //if (configService.ConfigDic.TryGetValue("JumpRope", out var obj) && obj is ConfigSet cfg)
- //{
- // for (int i = 0; i < actualPositions.Count && i < cfg.Points.Count; i++)
- // {
- // var (ax, ay) = actualPositions[i];
- // var pointCfg = cfg.Points[i];
-
- // if (!IsInsideCircle(ax, ay, pointCfg))
- // {
- // Console.WriteLine($"⚠️ 学生{i + 1} 已经出圈!(坐标 {ax},{ay})");
- // }
- // else
- // {
- // Console.WriteLine($"✅ 学生{i + 1} 在圈内。");
- // }
- // }
- //}
-
- //可以进一步进行人体识别等
- //var humanResult = await Task.Run(() => _humanPredictor.Predicting(buffer, frame.Number));
-
- //var human = humanResult?.Humans?.FirstOrDefault();
- //_detectQueue.Enqueue(frame.Number, human, null);
}
- private void OnSportTick(int counts, int times)
- {
- var ts = TimeSpan.FromSeconds(times);
-
- // 使用 Dispatcher 调用 UI 更新
- this.Dispatcher.BeginInvoke(new Action(() =>
- {
- this.sportCounts.Text = _sport.GetFormatCounts(); // counts.ToString();
- this.sportTimes.Text = _sport.GetFormatTimes(); // ts.ToString(@"mm\'ss\"");
-
- // 如果需要触发停止逻辑
- //if (!_sport.IsCounting)
- // this.startOrStop_Click(null, null);
- }));
-
- //VoiceBroadcast.PlayTick();
- }
-
- private async Task DrawJumpRope3DPointsWithGlow()
- {
- if (videoImage == null || overlayCanvas == null) return;
-
- ConfigSet jumpRopeConfig = new ConfigSet();
-
- double imgWidth = videoImage.ActualWidth;
- double imgHeight = videoImage.ActualHeight;
-
- if (imgWidth <= 0 || imgHeight <= 0) return;
-
- overlayCanvas.Children.Clear();
- overlayCanvas.Width = imgWidth;
- overlayCanvas.Height = imgHeight;
-
- bool needSaveConfig = false;
-
- var config = await _context.BasicConfig.FirstOrDefaultAsync();
-
- if (config!=null)
- {
- // 没有配置,则生成默认配置
- jumpRopeConfig = new ConfigSet { Name = "rope-skipping" };
- needSaveConfig = true;
-
- // ===== 前排 3 人 =====
- double frontRadius = 80;
- var frontPositions = new List<(double XNorm, double YNorm)>
- {
- (0.25, 0.70), (0.5, 0.70), (0.75, 0.70)
- };
-
- foreach (var pos in frontPositions)
- {
- double x = pos.XNorm * imgWidth;
- double y = pos.YNorm * imgHeight;
-
- jumpRopeConfig.Points.Add(new PointConfig
- {
- X = x,
- Y = y,
- Radius = frontRadius,
- XNorm = pos.XNorm,
- YNorm = pos.YNorm
- });
-
- AddGlowEllipse(x, y, frontRadius, 0.85, overlayCanvas, flattenFactor: 0.4);
- }
-
- // ===== 后排 3 人 =====
- double backRadius = 70;
- var backPositions = new List<(double XNorm, double YNorm)>
- {
- (0.2, 0.88), (0.5, 0.88), (0.8, 0.88)
- };
-
- foreach (var pos in backPositions)
- {
- double x = pos.XNorm * imgWidth;
- double y = pos.YNorm * imgHeight;
-
- jumpRopeConfig.Points.Add(new PointConfig
- {
- X = x,
- Y = y,
- Radius = backRadius,
- XNorm = pos.XNorm,
- YNorm = pos.YNorm
- });
-
- AddGlowEllipse(x, y, backRadius, 0.7, overlayCanvas, flattenFactor: 0.55);
- }
- }
- else
- {
- // 已有配置,按归一化坐标计算实际位置绘制
- foreach (var point in jumpRopeConfig.Points)
- {
- double x = point.XNorm * imgWidth;
- double y = point.YNorm * imgHeight;
-
- AddGlowEllipse(x, y, point.Radius, 0.8, overlayCanvas, flattenFactor: 0.5);
- }
- }
-
- // 保存配置(如果是新生成的)
- if (needSaveConfig)
- {
-
- }
- }
-
- ///
- /// 添加带渐变光的圆圈(中心红色,边缘蓝色)
- ///
- private void AddGlowEllipse(double centerX, double centerY, double radius, double opacity, Canvas canvas, double flattenFactor = 0.5)
- {
- var ellipse = new Ellipse
- {
- Width = radius * 2,
- Height = radius * flattenFactor, // 扁平化:越小越“贴地”
- Opacity = opacity,
- Fill = new RadialGradientBrush
- {
- GradientOrigin = new Point(0.5, 0.5),
- Center = new Point(0.5, 0.5),
- RadiusX = 0.5,
- RadiusY = 0.5,
- GradientStops = new GradientStopCollection
- {
- new GradientStop(Color.FromArgb(220, 255, 80, 80), 0.0), // 中心亮红
- new GradientStop(Color.FromArgb(180, 255, 0, 0), 0.4), // 中间红
- new GradientStop(Color.FromArgb(180, 0, 128, 255), 0.7), // 边缘蓝
- new GradientStop(Color.FromArgb(0, 0, 128, 255), 1.0) // 外部透明
- }
- }
- };
-
- // 定位到中心(Y 要根据压缩高度来调整)
- Canvas.SetLeft(ellipse, centerX - radius);
- Canvas.SetTop(ellipse, centerY - (radius * flattenFactor) / 2);
- canvas.Children.Add(ellipse);
- }
-
- private bool IsInsideCircle(double actualX, double actualY, PointConfig p)
- {
- var dx = actualX - p.X;
- var dy = actualY - p.Y;
- var distance = Math.Sqrt(dx * dx + dy * dy);
- return distance <= p.Radius;
- }
-
private void DrawCirclesWithText()
{
overlayCanvas.Children.Clear();
- //sports.Clear();
- //circleTexts.Clear();
+ double canvasWidth = overlayCanvas.ActualWidth;
+ double canvasHeight = overlayCanvas.ActualHeight;
- double imgWidth = overlayCanvas.ActualWidth;
- double imgHeight = overlayCanvas.ActualHeight;
- double radius = 100;
-
- // 每个圆的位置:X 和 Y 都归一化 0~1
- var circlePositions = new List<(double XNorm, double YNorm)>
- {
- (0.07, 0.58),
- (0.21, 0.88 ),
- (0.36, 0.58 ),
- (0.50, 0.88),
- (0.64, 0.58 ),
- (0.78, 0.88),
- (0.92, 0.58 )
- };
+ var circlePositions = GenerateCirclePositionsStaggered(
+ roomWidth: 5, // 房间宽度(米)
+ roomHeight: 3, // 房间高度(米)
+ yOffset: 0.6, // 前排纵坐标
+ yStep: 1.5, // 前后排间距
+ minDistance: 1.5, // 同排最小间距
+ minEdge: 1 // 两侧到边缘最小距离
+ );
foreach (var pos in circlePositions)
{
- double x = pos.XNorm * imgWidth;
- double y = pos.YNorm * imgHeight;
-
- // 绘制发光圆
+ double x = pos.XNorm * canvasWidth;
+ double y = pos.YNorm * canvasHeight;
AddGlowEllipse(x, y, overlayCanvas);
-
- // 创建文本控件
- //var text = new TextBlock
- //{
- // Text = "0",
- // Foreground = Brushes.Red,
- // FontWeight = FontWeights.Bold,
- // FontSize = 50,
- // TextAlignment = TextAlignment.Center,
- // Width = radius * 2
- //};
- //Canvas.SetLeft(text, x - radius);
- //Canvas.SetTop(text, y - radius - 25);
- //overlayCanvas.Children.Add(text);
- //circleTexts.Add(text);
-
- //// 绑定运动对象
- //var sport = SportBase.Create("rope-skipping");
- //int index = circleTexts.Count - 1;
- //sport.OnTicked += (count, times) =>
- //{
- // Application.Current.Dispatcher.Invoke(() =>
- // {
- // circleTexts[index].Text = count.ToString();
- // });
- //};
- //sport.Start();
- //sports.Add(sport);
}
}
+ /// 生成前后两排交错圆点坐标(归一化)
+ /// 同排最小间距 = minDistance
+ /// 两侧到边缘距离 ≥ minEdge
+ /// 前后排间距 = yStep
+ ///
+ public List<(double XNorm, double YNorm)> GenerateCirclePositionsStaggered(
+ double roomWidth,
+ double roomHeight,
+ double yOffset = 0.6,
+ double yStep = 1.5,
+ double minDistance = 1.5,
+ double minEdge = 1.0)
+ {
+ var circlePositions = new List<(double XNorm, double YNorm)>();
+
+ // 1️⃣ 可用宽度
+ double usableWidth = roomWidth - 2 * minEdge;
+
+ // 2️⃣ 前排点数
+ int count = (int)Math.Floor(usableWidth / minDistance) + 1;
+ if (count < 1) count = 1;
+
+ // 3️⃣ 前排实际步长(均分可用宽度)
+ double step = (count == 1) ? 0 : usableWidth / (count - 1);
+
+ // 4️⃣ 前排 X/Y
+ var frontX = new List();
+ double yFront = Math.Min(yOffset, roomHeight); // 避免超出高度
+ for (int i = 0; i < count; i++)
+ {
+ double x = minEdge + i * step;
+ frontX.Add(x);
+ circlePositions.Add((x / roomWidth, yFront / roomHeight));
+ }
+
+ // 5️⃣ 后排 X/Y(交错)
+ double yBack = Math.Min(yOffset + yStep, roomHeight); // 避免超出高度
+ for (int i = 0; i < count; i++)
+ {
+ double x;
+ if (i < count - 1)
+ x = (frontX[i] + frontX[i + 1]) / 2;
+ else
+ x = frontX[count - 1]; // 最后一个点和前排对齐
+ circlePositions.Add((x / roomWidth, yBack / roomHeight));
+ }
+
+ return circlePositions;
+ }
+
+
///
/// 添加带渐变光的圆圈(中心红色,边缘蓝色)