Skip to main content

Lesson 14.2: Detecting Field Locations using AprilTagProcessor


What AprilTags Are and Why They Matter

An AprilTag is a two-dimensional fiducial marker, a printed pattern that a camera can reliably detect and uniquely identify even under varying lighting conditions and at an angle. FTC places AprilTags on fixed field structures such as backdrop panels and alliance walls. Because the tags are at known physical locations on the field, a robot that can see a tag can compute its own position and orientation relative to that tag.

This is a fundamentally different capability from any sensor covered so far. Distance sensors and potentiometers tell the robot about the state of its own mechanisms. An AprilTag tells the robot something about where it is on the field. That positional awareness is what makes reliable autonomous scoring possible: the robot can align itself to a specific scoring target using vision feedback rather than relying on dead-reckoning from encoder counts alone.


How the AprilTagProcessor Works

The AprilTagProcessor runs as a background task inside the VisionPortal. On every camera frame, it scans the image for the tag36h11 pattern family (the family used in FTC). When a tag is found, the processor creates an AprilTagDetection object describing what was detected. These detection objects accumulate in an internal list that your code can poll at any time by calling aprilTagProcessor.getDetections().

The method returns a List<AprilTagDetection>. The list is empty when no tags are visible. When one or more tags are in the frame, each one appears as a separate entry. The iteration pattern is a standard for..each loop over the list, which was covered in Unit 6.

Each AprilTagDetection has a metadata field. When the detected tag's ID matches one of the tags defined in the SDK's built-in field database, metadata is populated with the tag's name, field position, and other reference data. When the tag's ID is not in the database (for example, a custom tag on your practice field), metadata is null. Always check detection.metadata != null before accessing any metadata fields to prevent a NullPointerException.


Iterating Safely Over Detections

Because the detection list can change between camera frames, and the camera runs on a background thread, it is good practice to call getDetections() once per loop cycle, assign the result to a local variable, and iterate over that local copy. This prevents the list from being modified by the camera thread mid-iteration.

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

for (AprilTagDetection detection : detections) {
if (detection.metadata != null) {
// Safe to access detection.id and detection.metadata fields
}
}

Annotated Code

note

This is an autonomous program. No gamepad input is used.

package org.firstinspires.ftc.teamcode;

import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
import com.qualcomm.robotcore.hardware.WebcamName;
import org.firstinspires.ftc.vision.VisionPortal;
import org.firstinspires.ftc.vision.apriltag.AprilTagDetection;
import org.firstinspires.ftc.vision.apriltag.AprilTagProcessor;
import java.util.List;

@Autonomous(name="AprilTag_Detection_Demo")
public class AprilTagDetectionDemo extends LinearOpMode {

private VisionPortal visionPortal;
private AprilTagProcessor aprilTagProcessor;

@Override
public void runOpMode() {
WebcamName webcam = hardwareMap.get(WebcamName.class, "Webcam 1");

aprilTagProcessor = AprilTagProcessor.easyCreateWithDefaults();
visionPortal = VisionPortal.easyCreateWithDefaults(webcam, aprilTagProcessor);

waitForStart();

while (opModeIsActive()) {
// Retrieve the current list of all visible tag detections
List<AprilTagDetection> detections = aprilTagProcessor.getDetections();

if (detections.isEmpty()) {
telemetry.addData("Tags", "None visible");
} else {
for (AprilTagDetection detection : detections) {
// Guard against tags not in the built-in field database
if (detection.metadata != null) {
telemetry.addData("Tag ID", detection.id);
telemetry.addData("Tag Name", detection.metadata.name);
} else {
telemetry.addData("Tag ID", detection.id);
telemetry.addData("Tag Name", "Unknown (not in database)");
}
}
}

telemetry.addData("Total Detections", detections.size());
telemetry.update();
}
}
}

Fill-in-the-Blank Practice

  1. The AprilTagProcessor returns a List of __________ objects containing data about each currently visible tag.
  2. To retrieve the current list of visible tags from the processor, call aprilTagProcessor.__________().
  3. Before accessing any fields on detection.metadata, you must confirm it is not __________ to avoid a runtime crash.
Show answers
  1. AprilTagDetection
  2. getDetections()
  3. null

Simulator Challenge

Write a method called scanForTag(int targetId) that returns true if a tag with the specified ID is currently visible and has valid metadata. The method should iterate over the current detection list and return false if the target ID is not found. Assume aprilTagProcessor is a class member that has already been initialized.

Telemark Unit 14.2 Simulator
Loads the lesson-specific Telemark computer vision challenge with incomplete starter code for students to complete.
The simulator editor contains the starter code; use the answer below only after trying the challenge.
Show answer
boolean scanForTag(int targetId) {
for (AprilTagDetection detection : aprilTagProcessor.getDetections()) {
if (detection.id == targetId && detection.metadata != null) {
return true;
}
}

return false;
}

A Team Filters Vision Results Behind One Method

Titan Robotics Club's vision layer asks its AprilTag helper for the best match from a list of target IDs. It checks the result before reading the detected ID. Here is that flow with names matched to this lesson:

TagResult findTag(int[] targetIds) {
TagResult result = aprilTagVision.findBestMatch(targetIds);

if (result != null) {
telemetry.addData("Tag ID", result.id);
}

return result;
}

TagResult, findBestMatch, and telemetry are lesson-focused names. The team's source delegates to getBestDetectedTargetInfo(aprilTagIds, null), checks for null, and then reads the detection ID. Camera selection, dashboard output, and pose conversion were omitted so the example stays focused on safe result handling. Adapted from Team 3543 Titan Robotics Club's Vision.java at a pinned commit under the MIT License.

Ready to move on?

Only mark complete if you genuinely understand the material. Your progress will be saved in this browser.

Stuck on this lesson?

Ask about anything on this page. It can see which lesson you have open and which part you are reading.