Skip to content Link Search Menu Expand Document
ModalAI DOCS
Store

Flying with VIO

What is VIO (Visual Inertial Odometry)?

VOXL supports two VIO engines: voxl-qvio-server and voxl-open-vins-server. Only one should be running at a time — disable the other before enabling one, e.g. with voxl-configure-qvio disable.

Configure Parameters

ModalAI’s reference drones User’s Guide should have instructions on how to configure the drone for VIO. If you are building your own setup, you need to configure the following before VIO will work:

Camera and IMU selection for the VIO servers lives in /etc/modalai/vio_cams.conf, which is managed with voxl-configure-vio-cams and can be inspected with voxl-inspect-vio-cams. voxl-configure-qvio itself accepts only the modes disable, factory_enable, and enable — the older factory_enable_imu_* modes are deprecated and will error.

Confirm PX4 is Getting VIO Data

As part of the package installation a voxl-vision-hub systemd service is created which starts the interface automatically on boot. This takes up to about 30 seconds to start. By the time you have plugged in a battery, walked back to your laptop, and opened QGroundControl voxl-vision-hub should be running. Confirm before flight by opening the mavlink inspector and checking that X&Y values are non-zero in the ODOMETRY mavlink telemetry packets.

voxl-vio-mavlink-inspector.png

Alternatively, if your computer running QGroundControl has its volume turned up you should hear it announce the swap from “manual” to “position” mode when you flip the flight mode switch from up to middle. If VIO data is not being transmitted, the system will fall back to altitude mode and you will hear “position mode rejected, altitude flight mode”. In this case, go back to manual flight mode (switch up) and wait a few more seconds for voxl-vision-hub to finish initializing.

If this doesn’t work, go back to Installation Step 1 and reconfirm VIO is working. You can also check the voxl-vision-hub systemd service for indications of what might have gone wrong. If you have changed the Flight Core’s PX4 EKF2 settings, you may need to set the parameters back into VIO mode. To make this easier, we have a parameters file helper located here which are the parameters that ship with the Starling drone.

Specifically the EKF2_EV_CTRL (external vision control) parameter is often overlooked as an important parameter for VIO flight. Note that the older EKF2_AID_MASK parameter no longer exists on PX4 1.14.

Confirm Data is in the Right Coordinate Frame

While looking at the ODOMETRY data in QGroundControl’s Mavlink Inspector, observe the xyz data as you move the quadcopter around. Make sure it aligns with NED coordinate frame. If there is a coordinate system mismatch PX4 will initially take off then very quickly run away out of control.

TODO: Video/pictures

Take off in Position Mode

It is safer to take off in Position Flight Mode than it is to flip to this mode mid-flight from Manual Flight Mode.

Take off and land with the same instructions as listed above but this time with the Flight Mode switch in the middle position. The quadcopter should take off straight up and be much easier to control than in manual mode.

Be ready to flip back to manual mode should anything go wrong. It is safer to flip to manual mode and land than to kill the motors mid-flight. Killing the motors mid-flight may result in the propellers loosening from the reverse-torque and flying straight up off the quadcopter. Only kill the motors after landing and spooling down the motors OR in a serious emergency.

Flip to Offboard Mode

While flying in Position Mode, you may flip the Flight Mode Switch all the way down to enable offboard mode. The quadcopter should immediately fly back to a position 2 meters above wherever VIO initialized. If you plugged in the battery and let the VOXL and PX4 power up at the takeoff location (suggested normal practice) then it should hold a position 2 meters above the takeoff location.

In Offboard Mode the 2 joysticks on the RC controller are ignored and the PX4 follows any commands given to it by VOXL over the UART link. In this case VOXL just sends a static location.

Troubleshooting VIO

For general service debugging — logs, restarts, running in the foreground — see Debugging Services.

Visual Inertial Odometry (VIO) fuses tracked visual features with IMU data to produce dead-reckoning odometry. It depends on two things: visible features to track and low-noise IMU data. Degrade either — low light, featureless surfaces, vibration — and tracking suffers. It is strongly recommended to stay comfortable switching between manual and position control with a backup R/C while learning where VIO works well.

On current SDKs, voxl-open-vins-server is the actively developed VIO and the recommended starting point — it supports multiple cameras and is continuously improved. voxl-qvio-server (single camera, closed source) is no longer being developed. The tools below cover both.

Triage Ladder

