mirror of
https://github.com/unclshura/splitter.git
synced 2026-06-21 16:12: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
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (oldValue != null)
|
||||
|
||||
@ -86,6 +86,12 @@ x:DataType="vm:InspectorPaneViewModel">
|
||||
Width="160"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- DetectAbove -->
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Text="Detect Above" Width="120"/>
|
||||
<TextBox Text="{Binding Selected.DetectAbove}" Width="160"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- OverrideTargetDuration -->
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Text="Target Duration" Width="120"/>
|
||||
|
||||
@ -18,6 +18,10 @@ public sealed class PreviewCanvas : Control
|
||||
public static readonly StyledProperty<Point2f> GravitateToProperty =
|
||||
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
|
||||
{
|
||||
get => GetValue(PreviewProperty);
|
||||
@ -43,10 +47,20 @@ public sealed class PreviewCanvas : Control
|
||||
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 Point2f _dragStartValue;
|
||||
|
||||
private bool _draggingDetectAbove;
|
||||
private double _dragStartDetectAbove; // normalized 0..1
|
||||
|
||||
static PreviewCanvas()
|
||||
{
|
||||
PreviewProperty.Changed.AddClassHandler<PreviewCanvas>(
|
||||
@ -228,6 +242,65 @@ public sealed class PreviewCanvas : Control
|
||||
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
|
||||
// ------------------------------------------------------------
|
||||
@ -238,23 +311,32 @@ public sealed class PreviewCanvas : Control
|
||||
|
||||
if (HitGravitate(p, out var g))
|
||||
{
|
||||
_dragging = true;
|
||||
_draggingGravitate = true;
|
||||
_dragStartCanvas = p;
|
||||
_dragStartValue = g; // normalized
|
||||
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)
|
||||
{
|
||||
if (!_dragging || Preview?.Frame is null)
|
||||
var preview = Preview;
|
||||
if (preview?.Frame is null)
|
||||
return;
|
||||
|
||||
var p = e.GetPosition(this);
|
||||
var dxCanvas = p.X - _dragStartCanvas.X;
|
||||
var dyCanvas = p.Y - _dragStartCanvas.Y;
|
||||
|
||||
var preview = Preview;
|
||||
var rawW = preview.Frame.PixelSize.Width;
|
||||
var rawH = preview.Frame.PixelSize.Height;
|
||||
|
||||
@ -287,43 +369,74 @@ public sealed class PreviewCanvas : Control
|
||||
else
|
||||
dy /= pixelAspect;
|
||||
|
||||
// start normalized → pixel
|
||||
var gx = _dragStartValue.X * rawW + dx;
|
||||
var gy = _dragStartValue.Y * rawH + dy;
|
||||
|
||||
switch (rotate)
|
||||
if (_draggingGravitate)
|
||||
{
|
||||
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;
|
||||
var gx = _dragStartValue.X * rawW + dx;
|
||||
var gy = _dragStartValue.Y * rawH + dy;
|
||||
|
||||
switch (rotate)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
var nx = (float)(gx / rawW);
|
||||
var ny = (float)(gy / rawH);
|
||||
switch (rotate)
|
||||
{
|
||||
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;
|
||||
if (ny < 0) ny = 0;
|
||||
if (nx > 1) nx = 1;
|
||||
if (ny > 1) ny = 1;
|
||||
var ny = gy / rawH;
|
||||
if (ny < 0) ny = 0;
|
||||
if (ny > 1) ny = 1;
|
||||
|
||||
GravitateTo = new Point2f(nx, ny);
|
||||
DetectAbove = (float)ny;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Render);
|
||||
}
|
||||
|
||||
private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||
{
|
||||
if (_dragging)
|
||||
if (_draggingGravitate || _draggingDetectAbove)
|
||||
{
|
||||
_dragging = false;
|
||||
_draggingGravitate = false;
|
||||
_draggingDetectAbove = false;
|
||||
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
|
||||
// ------------------------------------------------------------
|
||||
@ -474,5 +636,6 @@ public sealed class PreviewCanvas : Control
|
||||
RenderDetectedBoxes(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);
|
||||
RenderDetectAbove(context, preview, rawW, rawH, offsetX, offsetY, scale, rotate, pixelAspect);
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,9 @@
|
||||
Preview="{Binding Preview}"
|
||||
Sar="{Binding Sar}"
|
||||
RotateAngle="{Binding Rotate}"
|
||||
GravitateTo="{Binding GravitateTo, Mode=TwoWay}"/>
|
||||
GravitateTo="{Binding GravitateTo, Mode=TwoWay}"
|
||||
DetectAbove="{Binding DetectAbove, Mode=TwoWay}"
|
||||
/>
|
||||
|
||||
<Grid Grid.Row="1"
|
||||
ColumnDefinitions="Auto,*,Auto"
|
||||
|
||||
@ -90,6 +90,14 @@ public sealed class CommandLine
|
||||
{
|
||||
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")
|
||||
{
|
||||
Master.Crop = ParseCrop("");
|
||||
@ -351,6 +359,9 @@ Options:
|
||||
--detect=<name> Object detector to use for tracking.
|
||||
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.
|
||||
Coordinates are normalized (0.0 to 1.0).
|
||||
Example: --gravitate=0.2:0.5 (gravitate towards left-center)
|
||||
|
||||
@ -31,6 +31,11 @@ public class SingleJob
|
||||
/// </summary>
|
||||
public Point2f? GravitateTo { get; set; }
|
||||
/// <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.
|
||||
/// </summary>
|
||||
public string? Mask { get; set; }
|
||||
|
||||
@ -131,6 +131,10 @@ public class TrackingSplitter : LoggingBase, ISegmentProcessor, IDisposable
|
||||
Marshal.Copy(inBuffer, 0, frameMat.Data, inBytes);
|
||||
|
||||
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);
|
||||
|
||||
camera.Update(primary);
|
||||
|
||||
@ -139,10 +139,6 @@ public sealed class YoloOnnxObjectDetector : LoggingBase, IObjectDetector, IDisp
|
||||
w = Math.Clamp(w, 1, frameCont.Width - x);
|
||||
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 center = new Point2f(x + w / 2f, y + h / 2f);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user