Compare commits

...

2 Commits

10 changed files with 265 additions and 57 deletions

View File

@ -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;

View File

@ -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)

View File

@ -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"/>

View File

@ -7,8 +7,8 @@
x:Class="Splitter_UI.Views.MainWindow"
x:DataType="vm:MainViewModel"
x:Name="Root"
Width="1400"
Height="950"
Width="1800"
Height="790"
Title="Splitter UI"
Icon="avares://Splitter-UI/Assets/splitter.png">
@ -22,34 +22,32 @@
<views:StatusBarView DockPanel.Dock="Bottom"
DataContext="{Binding StatusBar}" />
<!-- Log Pane -->
<views:LogPane DockPanel.Dock="Bottom" Height="150"
DataContext="{Binding LogPane}" />
<Grid ColumnDefinitions="220,Auto,*,430"
IsVisible="{Binding TransformMode, Converter={StaticResource BoolInvertConverter}}">
<Grid>
<!-- Main Content -->
<Grid ColumnDefinitions="2*,3*,430" IsVisible="{Binding TransformMode, Converter={StaticResource BoolInvertConverter}}">
<!-- File List -->
<views:FileListView Grid.Column="0"
DataContext="{Binding FileList}" />
<!-- File List -->
<views:FileListView Grid.Column="0"
DataContext="{Binding FileList}" />
<!-- Splitter -->
<GridSplitter Grid.Column="1"
Width="6"
Background="#404040"
ResizeDirection="Columns"
ResizeBehavior="PreviousAndNext"
ShowsPreview="True" />
<!-- Preview -->
<views:PreviewPane Grid.Column="1"
DataContext="{Binding Preview}" />
<!-- Preview -->
<views:PreviewPane Grid.Column="2"
DataContext="{Binding Preview}" />
<!-- Inspector -->
<views:InspectorPane Grid.Column="2"
DataContext="{Binding Inspector}" />
<!-- Inspector -->
<views:InspectorPane Grid.Column="3"
DataContext="{Binding Inspector}" />
</Grid>
<!-- Progress view (replaces entire grid) -->
<views:ProgressView
DataContext="{Binding Progress}"
IsVisible="{Binding #Root.DataContext.TransformMode}"/>
</Grid>
</DockPanel>
</Window>

View File

@ -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);
}
}

View File

@ -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"

View File

@ -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)

View File

@ -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; }

View File

@ -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);

View File

@ -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);