A Unity project that implements AprilTag detection and FRC field localization for Meta Quest VR headsets using the Meta Passthrough Camera API. This project enables real-time marker detection, spatial anchor placement, and precise headset localization on FIRST Robotics Competition fields.
AprilTagUnity combines the power of:
- AprilTag detection using locally integrated AprilTag library (originally from Keijiro Takahashi)
- Meta Passthrough Camera API for accessing the Quest's camera feed
- Spatial Anchors for persistent, drift-free localization
- FRC Field Localization using official WPILib field layouts
- Unity XR for VR/MR application development
The project provides a seamless way to detect and track AprilTag markers in real-time within VR environments, enabling applications like:
- FIRST Robotics field localization - Track headset position in field coordinates
- Spatial tracking and calibration using Quest's inside-out tracking
- Mixed reality interactions with persistent anchors
- Augmented reality overlays on physical fields
- 🎯 Real-time AprilTag Detection: Detect multiple Tag36h11 markers simultaneously
- 📱 Meta Quest Integration: Works with Quest 2, Quest Pro, and Quest 3
- 🔧 Easy Setup: Automatic configuration with setup helper scripts
- 🎨 Visual Feedback: Configurable visualization for detected tags
- ⚡ Performance Optimized: GPU preprocessing, configurable detection frequency and decimation
- 🔍 Reflection-based Integration: No compile-time dependencies on Meta's Passthrough Camera API
- ⚓ Automatic Anchor Placement: Creates persistent OVRSpatialAnchors at detected tag locations
- 🎯 Confidence-based Placement: Only places anchors after stable, high-confidence detections
- 💾 Persistent Storage: Anchors survive app restarts and device reboots
- 🚫 Keep-out Zones: Prevents duplicate anchor placement near existing anchors
- 🔄 Anchor Loading: Automatically loads previously saved anchors on startup
- 🏟️ Field Coordinate Tracking: Transform headset pose to FRC field coordinates
- 📐 Multi-anchor Alignment: Uses 3+ spatial anchors for robust field alignment
- 🎲 Outlier Rejection: Automatically detects and removes bad anchor data
- ✅ Alignment Validation: RMS error checking ensures high-quality alignment
- 🔄 Continuous Improvement: Refines alignment as more anchors become available
- 💾 Persistent Alignment: Saves alignment across sessions
- 📊 Quality Metrics: Real-time display of alignment error and anchor count
- 🗺️ Official Field Layouts: Supports all WPILib field layouts (2022-2025)
- 🎮 Quest Inside-out Tracking: Leverages Quest's SLAM for drift-free localization
- Meta Quest 2, Quest Pro, or Quest 3 headset
- Android development environment (for building APKs)
- Unity 6000.2.6f2 or later
- Meta XR SDK v78.0.0 or later
- Android SDK with API level 32+
-
Clone the repository
git clone https://github.com/yourusername/AprilTagUnity.git cd AprilTagUnity -
Open in Unity
- Launch Unity Hub
- Click "Add" and select the project folder
- Open the project with Unity 6000.2.6f2 or later
-
Install Dependencies The project uses Unity Package Manager with the following key dependencies:
com.meta.xr.sdk.all(v78.0.0) - Meta XR SDKcom.unity.xr.openxr(v1.13.2) - OpenXR support
Note: The AprilTag library is locally integrated and doesn't require external package installation.
-
Build and Deploy
- Connect your Quest headset via USB
- Enable Developer Mode in the Quest settings
- Build and deploy to your headset
-
Add AprilTagController to your scene
// Create an empty GameObject and add the AprilTagController component var aprilTagController = gameObject.AddComponent<AprilTagController>();
-
Configure the controller
- Assign a
WebCamTextureManagerfrom Meta's Passthrough Camera samples - Set the tag size in meters (default: 0.08m for 8cm tags)
- Configure detection parameters (decimation, frequency, etc.)
- Assign a
-
Use the setup helper (recommended)
// Add AprilTagSetupHelper to automatically configure the controller var setupHelper = gameObject.AddComponent<AprilTagSetupHelper>(); setupHelper.SetupAprilTagController();
-
Add FRCFieldLocalizer to your AprilTagController GameObject
var localizer = aprilTagController.gameObject.AddComponent<FRCFieldLocalizer>();
-
Add field layout JSON to Resources
- Place WPILib field layout JSON files in
Assets/AprilTag/Resources/FieldLayouts/ - Supported fields:
2022-rapidreact,2023-chargedup,2024-crescendo,2025-reefscape-andymark,2025-reefscape-welded
- Place WPILib field layout JSON files in
-
Configure the localizer (in Inspector)
- Set
Field Layout Nameto your field (e.g., "2025-reefscape-andymark") - Set
Min Anchorsto 3 (default, minimum for alignment) - Enable
Auto Alignfor automatic field alignment
- Set
-
Look at AprilTags on the field
- Point Quest headset at 3+ different AprilTags on the field
- Spatial anchors are automatically created
- Field alignment happens automatically once enough anchors exist
-
Access field coordinates
if (localizer.IsAligned) { Vector3 fieldPos = localizer.GetFieldPosition(); Quaternion fieldRot = localizer.GetFieldRotation(); Debug.Log($"Headset at field position: {fieldPos}"); }
The project includes several sample scenes in Assets/PassthroughCameraApiSamples/:
- CameraViewer: Basic camera feed display
- MultiObjectDetection: Advanced object detection examples
- StartScene: Main menu with navigation to all samples
| Parameter | Description | Default |
|---|---|---|
tagFamily |
Tag family to detect (Tag36h11 or TagStandard41h12) | Tag36h11 |
tagSizeMeters |
Physical size of AprilTag markers | 0.08m |
decimate |
Downscale factor for detection (1-8) | 2 |
maxDetectionsPerSecond |
Detection frequency limit | 15 fps |
horizontalFovDeg |
Camera field of view | 78° |
scaleVizToTagSize |
Scale visualizations to tag size | true |
- Tag36h11 (default): Recommended for ArUcO detector compatibility and general use
- TagStandard41h12: Original AprilTag format with higher data density but requires more processing
Note: You can download tag images from the AprilTag Images repository. Print them or display them on a screen for testing.
- Decimation: Higher values (4-8) improve performance but reduce detection accuracy
- Detection Frequency: Lower values (5-10 fps) reduce CPU usage
- Tag Size: Accurate physical measurements improve pose estimation
public class MyAprilTagHandler : MonoBehaviour
{
[SerializeField] private AprilTagController aprilTagController;
void Start()
{
// Configure for tag36h11 (default) or tagStandard41h12
aprilTagController.tagFamily = AprilTag.Interop.TagFamily.Tag36h11;
}
void Update()
{
// Access detected tags through the controller
var detector = aprilTagController.GetDetector();
foreach (var tag in detector.DetectedTags)
{
Debug.Log($"Detected tag {tag.ID} at position {tag.Position}");
}
}
}// Create custom visualizations for detected tags
public GameObject customTagPrefab;
void OnTagDetected(int tagId, Vector3 position, Quaternion rotation)
{
var viz = Instantiate(customTagPrefab);
viz.transform.SetPositionAndRotation(position, rotation);
viz.name = $"Tag_{tagId}";
}Assets/
├── AprilTag/
│ ├── AprilTagController.cs # Main detection controller
│ ├── AprilTagPermissionsManager.cs # Android permission handling
│ ├── AprilTagSetupHelper.cs # Automatic setup helper
│ ├── AprilTagSceneSetup.cs # Quest runtime configuration
│ ├── Scripts/
│ │ ├── AprilTagSpatialAnchorManager.cs # Spatial anchor management
│ │ ├── AprilTagFieldLayout.cs # FRC field layout parser
│ │ ├── FRCFieldLocalizer.cs # Field coordinate localization
│ │ ├── AprilTagConfidenceManager.cs # Detection confidence tracking
│ │ ├── AprilTagGPUPreprocessor.cs # GPU image preprocessing
│ │ ├── AprilTagVisualization.cs # Tag visualization
│ │ └── AprilTagAnchorInteraction.cs # Controller-based anchor mgmt
│ ├── Resources/
│ │ └── FieldLayouts/ # WPILib field layout JSONs
│ │ ├── 2022-rapidreact.json
│ │ ├── 2023-chargedup.json
│ │ ├── 2024-crescendo.json
│ │ ├── 2025-reefscape-andymark.json
│ │ └── 2025-reefscape-welded.json
│ └── Library/ # Locally integrated AprilTag library
│ ├── Runtime/ # C# API and Unity integration
│ └── Plugin/ # Native libraries (Android/Windows)
├── PassthroughCameraApiSamples/ # Meta's official samples
│ ├── CameraViewer/ # Basic camera viewer
│ ├── MultiObjectDetection/ # AI object detection
│ └── StartScene/ # Main menu scene
└── Resources/ # Project settings
-
No WebCamTexture Available
- Ensure Meta's Passthrough Camera API is properly initialized
- Check that the WebCamTextureManager is present in the scene
- Verify camera permissions are granted on Quest
-
WebCamTexture GPU Initialization Errors
- The system now automatically waits for WebCamTexture to initialize
- Uses direct pixel access instead of Graphics.CopyTexture for better reliability
- Allow a few seconds for the camera feed to stabilize
-
Poor Detection Performance
- Increase decimation value (try 4-8)
- Reduce detection frequency
- Ensure good lighting conditions
- Enable GPU preprocessing for better image quality
-
Inaccurate Pose Estimation
- Verify the tag size parameter matches your physical tags
- Check camera calibration and FOV settings
- Ensure tags are not too small or far away
-
Spatial Anchors Not Persisting
- Verify spatial permissions are granted on Quest
- Check that anchors are being saved (see logs)
- Ensure sufficient stable detection frames before anchor placement
-
Field Alignment Fails
- Need at least 3 spatial anchors with known field positions
- Tags must be from the correct field layout
- Check alignment error threshold (default 0.5m)
- Verify field layout JSON is loaded correctly
-
Alignment Error Too High
- Increase outlier rejection threshold
- Check physical tag placement matches field layout
- Ensure tags are measured accurately (6.5 inches = 0.1651m)
- Look at more tags to improve alignment quality
Enable debug logging to troubleshoot issues:
// AprilTag detection
aprilTagController.logDebugInfo = true;
aprilTagController.logDetections = true;
// Field localization
frcFieldLocalizer.m_enableDebug = true;Monitor field position on Quest via adb:
adb logcat -s Unity:I | grep FieldPosThis will show field position updates in real-time:
[FieldPos] 1.234,0.500,4.567
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
This project is designed specifically for Meta Quest headsets, not Unity Editor debugging:
- All development targets Quest runtime, not editor play mode
- Configuration happens programmatically via
AprilTagSceneSetup.cs - No editor-only features (context menus, inspector-only tools)
- Testing must be done on actual Quest hardware
Unlike PhotonVision's continuous vision tracking approach:
- AprilTags are used ONLY for establishing spatial anchors at known field positions
- Quest's inside-out tracking handles all movement after anchor placement
- Vision-based detection is disabled after alignment - anchors provide the reference frame
- This provides drift-free, sub-millimeter precision using Quest's world-class SLAM system
- ✅ No drift from camera movements or lighting changes
- ✅ Works in any lighting condition once anchors are placed
- ✅ Leverages Quest's native spatial understanding
- ✅ Sub-millimeter precision from spatial anchors
- ✅ Minimal CPU overhead - no continuous tag detection needed
- Keijiro Takahashi for the excellent AprilTag Unity package (now locally integrated)
- April Robotics Laboratory for the original AprilTag system
- Meta for the Passthrough Camera API, Spatial Anchor system, and XR SDK
- WPILib for the official FRC field layout definitions
- PhotonVision for inspiration on multi-tag localization approaches
- Unity Technologies for the XR framework
- Create an issue for bug reports or feature requests
- Check the Meta XR Documentation
- Visit the AprilTag Documentation
- Review WPILib AprilTag Documentation
Note: This project requires a Meta Quest headset with Developer Mode enabled. It is designed for VR/MR applications running on Quest hardware and will not work in standard Unity editor play mode without proper XR setup and Quest device connection.