Skip to content
CrocoDocs
Starter GuideProgrammingEngineeringDesignPortfolio
Documentation

Introduction

IntroductionBIOBUZZ game overview

Getting started

Getting startedBlocksOnBot JavaAndroid StudioYour first OpModeJava basicsMotors and servosNonblocking codeDebugging robot code

Control systems

Control systemsJoystick mappingField-centric mecanum drivePID controlFeedforwardFlywheel velocity controlSystem identificationMotion profilingKalman filterLow-pass filter

Autonomous

AutonomousTimed and encoder movementOdometryLocalization troubleshootingMotion planningReliable autonomous sequencesPure PursuitSensor fusionAuto to TeleOp handoff

Season topics

Season topicsProjectile mathematicsTurret aimingShooting on the move

Code practices

Code practicesNaming conventionsCode organizationLoop timing and bulk readsFormattingComments and documentationTeam collaboration

Libraries

LibrariesNextFTCFTCLibSolversLibPedro PathingRoad RunnerFTC DashboardMercurial v2Mercurial v1PanelsSloth

Community software

Community softwareBlaze FTCOpenLoopHive Vision

Sensors and vision

Vision overviewVision basicsLimelight 3A
VisionPortal
Color blob locator
AprilTag detection
Starter Guide
Engineering
Design
Portfolio
Appearance
VisionPortal

AprilTag detection

Read FTC AprilTag detections safely, including SDK 12 clusters

An AprilTag is a high-contrast printed marker with a machine-readable pattern. The SDK can identify tags and estimate their pose relative to a camera. That estimate can help localization, but it is still a measurement: a blurry tag, a wrong tag size, poor camera calibration, or confusing the camera pose with the pose can lead to a bad decision.

Official BIOBUZZ HIVE CELL field element with its four AprilTags attached underneath.

SDK version

The code that reads detections changed in FTC SDK 12.0. This matters especially for the current BIOBUZZ : the official FTC documentation says BIOBUZZ has four AprilTag clusters and no standalone tags.

In SDK 12.0 and later, getDetections() can return either:

  • AprilTagSingleDetection, which has an id field; or
  • AprilTagClusterDetection, which has cluster metadata and a cluster name, but no .id field.

The common parent type is AprilTagDetection. Pose fields such as ftcPose are on that parent and can be read for either type. Do not copy older code that reads detection.id directly from every result.

Processor setup

Make the processor before building VisionPortal. This convenience method uses the FTC SDK defaults:

AprilTagProcessor aprilTag = AprilTagProcessor.easyCreateWithDefaults();
val aprilTag = AprilTagProcessor.easyCreateWithDefaults()

The default game library is supplied by the FTC SDK. If you need custom tags or processor settings, use the current FTC sample and Builder documentation rather than adding old-season IDs by hand.

Camera setup

VisionPortal portal = new VisionPortal.Builder()
    .setCamera(hardwareMap.get(WebcamName.class, "Webcam 1"))
    .addProcessor(aprilTag)
    .build();
val portal = VisionPortal.Builder()
    .setCamera(hardwareMap.get(WebcamName::class.java, "Webcam 1"))
    .addProcessor(aprilTag)
    .build()

As with any VisionPortal setup, Webcam 1 must match the configured hardware name. Keep the preview open during so you can see whether a marker is being detected before the robot moves.

Detection types

The Driver Hub preview below shows the processor finding BIOBUZZ AprilTag clusters in a real camera image. The colored boxes and labels are the detections overlaid on the frame. In the next step, you will read each detection from code.

FTC's AprilTag cluster example shown in a Driver Hub camera view with cluster detections overlaid.

This is a real FTC camera preview with the detected BIOBUZZ clusters labeled. The labels show recognition; the telemetry example below shows how your reads the measurements. Use your own live preview to verify detections before the robot acts.

Reading detections

The SDK gives you a list because the camera can see more than one detection at a time. In SDK 12, check whether each result is a single tag or a cluster before reading type-specific information:

List<AprilTagDetection> detections = aprilTag.getDetections();

for (AprilTagDetection detection : detections) {
    if (detection instanceof AprilTagSingleDetection) {
        AprilTagSingleDetection single = (AprilTagSingleDetection) detection;
        telemetry.addData("Single tag ID", single.id);
        if (single.metadata != null) {
            telemetry.addData("Tag name", single.metadata.name);
        }
    } else if (detection instanceof AprilTagClusterDetection) {
        AprilTagClusterDetection cluster = (AprilTagClusterDetection) detection;
        telemetry.addData("Cluster", cluster.metadata.name);
        telemetry.addData("Visible cluster", "%d%%", cluster.percentClusterFound);
    }

    if (detection.ftcPose != null) {
        telemetry.addData("XYZ (in)", "%.1f, %.1f, %.1f",
            detection.ftcPose.x, detection.ftcPose.y, detection.ftcPose.z);
        telemetry.addData("Pitch / Roll / Yaw (deg)", "%.1f / %.1f / %.1f",
            detection.ftcPose.pitch, detection.ftcPose.roll, detection.ftcPose.yaw);
        telemetry.addData("Range (in)", "%.1f", detection.ftcPose.range);
        telemetry.addData("Bearing (deg)", "%.1f", detection.ftcPose.bearing);
        telemetry.addData("Elevation (deg)", "%.1f", detection.ftcPose.elevation);
    }
}
val detections: List<AprilTagDetection> = aprilTag.detections

