Compare commits
4 Commits
main
..
3b161bc156
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b161bc156 | |||
| 765f386f57 | |||
| f6269e457e | |||
| 71ae1df57a |
@@ -6,15 +6,28 @@ namespace DahuaIPCRename
|
|||||||
{
|
{
|
||||||
public static Dictionary<string, string> listIPC = new Dictionary<string, string>
|
public static Dictionary<string, string> listIPC = new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
{"172.22.10.122","Этаж 2|Пом. 247"},
|
{"172.16.54.18","Ворота 35"},
|
||||||
{"172.22.10.123","Этаж 2|Пом. 248-4"},
|
{"172.16.54.20","Ворота 36"},
|
||||||
{"172.22.10.124","Этаж 2|Пом. 248-5"},
|
{"172.16.54.21","Ворота 31"},
|
||||||
{"172.22.10.125","Этаж 2|Пом. 248-2"},
|
{"172.16.54.22","Ворота 32"},
|
||||||
{"172.22.10.126","Этаж 2|Пом. 248-6"},
|
{"172.16.54.23","Ворота 33"},
|
||||||
{"172.22.10.127","Этаж 2|Пом. 248-3"},
|
{"172.16.54.24","Ворота 34"},
|
||||||
{"172.22.10.128","Этаж 2|Пом. 273-1"},
|
{"172.16.54.28","Ворота 16"},
|
||||||
{"172.22.10.129","Этаж 2|Пом. 268-1"},
|
{"172.16.54.29","Ворота 26"},
|
||||||
{"172.22.10.130","Этаж 2|Пом. 273-2"}
|
{"172.16.54.30","Ворота 20"},
|
||||||
|
{"172.16.54.31","Ворота 22"},
|
||||||
|
{"172.16.54.32","Ворота 18"},
|
||||||
|
{"172.16.54.33","Ворота 21"},
|
||||||
|
{"172.16.54.34","Ворота 24"},
|
||||||
|
{"172.16.54.35","Ворота 27"},
|
||||||
|
{"172.16.54.36","Ворота 29"},
|
||||||
|
{"172.16.54.37","Ворота 25"},
|
||||||
|
{"172.16.54.38","Ворота 23"},
|
||||||
|
{"172.16.54.39","Ворота 15"},
|
||||||
|
{"172.16.54.40","Ворота 17"},
|
||||||
|
{"172.16.54.41","Ворота 37"},
|
||||||
|
{"172.16.54.42","Ворота 19"},
|
||||||
|
{"172.16.54.43","Ворота 28"}
|
||||||
};
|
};
|
||||||
|
|
||||||
static async Task Main(string[] args)
|
static async Task Main(string[] args)
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Emgu.CV" Version="4.13.0.5924" />
|
|
||||||
<PackageReference Include="Emgu.CV.runtime.windows" Version="4.13.0.5924" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.2.11" />
|
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.9" />
|
|
||||||
<PackageReference Include="System.Drawing.Common" Version="10.0.9" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="haarcascade_frontalface_default.xml">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.SignalR;
|
|
||||||
|
|
||||||
namespace FaceRecognitionApp.Hubs;
|
|
||||||
|
|
||||||
public class FaceHub : Hub
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
namespace FaceRecognitionApp.Models;
|
|
||||||
|
|
||||||
public class Employee
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public string Name { get; set; } = string.Empty;
|
|
||||||
public string? Department { get; set; }
|
|
||||||
public byte[] FaceDescriptor { get; set; } = Array.Empty<byte>();
|
|
||||||
public byte[]? Photo { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DetectedFace
|
|
||||||
{
|
|
||||||
public byte[] ImageData { get; set; } = Array.Empty<byte>();
|
|
||||||
public bool IsRecognized { get; set; }
|
|
||||||
public Employee? MatchedEmployee { get; set; }
|
|
||||||
public double Confidence { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
using FaceRecognitionApp.Hubs;
|
|
||||||
using FaceRecognitionApp.Services;
|
|
||||||
using Microsoft.AspNetCore.SignalR;
|
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
|
||||||
|
|
||||||
// Регистрация сервисов
|
|
||||||
builder.Services.AddSingleton<DatabaseService>();
|
|
||||||
builder.Services.AddSingleton<FaceRecognitionService>();
|
|
||||||
builder.Services.AddSignalR();
|
|
||||||
|
|
||||||
var app = builder.Build();
|
|
||||||
|
|
||||||
// 1. Включаем поиск файлов по умолчанию (index.html, default.html и т.д.)
|
|
||||||
app.UseDefaultFiles();
|
|
||||||
|
|
||||||
// 2. Включаем раздачу статических файлов (css, js, картинки)
|
|
||||||
app.UseStaticFiles();
|
|
||||||
|
|
||||||
// 3. Подключаем SignalR хаб
|
|
||||||
app.MapHub<FaceHub>("/faceHub");
|
|
||||||
|
|
||||||
// Инициализация камеры и распознавания
|
|
||||||
var db = app.Services.GetRequiredService<DatabaseService>();
|
|
||||||
var faceService = app.Services.GetRequiredService<FaceRecognitionService>();
|
|
||||||
|
|
||||||
// URL камеры (замените на свой или используйте тестовый видеофайл)
|
|
||||||
var cameraUrl = Environment.GetEnvironmentVariable("CAMERA_URL") ?? "rtsp://admin:4NUDZhJ7@172.22.10.101:554/cam/realmonitor?channel=1&subtype=1";
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Connecting to camera: {cameraUrl}...");
|
|
||||||
var camera = new CameraService(cameraUrl, faceService, async (face) => {
|
|
||||||
var hub = app.Services.GetRequiredService<IHubContext<FaceHub>>();
|
|
||||||
await hub.Clients.All.SendAsync("NewFace", new
|
|
||||||
{
|
|
||||||
image = Convert.ToBase64String(face.ImageData),
|
|
||||||
isRecognized = face.IsRecognized,
|
|
||||||
name = face.MatchedEmployee?.Name ?? "Unknown"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
camera.Start();
|
|
||||||
Console.WriteLine("✅ Camera service started.");
|
|
||||||
|
|
||||||
// Корректное завершение работы камеры при остановке приложения
|
|
||||||
app.Lifetime.ApplicationStopping.Register(() => {
|
|
||||||
Console.WriteLine("Shutting down camera...");
|
|
||||||
camera.Dispose();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"⚠️ Camera Error: {ex.Message}");
|
|
||||||
Console.WriteLine("Application will run without camera monitoring.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine("🚀 Application is ready. Open http://localhost:5290 in your browser.");
|
|
||||||
app.Run();
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
|
||||||
"iisSettings": {
|
|
||||||
"windowsAuthentication": false,
|
|
||||||
"anonymousAuthentication": true,
|
|
||||||
"iisExpress": {
|
|
||||||
"applicationUrl": "http://localhost:43065",
|
|
||||||
"sslPort": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"profiles": {
|
|
||||||
"http": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"dotnetRunMessages": true,
|
|
||||||
"launchBrowser": true,
|
|
||||||
"applicationUrl": "http://localhost:5290",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"IIS Express": {
|
|
||||||
"commandName": "IISExpress",
|
|
||||||
"launchBrowser": true,
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
using Emgu.CV;
|
|
||||||
using FaceRecognitionApp.Models;
|
|
||||||
|
|
||||||
namespace FaceRecognitionApp.Services;
|
|
||||||
|
|
||||||
public class CameraService : IDisposable
|
|
||||||
{
|
|
||||||
private VideoCapture? _capture;
|
|
||||||
private readonly FaceRecognitionService _recognition;
|
|
||||||
private readonly Action<DetectedFace> _onDetected;
|
|
||||||
private CancellationTokenSource? _cts;
|
|
||||||
private Task? _task;
|
|
||||||
private DateTime _lastTime = DateTime.MinValue;
|
|
||||||
private bool _isDisposed = false;
|
|
||||||
|
|
||||||
public CameraService(string url, FaceRecognitionService recognition, Action<DetectedFace> onDetected)
|
|
||||||
{
|
|
||||||
_recognition = recognition;
|
|
||||||
_onDetected = onDetected;
|
|
||||||
|
|
||||||
Console.WriteLine($"Initializing camera with URL: {url}");
|
|
||||||
_capture = new VideoCapture(url);
|
|
||||||
|
|
||||||
// Даем камере время на инициализацию потока
|
|
||||||
Thread.Sleep(1000);
|
|
||||||
|
|
||||||
if (!_capture.IsOpened)
|
|
||||||
throw new Exception("Camera failed to open. Check URL and network connection.");
|
|
||||||
|
|
||||||
Console.WriteLine("Camera hardware initialized.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start()
|
|
||||||
{
|
|
||||||
if (_isDisposed) return;
|
|
||||||
|
|
||||||
_cts = new CancellationTokenSource();
|
|
||||||
_task = Task.Run(() => Loop(_cts.Token));
|
|
||||||
Console.WriteLine("Camera processing loop started.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Loop(CancellationToken token)
|
|
||||||
{
|
|
||||||
Console.WriteLine("🎥 Frame processing loop active...");
|
|
||||||
|
|
||||||
while (!token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var frame = new Mat();
|
|
||||||
|
|
||||||
// Читаем кадр с таймаутом
|
|
||||||
if (_capture!.Read(frame))
|
|
||||||
{
|
|
||||||
if (!frame.IsEmpty && frame.Width > 0 && frame.Height > 0)
|
|
||||||
{
|
|
||||||
var face = _recognition.ProcessFrame(frame);
|
|
||||||
|
|
||||||
if (face != null)
|
|
||||||
{
|
|
||||||
// Лимит частоты отправок (не чаще раза в 2 секунды)
|
|
||||||
if ((DateTime.Now - _lastTime).TotalMilliseconds > 2000)
|
|
||||||
{
|
|
||||||
_lastTime = DateTime.Now;
|
|
||||||
_onDetected(face);
|
|
||||||
|
|
||||||
string status = face.IsRecognized
|
|
||||||
? $"✅ Recognized: {face.MatchedEmployee?.Name}"
|
|
||||||
: "❌ Unknown face";
|
|
||||||
|
|
||||||
Console.WriteLine($"{DateTime.Now:HH:mm:ss} - {status}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Если Read вернул false, возможно потеря связи
|
|
||||||
Console.WriteLine("⚠️ Failed to read frame, retrying...");
|
|
||||||
await Task.Delay(500, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Небольшая задержка, чтобы не грузить процессор на 100%
|
|
||||||
await Task.Delay(50, token);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
// Это нормальная ситуация при остановке
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"⚠️ Error in camera loop: {ex.Message}");
|
|
||||||
await Task.Delay(1000, token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine("🛑 Camera loop finished.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Stop()
|
|
||||||
{
|
|
||||||
if (_cts == null) return;
|
|
||||||
|
|
||||||
Console.WriteLine("Stopping camera service...");
|
|
||||||
_cts.Cancel();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Ждем завершения задачи, но не бесконечно
|
|
||||||
if (_task != null)
|
|
||||||
{
|
|
||||||
_task.Wait(TimeSpan.FromSeconds(5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (AggregateException ex)
|
|
||||||
{
|
|
||||||
// Игнорируем ошибки отмены задачи
|
|
||||||
foreach (var inner in ex.InnerExceptions)
|
|
||||||
{
|
|
||||||
if (!(inner is TaskCanceledException || inner is OperationCanceledException))
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Unexpected error during stop: {inner.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Error stopping task: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_isDisposed) return;
|
|
||||||
|
|
||||||
Stop();
|
|
||||||
_capture?.Dispose();
|
|
||||||
_cts?.Dispose();
|
|
||||||
_isDisposed = true;
|
|
||||||
|
|
||||||
Console.WriteLine("Camera resources released.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
using Microsoft.Data.Sqlite;
|
|
||||||
using FaceRecognitionApp.Models;
|
|
||||||
|
|
||||||
namespace FaceRecognitionApp.Services;
|
|
||||||
|
|
||||||
public class DatabaseService
|
|
||||||
{
|
|
||||||
private readonly string _connectionString = "Data Source=faces.db";
|
|
||||||
|
|
||||||
public DatabaseService()
|
|
||||||
{
|
|
||||||
InitializeDatabase();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeDatabase()
|
|
||||||
{
|
|
||||||
using var connection = new SqliteConnection(_connectionString);
|
|
||||||
connection.Open();
|
|
||||||
var cmd = connection.CreateCommand();
|
|
||||||
cmd.CommandText = @"
|
|
||||||
CREATE TABLE IF NOT EXISTS Employees (
|
|
||||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
Name TEXT NOT NULL,
|
|
||||||
Department TEXT,
|
|
||||||
FaceDescriptor BLOB,
|
|
||||||
Photo BLOB
|
|
||||||
)";
|
|
||||||
cmd.ExecuteNonQuery();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddEmployee(string name, string? dept, byte[] descriptor, byte[] photo)
|
|
||||||
{
|
|
||||||
using var connection = new SqliteConnection(_connectionString);
|
|
||||||
connection.Open();
|
|
||||||
var cmd = connection.CreateCommand();
|
|
||||||
cmd.CommandText = "INSERT INTO Employees (Name, Department, FaceDescriptor, Photo) VALUES (@n, @d, @desc, @p)";
|
|
||||||
cmd.Parameters.AddWithValue("@n", name);
|
|
||||||
cmd.Parameters.AddWithValue("@d", (object?)dept ?? DBNull.Value);
|
|
||||||
cmd.Parameters.AddWithValue("@desc", descriptor);
|
|
||||||
cmd.Parameters.AddWithValue("@p", photo);
|
|
||||||
cmd.ExecuteNonQuery();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Employee> GetAllEmployees()
|
|
||||||
{
|
|
||||||
var list = new List<Employee>();
|
|
||||||
using var connection = new SqliteConnection(_connectionString);
|
|
||||||
connection.Open();
|
|
||||||
var cmd = connection.CreateCommand();
|
|
||||||
cmd.CommandText = "SELECT * FROM Employees";
|
|
||||||
using var reader = cmd.ExecuteReader();
|
|
||||||
while (reader.Read())
|
|
||||||
{
|
|
||||||
list.Add(new Employee
|
|
||||||
{
|
|
||||||
Id = reader.GetInt32(0),
|
|
||||||
Name = reader.GetString(1),
|
|
||||||
Department = reader.IsDBNull(2) ? null : reader.GetString(2),
|
|
||||||
FaceDescriptor = (byte[])reader[3],
|
|
||||||
Photo = reader.IsDBNull(4) ? null : (byte[])reader[4]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
using Emgu.CV;
|
|
||||||
using Emgu.CV.CvEnum;
|
|
||||||
using Emgu.CV.Face;
|
|
||||||
using Emgu.CV.Structure;
|
|
||||||
using Emgu.CV.Util;
|
|
||||||
using FaceRecognitionApp.Models;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace FaceRecognitionApp.Services;
|
|
||||||
|
|
||||||
public class FaceRecognitionService : IDisposable
|
|
||||||
{
|
|
||||||
private readonly LBPHFaceRecognizer _recognizer;
|
|
||||||
private readonly CascadeClassifier _cascade;
|
|
||||||
private readonly DatabaseService _db;
|
|
||||||
private List<Employee> _employees = new();
|
|
||||||
private bool _isTrained = false;
|
|
||||||
|
|
||||||
// Коэффициент уменьшения кадра для ускорения поиска
|
|
||||||
private const double ResizeScale = 0.5;
|
|
||||||
private const int OriginalMinSize = 30;
|
|
||||||
private readonly Size _scaledMinSize;
|
|
||||||
|
|
||||||
public FaceRecognitionService(DatabaseService db)
|
|
||||||
{
|
|
||||||
_db = db;
|
|
||||||
var cascadePath = Path.Combine(AppContext.BaseDirectory, "haarcascade_frontalface_default.xml");
|
|
||||||
if (!File.Exists(cascadePath))
|
|
||||||
throw new FileNotFoundException($"Cascade file missing at: {cascadePath}");
|
|
||||||
|
|
||||||
_cascade = new CascadeClassifier(cascadePath);
|
|
||||||
_recognizer = new LBPHFaceRecognizer(1, 8, 8, 8, 80.0);
|
|
||||||
|
|
||||||
// Вычисляем минимальный размер лица для уменьшенного кадра
|
|
||||||
int scaledMin = (int)(OriginalMinSize * ResizeScale);
|
|
||||||
_scaledMinSize = new Size(Math.Max(scaledMin, 10), Math.Max(scaledMin, 10));
|
|
||||||
|
|
||||||
TrainModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TrainModel()
|
|
||||||
{
|
|
||||||
_employees = _db.GetAllEmployees();
|
|
||||||
if (!_employees.Any()) return;
|
|
||||||
|
|
||||||
using var images = new VectorOfMat();
|
|
||||||
using var labels = new VectorOfInt();
|
|
||||||
|
|
||||||
foreach (var emp in _employees)
|
|
||||||
{
|
|
||||||
if (emp.Photo == null || emp.Photo.Length == 0) continue;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var ms = new MemoryStream(emp.Photo);
|
|
||||||
using var bmp = new Bitmap(ms);
|
|
||||||
using var mat = BitmapToMat(bmp);
|
|
||||||
using var gray = new Mat();
|
|
||||||
|
|
||||||
CvInvoke.CvtColor(mat, gray, ColorConversion.Bgr2Gray);
|
|
||||||
|
|
||||||
// Ищем лицо на фото сотрудника для обрезки
|
|
||||||
var rects = _cascade.DetectMultiScale(gray, 1.1, 5, new Size(30, 30));
|
|
||||||
|
|
||||||
if (rects.Length > 0)
|
|
||||||
{
|
|
||||||
using var faceMat = new Mat(gray, rects[0]);
|
|
||||||
using var resized = new Mat();
|
|
||||||
CvInvoke.Resize(faceMat, resized, new Size(100, 100));
|
|
||||||
|
|
||||||
images.Push(resized);
|
|
||||||
labels.Push(new int[] { emp.Id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Training error for {emp.Name}: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (images.Size > 0)
|
|
||||||
{
|
|
||||||
_recognizer.Train(images, labels);
|
|
||||||
_isTrained = true;
|
|
||||||
Console.WriteLine($"✅ Trained on {images.Size} faces.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLine("⚠️ No valid faces found for training.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public DetectedFace? ProcessFrame(Mat frame)
|
|
||||||
{
|
|
||||||
if (frame == null || frame.IsEmpty) return null;
|
|
||||||
|
|
||||||
// === УСКОРЕНИЕ: Уменьшаем кадр для поиска лиц ===
|
|
||||||
using var smallFrame = new Mat();
|
|
||||||
CvInvoke.Resize(frame, smallFrame, new Size(), ResizeScale, ResizeScale, Inter.Linear);
|
|
||||||
|
|
||||||
using var gray = new Mat();
|
|
||||||
CvInvoke.CvtColor(smallFrame, gray, ColorConversion.Bgr2Gray);
|
|
||||||
|
|
||||||
// Ищем лица на уменьшенном кадре
|
|
||||||
var rects = _cascade.DetectMultiScale(gray, 1.1, 5, _scaledMinSize);
|
|
||||||
|
|
||||||
if (rects.Length == 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var smallRect = rects[0];
|
|
||||||
|
|
||||||
// === ВОССТАНОВЛЕНИЕ координат к оригинальному размеру ===
|
|
||||||
int invScale = (int)(1.0 / ResizeScale);
|
|
||||||
var originalRect = new Rectangle(
|
|
||||||
smallRect.X * invScale,
|
|
||||||
smallRect.Y * invScale,
|
|
||||||
smallRect.Width * invScale,
|
|
||||||
smallRect.Height * invScale
|
|
||||||
);
|
|
||||||
|
|
||||||
// Проверка границ по оригинальному кадру
|
|
||||||
if (originalRect.X < 0 || originalRect.Y < 0 ||
|
|
||||||
originalRect.Right > frame.Width || originalRect.Bottom > frame.Height)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// Вырезаем лицо из ОРИГИНАЛЬНОГО кадра (лучшее качество для распознавания)
|
|
||||||
using var faceMat = new Mat(frame, originalRect);
|
|
||||||
using var faceGray = new Mat();
|
|
||||||
CvInvoke.CvtColor(faceMat, faceGray, ColorConversion.Bgr2Gray);
|
|
||||||
|
|
||||||
using var resized = new Mat();
|
|
||||||
CvInvoke.Resize(faceGray, resized, new Size(100, 100));
|
|
||||||
|
|
||||||
var result = new DetectedFace();
|
|
||||||
|
|
||||||
if (_isTrained)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var prediction = _recognizer.Predict(resized);
|
|
||||||
|
|
||||||
int label = prediction.Label;
|
|
||||||
double confidence = prediction.Distance;
|
|
||||||
|
|
||||||
if (label != -1 && confidence < 80)
|
|
||||||
{
|
|
||||||
result.IsRecognized = true;
|
|
||||||
result.MatchedEmployee = _employees.FirstOrDefault(e => e.Id == label);
|
|
||||||
result.Confidence = confidence;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Prediction error: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Конвертация в JPEG для веб-интерфейса
|
|
||||||
using var ms = new MemoryStream();
|
|
||||||
using var bmp = MatToBitmap(resized);
|
|
||||||
bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
|
|
||||||
result.ImageData = ms.ToArray();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Безопасная конвертация Bitmap → Mat через промежуточный буфер
|
|
||||||
/// </summary>
|
|
||||||
private static Mat BitmapToMat(Bitmap bitmap)
|
|
||||||
{
|
|
||||||
var mat = new Mat(bitmap.Height, bitmap.Width, DepthType.Cv8U, 3);
|
|
||||||
|
|
||||||
var data = bitmap.LockBits(
|
|
||||||
new Rectangle(0, 0, bitmap.Width, bitmap.Height),
|
|
||||||
System.Drawing.Imaging.ImageLockMode.ReadOnly,
|
|
||||||
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
int length = Math.Abs(data.Stride) * bitmap.Height;
|
|
||||||
byte[] buffer = new byte[length];
|
|
||||||
Marshal.Copy(data.Scan0, buffer, 0, length);
|
|
||||||
Marshal.Copy(buffer, 0, mat.DataPointer, length);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
bitmap.UnlockBits(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return mat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Безопасная конвертация Mat → Bitmap через промежуточный буфер
|
|
||||||
/// </summary>
|
|
||||||
private static Bitmap MatToBitmap(Mat mat)
|
|
||||||
{
|
|
||||||
var colorMat = new Mat();
|
|
||||||
if (mat.NumberOfChannels == 1)
|
|
||||||
CvInvoke.CvtColor(mat, colorMat, ColorConversion.Gray2Bgr);
|
|
||||||
else
|
|
||||||
mat.CopyTo(colorMat);
|
|
||||||
|
|
||||||
var bmp = new Bitmap(colorMat.Width, colorMat.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
|
|
||||||
|
|
||||||
var data = bmp.LockBits(
|
|
||||||
new Rectangle(0, 0, bmp.Width, bmp.Height),
|
|
||||||
System.Drawing.Imaging.ImageLockMode.WriteOnly,
|
|
||||||
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
int length = Math.Abs(data.Stride) * colorMat.Height;
|
|
||||||
byte[] buffer = new byte[length];
|
|
||||||
Marshal.Copy(colorMat.DataPointer, buffer, 0, length);
|
|
||||||
Marshal.Copy(buffer, 0, data.Scan0, length);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
bmp.UnlockBits(data);
|
|
||||||
colorMat.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
return bmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_cascade?.Dispose();
|
|
||||||
_recognizer?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"AllowedHosts": "*",
|
|
||||||
"Camera": {
|
|
||||||
"Url": "rtsp://admin:4NUDZhJ7@172.22.10.101:554/cam/realmonitor?channel=1&subtype=0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,73 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Face Recognition</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: sans-serif;
|
|
||||||
background: #222;
|
|
||||||
color: #fff;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#gallery {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
background: #333;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 8px;
|
|
||||||
width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
border: 3px solid red;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.known img {
|
|
||||||
border-color: green;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
margin-top: 5px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time {
|
|
||||||
font-size: 0.8em;
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Live Face Detection</h1>
|
|
||||||
<div id="gallery"></div>
|
|
||||||
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.0/signalr.min.js"></script>
|
|
||||||
<script>
|
|
||||||
const connection = new signalR.HubConnectionBuilder().withUrl("/faceHub").build();
|
|
||||||
|
|
||||||
connection.on("NewFace", (data) => {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.className = data.isRecognized ? 'card known' : 'card';
|
|
||||||
div.innerHTML = `
|
|
||||||
<img src="data:image/jpeg;base64,${data.image}" />
|
|
||||||
<div class="name">${data.isRecognized ? data.name : 'Unknown'}</div>
|
|
||||||
<div class="time">${new Date().toLocaleTimeString()}</div>
|
|
||||||
`;
|
|
||||||
const gallery = document.getElementById('gallery');
|
|
||||||
gallery.insertBefore(div, gallery.firstChild);
|
|
||||||
if (gallery.children.length > 20) gallery.lastChild.remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
connection.start().catch(console.error);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -3,21 +3,12 @@
|
|||||||
@using System.Text.Json
|
@using System.Text.Json
|
||||||
@using Newtonsoft.Json
|
@using Newtonsoft.Json
|
||||||
@using System.Text
|
@using System.Text
|
||||||
|
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
@inject NavigationManager NavManager
|
@inject NavigationManager NavManager
|
||||||
|
|
||||||
<PageTitle>База данных</PageTitle>
|
<PageTitle>База данных</PageTitle>
|
||||||
|
|
||||||
<ul class="nav nav-tabs">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" aria-current="page" href="database">Сводные данные</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link active" href="database/type">Типы устройств</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="modal fade modal1" id="addtype" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="addtypeLabel" aria-hidden="true">
|
<div class="modal fade modal1" id="addtype" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="addtypeLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
@@ -50,7 +41,7 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@* <ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" aria-current="page" href="database">Сводные данные</a>
|
<a class="nav-link" aria-current="page" href="database">Сводные данные</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -65,14 +56,7 @@ else
|
|||||||
href="database/addtype">Добавить Тип</a>
|
href="database/addtype">Добавить Тип</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
</ul> *@
|
</ul>
|
||||||
<br>
|
|
||||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addtype">
|
|
||||||
Добавить тип
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#deltype">
|
|
||||||
Удалить тип
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
<div class="tab-content" id="myTabContent">
|
<div class="tab-content" id="myTabContent">
|
||||||
@@ -100,7 +84,9 @@ else
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#deltype">
|
||||||
|
Удалить тип
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -129,7 +115,9 @@ else
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addtype">
|
||||||
|
Добавить тип
|
||||||
|
</button>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
//HttpClient http = new();
|
//HttpClient http = new();
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ public class DeviceController : ControllerBase
|
|||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
//=========================== GET =================================//
|
//=========================== GET =================================//
|
||||||
[HttpGet("GetTypes")]
|
[HttpGet("GetTypes")]
|
||||||
[ProducesResponseType<List<DeviceType>>(StatusCodes.Status200OK)]
|
[ProducesResponseType<List<DeviceType>>(StatusCodes.Status200OK)]
|
||||||
@@ -80,4 +79,5 @@ public class DeviceController : ControllerBase
|
|||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,10 @@
|
|||||||
public int ConnectedToSwitchId { get; set; }
|
public int ConnectedToSwitchId { get; set; }
|
||||||
public int ConnectedToPortOnSwitch { get; set; }
|
public int ConnectedToPortOnSwitch { get; set; }
|
||||||
|
|
||||||
public DeviceItem(string ip, string mac, string location)
|
//public DeviceItem(int type, string ip, string mac, string location)
|
||||||
{
|
//{
|
||||||
|
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Unknown = 0,
|
//Unknown = 0,
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,7 +1,4 @@
|
|||||||
<Solution>
|
<Solution>
|
||||||
<Folder Name="/Faces/">
|
|
||||||
<Project Path="FaceRecognitionApp/FaceRecognitionApp.csproj" Id="401c1b89-2e49-4ad0-8e88-0aff3edb4c6c" />
|
|
||||||
</Folder>
|
|
||||||
<Folder Name="/LC/">
|
<Folder Name="/LC/">
|
||||||
<Project Path="Ministreliy/Ministreliy.csproj" Id="ffd1e86c-d678-413c-a0fd-dd75b4ec1b2e" />
|
<Project Path="Ministreliy/Ministreliy.csproj" Id="ffd1e86c-d678-413c-a0fd-dd75b4ec1b2e" />
|
||||||
<Project Path="Strela/Strela.csproj" />
|
<Project Path="Strela/Strela.csproj" />
|
||||||
|
|||||||
Reference in New Issue
Block a user