Work down this list in order — it resolves the vast majority of “VIO doesn’t work” reports:

  1. Are the services running? voxl-inspect-services — you need voxl-camera-server, your VIO service (voxl-open-vins-server or voxl-qvio-server), voxl-vision-hub (it sends the MAVLink odometry to PX4), and voxl-portal for debugging.
  2. Is the tracking camera detected? voxl-camera-server -l lists detected cameras. Missing camera → see Debugging Camera Issues.
  3. Look at the image. In voxl-portal, check the tracking camera view: lens cover removed? Lens clean and in focus? Scene has texture with features both near and far?
  4. Do the hand-held test (below). Features locked to the world and low drift → VIO itself is fine.
  5. Check the pose reaching PX4: voxl-inspect-pose vvhub_body_wrt_local shows the pose after extrinsics are applied — it should match PX4’s local position. Good VIO but wrong pose here → extrinsics problem (Configure Extrinsics).
  6. Check what PX4 reports: in QGC’s MAVLink Inspector, watch LOCAL_POSITION_NED. Z updating but X/Y stuck at zero indicates an EKF2 configuration problem, not a VIO problem (see below).
  7. PX4 parameters must match your PX4 version. Load the airframe + VIO params from the matching version directory of voxl-px4-params — params from the wrong PX4 version cause subtle breakage.
  8. SDK coherence. Mixed package versions (one service upgraded, others not) break the pipeline. Prefer flashing a complete SDK release over upgrading individual packages.

The Hand-Held Test

A reliable check that VIO itself is healthy — repeat it any time you change the VIO configuration:

  1. Props off. Power up with VIO running and open the VIO tab in voxl-portal.
  2. Slowly pick the vehicle up and move it around — half a meter is enough. Watch the feature markers (the qvio_overlay stream for QVIO, or the VIO tab’s plot for Open-VINS): features should stay locked onto the same physical points as the vehicle moves. Features that appear and disappear or slide off their targets usually mean incorrect camera-to-IMU extrinsics.
  3. Carry the vehicle around the room and return it to the exact starting position and orientation. Reported drift should be a few percent of the distance traveled, with very small orientation drift.

Odometry X and Y Read Zero

The most common VIO complaint. First, the decision rule: if voxl-inspect-qvio / voxl-inspect-vins shows valid, moving position data, VIO is working — no amount of camera recalibration will help, and the problem is on the PX4 side. Then:

  • If VIO output itself is zero or frozen: VIO hasn’t initialized or has failed. Try voxl-reset-qvio (or voxl-reset-vins), restart the service, or power-cycle, and check the error codes below.
  • If VIO is good but LOCAL_POSITION_NED shows Z updating while X/Y stay zero: EKF2 is not fusing the vision inputs — check the EKF2 external-vision settings in your version-matched param file from voxl-px4-params (e.g. ekf2_indoor_vio.params). (On old PX4 versions this was the EKF2_AID_MASK parameter, value 280 for all-vision aiding; current PX4 uses the split EKF2_*_CTRL scheme in the param files.)
  • PX4 may be rejecting position mode because it wants a magnetometer. For VIO-only (indoor) flight on PX4 1.12+:
SYS_HAS_MAG	0
EKF2_MAG_TYPE	5

and for a vehicle with no GPS at all, also SYS_HAS_GPS 0. A connected GPS/mag unit fighting VIO can also corrupt the local position — for a quick experiment, disconnect it.

  • Old QGC versions can also display stale settings — update QGC before trusting its inspector.

Verifying Camera Calibration

  • Calibration files live in /data/modalai/ (opencv_<pipe>_intrinsics.yml / _extrinsics.yml). Each stores its own reprojection_error — the calibrator accepts ≤ 0.75 px (pinhole) or ≤ 0.60 px (fisheye); if your stored error is near the limit, recalibrate. See Calibrate Cameras.
  • Resolution must match: a calibration captured at 640x480 is invalid for a camera now running 1280x800. If tracking is bad after a camera or config change, check the resolution inside the cal file.
  • Swapping camera hardware (different sensor or module) always requires recalibration.
  • Tracking cameras are calibrated at the factory; hires (IMX412-class) cameras are not. ToF sensors carry intrinsics on the sensor itself. Extrinsics ship from CAD models — accurate, but not individually calibrated.
  • Lost your factory calibration? ModalAI keeps production calibration backups — ask on the forum with the serial number from the white sticker on the board.

IMU Checks

  • The VIO IMU is the apps-processor IMU (imu_apps), not the one PX4 flies on — both matter, but VIO problems point at the former.
  • Recalibrate with voxl-calibrate-imu (or via voxl-portal), then confirm gravity magnitude reads ≈9.81 m/s² in voxl-inspect-imu.
  • IMU temperature calibration is optional and can hurt if it was captured poorly — when debugging VIO drift, try removing the IMU temperature calibration file from /data/modalai/.
  • Vibration degrades VIO significantly. Evaluate with voxl-inspect-vibration and PX4 log analysis; improve mechanical damping before chasing software.

Environment and Optics

  • Feature-rich scenes with texture at both near and far range work best; far-only features (e.g. a camera pointed out a window) give orientation but poor position.
  • Difficult scenes: plain cement, waving grass, low light. Diffuse self-illumination on the airframe helps at night — avoid hot spots and glare.
  • Tracking camera focus: for the OV7251 fisheye, everything beyond ~15 cm should be in focus — test with two high-contrast objects at 15 cm and 1 m. The lens is plastic: clean gently with a microfiber cloth.
  • Anything added to the airframe that intrudes into the camera view (legs, payloads, wiring) hurts tracking — mask it out (below) or move it.

