Command line switches for changing various internal parameters.

This commit is contained in:
Alexander Shabarshov 2026-05-12 06:55:10 +01:00
parent 2b412694fb
commit a6b9d9c069
4 changed files with 136 additions and 35 deletions

View File

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

View File

@ -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<string, string> 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<T>(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: "<x>:<y>"
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.01.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=<name> Object detector to use for tracking.
Values: face (UltraFace), body (YoloOnnx, default), none (no tracking, just a center)
--gravitate=<x:y> 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:<name>=<value> 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.

View File

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

View File

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