243 lines
8.1 KiB
C#
243 lines
8.1 KiB
C#
using AiSportsMicrospaceDB.DBContext;
|
||
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;
|
||
using System.Drawing.Imaging;
|
||
using System.IO;
|
||
using System.Net.NetworkInformation;
|
||
using System.Runtime.InteropServices;
|
||
using System.Threading.Tasks;
|
||
using System.Windows;
|
||
using System.Windows.Controls;
|
||
using System.Windows.Interop;
|
||
using System.Windows.Media;
|
||
using System.Windows.Media.Imaging;
|
||
using System.Windows.Shapes;
|
||
using System.Windows.Threading;
|
||
using Wpf_AiSportsMicrospace.Common;
|
||
using Wpf_AiSportsMicrospace.Dto;
|
||
using Yztob.AiSports.Inferences.Abstractions;
|
||
using Yztob.AiSports.Inferences.Things;
|
||
using Yztob.AiSports.Postures.Sports;
|
||
using Yztob.AiSports.Postures.Things;
|
||
using Yztob.AiSports.Sensors.Abstractions;
|
||
using Yztob.AiSports.Sensors.Things;
|
||
using static System.Formats.Asn1.AsnWriter;
|
||
|
||
namespace Wpf_AiSportsMicrospace;
|
||
|
||
/// <summary>
|
||
/// Interaction logic for MainWindow.xaml
|
||
/// </summary>
|
||
public partial class MainWindow : Window
|
||
{
|
||
#region 私有成员
|
||
private WriteableBitmap _videoBitmap;
|
||
private int _lastFrameNumber = -1;
|
||
#endregion
|
||
|
||
public MainWindow(AppDbContext context)
|
||
{
|
||
InitializeComponent();
|
||
}
|
||
|
||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||
{
|
||
Application.Current.Dispatcher.InvokeAsync(() =>
|
||
{
|
||
DrawCirclesWithText();
|
||
}, DispatcherPriority.Loaded);
|
||
|
||
videoImage.SizeChanged += (s, ev) =>
|
||
{
|
||
DrawCirclesWithText();
|
||
};
|
||
|
||
using var scope = App.AppHost.Services.CreateScope();
|
||
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||
var baseData = db.BasicConfig.FirstOrDefault(x => x.Code == AppSettings.Code);
|
||
|
||
if (baseData == null)
|
||
{
|
||
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);
|
||
}
|
||
|
||
private async void OnFrameExtracted(VideoFrame frame)
|
||
{
|
||
if (frame == null) return;
|
||
|
||
// 跳帧:每秒只渲染 10~15 帧,降低 CPU 占用
|
||
if (_lastFrameNumber != -1 && frame.Number - _lastFrameNumber < 2) return;
|
||
_lastFrameNumber = (int)frame.Number;
|
||
|
||
//获得帧二进制流,保存图像、人体识别竺
|
||
var buffer = frame.GetImageBuffer(Yztob.AiSports.Sensors.Things.ImageFormat.Jpeg).ToArray();
|
||
//await File.WriteAllBytesAsync($"./temps/{frame.Number}.jpg", buffer);
|
||
|
||
// === 显示到 WPF ===
|
||
BitmapImage bitmap = null;
|
||
await Task.Run(() =>
|
||
{
|
||
using var ms = new MemoryStream(buffer);
|
||
bitmap = new BitmapImage();
|
||
bitmap.BeginInit();
|
||
bitmap.CacheOption = BitmapCacheOption.OnLoad;
|
||
bitmap.StreamSource = ms;
|
||
bitmap.EndInit();
|
||
bitmap.Freeze(); // 跨线程安全
|
||
});
|
||
|
||
// UI 线程显示
|
||
Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
if (videoImage == null) return;
|
||
|
||
if (_videoBitmap == null ||
|
||
_videoBitmap.PixelWidth != bitmap.PixelWidth ||
|
||
_videoBitmap.PixelHeight != bitmap.PixelHeight)
|
||
{
|
||
_videoBitmap = new WriteableBitmap(bitmap.PixelWidth, bitmap.PixelHeight,
|
||
96, 96, PixelFormats.Bgra32, null);
|
||
videoImage.Source = _videoBitmap;
|
||
}
|
||
|
||
_videoBitmap.Lock();
|
||
bitmap.CopyPixels(new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight),
|
||
_videoBitmap.BackBuffer,
|
||
_videoBitmap.BackBufferStride * bitmap.PixelHeight,
|
||
_videoBitmap.BackBufferStride);
|
||
_videoBitmap.AddDirtyRect(new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight));
|
||
_videoBitmap.Unlock();
|
||
});
|
||
}
|
||
|
||
private void DrawCirclesWithText()
|
||
{
|
||
overlayCanvas.Children.Clear();
|
||
double canvasWidth = overlayCanvas.ActualWidth;
|
||
double canvasHeight = overlayCanvas.ActualHeight;
|
||
|
||
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 * canvasWidth;
|
||
double y = pos.YNorm * canvasHeight;
|
||
AddGlowEllipse(x, y, overlayCanvas);
|
||
}
|
||
}
|
||
|
||
|
||
/// 生成前后两排交错圆点坐标(归一化)
|
||
/// 同排最小间距 = minDistance
|
||
/// 两侧到边缘距离 ≥ minEdge
|
||
/// 前后排间距 = yStep
|
||
/// </summary>
|
||
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>();
|
||
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;
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 添加带渐变光的圆圈(中心红色,边缘蓝色)
|
||
/// </summary>
|
||
private void AddGlowEllipse(double centerX, double centerY, Canvas canvas)
|
||
{
|
||
double radius = 70; // 统一半径
|
||
double flattenFactor = 0.5; // 统一扁平化比例
|
||
double opacity = 0.8; // 统一透明度
|
||
|
||
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, 255, 0), 0.0), // 中心黄
|
||
new GradientStop(Color.FromArgb(180, 255, 255, 0), 0.4), // 中间黄
|
||
new GradientStop(Color.FromArgb(180, 0, 0, 255), 0.7), // 边缘蓝
|
||
new GradientStop(Color.FromArgb(0, 0, 0, 255), 1.0) // 外部透明
|
||
}
|
||
}
|
||
};
|
||
|
||
// 定位到中心
|
||
Canvas.SetLeft(ellipse, centerX - radius);
|
||
Canvas.SetTop(ellipse, centerY - (radius * flattenFactor) / 2);
|
||
canvas.Children.Add(ellipse);
|
||
}
|
||
} |