144 lines
4.6 KiB
C#
144 lines
4.6 KiB
C#
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.");
|
|
}
|
|
} |