From a6b9d9c069bca7c743163269b77577c51f9bedb2 Mon Sep 17 00:00:00 2001 From: unclshura Date: Tue, 12 May 2026 06:55:10 +0100 Subject: [PATCH] Command line switches for changing various internal parameters. --- CameraController.cs | 74 +++++++++++++++++++++----------------- CommandLine.cs | 88 +++++++++++++++++++++++++++++++++++++++++++++ TrackingSplitter.cs | 7 ++-- splitter.cs | 2 +- 4 files changed, 136 insertions(+), 35 deletions(-) diff --git a/CameraController.cs b/CameraController.cs index 6466c17..4f633d7 100644 --- a/CameraController.cs +++ b/CameraController.cs @@ -17,49 +17,58 @@ public sealed class CameraController private readonly int _cropWidth; private readonly int _cropHeight; private readonly KalmanTracker _kalman; + private readonly CommandLine _cmd; private int _dropoutCounter; // --- Dropout tolerance --- - private const int DropoutToleranceFrames = 20; - private const float EmaFactor = 0.65f; // smoother but responsive - private const float CameraEasing = 0.03f; // stronger follow-through - private const int LostFreezeFrames = 60; // 2 seconds at 30 FPS + private int _dropoutToleranceFrames = 20; + private float _emaFactor = 0.65f; // smoother but responsive + private float _cameraEasing = 0.03f; // stronger follow-through + private int _lostFreezeFrames = 60; // 2 seconds at 30 FPS - private int _lostFrames; - private Point2f _cameraCenter; + private int _lostFrames; + private Point2f _cameraCenter; private TrackState _state; - private Point2f _smoothedCenter; - private Rect? _objectBox; - private Point2f? _objectCenter; - private Rect _roi; + private Point2f _smoothedCenter; + private Rect? _objectBox; + private Point2f? _objectCenter; + private Rect _roi; public CameraController( int videoWidth, int videoHeight, int cropWidth, int cropHeight, - KalmanTracker kalman + KalmanTracker kalman, + CommandLine cmd ) { - _videoWidth = videoWidth; - _videoHeight = videoHeight; - _cropWidth = cropWidth; - _cropHeight = cropHeight; - _kalman = kalman; - - _cameraCenter = new Point2f(videoWidth / 2f, videoHeight / 2f); + _videoWidth = videoWidth; + _videoHeight = videoHeight; + _cropWidth = cropWidth; + _cropHeight = cropHeight; + _kalman = kalman; + _cmd = cmd; + _cameraCenter = DefaultCenter; _state = TrackState.Tracking; + cmd.Override(ref _dropoutToleranceFrames, "DropoutToleranceFrames"); + cmd.Override(ref _emaFactor, "EmaFactor"); + cmd.Override(ref _cameraEasing, "CameraEasing"); + cmd.Override(ref _lostFreezeFrames, "LostFreezeFrames"); + _kalman.Reset(_cameraCenter); } - public int LostFrames => _lostFrames; - public Point2f CameraCenter => _cameraCenter; - public TrackState State => _state; + private Point2f DefaultCenter => _cmd.GravitateTo ?? new Point2f(_videoWidth / 2f, _videoHeight / 2f); + + public int LostFrames => _lostFrames; + public Point2f CameraCenter => _cameraCenter; + public TrackState State => _state; public Point2f SmoothedCenter => _smoothedCenter; - public Rect? ObjectBox => _objectBox; - public Point2f? ObjectCenter => _objectCenter; + public Rect? ObjectBox => _objectBox; + public Point2f? ObjectCenter => _objectCenter; public Rect Roi => _roi; public void Update((Rect box, Point2f center)? primary) @@ -78,7 +87,7 @@ public sealed class CameraController // --------------------------------------------------------- if (!objectCenter.HasValue) { - if (_dropoutCounter < DropoutToleranceFrames) + if (_dropoutCounter < _dropoutToleranceFrames) { objectCenter = _kalman.LastMeasurement; _dropoutCounter++; @@ -96,7 +105,7 @@ public sealed class CameraController { _lostFrames++; - if (_lostFrames <= LostFreezeFrames) + if (_lostFrames <= _lostFreezeFrames) { _state = TrackState.LostFreeze; objectCenter = null; @@ -104,7 +113,7 @@ public sealed class CameraController else { _state = TrackState.LostDrift; - objectCenter = new Point2f(_videoWidth / 2f, _videoHeight / 2f); + objectCenter = DefaultCenter; } } else @@ -124,13 +133,13 @@ public sealed class CameraController // NEW: EMA smoothing // --------------------------------------------------------- smoothedCenter = new Point2f( - smoothedCenter.X * (1 - EmaFactor) + _cameraCenter.X * EmaFactor, - smoothedCenter.Y * (1 - EmaFactor) + _cameraCenter.Y * EmaFactor + smoothedCenter.X * (1 - _emaFactor) + _cameraCenter.X * _emaFactor, + smoothedCenter.Y * (1 - _emaFactor) + _cameraCenter.Y * _emaFactor ); _cameraCenter = new Point2f( - _cameraCenter.X + (smoothedCenter.X - _cameraCenter.X) * CameraEasing, - _cameraCenter.Y + (smoothedCenter.Y - _cameraCenter.Y) * CameraEasing); + _cameraCenter.X + (smoothedCenter.X - _cameraCenter.X) * _cameraEasing, + _cameraCenter.Y + (smoothedCenter.Y - _cameraCenter.Y) * _cameraEasing); } else if (_state == TrackState.LostFreeze) @@ -165,8 +174,9 @@ public sealed class CameraController y = Math.Clamp(y, 0, _videoHeight - _cropHeight); _roi = new Rect(x, y, _cropWidth, _cropHeight); + _smoothedCenter = smoothedCenter; - _objectBox = objectBox; - _objectCenter = objectCenter; + _objectBox = objectBox; + _objectCenter = objectCenter; } } diff --git a/CommandLine.cs b/CommandLine.cs index 6bf3758..22eb9bc 100644 --- a/CommandLine.cs +++ b/CommandLine.cs @@ -10,6 +10,7 @@ public sealed class CommandLine public string InputFile { get; private init; } public string OutputFolder { get; private init; } public (int width, int height)? Crop { get; private init; } + public Point2f? GravitateTo { get; private init; } public string? Mask { get; private init; } public bool Debug { get; private init; } public string? Detect { get; private init; } @@ -19,6 +20,7 @@ public sealed class CommandLine public bool EstimateOnly { get; private init; } public bool ForceFixed { get; private init; } public bool SingleThreaded { get; private init; } + public Dictionary Parameters { get; } = []; public bool IsValid => !string.IsNullOrEmpty(InputFile) && !string.IsNullOrEmpty(OutputFolder); @@ -84,6 +86,11 @@ public sealed class CommandLine { SingleThreaded = true; } + else if (arg.StartsWith("--gravitate=")) + { + var val = arg.Substring("--gravitate=".Length); + GravitateTo = ParseGravitate(val); + } else if (arg.StartsWith("--duration=")) { var dur = arg.Substring("--duration=".Length); @@ -94,6 +101,17 @@ public sealed class CommandLine return; } } + else if (arg.StartsWith("-p:", StringComparison.Ordinal)) + { + var spec = arg.Substring("-p:".Length); + if (!TryParseParameter(spec, out var key, out var value)) + { + Console.WriteLine($"Invalid -p parameter: {spec}"); + return; + } + + Parameters[key] = value; + } else if (arg == "--estimate") { EstimateOnly = true; @@ -105,6 +123,63 @@ public sealed class CommandLine } } + public void Override(ref T member, string name) + { + if (!Parameters.TryGetValue(name, out var raw)) + return; + + try + { + // Convert.ChangeType handles int, float, double, etc. + var converted = (T)Convert.ChangeType( + raw, + typeof(T), + CultureInfo.InvariantCulture + ); + + member = converted; + } + catch + { + Console.WriteLine($"Invalid value for parameter '{name}': {raw}"); + } + } + + private static bool TryParseParameter(string spec, out string key, out string value) + { + key = ""; + value = ""; + + var idx = spec.IndexOf('='); + if (idx <= 0 || idx == spec.Length - 1) + return false; + + key = spec.Substring(0, idx).Trim(); + value = spec.Substring(idx + 1).Trim(); + + return key.Length > 0; + } + + private static Point2f? ParseGravitate(string value) + { + // Expected format: ":" + var parts = value.Split(':'); + if (parts.Length != 2) + return null; + + if (!float.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var x)) + return null; + + if (!float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var y)) + return null; + + // Normalized range check (0.0–1.0) + if (x < 0f || x > 1f || y < 0f || y > 1f) + return null; + + return new Point2f(x, y); + } + private static (int width, int height)? ParseCrop(string v) { // Default vertical Full HD for YouTube Shorts @@ -201,6 +276,10 @@ Options: --detect= Object detector to use for tracking. Values: face (UltraFace), body (YoloOnnx, default), none (no tracking, just a center) + --gravitate= Gravitate towards a specific point (x, y) in the video frame. + Coordinates are normalized (0.0 to 1.0). + Example: --gravitate=0.2:0.5 (gravitate towards left-center) + --text Display log in plain text. --single-thread Run in single-threaded mode (no parallel ffmpeg processes). @@ -208,6 +287,15 @@ Options: --debug Show debug overlay during face tracking. + -p:= Set a custom parameter for the object detector. + Example: -p:confidence=0.5 + + Tracking splitter defaults: + DropoutToleranceFrames = 20; + EmaFactor = 0.65; + CameraEasing = 0.03; + LostFreezeFrames = 60; + Passthrough: Anything after -- is passed directly to ffmpeg. diff --git a/TrackingSplitter.cs b/TrackingSplitter.cs index 3030c24..1d07191 100644 --- a/TrackingSplitter.cs +++ b/TrackingSplitter.cs @@ -16,8 +16,9 @@ public class TrackingSplitter : LoggingBase, ISegmentProcessor, IDisposable private readonly bool _plainText; private readonly IObjectDetector _detector; + private readonly CommandLine _cmd; - public TrackingSplitter(int segmentNo, int cropWidth, int cropHeight, bool debugOverlay, bool plainText, IObjectDetector detector) + public TrackingSplitter(int segmentNo, int cropWidth, int cropHeight, bool debugOverlay, bool plainText, IObjectDetector detector, CommandLine cmd) : base(segmentNo) { _segmentNo = segmentNo; @@ -26,6 +27,7 @@ public class TrackingSplitter : LoggingBase, ISegmentProcessor, IDisposable _debugOverlay = debugOverlay; _plainText = plainText; _detector = detector; + _cmd = cmd; } public void Dispose() @@ -83,7 +85,8 @@ public class TrackingSplitter : LoggingBase, ISegmentProcessor, IDisposable videoHeight, originalCropWidth, originalCropHeight, - kalman + kalman, + _cmd ); var startTime = DateTime.UtcNow; diff --git a/splitter.cs b/splitter.cs index 8760bfa..881e966 100644 --- a/splitter.cs +++ b/splitter.cs @@ -87,7 +87,7 @@ static class Program "body" => new YoloOnnxObjectDetector(), _ => throw new InvalidOperationException($"Unknown detector: {detect}") }; - return new TrackingSplitter(i, crop.Value.width, crop.Value.height, debug, cmd.PlainText, detector); + return new TrackingSplitter(i, crop.Value.width, crop.Value.height, debug, cmd.PlainText, detector, cmd); }; } else