Filters process raw tracking data to reduce noise, smooth movement, and improve tracking quality. OpenTrack includes several filter types optimized for different use cases.
Filter System Overview
Filters implement the IFilter interface:
struct OTR_API_EXPORT IFilter : module_status_mixin {
IFilter ();
// Perform filtering step
virtual void filter ( const double * input , double * output ) = 0 ;
// Optionally reset filter state when centering
virtual void center () {}
};
Module Selection
struct module_settings {
bundle b { make_bundle ( "modules" ) };
value < QString > filter_dll { b, "filter-dll" , "accela" };
};
Available Filters
Accela
EWMA
Hamilton
None
Accela Filter (Recommended) Adaptive smoothing based on head velocity. Provides strong smoothing at rest and minimal lag during fast movements. Best for : General gaming and simulationImplementation :struct accela : IFilter {
void filter ( const double* input , double * output ) override ;
void center () override { first_run = true ; }
spline spline_rot; // Rotation smoothing curve
spline spline_pos; // Translation smoothing curve
private:
double last_output [ 6 ] {};
double deltas [ 6 ] {};
Timer t;
bool first_run = true ;
};
How it works :
Calculate velocity (change per frame)
Map velocity through spline curve to get smoothing factor
Apply exponential smoothing based on factor
Advantages :
Strong smoothing at rest (reduces jitter)
Minimal lag during fast movements
Separate curves for rotation and translation
Highly configurable
Exponentially Weighted Moving Average Simple low-pass filter with constant smoothing. Best for : Quick setup, moderate smoothingConfiguration :// Alpha factor: 0.0 (max smooth) to 1.0 (no smooth)
// Typical value: 0.3-0.7
Algorithm :output = alpha * input + ( 1 - alpha) * prev_output;
Advantages :
Simple and predictable
Low CPU usage
Easy to tune
Disadvantages :
Fixed lag regardless of movement speed
Trade-off between smoothness and responsiveness
Hamilton Kalman Filter Uses quaternions for rotation and Kalman filtering for optimal estimation. Best for : High precision applications, VRImplementation :struct hamilton : IFilter {
void filter ( const double* input , double * output ) override ;
private:
dquat rot_state; // Rotation state
vec3 pos_state; // Position state
// Kalman filter matrices
};
Advantages :
Mathematically optimal for Gaussian noise
Excellent for rotation (no gimbal lock)
Good prediction of position
Disadvantages :
More complex to configure
Higher CPU usage
Requires understanding of Kalman filter parameters
No Filter Pass data through unchanged. Best for :
High-quality trackers with low noise
Testing and debugging
When game/application has built-in filtering
Filter Processing Pipeline
Filters are applied in the tracking pipeline:
void pipeline :: logic () {
// ... get tracker data ...
// Apply filter
if ( libs . pFilter ) {
Pose tmp (value);
libs . pFilter -> filter (value, tmp);
value = tmp;
}
logger . write_pose (value); // "filtered"
// ... continue processing ...
}
Input
Receive corrected pose data (after camera offset and centering): // input[0-5]: TX, TY, TZ, Yaw, Pitch, Roll
Filter Processing
Filter processes each frame: void filter ( const double * input , double * output ) {
const double dt = timer . elapsed_seconds ();
timer . start ();
for ( int i = 0 ; i < 6 ; i ++ ) {
output [i] = process_axis ( input [i], i, dt);
}
}
Filters must track their own timing using a timer. Frame rate is not guaranteed to be constant.
State Management
Filters maintain internal state: private:
double last_output [ 6 ] {}; // Previous output
double velocity [ 6 ] {}; // Current velocity
Timer t; // Frame timing
bool first_run = true ; // Skip first frame
Center Reset
When user centers, filter can reset: void center () override {
first_run = true ;
// Clear accumulated state
memset (last_output, 0 , sizeof (last_output));
}
Accela Filter Configuration
The Accela filter is the most powerful and commonly used:
Smoothing Curves
Accela uses two spline curves to map velocity to smoothing:
spline spline_rot; // Rotation axes (Yaw, Pitch, Roll)
spline spline_pos; // Translation axes (X, Y, Z)
Curve Design
X-axis: Velocity
Input velocity in degrees/second (rotation) or cm/second (translation)
0 : At rest (maximum smoothing)
50-100 : Moderate movement
200+ : Fast movement (minimal smoothing)
Y-axis: Smoothing Factor
Output smoothing strength:
0.0 : No smoothing (instant response)
0.5 : Moderate smoothing
0.95 : Strong smoothing (slow response)
Typical Curve
Velocity (°/s) → Smoothing Factor
─────────────────────────────────
0 → 0.90 (strong smooth)
50 → 0.70 (moderate)
100 → 0.40 (light)
200 → 0.10 (minimal)
This provides strong smoothing when still, but reduces lag during fast movements.
Rotation vs Translation
Rotation Curve
Translation Curve
Typically needs less aggressive smoothing: // Example rotation curve points
{ 0 , 0.85 }, // At rest: strong smooth
{ 45 , 0.60 }, // Slow turn
{ 90 , 0.35 }, // Normal turn
{ 180 , 0.15 }, // Fast turn
{ 300 , 0.05 } // Very fast: minimal lag
Head rotation is generally smoother than position tracking. Often needs more aggressive smoothing: // Example translation curve points
{ 0 , 0.92 }, // At rest: very strong smooth
{ 2 , 0.75 }, // Slow movement (2 cm/s)
{ 5 , 0.50 }, // Normal movement
{ 10 , 0.25 }, // Fast movement
{ 20 , 0.10 } // Very fast
Position tracking typically has more noise than rotation.
High smoothing values (>0.95) can cause significant lag. Balance smoothness with responsiveness based on your tracker quality.
EWMA Filter Configuration
Simpler than Accela, with fixed smoothing:
// Configuration
struct ewma_settings {
value < slider_value > alpha_rot; // Rotation smoothing
value < slider_value > alpha_pos; // Position smoothing
};
// Filter implementation
void ewma :: filter ( const double * input , double * output ) {
const double dt = t . elapsed_seconds ();
t . start ();
for ( int i = 0 ; i < 6 ; i ++ ) {
const double alpha = (i < 3 ) ? s . alpha_pos : s . alpha_rot ;
output [i] = alpha * input [i] + ( 1 - alpha) * last_output [i];
last_output [i] = output [i];
}
}
Tuning EWMA
Alpha = 0.1 : Maximum smoothing, high lag
Alpha = 0.5 : Balanced
Alpha = 0.9 : Minimal smoothing, minimal lag
Start with 0.5 and adjust. Lower values = smoother but more lag.
Filter Dialog Interface
Filters can provide live configuration:
struct OTR_API_EXPORT IFilterDialog : public BaseDialog {
virtual void register_filter ( IFilter * filter ) = 0 ;
virtual void unregister_filter () = 0 ;
};
Live Updates
class dialog_accela : public IFilterDialog {
void register_filter ( IFilter * f ) override {
filter = static_cast < accela *> (f);
// Can now access filter splines for live preview
}
void unregister_filter () override {
filter = nullptr ;
}
private:
accela * filter = nullptr ;
};
Custom Filter Development
Implement IFilter
class MyFilter : public IFilter {
public:
module_status initialize () override {
return status_ok ();
}
void filter ( const double * input , double * output ) override {
// Your filtering logic
}
void center () override {
// Reset filter state
}
};
Handle Timing
private:
Timer t;
bool first_run = true ;
void filter ( const double * input , double * output ) override {
if (first_run) {
first_run = false ;
memcpy (output, input, sizeof ( double ) * 6 );
return ;
}
const double dt = t . elapsed_seconds ();
t . start ();
// Use dt for time-based filtering
}
Register Plugin
class MyFilterMeta : public Metadata {
QString name () override { return "My Filter" ; }
QIcon icon () override { return QIcon ( ":/filter.png" ); }
};
OPENTRACK_DECLARE_FILTER (
MyFilter,
MyFilterDialog,
MyFilterMeta
)
Best Practices
Do’s
Use time-based filtering - Account for variable frame rate with dt
Handle first frame - Skip or initialize carefully on first run
Reset on center - Clear accumulated state when user centers
Separate rotation/translation - Different axes need different treatment
Don’ts
Don’t assume constant frame rate - Pipeline runs at ~250Hz but varies
Don’t introduce NaN/Infinity - Check for invalid math operations
Don’t block - Filter runs in tracking thread, keep it fast
Troubleshooting
Reduce smoothing factor (Accela: lower curve values)
Make curve steeper (faster response at higher velocities)
Try EWMA with higher alpha (0.7-0.9)
Check if game has additional filtering
Increase smoothing at low velocities
Check tracker quality and lighting
Try Hamilton filter for better noise handling
Verify tracker is running at stable frame rate
Verify filter is selected in modules
Check filter initialization didn’t fail
Restart tracking after changing filter
Check OpenTrack logs for errors
Different axes feel wrong
Configure separate smoothing per axis: // In custom filter
for ( int i = 0 ; i < 6 ; i ++ ) {
const double factor = smoothing_factors [i];
output [i] = factor * input [i] + ( 1 - factor) * last [i];
}
Filter CPU usage comparison (approximate):
None : 0% overhead
EWMA : <0.1% CPU
Accela : <0.5% CPU (includes spline evaluation)
Hamilton : 1-2% CPU (quaternion and matrix math)
All filters are designed to run efficiently at 250Hz.
Next Steps
Mapping Curves Fine-tune response after filtering
Configuration Advanced tracking settings
Tracker Setup Improve tracking quality at the source