2026-01-20 15:07:09 +08:00

243 lines
8.1 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}