Detection preview added.

This commit is contained in:
Alexander Shabarshov 2026-05-23 11:35:48 +01:00
parent 42408bba38
commit 18928a23f9
6 changed files with 137 additions and 13 deletions

View File

@ -1,6 +1,7 @@
using Avalonia;
using Avalonia.Media;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Splitter_UI;
@ -31,12 +32,28 @@ internal sealed class Program
services.AddTransient<StatusBarViewModel>();
services.AddTransient<LogPaneViewModel>();
// splitter services
services.AddSingleton<UltraFaceDetector>();
services.AddSingleton<YoloOnnxObjectDetector>();
services.AddSingleton( x => new SingleThreadedDetector<UltraFaceDetector>(x.GetRequiredService<UltraFaceDetector>()) );
services.AddSingleton( x => new SingleThreadedDetector<YoloOnnxObjectDetector>(x.GetRequiredService<YoloOnnxObjectDetector>()));
services.AddSingleton<Func<string, IObjectDetector>>( x => detectorName =>
{
return detectorName switch
{
"face" => x.GetRequiredService<SingleThreadedDetector<UltraFaceDetector>>(),
"body" => x.GetRequiredService<SingleThreadedDetector<YoloOnnxObjectDetector>>(),
_ => new DummyDetector()
};
});
services.AddSingleton<splitter.ILogger, GlobalLogger>();
// Domain services (your pipeline)
services.AddTransient<IFileProbeService, FileProbeService>();
services.AddTransient<IThumbnailService, ThumbnailService>();
services.AddTransient<IFileProbeService, FileProbeService>();
services.AddTransient<IThumbnailService, ThumbnailService>();
services.AddSingleton<IAutoDecisionService, AutoDecisionService>();
services.AddSingleton<IProcessingService, ProcessingService>();
services.AddSingleton<ILogService, LogService>();
services.AddSingleton<IProcessingService, ProcessingService>();
services.AddSingleton<ILogService, LogService>();
services.AddSingleton<IFileJobFactory, FileJobFactory>();

View File

@ -0,0 +1,43 @@
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Media.Imaging;
using OpenCvSharp;
namespace Splitter_UI.Services;
public static class AvaloniaBitmapExtensions
{
public static Mat ToMatContinuous(this Bitmap bmp)
{
var w = bmp.PixelSize.Width;
var h = bmp.PixelSize.Height;
var stride = w * 4;
var size = h * stride;
var buffer = new byte[size];
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
try
{
bmp.CopyPixels(
new PixelRect(0, 0, w, h),
handle.AddrOfPinnedObject(),
size,
stride);
return Mat.FromPixelData(h, w, MatType.CV_8UC4, buffer);
}
finally
{
handle.Free();
}
}
public static Mat ToMatBgrContinuous(this Bitmap bmp)
{
using var bgra = bmp.ToMatContinuous();
var bgr = new Mat();
Cv2.CvtColor(bgra, bgr, ColorConversionCodes.BGRA2BGR);
return bgr;
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
using OpenCvSharp;
namespace Splitter_UI.Services;
internal class DummyDetector : IObjectDetector
{
public List<(Rect box, splitter.Point2f center)> DetectAll(Mat frameCont) => [];
public void Dispose() {}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Splitter_UI.Services;
internal class GlobalLogger(ILogService _logService) : ILogger
{
public void ClearProgress(int progressLevel) { }
public void DrawProgress(string name, int progressLine, double progress, TimeSpan eta, double speed) { }
public void Log(string prefix, ConsoleColor color, string msg)
{
_logService.Write($"[{prefix}] {msg}");
}
}

View File

@ -0,0 +1,23 @@
using OpenCvSharp;
namespace Splitter_UI.Services;
public class SingleThreadedDetector<T>(IObjectDetector _detector) : IObjectDetector
where T : IObjectDetector
{
private Lock _lock = new();
public List<(Rect box, splitter.Point2f center)> DetectAll(Mat frameCont)
{
lock (_lock)
{
return _detector.DetectAll(frameCont);
}
}
public void Dispose()
{
if ( _detector is IDisposable d )
d.Dispose();
}
}

View File

@ -35,9 +35,11 @@ public partial class JobViewModel : ObservableObject
public IRelayCommand StepForwardCommand { get; }
public IRelayCommand StepBackwardCommand { get; }
private readonly IThumbnailService _thumbnails;
private readonly IFileProbeService _fileProbe;
private readonly DispatcherTimer _debounceTimer;
private readonly IThumbnailService _thumbnails;
private readonly IFileProbeService _fileProbe;
private readonly DispatcherTimer _debounceTimer;
private readonly Func<string, IObjectDetector> _detectorFactory;
private readonly ILogger _log;
public string FileName => Path.GetFileName(Job.InputFile);
@ -115,11 +117,13 @@ public partial class JobViewModel : ObservableObject
}
}
public JobViewModel(SingleJob job, IThumbnailService thumbnails, IFileProbeService fileProbe)
public JobViewModel(SingleJob job, IThumbnailService thumbnails, IFileProbeService fileProbe, Func<string, IObjectDetector> detectorFactory, ILogger log)
{
Job = job;
_thumbnails = thumbnails;
_fileProbe = fileProbe;
Job = job;
_thumbnails = thumbnails;
_fileProbe = fileProbe;
_detectorFactory = detectorFactory;
_log = log;
ParametersList.Add(new ParameterEntry("DropoutToleranceFrames", ""));
ParametersList.Add(new ParameterEntry("EmaFactor", ""));
@ -164,11 +168,21 @@ public partial class JobViewModel : ObservableObject
return;
try
{
var frame = await _thumbnails.CreateThumbnailAsync(Job.InputFile, Probe, TimeSpan.FromSeconds(PositionSeconds), Probe.Width, Probe.Height, Job.Rotate);
Preview = new PreviewData(frame, [], null);
var frame = await _thumbnails.CreateThumbnailAsync(Job.InputFile, Probe, TimeSpan.FromSeconds(PositionSeconds), Probe.Width, Probe.Height, Job.Rotate);
if ( frame == null )
return;
Preview = new PreviewData(frame, [], null);
var detector = _detectorFactory(Job.Detect ?? "");
var detections = detector.DetectAll(frame.ToMatContinuous());
var boxes = detections.Select(x => new Avalonia.Rect(x.box.X, x.box.Y, x.box.Width, x.box.Height)).ToList();
Preview = new PreviewData(frame, boxes, null);
}
catch (Exception ex)
{
_log.LogError($"Error creating preview for {FileName}: {ex.Message}");
}
}