for (detection in detections) {
    when (detection) {
        is AprilTagSingleDetection -> {
            telemetry.addData("Single tag ID", detection.id)
            detection.metadata?.let { telemetry.addData("Tag name", it.name) }
        }
        is AprilTagClusterDetection -> {
            telemetry.addData("Cluster", detection.metadata.name)
            telemetry.addData("Visible cluster", "%d%%", detection.percentClusterFound)
        }
    }

    detection.ftcPose?.let { pose ->
        telemetry.addData("XYZ (in)", "%.1f, %.1f, %.1f", pose.x, pose.y, pose.z)
        telemetry.addData("Pitch / Roll / Yaw (deg)", "%.1f / %.1f / %.1f",
            pose.pitch, pose.roll, pose.yaw)
        telemetry.addData("Range (in)", "%.1f", pose.range)
        telemetry.addData("Bearing (deg)", "%.1f", pose.bearing)
        telemetry.addData("Elevation (deg)", "%.1f", pose.elevation)
    }
}

Add these imports when using the example in a OpMode:

import java.util.List;
import org.firstinspires.ftc.vision.apriltag.AprilTagDetection;
import org.firstinspires.ftc.vision.apriltag.AprilTagSingleDetection;
import org.firstinspires.ftc.vision.apriltag.AprilTagClusterDetection;
import org.firstinspires.ftc.vision.apriltag.AprilTagDetection
import org.firstinspires.ftc.vision.apriltag.AprilTagSingleDetection
import org.firstinspires.ftc.vision.apriltag.AprilTagClusterDetection

The code has a reason for each check:

  1. getDetections() obtains the processor's current list.
  2. instanceof tells Java which kind of detection it is handling.
  3. A single-tag ID is read only after casting to AprilTagSingleDetection.
  4. Cluster name and visibility are read only after casting to AprilTagClusterDetection.
  5. Pose is read from the parent result type because both kinds of detection can have ftcPose.

If your project uses SDK 11 or earlier, this code will not compile because the cluster classes do not exist in that version. Use the samples and API docs that match your project's FTC SDK version.

Pose coordinates

ftcPose describes the detected target relative to the camera lens. In the FTC frame, Y points forward out of the lens, X points right, and Z points up. If a tag is left of the camera centerline, its X value is negative. Those translations and range use the processor's configured output distance unit (inches by default), independently of the tag metadata's units. AprilTagProcessor.Builder.setOutputUnits() configures distance and angle units; the default angle unit is degrees (normally inches for FTC field tags). The rotations are in degrees with the processor's default settings; their signs follow the FTC axes and right-hand rule.

The names are easy to mix up, so connect each value to a question:

Telemetry valueMeaning in the FTC camera frameBeginner check
xSide-to-side offset; positive is to camera-right.Move the tag left: X should become negative.
yForward distance from the lens to the tag .Move the tag farther away: Y should grow.
zVertical offset; positive is above the lens.Put the tag below the lens: Z should be negative.
pitchTag rotation about the FTC X axis.Tilt the tag up/down and observe this value.
rollTag rotation about the FTC Y axis.Rotate the tag clockwise/counterclockwise in its image plane.
yawTag rotation about the FTC Z axis (vertical).Turn the tag left/right; this is not the same as .
rangeHorizontal distance from camera to tag center, derived from X and Y.A sideways tag can have a large range even when Y is unchanged.
bearingHorizontal angle from the camera's forward direction to the tag.Use this to aim toward the tag, after checking the sign.
elevationVertical angle from forward to the tag center.A tag above or below the lens changes this angle.

Pitch, roll, and yaw names are the FTC SDK's mapping to its camera axes; do not assume another robotics library uses the same frame or angle naming. Bearing answers “which way should I turn to point at the center?” Yaw answers “how is the tag face rotated?” A tag can have near-zero bearing but nonzero yaw when it is centered in the image but turned sideways.

A cluster can report a target point placed somewhere useful in its group layout, rather than at the center of any printed marker. The official BIOBUZZ cluster guidance explains that the HIVE CELL cluster pose is designed to point toward the CELL opening. Do not substitute the center of one visible tag for the cluster pose.

Pose quality depends on the tag size and camera calibration. Check the current field tag layout and game manual before assuming a location or meaning. Do not use a stale detection after the camera no longer sees the target.

Detection testing

  1. Put the robot on or leave the disabled.
  2. Run the SDK's ConceptAprilTag sample that matches your SDK version.
  3. Keep the OpMode in INIT and open Camera Stream.
  4. Move a printed tag or cluster through the image and watch the detection name, range, and bearing.
  5. Hide the target. Confirm your program handles an empty detection list.
  6. Test the actual robot camera placement and real field lighting before using measurements for localization.

Common mistakes

MistakeWhy it failsBetter check
Reading .id on every AprilTagDetectionCluster detections do not have a single-tag ID.Check instanceof AprilTagSingleDetection first.
Assuming every season uses single tagsSDK 12 added cluster detections; BIOBUZZ uses clusters.Inspect the current game library and matching sample.
Treating camera pose as robot poseThe camera can be offset and rotated on the robot.Apply the camera-to-robot transform or use the SDK's robot-pose support when configured.
Trusting a pose with unknown tag size or calibrationScale and orientation estimates can be wrong.Verify tag metadata and camera calibration for the selected resolution.
Acting on an empty or stale resultThe target may be outside the view or no longer visible.Require a fresh detection and define a safe fallback.

Sources

  • FTC AprilTag ID codes and SDK 12 results
  • FIRST: AprilTag Clusters — field illustration, Figure 1
  • FTC AprilTag pose
  • FTC AprilTag reference frame
  • FTC explanation of detection values
  • FTC camera calibration
  • Current season game resources

Color blob locator

Step-by-step introduction to the FTC Color Locator processor

Vision in autonomous

Turn a tested camera detection into a safe autonomous choice

On this page

SDK versionProcessor setupCamera setupDetection typesReading detectionsPose coordinatesDetection testingCommon mistakesSources