Masking Propellers and Landing Gear (QVIO)

voxl-qvio-server can ignore image regions via a mask image, set with mask_file_path in /etc/modalai/voxl-qvio-server.conf:

  • Format: 160x120 PGM (P5), where white (255) marks regions to block from feature detection and black (0) allows them. A known-good example: mask_example_160x120_fixed.pgm.
  • The PGM header must be exactly P5 # 160 120 255 style with the comment marker — some editors write a header the underlying tracker rejects silently.
  • The path is resolved relative to the service’s working directory — use an absolute path.
  • Sanity check: load a half-white/half-black mask and confirm new features only appear in the allowed half. (Features that begin in the allowed region may still be tracked into the masked region — that is expected.)

VIO States, Quality, and Error Codes

voxl-inspect-qvio, voxl-inspect-vins, and the portal all report three key fields from the VIO pipes:

StateFAIL (0), INIT (1), OKAY (2). INIT means the estimator is still initializing; QVIO requires the vehicle stationary to initialize (and hard-resets every 3 seconds until it succeeds), while Open-VINS initializes dynamically at any attitude within tens of milliseconds.

Quality — unitless, higher is better, −1 means failed. For QVIO it reflects how much of the image grid is covered by well-constrained features (100% ≈ half the grid covered); brief dips during fast motion are normal. Open-VINS applies hysteresis so quality transitions between good/bad states rather than flickering.

Error codes — a hex bitfield, decoded by the inspect tools. Primarily meaningful when state is FAIL; the most common:

BitNameMeaning
0x1COV_ERRORcovariance blew up — usually vibration or bad calibration
0x2IMU_OOBIMU exceeded measurement range (hard impact); in Open-VINS also raised for sustained fast spin-in-place
0x4IMU_BWIMU bandwidth too low
0x8NOT_STATIONARYnot stationary at initialization (QVIO); in Open-VINS repurposed for “quality below threshold too long”
0x10NO_FEATURESno trackable features for too long
0x20CONSTRAINT_ERRORfeatures gave insufficient geometric constraints
0x40FEATURE_ADD_ERRORfailed to add new features
0x80 / 0x100VEL_INST_CERT / VEL_WINDOW_CERTvelocity uncertainty exceeded limits (instant / windowed)
0x400 / 0x2000DROPPED_IMU / DROPPED_CAMdropped sensor samples — check system load
0x800BAD_CAM_CALintrinsic calibration questionable — recalibrate
0x1000LOW_FEATURESnot enough good features to initialize
0x8000BAD_TIMESTAMPsensor timestamps inconsistent
0x10000 / 0x20000IMU_MISSING / CAM_MISSINGsensor pipe disconnected — is the server running?
0x40000 / 0x80000CAM_BAD_RES / CAM_BAD_FORMATcamera resolution/format not supported by the VIO config
0x200000STALLEDframe processing stalled

Both servers auto-reset on serious estimator errors (configurable via the en_auto_reset and auto_reset_* fields in their config files); transient transport warnings like dropped frames do not trigger resets.

Debugging Tools

VIO tab in voxl-portal

  • The VIO tab in voxl-portal shows the live trajectory and feature points.
    • With voxl-qvio-server, the qvio_overlay camera stream draws tracked features over the tracking image, plus quality, position, feature count, and decoded error codes.
    • voxl-open-vins-server does not publish an overlay stream (its pipes are ov, ov_extended, and ov_status) — use the VIO tab’s plot, optionally with the raw tracking feed alongside.
  • Command-line: voxl-inspect-qvio for QVIO, voxl-inspect-vins for Open-VINS, and voxl-inspect-vio for the gravity-aligned output voxl-vision-hub sends to PX4.
  • Run the servers in the foreground for verbose output: voxl-qvio-server -d (also -p position prints, timing flags -i/-t/-u), voxl-open-vins-server -d.
  • Check MAVLink actually flowing with voxl-inspect-mavlink on mavlink_onboard, and PX4’s view with px4-listener vehicle_odometry.
  • AprilTag relocalization can remove accumulated drift in mapped environments.

Logging VIO Data

Use the following to log VIO data for offline analysis:

voxl-logger --preset_odometry

For Open-VINS, voxl-logger -b -v ov captures a replayable log for offline evaluation with voxl-evaluate-vins.

“VOXL rebooting PX4”

If voxl-vision-hub reports “VOXL rebooting PX4”, this is likely due to poor camera calibration. Follow the instructions here to calibrate the tracking sensor.

ArduPilot VIO

Support for ArduPilot is progressing! Here is the ArduPilot pull request for VOXL-compatible Visual Inertial Odometry support Github