mirror of
https://github.com/unclshura/splitter.git
synced 2026-06-22 00:22:01 +00:00
Command line switches for changing various internal parameters.
This commit is contained in:
parent
2b412694fb
commit
a6b9d9c069
@ -17,49 +17,58 @@ public sealed class CameraController
|
|||||||
private readonly int _cropWidth;
|
private readonly int _cropWidth;
|
||||||
private readonly int _cropHeight;
|
private readonly int _cropHeight;
|
||||||
private readonly KalmanTracker _kalman;
|
private readonly KalmanTracker _kalman;
|
||||||
|
private readonly CommandLine _cmd;
|
||||||
private int _dropoutCounter;
|
private int _dropoutCounter;
|
||||||
|
|
||||||
// --- Dropout tolerance ---
|
// --- Dropout tolerance ---
|
||||||
private const int DropoutToleranceFrames = 20;
|
private int _dropoutToleranceFrames = 20;
|
||||||
private const float EmaFactor = 0.65f; // smoother but responsive
|
private float _emaFactor = 0.65f; // smoother but responsive
|
||||||
private const float CameraEasing = 0.03f; // stronger follow-through
|
private float _cameraEasing = 0.03f; // stronger follow-through
|
||||||
private const int LostFreezeFrames = 60; // 2 seconds at 30 FPS
|
private int _lostFreezeFrames = 60; // 2 seconds at 30 FPS
|
||||||
|
|
||||||
|
|
||||||
private int _lostFrames;
|
private int _lostFrames;
|
||||||
private Point2f _cameraCenter;
|
private Point2f _cameraCenter;
|
||||||
private TrackState _state;
|
private TrackState _state;
|
||||||
private Point2f _smoothedCenter;
|
private Point2f _smoothedCenter;
|
||||||
private Rect? _objectBox;
|
private Rect? _objectBox;
|
||||||
private Point2f? _objectCenter;
|
private Point2f? _objectCenter;
|
||||||
private Rect _roi;
|
private Rect _roi;
|
||||||
|
|
||||||
public CameraController(
|
public CameraController(
|
||||||
int videoWidth,
|
int videoWidth,
|
||||||
int videoHeight,
|
int videoHeight,
|
||||||
int cropWidth,
|
int cropWidth,
|
||||||
int cropHeight,
|
int cropHeight,
|
||||||
KalmanTracker kalman
|
KalmanTracker kalman,
|
||||||
|
CommandLine cmd
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_videoWidth = videoWidth;
|
_videoWidth = videoWidth;
|
||||||
_videoHeight = videoHeight;
|
_videoHeight = videoHeight;
|
||||||
_cropWidth = cropWidth;
|
_cropWidth = cropWidth;
|
||||||
_cropHeight = cropHeight;
|
_cropHeight = cropHeight;
|
||||||
_kalman = kalman;
|
_kalman = kalman;
|
||||||
|
_cmd = cmd;
|
||||||
_cameraCenter = new Point2f(videoWidth / 2f, videoHeight / 2f);
|
_cameraCenter = DefaultCenter;
|
||||||
_state = TrackState.Tracking;
|
_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);
|
_kalman.Reset(_cameraCenter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int LostFrames => _lostFrames;
|
private Point2f DefaultCenter => _cmd.GravitateTo ?? new Point2f(_videoWidth / 2f, _videoHeight / 2f);
|
||||||
public Point2f CameraCenter => _cameraCenter;
|
|
||||||
public TrackState State => _state;
|
public int LostFrames => _lostFrames;
|
||||||
|
public Point2f CameraCenter => _cameraCenter;
|
||||||
|
public TrackState State => _state;
|
||||||
public Point2f SmoothedCenter => _smoothedCenter;
|
public Point2f SmoothedCenter => _smoothedCenter;
|
||||||
public Rect? ObjectBox => _objectBox;
|
public Rect? ObjectBox => _objectBox;
|
||||||
public Point2f? ObjectCenter => _objectCenter;
|
public Point2f? ObjectCenter => _objectCenter;
|
||||||
public Rect Roi => _roi;
|
public Rect Roi => _roi;
|
||||||
|
|
||||||
public void Update((Rect box, Point2f center)? primary)
|
public void Update((Rect box, Point2f center)? primary)
|
||||||
@ -78,7 +87,7 @@ public sealed class CameraController
|
|||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
if (!objectCenter.HasValue)
|
if (!objectCenter.HasValue)
|
||||||
{
|
{
|
||||||
if (_dropoutCounter < DropoutToleranceFrames)
|
if (_dropoutCounter < _dropoutToleranceFrames)
|
||||||
{
|
{
|
||||||
objectCenter = _kalman.LastMeasurement;
|
objectCenter = _kalman.LastMeasurement;
|
||||||
_dropoutCounter++;
|
_dropoutCounter++;
|
||||||
@ -96,7 +105,7 @@ public sealed class CameraController
|
|||||||
{
|
{
|
||||||
_lostFrames++;
|
_lostFrames++;
|
||||||
|
|
||||||
if (_lostFrames <= LostFreezeFrames)
|
if (_lostFrames <= _lostFreezeFrames)
|
||||||
{
|
{
|
||||||
_state = TrackState.LostFreeze;
|
_state = TrackState.LostFreeze;
|
||||||
objectCenter = null;
|
objectCenter = null;
|
||||||
@ -104,7 +113,7 @@ public sealed class CameraController
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
_state = TrackState.LostDrift;
|
_state = TrackState.LostDrift;
|
||||||
objectCenter = new Point2f(_videoWidth / 2f, _videoHeight / 2f);
|
objectCenter = DefaultCenter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -124,13 +133,13 @@ public sealed class CameraController
|
|||||||
// NEW: EMA smoothing
|
// NEW: EMA smoothing
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
smoothedCenter = new Point2f(
|
smoothedCenter = new Point2f(
|
||||||
smoothedCenter.X * (1 - EmaFactor) + _cameraCenter.X * EmaFactor,
|
smoothedCenter.X * (1 - _emaFactor) + _cameraCenter.X * _emaFactor,
|
||||||
smoothedCenter.Y * (1 - EmaFactor) + _cameraCenter.Y * EmaFactor
|
smoothedCenter.Y * (1 - _emaFactor) + _cameraCenter.Y * _emaFactor
|
||||||
);
|
);
|
||||||
|
|
||||||
_cameraCenter = new Point2f(
|
_cameraCenter = new Point2f(
|
||||||
_cameraCenter.X + (smoothedCenter.X - _cameraCenter.X) * CameraEasing,
|
_cameraCenter.X + (smoothedCenter.X - _cameraCenter.X) * _cameraEasing,
|
||||||
_cameraCenter.Y + (smoothedCenter.Y - _cameraCenter.Y) * CameraEasing);
|
_cameraCenter.Y + (smoothedCenter.Y - _cameraCenter.Y) * _cameraEasing);
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (_state == TrackState.LostFreeze)
|
else if (_state == TrackState.LostFreeze)
|
||||||
@ -165,8 +174,9 @@ public sealed class CameraController
|
|||||||
y = Math.Clamp(y, 0, _videoHeight - _cropHeight);
|
y = Math.Clamp(y, 0, _videoHeight - _cropHeight);
|
||||||
|
|
||||||
_roi = new Rect(x, y, _cropWidth, _cropHeight);
|
_roi = new Rect(x, y, _cropWidth, _cropHeight);
|
||||||
|
|
||||||
_smoothedCenter = smoothedCenter;
|
_smoothedCenter = smoothedCenter;
|
||||||
_objectBox = objectBox;
|
_objectBox = objectBox;
|
||||||
_objectCenter = objectCenter;
|
_objectCenter = objectCenter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ public sealed class CommandLine
|
|||||||
public string InputFile { get; private init; }
|
public string InputFile { get; private init; }
|
||||||
public string OutputFolder { get; private init; }
|
public string OutputFolder { get; private init; }
|
||||||
public (int width, int height)? Crop { 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 string? Mask { get; private init; }
|
||||||
public bool Debug { get; private init; }
|
public bool Debug { get; private init; }
|
||||||
public string? Detect { 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 EstimateOnly { get; private init; }
|
||||||
public bool ForceFixed { get; private init; }
|
public bool ForceFixed { get; private init; }
|
||||||
public bool SingleThreaded { get; private init; }
|
public bool SingleThreaded { get; private init; }
|
||||||
|
public Dictionary<string, string> Parameters { get; } = [];
|
||||||
|
|
||||||
public bool IsValid => !string.IsNullOrEmpty(InputFile) && !string.IsNullOrEmpty(OutputFolder);
|
public bool IsValid => !string.IsNullOrEmpty(InputFile) && !string.IsNullOrEmpty(OutputFolder);
|
||||||
|
|
||||||
@ -84,6 +86,11 @@ public sealed class CommandLine
|
|||||||
{
|
{
|
||||||
SingleThreaded = true;
|
SingleThreaded = true;
|
||||||
}
|
}
|
||||||
|
else if (arg.StartsWith("--gravitate="))
|
||||||
|
{
|
||||||
|
var val = arg.Substring("--gravitate=".Length);
|
||||||
|
GravitateTo = ParseGravitate(val);
|
||||||
|
}
|
||||||
else if (arg.StartsWith("--duration="))
|
else if (arg.StartsWith("--duration="))
|
||||||
{
|
{
|
||||||
var dur = arg.Substring("--duration=".Length);
|
var dur = arg.Substring("--duration=".Length);
|
||||||
@ -94,6 +101,17 @@ public sealed class CommandLine
|
|||||||
return;
|
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")
|
else if (arg == "--estimate")
|
||||||
{
|
{
|
||||||
EstimateOnly = true;
|
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.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)
|
private static (int width, int height)? ParseCrop(string v)
|
||||||
{
|
{
|
||||||
// Default vertical Full HD for YouTube Shorts
|
// Default vertical Full HD for YouTube Shorts
|
||||||
@ -201,6 +276,10 @@ 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)
|
||||||
|
|
||||||
|
--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.
|
--text Display log in plain text.
|
||||||
|
|
||||||
--single-thread Run in single-threaded mode (no parallel ffmpeg processes).
|
--single-thread Run in single-threaded mode (no parallel ffmpeg processes).
|
||||||
@ -208,6 +287,15 @@ Options:
|
|||||||
|
|
||||||
--debug Show debug overlay during face tracking.
|
--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:
|
Passthrough:
|
||||||
Anything after -- is passed directly to ffmpeg.
|
Anything after -- is passed directly to ffmpeg.
|
||||||
|
|
||||||
|
|||||||
@ -16,8 +16,9 @@ public class TrackingSplitter : LoggingBase, ISegmentProcessor, IDisposable
|
|||||||
private readonly bool _plainText;
|
private readonly bool _plainText;
|
||||||
|
|
||||||
private readonly IObjectDetector _detector;
|
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)
|
: base(segmentNo)
|
||||||
{
|
{
|
||||||
_segmentNo = segmentNo;
|
_segmentNo = segmentNo;
|
||||||
@ -26,6 +27,7 @@ public class TrackingSplitter : LoggingBase, ISegmentProcessor, IDisposable
|
|||||||
_debugOverlay = debugOverlay;
|
_debugOverlay = debugOverlay;
|
||||||
_plainText = plainText;
|
_plainText = plainText;
|
||||||
_detector = detector;
|
_detector = detector;
|
||||||
|
_cmd = cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@ -83,7 +85,8 @@ public class TrackingSplitter : LoggingBase, ISegmentProcessor, IDisposable
|
|||||||
videoHeight,
|
videoHeight,
|
||||||
originalCropWidth,
|
originalCropWidth,
|
||||||
originalCropHeight,
|
originalCropHeight,
|
||||||
kalman
|
kalman,
|
||||||
|
_cmd
|
||||||
);
|
);
|
||||||
|
|
||||||
var startTime = DateTime.UtcNow;
|
var startTime = DateTime.UtcNow;
|
||||||
|
|||||||
@ -87,7 +87,7 @@ static class Program
|
|||||||
"body" => new YoloOnnxObjectDetector(),
|
"body" => new YoloOnnxObjectDetector(),
|
||||||
_ => throw new InvalidOperationException($"Unknown detector: {detect}")
|
_ => 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
|
else
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user