mirror of
https://github.com/unclshura/splitter.git
synced 2026-06-22 00:22:01 +00:00
Configurable DetectAbove added. UI added too.
This commit is contained in:
parent
4bc4b02007
commit
8c611e31d7
@ -194,6 +194,17 @@ public partial class JobViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float DetectAbove
|
||||||
|
{
|
||||||
|
get => Job.DetectAbove;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Job.DetectAbove = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
Task.Run(CreatePreview);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public double? OverrideTargetDuration
|
public double? OverrideTargetDuration
|
||||||
{
|
{
|
||||||
get => Job.OverrideTargetDuration;
|
get => Job.OverrideTargetDuration;
|
||||||
|
|||||||
@ -23,6 +23,18 @@ public partial class PreviewPaneViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float DetectAbove
|
||||||
|
{
|
||||||
|
get => Selected?.DetectAbove ?? 0.7f;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Selected == null)
|
||||||
|
return;
|
||||||
|
Selected.DetectAbove = value;
|
||||||
|
OnPropertyChanged(nameof(DetectAbove));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
partial void OnSelectedChanged(JobViewModel? oldValue, JobViewModel? newValue)
|
partial void OnSelectedChanged(JobViewModel? oldValue, JobViewModel? newValue)
|
||||||
{
|
{
|
||||||
if (oldValue != null)
|
if (oldValue != null)
|
||||||
|
|||||||
@ -86,6 +86,12 @@ x:DataType="vm:InspectorPaneViewModel">
|
|||||||
Width="160"/>
|
Width="160"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- DetectAbove -->
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<TextBlock Text="Detect Above" Width="120"/>
|
||||||
|
<TextBox Text="{Binding Selected.DetectAbove}" Width="160"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<!-- OverrideTargetDuration -->
|
<!-- OverrideTargetDuration -->
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
<TextBlock Text="Target Duration" Width="120"/>
|
<TextBlock Text="Target Duration" Width="120"/>
|
||||||
|
|||||||
@ -18,6 +18,10 @@ public sealed class PreviewCanvas : Control
|
|||||||
public static readonly StyledProperty<Point2f> GravitateToProperty =
|
public static readonly StyledProperty<Point2f> GravitateToProperty =
|
||||||
AvaloniaProperty.Register<PreviewCanvas, Point2f>(nameof(GravitateTo));
|
AvaloniaProperty.Register<PreviewCanvas, Point2f>(nameof(GravitateTo));
|
||||||
|
|
||||||
|
// normalized 0..1 from top of frame
|
||||||
|
public static readonly StyledProperty<float> DetectAboveProperty =
|
||||||
|
AvaloniaProperty.Register<PreviewCanvas, float>(nameof(DetectAbove), 0.2f);
|
||||||
|
|
||||||
public PreviewData? Preview
|
public PreviewData? Preview
|
||||||
{
|
{
|
||||||
get => GetValue(PreviewProperty);
|
get => GetValue(PreviewProperty);
|
||||||
@ -43,10 +47,20 @@ public sealed class PreviewCanvas : Control
|
|||||||
set => SetValue(GravitateToProperty, value);
|
set => SetValue(GravitateToProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _dragging;
|
// DetectAbove is normalized (0..1) from top
|
||||||
|
public float DetectAbove
|
||||||
|
{
|
||||||
|
get => GetValue(DetectAboveProperty);
|
||||||
|
set => SetValue(DetectAboveProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _draggingGravitate;
|
||||||
private Avalonia.Point _dragStartCanvas;
|
private Avalonia.Point _dragStartCanvas;
|
||||||
private Point2f _dragStartValue;
|
private Point2f _dragStartValue;
|
||||||
|
|
||||||
|
private bool _draggingDetectAbove;
|
||||||
|
private double _dragStartDetectAbove; // normalized 0..1
|
||||||
|
|
||||||
static PreviewCanvas()
|
static PreviewCanvas()
|
||||||
{
|
{
|
||||||
PreviewProperty.Changed.AddClassHandler<PreviewCanvas>(
|
PreviewProperty.Changed.AddClassHandler<PreviewCanvas>(
|
||||||
@ -228,6 +242,65 @@ public sealed class PreviewCanvas : Control
|
|||||||
return hit;
|
return hit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Hit test for DetectAbove knob (normalized)
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
private bool HitDetectAbove(Avalonia.Point p, out double value)
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
|
||||||
|
var preview = Preview;
|
||||||
|
if (preview?.Frame is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var rawW = preview.Frame.PixelSize.Width;
|
||||||
|
var rawH = preview.Frame.PixelSize.Height;
|
||||||
|
|
||||||
|
var rotate = RotateAngle;
|
||||||
|
var sar = Sar ?? new Point2f(1, 1);
|
||||||
|
var pixelAspect = sar.X / sar.Y;
|
||||||
|
|
||||||
|
var dispW = Bounds.Width;
|
||||||
|
var dispH = Bounds.Height;
|
||||||
|
|
||||||
|
double displayW, displayH;
|
||||||
|
if (rotate == 0 || rotate == 180)
|
||||||
|
{
|
||||||
|
displayW = rawW * pixelAspect;
|
||||||
|
displayH = rawH;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
displayW = rawW;
|
||||||
|
displayH = rawH * pixelAspect;
|
||||||
|
}
|
||||||
|
|
||||||
|
var scale = Math.Min(dispW / displayW, dispH / displayH);
|
||||||
|
var offsetX = (dispW - displayW * scale) / 2;
|
||||||
|
var offsetY = (dispH - displayH * scale) / 2;
|
||||||
|
|
||||||
|
var da = DetectAbove;
|
||||||
|
var py = da * rawH;
|
||||||
|
var px = rawW / 2.0;
|
||||||
|
|
||||||
|
var (cx, cy) = TransformPoint(
|
||||||
|
px, py,
|
||||||
|
rawW, rawH,
|
||||||
|
offsetX, offsetY,
|
||||||
|
scale,
|
||||||
|
rotate,
|
||||||
|
pixelAspect);
|
||||||
|
|
||||||
|
const double radius = 10;
|
||||||
|
var hit = (p.X - cx) * (p.X - cx) + (p.Y - cy) * (p.Y - cy) <= radius * radius;
|
||||||
|
|
||||||
|
if (hit)
|
||||||
|
value = da;
|
||||||
|
|
||||||
|
return hit;
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
// Pointer events
|
// Pointer events
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
@ -238,23 +311,32 @@ public sealed class PreviewCanvas : Control
|
|||||||
|
|
||||||
if (HitGravitate(p, out var g))
|
if (HitGravitate(p, out var g))
|
||||||
{
|
{
|
||||||
_dragging = true;
|
_draggingGravitate = true;
|
||||||
_dragStartCanvas = p;
|
_dragStartCanvas = p;
|
||||||
_dragStartValue = g; // normalized
|
_dragStartValue = g; // normalized
|
||||||
e.Pointer.Capture(this);
|
e.Pointer.Capture(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HitDetectAbove(p, out var da))
|
||||||
|
{
|
||||||
|
_draggingDetectAbove = true;
|
||||||
|
_dragStartCanvas = p;
|
||||||
|
_dragStartDetectAbove = da; // normalized
|
||||||
|
e.Pointer.Capture(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPointerMoved(object? sender, PointerEventArgs e)
|
private void OnPointerMoved(object? sender, PointerEventArgs e)
|
||||||
{
|
{
|
||||||
if (!_dragging || Preview?.Frame is null)
|
var preview = Preview;
|
||||||
|
if (preview?.Frame is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var p = e.GetPosition(this);
|
var p = e.GetPosition(this);
|
||||||
var dxCanvas = p.X - _dragStartCanvas.X;
|
var dxCanvas = p.X - _dragStartCanvas.X;
|
||||||
var dyCanvas = p.Y - _dragStartCanvas.Y;
|
var dyCanvas = p.Y - _dragStartCanvas.Y;
|
||||||
|
|
||||||
var preview = Preview;
|
|
||||||
var rawW = preview.Frame.PixelSize.Width;
|
var rawW = preview.Frame.PixelSize.Width;
|
||||||
var rawH = preview.Frame.PixelSize.Height;
|
var rawH = preview.Frame.PixelSize.Height;
|
||||||
|
|
||||||
@ -287,43 +369,74 @@ public sealed class PreviewCanvas : Control
|
|||||||
else
|
else
|
||||||
dy /= pixelAspect;
|
dy /= pixelAspect;
|
||||||
|
|
||||||
// start normalized → pixel
|
if (_draggingGravitate)
|
||||||
var gx = _dragStartValue.X * rawW + dx;
|
|
||||||
var gy = _dragStartValue.Y * rawH + dy;
|
|
||||||
|
|
||||||
switch (rotate)
|
|
||||||
{
|
{
|
||||||
case 90:
|
var gx = _dragStartValue.X * rawW + dx;
|
||||||
(gx, gy) = (gy, rawH - gx);
|
var gy = _dragStartValue.Y * rawH + dy;
|
||||||
break;
|
|
||||||
case 180:
|
switch (rotate)
|
||||||
gx = rawW - gx;
|
{
|
||||||
gy = rawH - gy;
|
case 90:
|
||||||
break;
|
(gx, gy) = (gy, rawH - gx);
|
||||||
case 270:
|
break;
|
||||||
(gx, gy) = (rawW - gy, gx);
|
case 180:
|
||||||
break;
|
gx = rawW - gx;
|
||||||
|
gy = rawH - gy;
|
||||||
|
break;
|
||||||
|
case 270:
|
||||||
|
(gx, gy) = (rawW - gy, gx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nx = (float)(gx / rawW);
|
||||||
|
var ny = (float)(gy / rawH);
|
||||||
|
|
||||||
|
if (nx < 0) nx = 0;
|
||||||
|
if (ny < 0) ny = 0;
|
||||||
|
if (nx > 1) nx = 1;
|
||||||
|
if (ny > 1) ny = 1;
|
||||||
|
|
||||||
|
GravitateTo = new Point2f(nx, ny);
|
||||||
}
|
}
|
||||||
|
else if (_draggingDetectAbove)
|
||||||
|
{
|
||||||
|
var gx = rawW / 2.0;
|
||||||
|
var gy = _dragStartDetectAbove * rawH + dy;
|
||||||
|
|
||||||
// pixel → normalized
|
switch (rotate)
|
||||||
var nx = (float)(gx / rawW);
|
{
|
||||||
var ny = (float)(gy / rawH);
|
case 90:
|
||||||
|
(gx, gy) = (gy, rawH - gx);
|
||||||
|
break;
|
||||||
|
case 180:
|
||||||
|
gx = rawW - gx;
|
||||||
|
gy = rawH - gy;
|
||||||
|
break;
|
||||||
|
case 270:
|
||||||
|
(gx, gy) = (rawW - gy, gx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (nx < 0) nx = 0;
|
var ny = gy / rawH;
|
||||||
if (ny < 0) ny = 0;
|
if (ny < 0) ny = 0;
|
||||||
if (nx > 1) nx = 1;
|
if (ny > 1) ny = 1;
|
||||||
if (ny > 1) ny = 1;
|
|
||||||
|
|
||||||
GravitateTo = new Point2f(nx, ny);
|
DetectAbove = (float)ny;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Render);
|
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Render);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
{
|
{
|
||||||
if (_dragging)
|
if (_draggingGravitate || _draggingDetectAbove)
|
||||||
{
|
{
|
||||||
_dragging = false;
|
_draggingGravitate = false;
|
||||||
|
_draggingDetectAbove = false;
|
||||||
e.Pointer.Capture(null);
|
e.Pointer.Capture(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -418,6 +531,55 @@ public sealed class PreviewCanvas : Control
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RenderDetectAbove(
|
||||||
|
DrawingContext context,
|
||||||
|
PreviewData preview,
|
||||||
|
double rawW, double rawH,
|
||||||
|
double offsetX, double offsetY,
|
||||||
|
double scale,
|
||||||
|
int rotate,
|
||||||
|
double pixelAspect)
|
||||||
|
{
|
||||||
|
var da = DetectAbove;
|
||||||
|
var rawY = da * rawH;
|
||||||
|
|
||||||
|
var (x1, y1) = TransformPoint(
|
||||||
|
0, rawY,
|
||||||
|
rawW, rawH,
|
||||||
|
offsetX, offsetY,
|
||||||
|
scale,
|
||||||
|
rotate,
|
||||||
|
pixelAspect);
|
||||||
|
|
||||||
|
var (x2, y2) = TransformPoint(
|
||||||
|
rawW, rawY,
|
||||||
|
rawW, rawH,
|
||||||
|
offsetX, offsetY,
|
||||||
|
scale,
|
||||||
|
rotate,
|
||||||
|
pixelAspect);
|
||||||
|
|
||||||
|
var pen = new Pen(Brushes.Lime, 2);
|
||||||
|
context.DrawLine(pen, new Avalonia.Point(x1, y1), new Avalonia.Point(x2, y2));
|
||||||
|
|
||||||
|
const double radius = 10;
|
||||||
|
var (kx, ky) = TransformPoint(
|
||||||
|
rawW / 2.0, rawY,
|
||||||
|
rawW, rawH,
|
||||||
|
offsetX, offsetY,
|
||||||
|
scale,
|
||||||
|
rotate,
|
||||||
|
pixelAspect);
|
||||||
|
|
||||||
|
var knob = new EllipseGeometry(
|
||||||
|
new Rect(kx - radius, ky - radius, radius * 2, radius * 2));
|
||||||
|
|
||||||
|
var knobPen = new Pen(Brushes.Lime, 2);
|
||||||
|
var knobBrush = Brushes.Lime;
|
||||||
|
|
||||||
|
context.DrawGeometry(knobBrush, knobPen, knob);
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
// Main Render
|
// Main Render
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
@ -474,5 +636,6 @@ public sealed class PreviewCanvas : Control
|
|||||||
RenderDetectedBoxes(context, preview, rawW, rawH, offsetX, offsetY, scale, rotate, pixelAspect);
|
RenderDetectedBoxes(context, preview, rawW, rawH, offsetX, offsetY, scale, rotate, pixelAspect);
|
||||||
RenderCropRectangle(context, preview, rawW, rawH, offsetX, offsetY, scale, rotate, pixelAspect);
|
RenderCropRectangle(context, preview, rawW, rawH, offsetX, offsetY, scale, rotate, pixelAspect);
|
||||||
RenderGravitateTo(context, preview, rawW, rawH, offsetX, offsetY, scale, rotate, pixelAspect);
|
RenderGravitateTo(context, preview, rawW, rawH, offsetX, offsetY, scale, rotate, pixelAspect);
|
||||||
|
RenderDetectAbove(context, preview, rawW, rawH, offsetX, offsetY, scale, rotate, pixelAspect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,9 @@
|
|||||||
Preview="{Binding Preview}"
|
Preview="{Binding Preview}"
|
||||||
Sar="{Binding Sar}"
|
Sar="{Binding Sar}"
|
||||||
RotateAngle="{Binding Rotate}"
|
RotateAngle="{Binding Rotate}"
|
||||||
GravitateTo="{Binding GravitateTo, Mode=TwoWay}"/>
|
GravitateTo="{Binding GravitateTo, Mode=TwoWay}"
|
||||||
|
DetectAbove="{Binding DetectAbove, Mode=TwoWay}"
|
||||||
|
/>
|
||||||
|
|
||||||
<Grid Grid.Row="1"
|
<Grid Grid.Row="1"
|
||||||
ColumnDefinitions="Auto,*,Auto"
|
ColumnDefinitions="Auto,*,Auto"
|
||||||
|
|||||||
@ -90,6 +90,14 @@ public sealed class CommandLine
|
|||||||
{
|
{
|
||||||
Master.Crop = ParseCrop(arg.Substring("--crop=".Length));
|
Master.Crop = ParseCrop(arg.Substring("--crop=".Length));
|
||||||
}
|
}
|
||||||
|
else if (arg.StartsWith("--detect-above="))
|
||||||
|
{
|
||||||
|
var val = arg.Substring("--detect-above=".Length);
|
||||||
|
if (float.TryParse(val, NumberStyles.Float, CultureInfo.InvariantCulture, out var detectAbove) && detectAbove >= 0.0f && detectAbove <= 1.0f)
|
||||||
|
Master.DetectAbove = detectAbove;
|
||||||
|
else
|
||||||
|
Master.DetectAbove = 0.7f;
|
||||||
|
}
|
||||||
else if (arg == "--crop")
|
else if (arg == "--crop")
|
||||||
{
|
{
|
||||||
Master.Crop = ParseCrop("");
|
Master.Crop = ParseCrop("");
|
||||||
@ -351,6 +359,9 @@ Options:
|
|||||||
--detect=<name> Object detector to use for tracking.
|
--detect=<name> Object detector to use for tracking.
|
||||||
Values: face (UltraFace), body (YoloOnnx, default), none (no tracking, just a center)
|
Values: face (UltraFace), body (YoloOnnx, default), none (no tracking, just a center)
|
||||||
|
|
||||||
|
--detect-above=<0-1> Face or human detectors should only report detections if their upper bound starts below this threshold.
|
||||||
|
This is a value between 0.0 and 1.0 mapped to 0..Height.
|
||||||
|
|
||||||
--gravitate=<x:y> Gravitate towards a specific point (x, y) in the video frame when tracking.
|
--gravitate=<x:y> Gravitate towards a specific point (x, y) in the video frame when tracking.
|
||||||
Coordinates are normalized (0.0 to 1.0).
|
Coordinates are normalized (0.0 to 1.0).
|
||||||
Example: --gravitate=0.2:0.5 (gravitate towards left-center)
|
Example: --gravitate=0.2:0.5 (gravitate towards left-center)
|
||||||
|
|||||||
@ -31,6 +31,11 @@ public class SingleJob
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Point2f? GravitateTo { get; set; }
|
public Point2f? GravitateTo { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// Face or human detectors should only report detections if their upper bound starts below this threshold.
|
||||||
|
/// This is a value between 0.0 and 1.0 mapped to 0..Height.
|
||||||
|
/// </summary>
|
||||||
|
public float DetectAbove { get; set; } = 0.3f;
|
||||||
|
/// <summary>
|
||||||
/// Destination file mask.
|
/// Destination file mask.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Mask { get; set; }
|
public string? Mask { get; set; }
|
||||||
|
|||||||
@ -131,6 +131,10 @@ public class TrackingSplitter : LoggingBase, ISegmentProcessor, IDisposable
|
|||||||
Marshal.Copy(inBuffer, 0, frameMat.Data, inBytes);
|
Marshal.Copy(inBuffer, 0, frameMat.Data, inBytes);
|
||||||
|
|
||||||
var objects = _detector.DetectAll(job, frameMat);
|
var objects = _detector.DetectAll(job, frameMat);
|
||||||
|
|
||||||
|
// Ignore detections starting in the lower 1/2 of the frame
|
||||||
|
objects = objects.Where(o => o.center.Y <= frameMat.Height * job.Job.DetectAbove).ToList();
|
||||||
|
|
||||||
var primary = SelectTrackedObject(objects, kalman.LastMeasurement);
|
var primary = SelectTrackedObject(objects, kalman.LastMeasurement);
|
||||||
|
|
||||||
camera.Update(primary);
|
camera.Update(primary);
|
||||||
|
|||||||
@ -139,10 +139,6 @@ public sealed class YoloOnnxObjectDetector : LoggingBase, IObjectDetector, IDisp
|
|||||||
w = Math.Clamp(w, 1, frameCont.Width - x);
|
w = Math.Clamp(w, 1, frameCont.Width - x);
|
||||||
h = Math.Clamp(h, 1, frameCont.Height - y);
|
h = Math.Clamp(h, 1, frameCont.Height - y);
|
||||||
|
|
||||||
// Ignore detections starting in the lower 1/2 of the frame
|
|
||||||
if (y > frameCont.Height * 0.5f)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var rect = new Rect(x, y, w, h);
|
var rect = new Rect(x, y, w, h);
|
||||||
var center = new Point2f(x + w / 2f, y + h / 2f);
|
var center = new Point2f(x + w / 2f, y + h / 2f);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user