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:
- Configure the Extrinsic Parameters
- Configure PX4’s EKF2 parameters for vision-enabled flight. There are EKF2 helpers in Gitlab, here is a link to the PX4 v1.14 EKF2 Helpers Directory
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.

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:
- 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. - Is the tracking camera detected?
voxl-camera-server -llists detected cameras. Missing camera → see Debugging Camera Issues. - 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?
- Do the hand-held test (below). Features locked to the world and low drift → VIO itself is fine.
- Check the pose reaching PX4:
voxl-inspect-pose vvhub_body_wrt_localshows the pose after extrinsics are applied — it should match PX4’s local position. Good VIO but wrong pose here → extrinsics problem (Configure Extrinsics). - 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). - 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.
- 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:
- Props off. Power up with VIO running and open the VIO tab in voxl-portal.
- 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.
- 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(orvoxl-reset-vins), restart the service, or power-cycle, and check the error codes below. - If VIO is good but
LOCAL_POSITION_NEDshows 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 theEKF2_AID_MASKparameter, value 280 for all-vision aiding; current PX4 uses the splitEKF2_*_CTRLscheme 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 ownreprojection_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² invoxl-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 255style 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:
State — FAIL (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:
| Bit | Name | Meaning |
|---|---|---|
| 0x1 | COV_ERROR | covariance blew up — usually vibration or bad calibration |
| 0x2 | IMU_OOB | IMU exceeded measurement range (hard impact); in Open-VINS also raised for sustained fast spin-in-place |
| 0x4 | IMU_BW | IMU bandwidth too low |
| 0x8 | NOT_STATIONARY | not stationary at initialization (QVIO); in Open-VINS repurposed for “quality below threshold too long” |
| 0x10 | NO_FEATURES | no trackable features for too long |
| 0x20 | CONSTRAINT_ERROR | features gave insufficient geometric constraints |
| 0x40 | FEATURE_ADD_ERROR | failed to add new features |
| 0x80 / 0x100 | VEL_INST_CERT / VEL_WINDOW_CERT | velocity uncertainty exceeded limits (instant / windowed) |
| 0x400 / 0x2000 | DROPPED_IMU / DROPPED_CAM | dropped sensor samples — check system load |
| 0x800 | BAD_CAM_CAL | intrinsic calibration questionable — recalibrate |
| 0x1000 | LOW_FEATURES | not enough good features to initialize |
| 0x8000 | BAD_TIMESTAMP | sensor timestamps inconsistent |
| 0x10000 / 0x20000 | IMU_MISSING / CAM_MISSING | sensor pipe disconnected — is the server running? |
| 0x40000 / 0x80000 | CAM_BAD_RES / CAM_BAD_FORMAT | camera resolution/format not supported by the VIO config |
| 0x200000 | STALLED | frame 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

- 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-vinsfor 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-pposition 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 withpx4-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