libsurvive

repository·master·Indexed 21 days ago

https://github.com/collabora/libsurvive

An open-source set of tools and libraries for 6 DOF tracking on Lighthouse and Vive-based systems. It provides low-level and high-level APIs, as well as bindings for Python (pysurvive) and C#. The project includes CNKalman, a numerically stable implementation of Linear, Extended, and Iterated Extended Kalman Filters with Python-based code generation for analytical Jacobians, and CNMatrix, a C interface for various matrix backends optimized for embedded systems.

Tokens
12.3K
Snippets
27
Records
49
Agent score
72%

What's inside libsurvive

  1. Overview of CNMatrix

    master

    CNMatrix is a library that provides a consistent C interface to various matrix backends. It is designed to be more ergonomic than raw LAPACK or BLAS calls while maintaining performance for medium to large matrices.

    Key characteristics include:

    • Cross-platform compatibility: Designed to work across different platforms.
    • Embedded friendly: Optimized for low-latency embedded systems by consistently attempting to avoid heap allocations.
    • C-centric: While usable in C++, it is specifically optimized for C codebases. For C++ projects, using Eigen directly is generally recommended over CNMatrix.
  2. Overview of the libsurvive Static Library Project

    master
    This project is provided as a static library structure generated via AppWizard. It is designed to be integrated into Visual C++ environments. The project contains the necessary configuration files for Visual Studio to manage the library, but does not include pre-written source files; instead, it provides the project scaffolding for developers to implement their own logic or integrate the libsurvive components.
  3. Features of CNKalman

    master

    CNKalman is a low-level, numerically stable, and portable implementation of Kalman filters. Key features include:

    • Filter Types: Support for Linear Kalman Filters, Extended Kalman Filters (EKF), and Iterated Extended Kalman Filters (IEKF).
    • Advanced Models: Support for error-state Kalman filters and adaptive measurement covariance.
    • Jacobian Support: Built-in support for numerical-based Jacobians with an option to debug user-provided analytical Jacobians against numerical results.
    • Efficiency: Minimal heap allocations and support for multiple measurement models per filter that can be integrated at varying frequencies.
    • Developer Experience: C++ bindings for object-oriented applications and automatic code generation for analytical Jacobians via Python/SymEngine.
  4. Understand the Iterative Extended Kalman Filter (EKF) state and measurements

    master

    The EKF models tracked objects using a 21-dimensional state space.

    Default State Space:

    • Pose (Vec3 + Quaternion)
    • Velocity Estimate (Vec3 + Axis-angle)
    • Acceleration
    • Acceleration Scale
    • IMU rotational correction

    Measurement Models:

    • IMU: Uses Gyroscope and Accelerometer data.
    • Light Data: Processed in batches for efficiency. It uses an iterative approach (including line search scaling and Gauss-Newton steps) to ensure the linearization assumptions do not destabilize the filter.
    • Poser Output: Provides 6DoF + a covariance matrix.

    Note: While the system can run with few or no poser outputs after the initial scene solve (relying on light-based odometry), the tracking is more robust when poser outputs are available.

  5. Calibrate libsurvive devices

    master

    Calibration establishes the relationship between lighthouses and tracked objects.

    Key Concepts:

    • Automatic Calibration: On the first run, libsurvive communicates with lighthouses to determine their positions. This can take up to ten seconds.
    • Continuous Integration: The system continuously integrates object data while objects are stationary. Lighthouses may shift slightly during this process until a lock is achieved.
    • Persistence: Calibration data is saved to config.json in XDG_CONFIG_HOME/libsurvive.
    • Handling Moved Lighthouses: If a calibrated lighthouse is moved, the calibration becomes invalid. You must force a recalibration.

    Tasks:

    • Force Recalibration: Use the --force-calibrate flag with any libsurvive tool or manually delete the config.json file.
    • Large Space Calibration: If you cannot see all lighthouses from one spot, calibrate a few lighthouses first. Then, move a tracked object into the field of view of an uncalibrated lighthouse while keeping it in view of at least one calibrated lighthouse. Set the object down so it remains stationary to allow the remaining lighthouses to calibrate.
    # Example of forcing calibration via CLI
    ./bin/survive-cli --force-calibrate
  6. Understand the Visual C++ project files

    master

    The project includes the following key files for Visual Studio integration:

    • libsurvive.vcxproj: The main Visual C++ project file. It defines the platforms, configurations, and project features selected during the AppWizard generation process.
    • libsurvive.vcxproj.filters: A filters file used by the IDE to organize files into logical groups (e.g., grouping .cpp files under a "Source Files" node).
  7. How the Poser provides initial tracking and updates

    master

    The Poser provides 6DoF updates to the Kalman filter to assist in bootstrapping and improving precision. It uses two main methods depending on the tracking state:

    1. MPFit (Main Solver): Uses the MPFit C library for non-linear least squares via Levenberg-Marquardt optimization. It optimizes in axis-angle space and uses accelerometer data when stationary to establish an 'up' direction. It is used when the system is sufficiently overdetermined.
    2. Barycentric SVD (Seed Poser): A fallback solver used during startup or when tracking is lost. It is mathematically similar to the Efficient Perspective-n-Point (EPnP) algorithm but adapted for lighthouse plane equations. It is more expensive to run and less resilient to noise than MPFit.

    Initial Estimate Logic: The solver attempts to use the most recent solution as a starting point. If no recent solution exists or it is stale, the Barycentric SVD seed poser is used.

  8. How the libsurvive tracking pipeline works

    master

    libsurvive follows a layered architecture to transform raw sensor data into stable tracking poses:

    1. Driver Layer: Driver plugins (like the Vive driver) poll raw light, configuration, and IMU data via USB.
    2. Data Processing: Raw data is disambiguated into angle data, OOTX configuration/calibration data is collected, and stray light/reflections are filtered out using outlier removal based on standard deviation.
    3. Poser & Kalman Filter:
      • A Poser (e.g., MPFit or Barycentric SVD) solves for the initial scene and lighthouse positions, then provides 6DoF poses as measurements.
      • An Iterative Extended Kalman Filter (EKF) integrates IMU data, processed light data, and poser outputs to maintain a stable state.
    4. Output: Once the Kalman filter's covariance matrix trace is sufficiently low, the system emits the position and velocity of the object via user-registerable callbacks.
  9. How posers work in libsurvive

    master

    Posers are specialized modules in libsurvive used to solve for spatial positions based on various input signals. They serve two primary roles:

    1. Lighthouse Localization: Solving for the position of lighthouses when their position is unknown.
    2. Object Tracking: Solving for the position of objects within a scene when lighthouse positions are already known.

    Posers receive different types of input signals (such as light data, full scene data, or IMU data) and output pose information to the system.

  10. Use the Vive driver for SteamVR device interoperability

    master

    The Vive driver is the primary driver for interacting with consumer SteamVR devices. It uses libusb to provide cross-platform support (Linux, Windows, Android) and handles:

    • Protocol Deciphering: Polling devices and interpreting USB protocols.
    • Metadata Retrieval: Performing feature requests to get JSON-encoded configuration data required for tracking.
    • Input Handling: Capturing controller/input events. You can define custom event handlers to process:
      • Digital button presses.
      • Analog joystick/touchpad input.
      • Battery state.
      • Finger proximity.
  11. Install libsurvive on Debian

    master

    To build and install libsurvive from source on a Debian-based system, follow these steps:

    1. Clone the repository:
      git clone https://github.com/cntools/libsurvive.git
      cd libsurvive
    2. Install udev rules to allow device access without root:
      sudo cp ./useful_files/81-vive.rules /etc/udev/rules.d/
      sudo udevadm control --reload-rules && sudo udevadm trigger
    3. Install dependencies and build:
      sudo apt update && sudo apt install build-essential zlib1g-dev libx11-dev libusb-1.0-0-dev freeglut3-dev liblapacke-dev libopenblas-dev libatlas-base-dev cmake
      make
    4. Run the CLI to calibrate and display your setup:
      ./bin/survive-cli
    git clone https://github.com/cntools/libsurvive.git
    cd libsurvive
    sudo cp ./useful_files/81-vive.rules /etc/udev/rules.d/
    sudo udevadm control --reload-rules && sudo udevadm trigger
    sudo apt update && sudo apt install build-essential zlib1g-dev libx11-dev libusb-1.0-0-dev freeglut3-dev liblapacke-dev libopenblas-dev libatlas-base-dev cmake
    make
    ./bin/survive-cli
  12. Implement a Kalman Filter using CNKalman in C

    master

    To use CNKalman in a C application, you must define three callback functions that describe your system's dynamics and measurement models, then initialize the state and measurement models.

    Required Callbacks

    1. kalman_transition_model_fn: Defines the next state $x_1$ and the transition matrix $F$ based on a time delta dt.
    2. kalman_process_noise_fn: Defines the process covariance matrix $Q$.
    3. kalman_measurement_model_fn: Defines the residuals $y$ and the Jacobian of the measurement function $H_k$. This function should return true if the Jacobian and evaluation were valid.

    Workflow

    1. Initialize the state using cnkalman_state_init.
    2. (Optional) Set kalman_state.transition_jacobian_mode to a numerical mode (e.g., cnkalman_jacobian_mode_two_sided) to use numerical Jacobians instead of analytical ones.
    3. Initialize a measurement model using cnkalman_meas_model_init.
    4. (Optional) Set kalman_meas_model.meas_jacobian_mode for numerical measurement Jacobians.
    5. Perform the prediction and update step using cnkalman_meas_model_predict_update.
    #include <cnkalman/kalman.h>
    #include <stdio.h>
    
    static inline void kalman_transition_model_fn(FLT dt, const struct cnkalman_state_s *k, const struct CnMat *x0, 
    struct CnMat *x1, struct CnMat *F) {
        // Logic to fill in the next state x1 and the associated transition matrix F
    }
    
    static inline void kalman_process_noise_fn(void *user, FLT dt, const struct CnMat *x, struct CnMat *Q) {
        // Logic to fill in the process covariance Q
    }
    
    static inline bool kalman_measurement_model_fn(void *user, const struct CnMat *Z, const struct CnMat *x_t, 
    struct CnMat *y, struct CnMat *H_k) {
        // Logic to fill in the residuals `y`, and the jacobian of the predicted measurement function `h`
        return true; 
    }
    
    int main() {
        int state_cnt = 1;
        cnkalman_state_t kalman_state = { 0 };
        cnkalman_state_init(&kalman_state, state_cnt, kalman_transition_model_fn, kalman_process_noise_fn, 0, 0);
        
        cnkalman_meas_model_t kalman_meas_model = { 0 };
        cnkalman_meas_model_init(&kalman_state, "Example Measurement", &kalman_meas_model, kalman_measurement_model_fn);
        
        CnMat Z, R;
        // Logic to fill in measurement matrix Z and measurement covariance matrix R
        cnkalman_meas_model_predict_update(1, &kalman_meas_model, 0, &Z, &R);
        
        printf("Output:%f\n", cn_as_vector(&kalman_state.state)[0]);
        return 0;
    }